Custody Tool Description
The majority of Chia Network Inc's prefarm is being held in a cold wallet, secured by a complex set of custodial rules. This document will describe the details of the custodial arrangement. A moderate level of technical proficiency is probably needed to understand the details. For a high-level overview of the custody wallet, see our blog post.
Other relevant documents:
- Flow chart to visualize how the custody tool works
- User guide to help you get up and running
- CLI reference for all custody commands used in this tutorial
Singleton Structure
The prefarm uses a singleton with the following features:
- Multisig -- required to perform actions on the singleton, where:
- The total number of keys in the multisig is initially set to 5, which will be referred to as
n
for the rest of this document.n
can be changed with a rekey (explained later) - Initially, 3 keys will be required to perform withdrawals and standard rekeys. This number will be referred to as
m
for the rest of this document.m
can be thought of as the security level for the wallet. This variable can be modified to be as large asn
. For the prefarm, it can be as small as 1, though other custodial wallets could set the minimum to a larger number
- Merkle root -- Chialisp puzzles representing the
n
keys are stored in a Merkle tree, where:
- Puzzles representing every combination of keys, from 1 to
m
, are stored. If the keys are A, B, C, D and E, andm
is 3, then the combinations to be stored are ABC, ABD, ABE, ACD, ACE, ADE, BCD, BCE, BDE, CDE, AB, AC, AD, AE, BC, BD, BE, CD, CE, DE, A, B, C, D and E - The Merkle root of this tree is curried (pre-committed) into the singleton
- The Merkle root of a tree containing puzzles of all possible combinations for
m
+ 1 is also curried into the singleton. This is required in case of a lock level increase (explained later). This root is recursive, in that it contains puzzles that have combinations form
+ 2 committed to them, leading up to the level wherem
=n
. - In order to spend a coin from this wallet, a node in the Merkle tree, along with a Proof of Inclusion, are required to be passed into the singleton's solution. The Proof of Inclusion must prove the node's existence in the current Merkle root in order for the spend to succeed
- The Merkle tree is stored in multiple private locations. However, even if a copy is stolen, the thief will not gain access to the wallet because
m
digital signatures are still required (see below for a more detailed analysis) - The Merkle tree is generated deterministically, based on the
n
pubkeys. Therefore, if the Merkle tree is lost, it can be regenerated by using then
pubkeys
-
Withdrawal Timelock -- This is a timelock on initiating a withdrawal, referred to as
wt
for the rest of this document. The value ofwt
is set upon the wallet's creation and can never be changed. It will be explained in detail below. -
Rekey Timelock -- This is a timelock on initiating a rekey, referred to as
rt
for the rest of this document. The value ofrt
is set upon the wallet's creation and can never be changed. It will be explained in detail below.
Singleton Settings
The singleton comes in two layers -- one permanent and one non-permanent.
Permanent layer
Setting | Prefarm Value | Description |
---|---|---|
wt | 30 days | Withdrawal Timelock -- When attempting to begin a withdrawal, this is the minimum number of seconds that must have elapsed since the last withdrawal, rekey or clawback. |
pc | 90 days | Payment Clawback -- The minimum number of seconds that must elapse after initiating a withdrawal before the withdrawal can be completed. Clawbacks are possible during this window. |
rt | 15 days | Rekey Timelock -- When attempting to begin a standard rekey, this is the minimum number of seconds that must have elapsed since the last withdrawal, rekey or clawback. For a slow rekey, this amount gets added for each key less than m (in addition to the amount of time that would have been required in a standard rekey). |
rc | 30 days | Rekey Clawback -- The minimum number of seconds that must elapse after initiating a rekey before the rekey can be completed. Clawbacks are possible during this window. |
sp | 45 days | Slow rekey Penalty -- This amount gets added to the Rekey Timelock when a slow rekey is being performed. |
Non-permanent layer
Setting | Initial Value | Description |
---|---|---|
m | 3 | The initial number of pubkeys required to do a withdrawal or standard rekey |
n | 5 | The maximum number of pubkeys required to do a withdrawal or standard rekey |
pks | A comma separated list of pubkey files that will control this money |
Allowed Actions
Three separate actions are allowed on the wallet's singleton: withdrawals, rekeys (normal and slow) and lock level increases. Each of these actions will be discussed in detail in this section.
Withdrawal
This action removes money from the wallet. In order for a withdrawal to be initiated, exactly m
of n
signatures are required.
Two phases must be completed to perform a withdrawal: a withdrawal timelock and a drop coin.
Withdrawal Timelock
Even though this is called a timelock, it acts more like a gateway, which either allows or disallows the singleton to begin the withdrawal process. It has the following rules:
wt
seconds must have elapsed since any actions (other than a lock level increase) have been performed on the singleton- The
wt
condition either has, or has not, been met wt
is part of the singleton's permanent layer, so it can never be modified
If the withdrawal timelock condition has not been met, then a withdrawal may not be initiated. If it has been met, then a withdrawal may be initiated. The next phase is the drop coin.
Drop Coin
Upon entering this phase, the singleton creates a drop coin of the amount of XCH to be withdrawn. A drop coin has several rules:
- It has a curried XCH payout address, to where the money is flagged to be withdrawn
- It has a payment clawback timelock, referred to as
pc
for the rest of this document - Because the drop coin cannot be spent for
pc
seconds, it functions as a permissionless escrow - The drop coin contains the same Merkle root that was used in the singleton
- At any point before the drop coin has been spent, clawback is allowed. Clawback has the following features:
- It cancels the withdrawal and returns the money to the custodial wallet's singleton. It uses
p2_singleton
to accomplish this, so there is further action needed for the funds to be absorbed into the singleton - It requires
m
ofn
signatures - The
m
signatures don't have to be the same as the ones that initiated the withdrawal. In other words, different people could claw back the withdrawal than those who initiated it
- It cancels the withdrawal and returns the money to the custodial wallet's singleton. It uses
- After
pc
seconds, if a clawback has not been performed, completion becomes possible. Completion has the following features:- It completes the withdrawal to a pre-specified XCH address
- Anyone is allowed to perform a completion
- Even though a completion can be performed by anyone, it is secure because the withdrawal address is not changeable
Rekey
This action changes the keys associated with the wallet's singleton.
k
keys are required for a rekey, where:
- For a standard rekey,
k = m
- For a slow rekey,
1 <= k < m
. (By default, a slow rekey could be performed with just one key.)
Either type of rekey can also modify m
and/or n
if desired. We'll discuss the circumstances where each type of rekey will be performed later in this section.
Two phases must be completed to perform a rekey, an initiation timelock and a drop coin.
Initiation
Before a rekey can begin, a certain amount of time must have elapsed since the last action (other than a lock level increase) was performed on the singleton. This is a de facto gateway; the time condition either has, or has not been met.
The amount of time before the rekey can begin depends on the number of keys used. In order to calculate this time, several factors must be considered:
- Start by calculating
x
, wherex = m - k + 1
- If
x
is1
, then it's a standard rekey (k = m
) - If
x > 1
, then it's a slow rekey.x
represents the gap between thek
keys being used with this rekey andm + 1
(the number of keys required for a lock level increase, as explained below)
- If
- For a standard rekey, the initiation timelock duration is
rt
. As discussed above,rt
is stored in the singleton and can never be changed - For a slow rekey, a time penalty
sp
seconds is automatically applied. The value ofsp
also can never be changed - The duration of the initiation timelock of a slow rekey is
sp + rt*x
seconds. In other words, a penalty ofrt
seconds gets added for each key less thanm
The following table illustrates a few examples of initiation timelock lengths, for various values of m
and k
. For this table, rt
is set to 15 days and sp
is set to 45 days (these are both denominated in seconds). The table assumes that n
(5) and the minimum k
(1) have not been modified from their default values:
m | k | Days | Comment |
---|---|---|---|
3 | 3 | 15 | Standard rekey, no penalty |
3 | 2 | 75 | Slow rekey, sp day penalty + 2 * standard rt days |
3 | 1 | 90 | Slow rekey, sp day penalty + 3 * rt days |
1 | 1 | 15 | This is a case where, after a prior rekey, m was reduced to 1. There is no penalty, even with a single key |
5 | 3 | 90 | In this case, a lock level increase has been performed, so 5 of 5 keys are required to avoid a penalty |
5 | 1 | 120 | This is the longest possible initiation timelock duration when n is 5 and the minimum k is 1. In this case, m has been increased to 5, and 1 key is being used for the rekey |
... | ... | ... | Other combinations for m and k are possible, but not shown here |
If the singleton has been modified within the timelock's required number of seconds, then the initiation timelock condition has not been met, and a rekey may not be initiated. If the initiation timelock condition has been met, then a withdrawal may be initiated. The next phase is the drop coin. k
is carried over to this phase.
Drop Coin
Upon entering this phase, the singleton creates a drop coin with 0 value. This drop coin has several rules:
- It has a hard-coded timelock, hereafter referred to as
rc
- The same Merkle root that was used in the singleton is curried into the drop coin
- A new Merkle root is also curried into the drop coin. After the rekey has completed, this will become the Merkle root of the puzzles representing the new keys. Therefore, the new keys, along with the new Merkle tree, must have been generated before the drop coin was created
- At any point before the drop coin has been spent, clawback is allowed. Clawback has the following features:
- It cancels the rekey; the custodial wallet's singleton is left unmodified
- It requires
k
signatures (the number of keys that initiated the rekey) - The
k
signatures don't have to be the same as the ones that initiated the rekey. In other words, different people could claw back the rekey than those who initiated it
- After
rc
seconds, if a clawback has not been performed, completion becomes possible. Completion has the following features:- It spends the drop coin, which creates a puzzle announcement for the singleton to use
- It also spends the singleton, which asserts the puzzle announcement from the drop coin, and recreates itself with the new Merkle root curried in
m
and/orn
could be set to different numbers in the new singleton- Anyone is allowed to perform the completion
- Even though the completion can be performed by anyone, it is secure because the new Merkle root has already been committed to
Note that because rc
is hard-coded, a second rekey can't overtake a rekey that has already been initiated. If rekey A is in progress and someone attempts rekey B, then A will succeed and B will fail.
Lock Level Increase
This action increases the wallet's security (m
) by 1. It has the following features:
m + 1
keys are required to perform a lock level increase- The effect is immediate -- there is no timelock
- The keys themselves don't change
- This action is secure because the new Merkle root has been pre-committed
- In running this action, the singleton is spent and a new copy is created, with a new Merkle root curried in. This new root is taken from a Merkle tree containing every possible combination of keys from the new
m
down to 1 - This action automatically invalidates all outstanding rekey attempts because the Merkle root has changed in the new copy of the singleton
- This action does not invalidate outstanding withdrawal attempts
When are Normal Rekey, Slow Rekey and Lock Level Increase needed?
Normally, each of the keys will be in a Secure state. This means that the original owner still possesses the key, and no adversaries have gained access to it.
Keys have three other possible states:
- Sniffed -- An adversary has gained access to the key. The owner still has a copy
- Stolen -- An adversary has gained access to the key. The owner no longer has a copy
- Lost -- The owner has lost the key. Nobody else has gained access to it
If a sufficient number of keys are sniffed, stolen or lost, there are three potential catastrophic consequences:
- Deadlocked -- The owner and adversary each attempt to rekey and claw back those attempts. Neither side obtains the funds until the other side gives up
- Drained -- An adversary is able to steal the funds
- Bricked -- Nobody is able to access the funds
Any time one or more keys have been sniffed, stolen or lost, a rekey will be performed if possible.
The Merkle tree is needed to perform withdrawals, rekeys and lock level increases. Multiple copies of the tree will be kept in private locations. The assumption is that if any of the keys are sniffed, stolen or lost, the Merkle tree has been sniffed.
The following table lists the action/consequence, given the current value of m
and the state of the keys:
m | Keys Sniffed | Keys Stolen | Keys Lost |
---|---|---|---|
3 | 0-2: normal rekey 3: lock level increase to 4, normal rekey 4: lock level increase to 5, normal rekey 5: deadlocked | 0-2: normal rekey 3-5: drained | 0-2: normal rekey 3-4: slow rekey 5: bricked |
4 | 0-3: normal rekey 4: lock level increase to 5, normal rekey 5: deadlocked | 0-1: normal rekey 2: slow rekey 3-5: drained | 0-1: normal rekey 2-4: slow rekey 5: bricked |
5 | 0-4: normal rekey 5: deadlocked | 0: normal rekey 1-2: slow rekey 3-5: drained | 0: normal rekey 1-4: slow rekey 5: bricked |
Source Code
The source code for the custody solution is in the internal-custody GitHub repository.
There are two configuration files, one public (for observers) and one private.
Public Configuration
An observer can track the prefarm's configuration information from prefarm_info.py, which contains the following variables:
launcher_id
:bytes32
-- This is pre-set; the user cannot change itpuzzle_root
:bytes32
-- This is pre-set; the user cannot change itwithdrawal_timelock
:uint64
-- For how long must the singleton remain unspent in order to initiate a withdrawal?payment_clawback_period
:uint64
-- How much time do signers have to claw back a payment?rekey_clawback_period
:uint64
-- How much time do signers have to claw back a rekey?slow_rekey_timelock
:uint64
-- What is the penalty P that is applied to the singleton timelock when initiating a slow rekey?rekey_increments
:uint64
-- What is the timelock increment for using fewer keys?
Private Configuration
The necessary information to spend the prefarm is located in puzzle_root_construction.py. This information is considered private. However, if an attacker obtained this information, it would still be insufficient to spend the prefarm because valid signatures would be required. However, the Merkle tree would be considered sniffed, so a rekey would be required.
This code contains the following variables:
prefarm_info
:PrefarmInfo
-- The info from the public filepubkey_list
:List[G1Element]
-- What are the set of pubkeys?required_pubkeys
:uint32
-- How many keys are required for payments and standard rekeys (m
initially is 3 for the prefarm)?maximum_pubkeys
:uint32
-- What is the maximum lock level (n
is 5 for the prefarm)?minimum_pubkeys
:uint32
-- What is the minimum number of keys required for a slow rekey (1 for the prefarm, but could be higher if so desired).next_root
:Optional[bytes32]
-- What will the root key be in the event of a rekey?filter_proofs
:ProofType
leaf_proofs
:ProofType