Difference between revisions of "Chain-key Bitcoin"

From Internet Computer Wiki
Jump to: navigation, search
m
m
 
(22 intermediate revisions by 2 users not shown)
Line 5: Line 5:
 
Unlike other tokens pegged to bitcoin, the ckBTC token does not rely on a third-party bridge for the conversion between BTC and ckBTC, making it a substantially more secure alternative to “wrapped” tokens.
 
Unlike other tokens pegged to bitcoin, the ckBTC token does not rely on a third-party bridge for the conversion between BTC and ckBTC, making it a substantially more secure alternative to “wrapped” tokens.
  
While chain-key bitcoin and regular bitcoin have the same value, the advantage of chain-key bitcoin is fast and cheap transfers: A transfer is finalized within a few seconds (a speed-up of roughly three orders of magnitude compared to transfers on the Bitcoin blockchain) and only costs 0.0000001 ckBTC (approximately two orders of magnitude lower than the Bitcoin miner fees).  
+
While chain-key bitcoin and regular bitcoin have the same value, the advantage of chain-key bitcoin is fast and cheap transfers: A transfer is finalized within a few seconds (a speed-up of roughly three orders of magnitude compared to transfers on the Bitcoin blockchain when waiting for 6 confirmations) and only costs 0.0000001 ckBTC (approximately two orders of magnitude lower than the Bitcoin miner fees).  
  
 
== Architecture ==
 
== Architecture ==
Line 11: Line 11:
 
The ckBTC functionality is built upon the [[Bitcoin integration]] of the Internet Computer, which makes it possible for canisters to receive, hold, and send bitcoin.
 
The ckBTC functionality is built upon the [[Bitcoin integration]] of the Internet Computer, which makes it possible for canisters to receive, hold, and send bitcoin.
  
There are two canisters, the ckBTC minter and ckBTC ledger, that together provide the ckBTC functionality. The ckBTC minter mints new ckBTC tokens whenever it receives bitcoin. Likewise, it burns ckBTC tokens whenever an owner of ckBTC tokens requests a withdrawal of bitcoin. The ckBTC minter needs to receive BTC, based on a large number of confirmations due to the lack of finality in Bitcoin, before it mints the same amount in ckBTC and it burns ckBTC before it transfers BTC back to the users.
+
There are two canisters, the ckBTC minter and ckBTC ledger, that together provide the ckBTC functionality. The ckBTC minter mints new ckBTC tokens whenever it receives bitcoin. Likewise, it burns ckBTC tokens whenever an owner of ckBTC tokens requests a withdrawal of bitcoin. The ckBTC minter waits for a large number of confirmations before minting ckBTC and it burns ckBTC before it transfers BTC back to the users.
  
A best-effort approach is used for the transfer of BTC to the users, i.e., the user cannot get the burned ckBTC back. Rather, the ckBTC minter repeatedly attempts to transfer BTC out. Note that “user” refers to the caller of the functions exposed by the ckBTC minter and ckBTC ledger in the following. A human user or another canister may be behind the function invocations.
+
A best-effort approach is used for the transfer of BTC to the users, i.e., the user cannot get the burned ckBTC back. Rather, the ckBTC minter repeatedly attempts to transfer BTC out until the transfer succeeds. Note that “user” refers to the caller of the functions exposed by the ckBTC minter and ckBTC ledger in the following. A human user or another canister may be behind the function invocations.
  
The ckBTC ledger is [https://github.com/dfinity/ICRC-1/blob/main/standards/ICRC-1/README.md ICRC-1 compliant], updating the balance accounts when ckBTC tokens are transferred and executing the mint and burn operations coming from the ckBTC minter.  
+
The ckBTC ledger is [https://github.com/dfinity/ICRC-1/blob/main/standards/ICRC-2/README.md ICRC-2] and [https://github.com/dfinity/ICRC-1/blob/main/standards/ICRC-1/README.md ICRC-1] compliant, updating the balance accounts when ckBTC tokens are transferred and executing the mint and burn operations coming from the ckBTC minter.  
  
 
An overview of the basic architecture is depicted in the following figure.
 
An overview of the basic architecture is depicted in the following figure.
Line 31: Line 31:
  
 
=== ckBTC Ledger ===
 
=== ckBTC Ledger ===
The ckBTC ledger is a [https://dashboard.internetcomputer.org/canister/mxzaz-hqaaa-aaaar-qaada-cai canister], controlled by the NNS (specifically, the [https://dashboard.internetcomputer.org/canister/r7inp-6aaaa-aaaaa-aaabq-cai NNS root canister]), running on the [https://dashboard.internetcomputer.org/subnet/pzp6e-ekpqk-3c5x7-2h6so-njoeq-mt45d-h3h6c-q3mxf-vpeq5-fk5o7-yae pzp6e] subnet. The ckBTC ledger, which complies with the [https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-1 ICRC-1] standard, is responsible for keeping account balances and for transferring ckBTC between accounts. It provides the following functionality:  
+
The ckBTC ledger is a [https://dashboard.internetcomputer.org/canister/mxzaz-hqaaa-aaaar-qaada-cai canister], controlled by the NNS (specifically, the [https://dashboard.internetcomputer.org/canister/r7inp-6aaaa-aaaaa-aaabq-cai NNS root canister]), running on the [https://dashboard.internetcomputer.org/subnet/pzp6e-ekpqk-3c5x7-2h6so-njoeq-mt45d-h3h6c-q3mxf-vpeq5-fk5o7-yae pzp6e] subnet. The ckBTC ledger, which complies with the [https://github.com/dfinity/ICRC-1/blob/main/standards/ICRC-2/README.md ICRC-2] and [https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-1 ICRC-1] standards, is responsible for keeping account balances and for transferring ckBTC between accounts. It provides the following functionality:  
  
 
* It enables the ckBTC minter to mint and burn ckBTC.
 
* It enables the ckBTC minter to mint and burn ckBTC.
 
* It enables the transfer of ckBTC among users.
 
* It enables the transfer of ckBTC among users.
  
As mentioned above, the transfer fee is 0.0000001 ckBTC, the equivalent of 10 Satoshi. The transaction fee is sent to the account with the ckBTC minter as the owner and the subaccount <code>0xfee</code>. All collected fees will eventually be used to cover the cycle cost of the ckBTC minter.  
+
As mentioned above, the transfer fee is 0.0000001 ckBTC, the equivalent of 10 satoshi. The transaction fee is sent to the account with the ckBTC minter as the owner and the subaccount <code>0xfee</code>. All collected fees will eventually be used to cover the cycle cost of the ckBTC minter.  
  
 
The '''minting account''' is the ckBTC minter’s default account; that is, the account derived from the ckBTC minter’s principal ID and the all-zero subaccount.
 
The '''minting account''' is the ckBTC minter’s default account; that is, the account derived from the ckBTC minter’s principal ID and the all-zero subaccount.
Line 50: Line 50:
 
* For a certain principal ID and an optional subaccount, it returns a specific Bitcoin address under the ckBTC minter’s control.
 
* For a certain principal ID and an optional subaccount, it returns a specific Bitcoin address under the ckBTC minter’s control.
 
* Users can inform the ckBTC minter about bitcoins that were sent to an address controlled by the ckBTC minter. If the balance has increased, the ckBTC minter mints ckBTC for the user associated with the Bitcoin address.
 
* Users can inform the ckBTC minter about bitcoins that were sent to an address controlled by the ckBTC minter. If the balance has increased, the ckBTC minter mints ckBTC for the user associated with the Bitcoin address.
* Users can request to get bitcoins back. The ckBTC minter burns the same amount of ckBTC and transfers the corresponding BTC amount minus fees to  to the address provided by the user.
+
* Users can request to get bitcoins back. The ckBTC minter burns the same amount of ckBTC and transfers the corresponding BTC amount minus fees to the address provided by the user.
  
 
The ckBTC minter canister has a few important configuration parameters including:
 
The ckBTC minter canister has a few important configuration parameters including:
  
 
* <code>retrieve_btc_min_amount</code>: This is the minimum ckBTC amount that can be burned and, correspondingly, the minimum BTC amount that can be withdrawn. The parameter is set to 0.001 BTC, or 100,000 satoshi.
 
* <code>retrieve_btc_min_amount</code>: This is the minimum ckBTC amount that can be burned and, correspondingly, the minimum BTC amount that can be withdrawn. The parameter is set to 0.001 BTC, or 100,000 satoshi.
* <code>max_time_in_queue_nanos</code>: Any BTC retrieval request should be kept in a queue for at most this time. Caching requests rather than handling them right away has the advantage that multiple requests can be served in a single transaction, saving Bitcoin miner fees. The parameter is set to 10 minutes, which corresponds to the expected time between Bitcoin blocks.
+
* <code>max_time_in_queue_nanos</code>: Any BTC retrieval request should be kept in a queue for at most this time. Caching requests rather than handling them right away has the advantage that multiple requests can be served in a single transaction, saving Bitcoin miner fees. The parameter is currently set to 5 minutes.
* <code>min_confirmations</code>:  The number of confirmations required for the ckBTC minter to accept a Bitcoin transaction. In particular, the ckBTC minter does not mint ckBTC before a transaction transferring BTC to a Bitcoin address managed by the ckBTC minter reaches this number of transactions. The parameter was initially set to 72 but has been reduced to 12 in the meantime.
+
* <code>min_confirmations</code>:  The number of confirmations required for the ckBTC minter to accept a Bitcoin transaction. In particular, the ckBTC minter does not mint ckBTC before a transaction transferring BTC to a Bitcoin address managed by the ckBTC minter reaches this number of transactions. The parameter is currently set to 6.
 
* <code>kyt_fee</code>: The fee that must be paid for KYT checks. It is currently set to 2000 satoshi.
 
* <code>kyt_fee</code>: The fee that must be paid for KYT checks. It is currently set to 2000 satoshi.
 
The other parameters are self-explanatory and can be found in the [https://github.com/dfinity/ic/blob/master/rs/bitcoin/ckbtc/minter/ckbtc_minter.did ckBTC minter Candid file].
 
The other parameters are self-explanatory and can be found in the [https://github.com/dfinity/ic/blob/master/rs/bitcoin/ckbtc/minter/ckbtc_minter.did ckBTC minter Candid file].
  
=== State Management ===
+
The following sections explain how the ckBTC minter manages its internal state.
  
==== Managed Addresses ====
+
==== Addresses ====
A Bitcoin address that is controlled by the ckBTC minter and has a positive balance is called a ''managed address''. If the balance of a managed address reduces to zero, the address is no longer managed and can be removed from the state.
+
All Bitcoin addresses that are controlled by the ckBTC minter and have a positive balance are part of the ckBTC minter's state. If the balance of such an address reduces to zero, the address is removed from the state. It can be added back if the balance becomes positive again.
 
 
Note that it is possible to increase the balance again, in which case the address becomes managed again.
 
  
 
==== Unspent Transaction Outputs ====
 
==== Unspent Transaction Outputs ====
Once a new Unspent Transaction Output (UTXO) under the control of the ckBTC minter is discovered (using the <code>update_balance</code> function), it is stored internally in a set called <code>available_utxos</code> (defined [https://github.com/dfinity/ic/blob/2348b094d3d27616ee3f049d3048baa1da8d625a/rs/bitcoin/ckbtc/minter/src/state.rs#L305C14-L305C14 here] in the source code).
+
Once a new ''unspent transaction output'' (UTXO) under the control of the ckBTC minter is discovered (using the <code>update_balance</code> function), it is stored internally in a set called <code>available_utxos</code> (defined [https://github.com/dfinity/ic/blob/2348b094d3d27616ee3f049d3048baa1da8d625a/rs/bitcoin/ckbtc/minter/src/state.rs#L305C14-L305C14 here] in the source code).
  
 
All discovered UTXOs remain in this set until a Bitcoin transaction is created to spend one or more of them when retrieving bitcoins. When a transaction is created spending some UTXOs, these UTXOs are removed from the set <code>available_utxos</code> and inserted in the <code>used_utxos</code> field of the <code>SubmittedBtcTransaction</code> struct (defined [https://github.com/dfinity/ic/blob/70d19f16c17f8f42987a46d473ba27705927cdb7/rs/bitcoin/ckbtc/minter/src/state.rs#L87 here] in the source code), which is the internal representation of a Bitcoin transaction.
 
All discovered UTXOs remain in this set until a Bitcoin transaction is created to spend one or more of them when retrieving bitcoins. When a transaction is created spending some UTXOs, these UTXOs are removed from the set <code>available_utxos</code> and inserted in the <code>used_utxos</code> field of the <code>SubmittedBtcTransaction</code> struct (defined [https://github.com/dfinity/ic/blob/70d19f16c17f8f42987a46d473ba27705927cdb7/rs/bitcoin/ckbtc/minter/src/state.rs#L87 here] in the source code), which is the internal representation of a Bitcoin transaction.
Line 75: Line 73:
  
 
==== Transactions ====
 
==== Transactions ====
To provide information about current transactions, outgoing transactions must be cached. A transaction is cached until its inputs are no longer considered unspent based on a large number of confirmations.
+
Every transaction that the ckBTC minter creates has an output that sends the ckBTC minter fee plus the transaction change back to its main BTC address (the P2WPKH address derived from its public key with an empty derivation path).
  
Every transaction that the ckBTC minter creates has an output that sends the ckBTC minter fee plus the transaction change back to its main BTC address (the P2WPKH address derived from its public key with an empty derivation path) using the Bitcoin integration’s <code>bitcoin_get_utxos</code> function.
+
A transaction can be removed from the cache if the transaction output that belongs to the ckBTC minter appears in the returned list of UTXOs of the ckBTC minter’s main BTC address with at least <code>min_confirmations=6</code> confirmations.
  
A transaction can be removed from the cache if the transaction output that belongs to the ckBTC minter appears in the returned list when calling <code>bitcoin_get_utxos</code> with sufficient confirmations for the ckBTC minter’s main BTC address.
+
The ckBTC minter may resubmit transactions, making use of Bitcoin’s request by fee (RBF) mechanism as defined in [https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki BIP-125]. In the case of ckBTC, a resubmission adds a transaction to the cache that spends exactly the same UTXOs as the transaction it replaces. The only difference is that the BTC amount sent to the user(s) is reduced in order to increase the fee.
  
The ckBTC minter may resubmit transactions, making use of Bitcoin’s request by fee (RBF) mechanism as defined in [https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki BIP-125]. In the case of ckBTC, a resubmission adds a transaction to the cache that spends exactly the same UTXOs as the transaction it replaces. The only difference is that the BTC amount sent to the user is reduced in order to increase the fee.
+
BIP-125 states that at most 100 transactions may be evicted from the mempool, i.e., the fee cannot be increased more than 100 times. Moreover, the fee must be increased at least by the minimum relay fee (see minrelaytxfee [https://en.bitcoin.it/wiki/Miner_fees#Relaying here]) of 1 satoshi/vbyte.
  
BIP-125 states that at most 100 transactions may be evicted from the mempool, i.e., the fee cannot be increased more than 100 times. Moreover, the fee must be increased at least by the minimum relay fee (see minrelaytxfee [https://en.bitcoin.it/wiki/Miner_fees#Relaying here]) of 1 Satoshi/vbyte.
+
For example, if we assume a minimum increase of 200 satoshi (the minimum fee for a basic <code>segwit</code> transaction with one input and one output is 192 satoshi and the number per output is always lower than 200 if there are at least as many outputs as inputs), the minimum transfer amount should be at least 20,000 satoshi which equals 0.0002 BTC. When adding a base fee at a large fee rate of 100 satoshi/vbyte and assuming a virtual transaction size of 200 vbyte per output, we get a minimum transfer amount of 0.0004 BTC. Adding a security margin, we  get the minimum retrieval amount of 0.001 BTC that is used for the configuration parameter <code>retrieve_btc_min_amount</code>. The RBF flag is set on every transaction to ensure that they can be updated if necessary.
  
For example, if we assume a minimum increase of 200 Satoshi (the minimum fee for a basic <code>segwit</code> transaction with one input and one output is 192 Satoshi and the number per output is always lower than 200 if there are at least as many outputs as inputs), the minimum transfer amount should be at least 20,000 Satoshi which equals 0.0002 BTC. When adding a base fee at a large fee rate of 100 Satoshi/vbyte and assuming a virtual transaction size of 200 vbyte per output, we get a minimum transfer amount of 0.0004 BTC. Adding a security margin, we  get the minimum retrieval amount of 0.001 BTC that is used for the configuration parameter <code>retrieve_btc_min_amount</code>. The RBF flag is set on every transaction to ensure that they can be updated if necessary.
+
Transactions with <code>min_confirmations=6</code> confirmations or more are considered ''finalized''. The ckBTC minter stores information about finalized transactions forever.
  
 
====Know Your Transaction & Fees====
 
====Know Your Transaction & Fees====
Before UTXOs are accepted, they undergo a know-your-transaction (KYT) check where information about the UTXO is sent to a “KYT canister”, which interacts with KYT service providers, such as Chainalysis, to check if the UTXO is “clean” using HTTPS outcalls.
+
Before UTXOs are accepted, they undergo a know-your-transaction (KYT) check where information about the UTXO is sent to a [[Know-Your-Transaction (KYT) Canister|KYT canister]], which interacts with KYT service providers such as Chainalysis, to check if the UTXO is “clean” using HTTPS outcalls.
  
 
In a similar fashion, there is a KYT check of the destination address when attempting to transfer bitcoins out of the ckBTC minter.
 
In a similar fashion, there is a KYT check of the destination address when attempting to transfer bitcoins out of the ckBTC minter.
Line 97: Line 95:
 
*The KYT fee is 2000 satoshi per KYT check.
 
*The KYT fee is 2000 satoshi per KYT check.
  
The KYT canister supports multiple KYT access key providers, called maintainers, each having a principal ID and (secret) access key to the external KYT service. Since the maintainers pay for their subscriptions, they must be remunerated. To this end, the ckBTC minter maintains a mapping of maintainers to the owed amount, which is the number of KYT checks that were performed with the respective maintainer’s credentials times the KYT fee of 2000 Satoshi.
+
The KYT canister supports multiple KYT access key providers, called maintainers, each having a principal ID and (secret) access key to the external KYT service. Since the maintainers pay for their subscriptions, they must be remunerated. To this end, the ckBTC minter maintains a mapping of maintainers to the owed amount, which is the number of KYT checks that were performed with the respective maintainer’s credentials times the KYT fee of 2000 satoshi.
  
 
Each successful response from the KYT canister contains the principal ID of the maintainer so that the ckBTC minter can update the owed amount correctly. Once every 24 hours, the ckBTC minter pays out the owed amounts in ckBTC to all maintainers.
 
Each successful response from the KYT canister contains the principal ID of the maintainer so that the ckBTC minter can update the owed amount correctly. Once every 24 hours, the ckBTC minter pays out the owed amounts in ckBTC to all maintainers.
Line 107: Line 105:
 
The first step is for the user to determine the Bitcoin address where the user is supposed to transfer bitcoin for the minting process by calling the <code>get_btc_address</code> endpoint. Next, the user transfers the desired BTC amount to this Bitcoin address.  
 
The first step is for the user to determine the Bitcoin address where the user is supposed to transfer bitcoin for the minting process by calling the <code>get_btc_address</code> endpoint. Next, the user transfers the desired BTC amount to this Bitcoin address.  
  
Internally, the tracking works as follows. Since the expected time between Bitcoin blocks is 10 minutes, the ckBTC minter first checks if new unspent transaction outputs (UTXOs) are available for that address with at least the required number of confirmations after <code>10*min_confirmations</code> minutes. If there is at least one new UTXO, tracking stops and the ckBTC minter instructs the ckBTC ledger to mint the same amount of ckBTC tokens into the account derived from the principal ID and the subaccount.
+
Once the transaction has <code>min_confirmations=6</code> confirmations, the user notifies the ckBTC minter to update the balance of the user's account on the ckBTC ledger by calling the <code>update_balance</code> function. The ckBTC minter uses the <code>bitcoin_get_utxos</code> endpoint to retrieve the current list of UTXOs for the Bitcoin address associated with the user. If there are new UTXOs, the ckBTC minter performs the KYT check for the newly discovered UTXOs and then, if the checks are successful, issues a minting transaction to the ckBTC ledger per UTXO, minting the value of the UTXO minus the KYT fee into the user’s account.  
 
 
If no new UTXOs are discovered with sufficiently many confirmations, the ckBTC ledger checks if there are new UTXOs with at least one confirmation. If this is not the case, tracking stops as well. Otherwise, the expected time until the first new UTXO reaches the desired number of confirmations is computed, which is 10 minutes times the difference between the desired number of confirmations and the current number of confirmations. The same process then repeats until ckBTC tokens are minted or tracking stops.
 
 
 
It is evident from this description that it’s possible that tracking may stop before ckBTC tokens are minted, for example, if it takes an unusually long time until the transaction appears in a block. In this case, the endpoint can be invoked again. Alternatively, the user can wait until the transaction has the required number of confirmations and call the <code>update_balance</code> endpoint.
 
  
 
===Technical Details===
 
===Technical Details===
As mentioned above, a call to the <code>update_balance</code> endpoint triggers a call to the Bitcoin canister to get UTXOs for the Bitcoin address associated with the given <code>principal-subaccount</code> pair. Let '''''R''''' denote the set of returned UTXOs. The following pseudo-code illustrates how the UTXOs are processed:<syntaxhighlight lang="rust">
+
As mentioned above, a call to the <code>update_balance</code> endpoint triggers a call to the Bitcoin canister to get UTXOs for the Bitcoin address associated with the given principal-subaccount pair. Let <code>R</code> denote the set of returned UTXOs. The following pseudo-code illustrates how the UTXOs are processed:<syntaxhighlight lang="rust">
 
for utxo in new_utxos(R):        // R = set of received UTXOs in the get_utxos call
 
for utxo in new_utxos(R):        // R = set of received UTXOs in the get_utxos call
     if utxo.value >= KYT_FEE:
+
     if utxo.value >= kyt_fee:
         if utxo in checked_utxos:
+
         if utxo in checked_utxos:
 
             (uuid, state) = checked_utxos.get(utxo)
 
             (uuid, state) = checked_utxos.get(utxo)
 
         else:
 
         else:
 
             (uuid, state, kyt_provider) = kyt_canister.check(utxo).await?
 
             (uuid, state, kyt_provider) = kyt_canister.check(utxo).await?
 
             if state == clean:
 
             if state == clean:
                D = D \ {utxo}
 
                P = P ∪ {utxo}
 
 
                 checked_utxos.set(utxo, (uuid, clean))
 
                 checked_utxos.set(utxo, (uuid, clean))
 
                 owed_kyt_fee[kyt_provider] += KYT_FEE
 
                 owed_kyt_fee[kyt_provider] += KYT_FEE
 
         if state == clean:
 
         if state == clean:
             ckbtc_ledger.mint(utxo.value-KYT_FEE, recipient_account, uuid).await?
+
             ckbtc_ledger.mint(utxo.value-KYT_FEE, recipient_account, uuid).await?
             checked_utxos.remove(utxo)
+
            available_utxos.add(utxo)
 +
             checked_utxos.remove(utxo)
 
         else:
 
         else:
 
             add_to_quarantine_list(utxo)
 
             add_to_quarantine_list(utxo)
Line 134: Line 127:
 
         add_to_ignore_list(utxo)
 
         add_to_ignore_list(utxo)
 
return response with UTXO statuses
 
return response with UTXO statuses
</syntaxhighlight>A UTXO is considered if its value is at least the KYT fee, which is a configuration parameter of the ckBTC minter. UTXOs whose value lie below the KYT fee are added to an ignore list. The additional state <code>checked_utxos</code> is maintained to remember that a UTXO was checked if the state is clean. Once the corresponding amount of ckBTC has been minted, this state can be removed again. If the UTXO is tainted, it is moved to a quarantine list instead. UTXOs in the ignore list and quarantine list remain there.
+
</syntaxhighlight>The function <code>new_utxos</code> extracts the newly discovered UTXOs from <code>R</code>. Details about this function are provided further below.
 +
 
 +
A UTXO is considered if its value is at least <code>kyt_fee</code>. UTXOs with a value lower than the KYT fee are added to an ignore list. The additional state <code>checked_utxos</code> is maintained to remember that a UTXO was checked if the state is clean. Once the corresponding amount of ckBTC has been minted, this state can be removed again. If the UTXO is tainted, it is moved to a quarantine list instead.
 +
 
 +
The function <code>new_utxos</code> filters out all UTXOs in the ignore list, the quarantine list, and the set <code>available_utxos</code>, as well as the UTXOs in any <code>used_utxos</code> list of <code>SubmittedBtcTransaction</code> structs. By contrast, the UTXOs in <code>checked_utxos</code> are not filtered.
 +
 
 +
Note that the implementation uses the map <code>utxos_state_addresses</code> instead of the set <code>available_utxos</code>. For each address, the map contains all UTXOs, including UTXOs already used in outgoing transactions. It is therefore not necessary to parse all <code>SubmittedBtcTransaction</code> structs when using the map because UTXOs that have been used in transactions are already considered.
  
The function <code>new_utxos</code> filters out all UTXOs on the ignore list, the quarantine list, and the sets '''''P''''' and '''''S'''''. On the other hand, the <code>checked_utxos</code> data structure is not considered.
+
UTXOs in the ignore list and quarantine list remain there indefinitely. Mechanisms to enable the owner to transfer the funds in these UTXOs back out may be added in the future.
  
 
==Converting ckBTC to BTC==
 
==Converting ckBTC to BTC==
Line 142: Line 141:
 
The process to convert ckBTC to BTC consists of the following steps:  
 
The process to convert ckBTC to BTC consists of the following steps:  
  
#Transfer request: The user moves the ckBTC that he/she wants to convert to BTC to a special ckBTC withdrawal account under the control of the ckBTC minter and requests a transfer. The destination Bitcoin address undergoes a KYT check. If the check is successful, the request is accepted and put into a queue.
+
#Transfer request: The user makes the desired ckBTC amount available to the ckBTC minter and requests a conversion. The destination Bitcoin address undergoes a KYT check. If the check is successful, the request is accepted and put into a queue.
#Submission: On a heartbeat, the ckBTC minter attempts to submit transactions for validated transfer requests.
+
#Submission: The ckBTC minter periodically attempts to submit transactions for validated transfer requests.
#Finalization: On a heartbeat, the ckBTC minter checks which transactions went through and finalizes these transactions.
+
#Finalization: The ckBTC minter periodically checks which transactions went through and finalizes these transactions.
#Resubmission: The ckBTC minter can resubmit a transaction that has been pending at least for a specific amount of time with a higher fee.
+
#Resubmission: The ckBTC minter can resubmit a transaction that has been pending for at least one day with a higher fee.
 
 
 
The individual parts are discussed in greater detail in the following sections.
 
The individual parts are discussed in greater detail in the following sections.
  
The first step to convert ckBTC to BTC is to transfer the amount to be retrieved to the owner-specific withdrawal account under the ckBTC minter’s control. This step is required because only the ckBTC minter can burn tokens and it can only burn those tokens in one of its accounts. The withdrawal account can be obtained by calling the <code>get_withdrawal_account</code> endpoint.
+
There are two flows to convert ckBTC to BTC. The newer, recommended flow is based on the ICRC-2 standard and requires the user to allow the ckBTC minter to withdraw the desired amount from a user-controlled account by calling <code>icrc2_approve</code> on the ckBTC ledger. Subsequently, the user can call the <code>retrieve_btc_with_approval</code> endpoint to inform the ckBTC minter about the withdrawal intent.  In addition to specifying the withdrawal amount, the Bitcoin address where the withdrawn funds are to be sent must be specified as well.
  
After the user has transferred the desired ckBTC amount to the withdrawal account, the user can call the <code>retrieve_btc</code> endpoint to inform the ckBTC minter about the withdrawal intent. In addition to specifying the withdrawal amount, the Bitcoin address where the withdrawn funds are to be sent must be specified as well.
+
The ckBTC minter performs a KYT check against the targeted Bitcoin address. If the check is successful, the ckBTC minter deducts the KYT fee from the amount to be retrieved and puts the corresponding retrieval request into a queue and checks the status of the queue on a timer.
  
The ckBTC minter first performs a KYT check against the targeted Bitcoin address. If the check is successful, the ckBTC minter instructs the ckBTC ledger to burn the ckBTC in the withdrawal account. Lastly, the ckBTC minter deducts the KYT fee from the amount to be retrieved and puts the corresponding retrieval request into a queue and checks the status of the queue on a heartbeat.
+
If the oldest request has been in the queue for at least 10 minutes or at least 20 retrieval requests have been accumulated, the ckBTC minter creates a single Bitcoin transaction to serve up to 100 retrieval requests as follows:
 
 
If the oldest request has been in the queue for at least 10 minutes or at least 20 retrieval requests have been accumulated at the time of the heartbeat, the ckBTC minter creates a single Bitcoin transaction to serve up to 100 retrieval requests as follows:
 
  
 
#It selects available UTXOs with a total sum of at least the sum in the retrieval requests.
 
#It selects available UTXOs with a total sum of at least the sum in the retrieval requests.
#It constructs a Bitcoin transaction with the selected UTXOs as inputs and an output for each retrieval request plus an additional output for the ckBTC minter’s fee.
+
#It constructs a Bitcoin transaction with the selected UTXOs as inputs and an output for each retrieval request plus an additional output for the ckBTC minter’s fee and the change.
 
# It uses the Bitcoin canister’s fee API to determine an appropriate fee for the transaction, using the median fee rate.
 
# It uses the Bitcoin canister’s fee API to determine an appropriate fee for the transaction, using the median fee rate.
#It distributes the fee evenly among all outputs other than the output for the ckBTC minter’s fee.
+
#It distributes the fee evenly among all outputs other than the output for the ckBTC minter’s fee plus change.
 
#For each input of the transaction, the ckBTC minter invokes the threshold ECDSA functionality (calling the <code>sign_with_ecdsa</code> function) to obtain the required signatures and puts them into the transaction.
 
#For each input of the transaction, the ckBTC minter invokes the threshold ECDSA functionality (calling the <code>sign_with_ecdsa</code> function) to obtain the required signatures and puts them into the transaction.
#Lastly, it sends the Bitcoin transaction by invoking the <code>send_transaction</code> function of the Bitcoin integration API.
+
#Lastly, it sends the Bitcoin transaction by invoking the <code>bitcoin_end_transaction</code> function of the Bitcoin integration API.
  
 
The BTC retrieval process is depicted in the following figure.
 
The BTC retrieval process is depicted in the following figure.
[[File:CkBTC Specification - Withdrawal (simplified)- Version 2.png|alt=|center|thumb|600x600px|Process of converting ckBTC to BTC.]]
+
[[File:CkBTC Specification - Withdrawal ICRC-2 (simplified).png|alt=|center|thumb|600x600px|Process of converting ckBTC to BTC.]]
 +
Note that the amounts in the transfer to the withdrawal account and the retrieval request need not be the same. The <code>retrieve_btc_status_v2</code> endpoint can be used to query the current status of a retrieval request.
  
 +
The other, older mechanism, which is not based on ICRC-2, is summarized here briefly for the sake of completeness. Since the ckBTC minter can only burn ckBTC in an account that it controls, the first step is to transfer the amount to be retrieved to the owner-specific ''withdrawal account'' under the ckBTC minter’s control. After the user has transferred the desired ckBTC amount to the withdrawal account, the user can call the <code>retrieve_btc</code> endpoint, specifying the withdrawal amount and the destination Bitcoin address. The ckBTC minter will then attempt to burn the specified ckBTC amount in the withdrawal account and, if the KYT check succeeds and the destination address is found to be clean, record the retrieval request, which is handled on a timer as before.
  
Note that the amounts in the transfer to the withdrawal account and the retrieval request need not be the same. The <code>retrieve_btc_status</code> endpoint can be used to query the current status of a retrieval request.
+
The advantage of the ICRC-2-based flow is that the ckBTC amount stays with the user until a request is made to retrieve BTC, i.e., the risk that the funds get stuck in the withdrawal account is removed.
 
 
 
===Technical Details===
 
===Technical Details===
 +
As mentioned above, the first step is to approve the ckBTC minter to withdraw the desired ckBTC amount from (one of) the user's accounts. To this end, the user calls icrc2_approve on the ckBTC ledger. The required parameters are <code>spender</code> and <code>amount</code> but there are also several optional parameters such as <code>from_subaccount</code>.
  
====Transfer Request====
+
Subsequently, the user can call <code>retrieve_btc_with_approval</code> on the ckBTC minter with parameters <code>address</code>, specifying the Bitcoin address that should receive the retrieved bitcoins, and <code>amount</code> (plus, optionally, <code>from_subaccount</code>), which causes the ckBTC minter to attempt to transfer the specified amount from the user's account to the minting account. As defined in ICRC-1, transferring tokens to the minting account constitutes a burn operation.
The ckBTC minter can only burn ckBTC under its control. Therefore, the first step is to transfer ckBTC to the ckBTC withdrawal account controlled by the ckBTC minter and associated with the owner of the ckBTC that are to be burned. Specifically, the withdrawal account is a ckBTC ledger account where the principal ID is the ckBTC minter and the subaccount is derived deterministically from the owner’s principal ID.
 
 
 
After this transfer, the user sends a <code>retrieve_btc</code> request, specifying the amount that they would like to retrieve, the Bitcoin address where the funds are to be transferred, and, optionally, a fee level (which have yet to be defined).
 
 
 
Upon receiving such a request, the ckBTC minter first performs a KYT check against the Bitcoin address where funds are supposed to be sent. If the check is successful, the ckBTC minter issues a burn request to the ckBTC ledger, burning the amount in the received request in the user’s ckBTC withdrawal account. If the burn operation is successful, the ckBTC minter creates a transfer request, removing the <code>KYT_FEE</code> from the transfer amount, and appends it to its pending-requests queue.
 
  
Alternatively, if the destination address does not pass the KYT check, a burn request is sent to the ckBTC ledger, burning the <code>KYT_FEE</code> amount.
+
Note that specifying an amount to be retrieved smaller than the minimum retrieval amount (<code>retrieve_btc_min_amount</code>) results in an immediate rejection of the request.
  
Recall that the ckBTC ledger uses the ckBTC minter default account as the minting account. Withdrawal accounts should never resolve to the default minting account because the ckBTC ledger considers the minting account for minting only.
+
If the burn operation fails, the retrieval process is aborted and an error is returned to the user. If the ckBTC tokens are burned successfully, the ckBTC minter performs a KYT check against the Bitcoin address where funds are supposed to be sent. If this check fails, a task is created internally to reimburse the burned amount to the user and an error is returned. On the other hand, if the KYT check returns a result, the steps depend on the classification of the Bitcoin address: if the address is ''clean'', a task to transfer the amount minus the KYT fee to the destination address is created and the user receives the signal that the request was accepted in the form of the block index of the burn operation on the ckBTC ledger. If the address is considered ''tainted'', a task to reimburse the amount ''minus the KYT fee'' is created and a corresponding error message is returned to the user.
  
The following pseudo-code illustrates how the <code>retrieve_btc</code> endpoint works, given the parameters <code>amount</code> and <code>btc_address</code>.<syntaxhighlight lang="rust">
+
The following pseudo-code illustrates how the <code>retrieve_btc_with_approval</code> endpoint works, given the parameters <code>amount</code> and <code>btc_address</code>.<syntaxhighlight lang="rust">
assert(amount >= max(MIN_RETRIEVAL_AMOUNT, KYT_FEE))
+
assert(max(retrieve_btc_min_amount, KYT_FEE) <= amount)
    assert(ckbtc_ledger.balance_of(withdrawal_account).await? >= amount)
+
index = ckbtc_ledger.icrc2_transfer_from(user_account, minting_account, amount).await?
  (uuid, state, kyt_provider) = kyt_canister.check(btc_address).await?

+
(uuid, state, kyt_provider) = kyt_canister.check(btc_address).await
 
+
if uuid = None:
if state == clean:
+
     create_reimbursement(amount, index, btc_address, uuid)
     index = ckbtc_ledger.burn(amount, withdrawal_account).await?
+
    return Error("Failed to perform KYT check")
 +
else:
 
     owed_kyt_fee[kyt_provider] += KYT_FEE
 
     owed_kyt_fee[kyt_provider] += KYT_FEE
     create_request(amount-KYT_FEE, index, btc_address, uuid)
+
     if state == clean:   
else:
+
        create_request(amount-KYT_FEE, index, btc_address, uuid)
    index = ckbtc_ledger.burn(KYT_FEE, withdrawal_account).await?

+
        return index
    owed_kyt_fee[kyt_provider] += KYT_FEE
+
    else:
    return Error("Failed KYT check", index)
+
        create_reimbursement(amount-KYT_FEE, index, btc_address, uuid)
</syntaxhighlight>Note that a failure to perform the KYT check results in a rejected request. Further note that if the KYT check succeeds but the burn transaction fails (regardless of the state of the KYT response), no KYT fee is charged and the request is rejected. A subsequent request with the same parameters will result in another call to the KYT canister again, which is acceptable because multiple identical calls to the KYT service provider do not result in increased fees.
+
        return Error("Tainted destination address")
  
For each retrieval request, the ckBTC minter stores the following data:
+
</syntaxhighlight>For each recorded retrieval request, the ckBTC minter stores the following data:
  
 
*<code>block_index</code>: The block index of the burn operation used to burn the ckBTC. Since the block index is unique, it is used as the request ID.
 
*<code>block_index</code>: The block index of the burn operation used to burn the ckBTC. Since the block index is unique, it is used as the request ID.
Line 204: Line 198:
 
*<code>address</code>: The address where the bitcoins will be sent.
 
*<code>address</code>: The address where the bitcoins will be sent.
 
*<code>received_at</code>: The timestamp when the request was received.
 
*<code>received_at</code>: The timestamp when the request was received.
*<code>fee_level</code>: If no fee level is provided, a medium fee level is used.
+
 
 +
 
 +
Pseudo-code for the older <code>retrieve_btc</code> endpoint whose retrieval flow is based on the concept of withdrawal accounts is shown here, also requiring the parameters <code>amount</code> and <code>btc_address</code>.<syntaxhighlight lang="rust">
 +
assert(amount >= max(retrieve_btc_min_amount, kyt_fee))
 +
    assert(ckbtc_ledger.balance_of(withdrawal_account).await? >= amount)
 +
    (uuid, state, kyt_provider) = kyt_canister.check(btc_address).await?
 +
 
 +
if state == clean:
 +
    index = ckbtc_ledger.icrc1_transfer(withdrawal_account, minting_account, amount).await?
 +
    owed_kyt_fee[kyt_provider] += kyt_fee
 +
    create_request(amount-kyt_fee, index, btc_address, uuid)
 +
    return index
 +
else:
 +
    index = ckbtc_ledger.burn(kyt_fee, withdrawal_account).await?
 +
    owed_kyt_fee[kyt_provider] += kyt_fee
 +
    return Error("Tainted destination address", index)
 +
</syntaxhighlight>Note that while the <code>retrieve_btc</code> endpoint achieves the same result as <code>retrieve_btc_with_approval</code>, it works quite differently internally. For example, the KYT check is performed before the ckBTC tokens are burned. Further note that if the KYT check succeeds but the burn transaction fails (regardless of the state of the KYT response), no KYT fee is charged and the request is rejected, which implies that a subsequent request with the same parameters will result in another call to the KYT canister.
  
 
====Submission====
 
====Submission====
The ckBTC minter uses the heartbeat mechanism to initiate Bitcoin transfers. On a heartbeat, the following steps are carried out:
+
The ckBTC minter uses the [https://internetcomputer.org/docs/current/developer-docs/backend/periodic-tasks timer functionality] to initiate Bitcoin transfers. The following steps are carried out periodically:
  
 
#Check if there is at least one request that is 10 minutes old or there are at least 20 requests in the pending-requests queue. If not, stop.
 
#Check if there is at least one request that is 10 minutes old or there are at least 20 requests in the pending-requests queue. If not, stop.
#Update the balance of the ckBTC minter’s main BTC address (the P2WPKH address derived from its public key with an empty derivation path) using the Bitcoin integration’s <code>bitcoin_get_utxos</code> function.
+
#Update the balance of the ckBTC minter’s main BTC address (the P2WPKH address derived from its public key with an empty derivation path) using the Bitcoin integration’s <code>bitcoin_get_utxos</code> function. Newly discovered UTXOs are added to the set <code>available_utxos</code>.
#Determine the total amount of bitcoins available, which is the sum of all bitcoins in processed UTXOs, plus bitcoins under the ckBTC minter’s main BTC address.
+
#Determine the total amount of bitcoins available, which is the sum of all bitcoins in <code>available_utxos</code>.
#Call the transfer function with the next batch of requests with the same fee level that can be served given the total amount of available bitcoins. A transaction is created, setting the transaction ID for each request in the batch, and sent to the Bitcoin network.
+
#Call the transfer function with the next batch of requests that can be served given the total amount of available bitcoins. A transaction is created, setting the transaction ID for each request in the batch, and sent to the Bitcoin network.
 
#Every request in this batch is then moved to the unconfirmed-transfers queue.
 
#Every request in this batch is then moved to the unconfirmed-transfers queue.
 +
  
 
As evident from the steps outlined above, the transfer function can handle multiple requests at the same time. Handling multiple requests in a single transaction has several advantages over sending individual transactions:
 
As evident from the steps outlined above, the transfer function can handle multiple requests at the same time. Handling multiple requests in a single transaction has several advantages over sending individual transactions:
Line 223: Line 234:
 
Given this set of requests, the next step is to select UTXOs for the transaction.
 
Given this set of requests, the next step is to select UTXOs for the transaction.
  
Since UTXOs are always spent entirely, the difference between the sum of bitcoins in the spent UTXOs and the requested amount must be transferred to a new UTXO as well. The ckBTC minter uses its main BTC address to accumulate “change”.
+
Since UTXOs are always spent entirely, the difference between the sum of bitcoins in the spent UTXOs and the requested amount minus the Bitcoin miner fee must be transferred to a new UTXO as well. As mentioned before, the ckBTC minter uses its main BTC address to accumulate change.
 
 
All UTXOs of its main BTC address are immediately transferred to the corresponding set '''''P''''' of processed UTXOs. The reason is that there is no minting for the balances in these UTXOs.
 
  
 
The transfer function performs the following steps:
 
The transfer function performs the following steps:
  
#First, the transfer function determines the “target”, the total number t (≤ T) of bitcoins that must be transferred out to handle all requests in the given batch.
+
#Determine the target <code>t</code> of bitcoins that must be transferred out to handle all requests in the given batch.
# Then, the function selects UTXOs for the transaction from one or more sets '''''P''''' of its managed addresses. The selected UTXOs are moved from the sets '''''P''''' to the corresponding sets '''''S'''''.
+
#Select UTXOs for the transaction from the set <code>available_utxos</code>.
# Next, the Bitcoin transaction is built and computes the fee based on current Bitcoin fees (using the <code>get_current_fee_percentiles</code> Bitcoin integration endpoint) and the size of the transaction. The fee rate in Satoshi/vbyte is determined by the fee level. Note that the fee is deducted by splitting it evenly among the handled retrieval requests, deducting the same fraction of the total fee from each output that is not returning change and the ckBTC minter fee to the ckBTC minter. The ckBTC minter fee is <code>246*in + 7*out + 52 Satoshi</code>, where in and out denote the number of transaction inputs and outputs, respectively (as specified here).
+
# Build the Bitcoin transaction and compute the Bitcoin miner fee based on current Bitcoin fees using the median fee rate of the return value of <code>bitcoin_get_current_fee_percentiles</code> and the (virtual) size of the transaction. The ckBTC minter fee is <code>246*in + 7*out + 52 satoshi</code>, where <code>in</code> and <code>out</code> denote the number of transaction inputs and outputs, respectively. Note that the fee is split evenly among the handled retrieval requests, deducting the same fraction of the total fee from each output that is not returning change and the ckBTC minter fee to the ckBTC minter.
#Every input is signed using the threshold ECDSA interface.
+
#Sign every input using the threshold ECDSA interface.
#Finally, the transaction is submitted using the <code>send_transaction</code> Bitcoin integration endpoint.
+
#Submit the transaction using the <code>bitcoin_send_transaction</code> endpoint.
 +
#Create a transaction record in the form of a <code>SubmittedBtcTransaction</code> struct. The UTXOs selected for this transaction are moved from the set <code>available_utxos</code> to the <code>used_utxos</code> field in the <code>SubmittedBtcTransaction</code> struct.
  
 
The following UTXO selection algorithm, in pseudo-code, is used:<syntaxhighlight lang="rust">
 
The following UTXO selection algorithm, in pseudo-code, is used:<syntaxhighlight lang="rust">
// t = target (satoshis), P = Union of all sets of processed UTXOs
+
// t = target, A = available_utxos, k = # outputs
// Pre-condition: sum(P) >= t
+
// Pre-condition: sum(A) >= t
select_utxos(t, P):
+
fn select_utxos(t, A, k)
    return greedy(t, P)
 
  
greedy(t, A):               // t = target, A = Available UTXOs
+
    fn greedy(t, A):
    if t ≤ 0 or |A| = 0: return {}
+
        if t ≤ 0 or |A| = 0: return {}
    m := max(A)             // The UTXO with the largest value
+
        m := max(A)   // The UTXO with the largest value
    if m.value < t:
+
        if m.value < t:
        return {m} ∪ greedy(t-m.value, A \ {m})
+
            return {m} ∪ greedy(t-m.value, A \ {m})
 +
        else:
 +
            return min({a ∊ A | a.value ≥ t})
 +
 
 +
    S := greedy(t, A)
 +
    if |A| > UTXOS_COUNT_THRESHOLD:
 +
        A := A \ S
 +
        a := min(A)
 +
        while a ≠ Ø and |S| < k:
 +
            S := S ∪ {a}
 +
        A := A \ {a}
 
     else:
 
     else:
         return min({p ∊ A | p.value ≥ t})
+
         A := A \ S
 +
    return S
 +
</syntaxhighlight>The algorithm has the following properties. If there are at most <code>UTXOS_COUNT_THRESHOLD</code> (currently set to 1,000) UTXOs, the algorithm greedily chooses the smallest number of UTXOs possible for the given target. If a single UTXO suffices, it uses the UTXO that results in the smallest change.
  
</syntaxhighlight>The algorithm has the following properties:
+
If there are more than <code>UTXOS_COUNT_THRESHOLD</code> UTXOs, the UTXOs with the smallest values are added to the greedy solution until the number of inputs <code>k</code> matches the output that the transaction produces. Note that a transaction with <code>k</code> outputs handles <code>k-1</code> retrieval requests as there is always one output that returns the change and fee to the ckBTC minter.
 
 
*It uses the smallest number of UTXOs possible for the given target.
 
*If a single UTXO suffices, it uses the UTXO that results in the smallest change.
 
  
 
Once the transaction is sent, the requests are moved to the unconfirmed-transfers queue.  
 
Once the transaction is sent, the requests are moved to the unconfirmed-transfers queue.  
  
 
====Finalization====
 
====Finalization====
The ckBTC minter uses the heartbeat mechanism to determine the status of sent transactions as well. Specifically, the ckBTC minter periodically wakes up and checks the state of the requests in the unconfirmed-transfers queue.The  ckBTC minter checks the UTXOs of its main account to determine which transactions have sufficiently many confirmations.
+
The ckBTC minter uses the timer mechanism to determine the status of sent transactions as well. Specifically, the ckBTC minter periodically wakes up and checks the state of the requests in the unconfirmed-transfers queue.The ckBTC minter checks the UTXOs of its main account to determine which transactions have sufficiently many confirmations. Concretely, If an output returning the ckBTC fee and change is discovered, the corresponding transaction is considered final and is discarded.
 
 
Such requests can then be stored in a confirmed-transfers queue for some time in order to enable a more long-term look-up of the transaction status.
 
  
 
====Resubmission====
 
====Resubmission====
It is possible that it takes a long time for a transaction to be included in a block. If fees increase significantly for some time, a transaction may even be stuck for a long time or dropped entirely. While the ckBTC minter enforces a reasonable fee through its fee levels, it may still be necessary to issue a transaction again because burned ckBTC are never returned and UTXOs are never freed and are thus stuck when the transaction spending these UTXOs is stuck.
+
It is possible that it takes a long time for a transaction to be included in a block. If fees increase significantly for some time, a transaction may even be stuck for a long time or dropped entirely. While the ckBTC minter uses a reasonable fee, it may still be necessary to issue a transaction again because burned ckBTC are never returned and UTXOs are never freed and are thus stuck when the transaction spending these UTXOs is stuck.
  
The ckBTC minter resubmits a transaction that has not been confirmed within a certain number of hours. Given that the ckBTC minter always waits for many confirmations, the waiting time will likely be multiple hours up to one day, though this parameter has not been defined yet.  
+
The ckBTC minter resubmits a transaction that has not been confirmed within 24 hours.  
  
The new transaction will use the UTXOs reserved for the requests. The transaction will be identical except that the outputs for each user will be reduced due to the increased fee. The new fee is the sum of the old transaction fee plus the size of the transaction (in vbytes) times the minimum relay fee of 1 Satoshi/vbyte plus the ckBTC minter fee again because it must acquire new signatures and send the new transaction to the Bitcoin canister.
+
If a transaction is replaced, the new transaction uses the same UTXOs as the original transaction but the fee is increased. In other words, the transaction is identical except that the outputs for each user is reduced due to the increased fee. The new fee is the sum of the old transaction fee plus the size of the transaction (in <code>vbytes</code>) times the minimum relay fee of 1 satoshi/vbyte plus the ckBTC minter fee again because the ckBTC minter must acquire new signatures and send the new transaction to the Bitcoin canister.
  
 
==Fees==
 
==Fees==
Line 272: Line 288:
 
The ckBTC canisters run on an application subnet and must be self-sustainable. Rather than charging cycles for the endpoints, the ckBTC minter accumulates a surplus of BTC over time. In the future, the ckBTC minter will mint ckBTC to get the total ckBTC supply and the BTC amount under the ckBTC minter's control to match. The ckBTC minter can then trade these extra ckBTC tokens for cycles to charge both the ckBTC minter and ckBTC ledger.
 
The ckBTC canisters run on an application subnet and must be self-sustainable. Rather than charging cycles for the endpoints, the ckBTC minter accumulates a surplus of BTC over time. In the future, the ckBTC minter will mint ckBTC to get the total ckBTC supply and the BTC amount under the ckBTC minter's control to match. The ckBTC minter can then trade these extra ckBTC tokens for cycles to charge both the ckBTC minter and ckBTC ledger.
  
There is a growing surplus of BTC because there is a ckBTC transfer fee of 0.0000001 ckBTC, which is burned, and a ckBTC minter fee when retrieving BTC. The latter is determined as follows:
+
There is a growing surplus of BTC because it receives the ckBTC transaction fees in the subaccount <code>0xfee</code> as mentioned before. Moreover, it collects a fee when bitcoins are retrieved. The formula for the ckBTC minter fee when calling <code>retrieve_btc</code> is determined as follows:
  
*Under the conservative assumption that 1 BTC = 10,000 XDR, 1 billion cycles corresponds to 10 Satoshi (because 1 trillion cycles corresponds to 1 XDR).
+
*Under the conservative assumption that 1 BTC = 10,000 XDR, 1 billion cycles corresponds to 10 satoshi (because 1 trillion cycles corresponds to 1 XDR).
 
* The [https://internetcomputer.org/docs/current/developer-docs/deploy/computation-and-storage-costs/ cost] to obtain a single EDCSA signature is approximately 21.54 billion cycles on a 28-node subnet, whereas sending a Bitcoin transaction costs 5 billion cycles plus 20 million cycles per byte.
 
* The [https://internetcomputer.org/docs/current/developer-docs/deploy/computation-and-storage-costs/ cost] to obtain a single EDCSA signature is approximately 21.54 billion cycles on a 28-node subnet, whereas sending a Bitcoin transaction costs 5 billion cycles plus 20 million cycles per byte.
  
Line 284: Line 300:
 
  < 246*in + 7*out + 52 satoshi.
 
  < 246*in + 7*out + 52 satoshi.
  
The formula <code>246*in + 7*out + 52</code> is used to determine the ckBTC minter’s fee in satoshi. Since every transaction has at least one input and one output, the fee is at least 305 Satoshi.
+
The formula <code>246*in + 7*out + 52</code> is used to determine the ckBTC minter’s fee in satoshi. Since every transaction has at least one input and one output, the fee is at least 305 satoshi.
  
 
This conservative pricing strategy is used to subsidize the other endpoints, which are free of charge. Moreover, while the <code>retrieve_btc</code> endpoint is relatively expensive, the fee is typically still lower than the Bitcoin miner fee.
 
This conservative pricing strategy is used to subsidize the other endpoints, which are free of charge. Moreover, while the <code>retrieve_btc</code> endpoint is relatively expensive, the fee is typically still lower than the Bitcoin miner fee.
  
As mentioned above, there is also a KYT fee (currently 2000 Satoshi) when converting BTC to ckBTC and vice versa.
+
As mentioned above, there is also a KYT fee (currently 2000 satoshi) when converting BTC to ckBTC and vice versa.
  
 
==ckBTC Minter API==
 
==ckBTC Minter API==
Line 297: Line 313:
 
*<code>update_balance</code>: Instructs the ckBTC minter to check the balance of a Bitcoin address and mint ckBTC into the account of the owner.
 
*<code>update_balance</code>: Instructs the ckBTC minter to check the balance of a Bitcoin address and mint ckBTC into the account of the owner.
 
*<code>estimate_withdrawal_fee</code>: Returns a current estimate for the fee to be paid when retrieving a certain BTC amount.
 
*<code>estimate_withdrawal_fee</code>: Returns a current estimate for the fee to be paid when retrieving a certain BTC amount.
* <code>get_deposit_fee</code>: Returns the fee charged when minting ckBTC.
+
* <code>get_deposit_fee</code>: Returns the fee charged when minting ckBTC. This fee currently corresponds to <code>kyt_fee</code>.
 
*<code>get_withdrawal_account</code>: Returns a specific ckBTC account where the owner must transfer ckBTC before being able to retrieve BTC.
 
*<code>get_withdrawal_account</code>: Returns a specific ckBTC account where the owner must transfer ckBTC before being able to retrieve BTC.
*<code>retrieve_btc</code>: Instructs the ckBTC minter to burn a certain ckBTC amount and send the corresponding BTC amount, minus fees, to a provided Bitcoin address.
+
*<code>retrieve_btc_with_approval</code>: Instructs the ckBTC minter to burn a certain ckBTC amount and send the corresponding BTC amount, minus fees, to a provided Bitcoin address.
* <code>retrieve_btc_status</code>: Returns the status of a previous <code>retrieve_btc</code> call.
+
*<code>retrieve_btc</code>: Serves the same purpose as <code>retrieve_btc_with_approval</code> but requires the user to first deposit ckBTC in a specific subaccount of the ckBTC minter.
 +
*<code>retrieve_btc_status_v2</code>: Returns the status of a previous <code>retrieve_btc</code> or <code>retrieve_btc_with_approval</code> call.
 +
*<code>retrieve_btc_status_v2_by_account</code>: Provides the status of all recent <code>retrieve_btc</code> or <code>retrieve_btc_with_approval</code> calls associated with the provided account.
 +
*<code>retrieve_btc_status</code>: Serves the same purpose as <code>retrieve_btc_status_v2</code> but returns less status information. This endpoint is considered ''deprecated''.
 
*<code>get_minter_info</code>: Returns information about the ckBTC minter itself.
 
*<code>get_minter_info</code>: Returns information about the ckBTC minter itself.
 
*<code>get_events</code>: Returns a set of events at the ckBTC minter.
 
*<code>get_events</code>: Returns a set of events at the ckBTC minter.

Latest revision as of 07:32, 21 June 2024

Overview

Chain-key Bitcoin (ckBTC) is a token on the Internet Computer that is backed 1:1 by bitcoin (BTC) such that 1 ckBTC can always be redeemed for 1 BTC and vice versa.

Unlike other tokens pegged to bitcoin, the ckBTC token does not rely on a third-party bridge for the conversion between BTC and ckBTC, making it a substantially more secure alternative to “wrapped” tokens.

While chain-key bitcoin and regular bitcoin have the same value, the advantage of chain-key bitcoin is fast and cheap transfers: A transfer is finalized within a few seconds (a speed-up of roughly three orders of magnitude compared to transfers on the Bitcoin blockchain when waiting for 6 confirmations) and only costs 0.0000001 ckBTC (approximately two orders of magnitude lower than the Bitcoin miner fees).

Architecture

The ckBTC functionality is built upon the Bitcoin integration of the Internet Computer, which makes it possible for canisters to receive, hold, and send bitcoin.

There are two canisters, the ckBTC minter and ckBTC ledger, that together provide the ckBTC functionality. The ckBTC minter mints new ckBTC tokens whenever it receives bitcoin. Likewise, it burns ckBTC tokens whenever an owner of ckBTC tokens requests a withdrawal of bitcoin. The ckBTC minter waits for a large number of confirmations before minting ckBTC and it burns ckBTC before it transfers BTC back to the users.

A best-effort approach is used for the transfer of BTC to the users, i.e., the user cannot get the burned ckBTC back. Rather, the ckBTC minter repeatedly attempts to transfer BTC out until the transfer succeeds. Note that “user” refers to the caller of the functions exposed by the ckBTC minter and ckBTC ledger in the following. A human user or another canister may be behind the function invocations.

The ckBTC ledger is ICRC-2 and ICRC-1 compliant, updating the balance accounts when ckBTC tokens are transferred and executing the mint and burn operations coming from the ckBTC minter.

An overview of the basic architecture is depicted in the following figure.

High-level overview of chain-key Bitcoin.

The figure shows the main flow at a high level of abstraction: Users interact with the ckBTC minter and the ckBTC ledger to convert ckBTC/BTC and transfer ckBTC, respectively. The ckBTC minter interacts with the Bitcoin canister to retrieve information about the Bitcoin network state and send Bitcoin transactions.

The ckBTC minter further interacts with the KYT canister for "know-your-transaction" (KYT) checks. These checks are meant to ensure that the ckBTC minter only uses "clean" bitcoins to back the issued ckBTC tokens and to prevent transferring bitcoins to Bitcoin addresses that are considered to be associated with illicit activity. As such, these KYT checks provide an additional layer of security to ckBTC users.

Technical Details

Bitcoin Addresses

All Bitcoin addresses under the ckBTC minters control are P2WPKH (“pay to witness public key hash”) addresses as defined in BIP-141. These addresses are rendered in the Bech32 format as defined in BIP-173. The main advantage of P2WPKH over the legacy P2PKH address type is that its use results in lower transaction fees.

While the ckBTC minter exclusively uses P2WPKH addresses internally, it supports all currently used address formats (P2PKH, P2SH, P2WPKH, P2TR) for retrievals.

ckBTC Ledger

The ckBTC ledger is a canister, controlled by the NNS (specifically, the NNS root canister), running on the pzp6e subnet. The ckBTC ledger, which complies with the ICRC-2 and ICRC-1 standards, is responsible for keeping account balances and for transferring ckBTC between accounts. It provides the following functionality:

  • It enables the ckBTC minter to mint and burn ckBTC.
  • It enables the transfer of ckBTC among users.

As mentioned above, the transfer fee is 0.0000001 ckBTC, the equivalent of 10 satoshi. The transaction fee is sent to the account with the ckBTC minter as the owner and the subaccount 0xfee. All collected fees will eventually be used to cover the cycle cost of the ckBTC minter.

The minting account is the ckBTC minter’s default account; that is, the account derived from the ckBTC minter’s principal ID and the all-zero subaccount.

The initial supply of the ckBTC ledger is 0. ckBTC tokens are minted only when the ckBTC minter receives bitcoin, ensuring that the ckBTC supply managed by the ckBTC ledger is upper bounded by the amount of bitcoin held by the ckBTC minter.

ckBTC Minter

The ckBTC minter is a canister that is controlled by the NNS and running on the pzp6e subnet as well.

The ckBTC minter is the canister responsible for managing deposited BTC and minting/burning ckBTC based on the amount of deposited BTC. It provides the following functionality:

  • For a certain principal ID and an optional subaccount, it returns a specific Bitcoin address under the ckBTC minter’s control.
  • Users can inform the ckBTC minter about bitcoins that were sent to an address controlled by the ckBTC minter. If the balance has increased, the ckBTC minter mints ckBTC for the user associated with the Bitcoin address.
  • Users can request to get bitcoins back. The ckBTC minter burns the same amount of ckBTC and transfers the corresponding BTC amount minus fees to the address provided by the user.

The ckBTC minter canister has a few important configuration parameters including:

  • retrieve_btc_min_amount: This is the minimum ckBTC amount that can be burned and, correspondingly, the minimum BTC amount that can be withdrawn. The parameter is set to 0.001 BTC, or 100,000 satoshi.
  • max_time_in_queue_nanos: Any BTC retrieval request should be kept in a queue for at most this time. Caching requests rather than handling them right away has the advantage that multiple requests can be served in a single transaction, saving Bitcoin miner fees. The parameter is currently set to 5 minutes.
  • min_confirmations: The number of confirmations required for the ckBTC minter to accept a Bitcoin transaction. In particular, the ckBTC minter does not mint ckBTC before a transaction transferring BTC to a Bitcoin address managed by the ckBTC minter reaches this number of transactions. The parameter is currently set to 6.
  • kyt_fee: The fee that must be paid for KYT checks. It is currently set to 2000 satoshi.

The other parameters are self-explanatory and can be found in the ckBTC minter Candid file.

The following sections explain how the ckBTC minter manages its internal state.

Addresses

All Bitcoin addresses that are controlled by the ckBTC minter and have a positive balance are part of the ckBTC minter's state. If the balance of such an address reduces to zero, the address is removed from the state. It can be added back if the balance becomes positive again.

Unspent Transaction Outputs

Once a new unspent transaction output (UTXO) under the control of the ckBTC minter is discovered (using the update_balance function), it is stored internally in a set called available_utxos (defined here in the source code).

All discovered UTXOs remain in this set until a Bitcoin transaction is created to spend one or more of them when retrieving bitcoins. When a transaction is created spending some UTXOs, these UTXOs are removed from the set available_utxos and inserted in the used_utxos field of the SubmittedBtcTransaction struct (defined here in the source code), which is the internal representation of a Bitcoin transaction.

A UTXO is removed from the ckBTC state when the SubmittedBtcTransaction struct that contains the UTXO is removed from the state.

Transactions

Every transaction that the ckBTC minter creates has an output that sends the ckBTC minter fee plus the transaction change back to its main BTC address (the P2WPKH address derived from its public key with an empty derivation path).

A transaction can be removed from the cache if the transaction output that belongs to the ckBTC minter appears in the returned list of UTXOs of the ckBTC minter’s main BTC address with at least min_confirmations=6 confirmations.

The ckBTC minter may resubmit transactions, making use of Bitcoin’s request by fee (RBF) mechanism as defined in BIP-125. In the case of ckBTC, a resubmission adds a transaction to the cache that spends exactly the same UTXOs as the transaction it replaces. The only difference is that the BTC amount sent to the user(s) is reduced in order to increase the fee.

BIP-125 states that at most 100 transactions may be evicted from the mempool, i.e., the fee cannot be increased more than 100 times. Moreover, the fee must be increased at least by the minimum relay fee (see minrelaytxfee here) of 1 satoshi/vbyte.

For example, if we assume a minimum increase of 200 satoshi (the minimum fee for a basic segwit transaction with one input and one output is 192 satoshi and the number per output is always lower than 200 if there are at least as many outputs as inputs), the minimum transfer amount should be at least 20,000 satoshi which equals 0.0002 BTC. When adding a base fee at a large fee rate of 100 satoshi/vbyte and assuming a virtual transaction size of 200 vbyte per output, we get a minimum transfer amount of 0.0004 BTC. Adding a security margin, we get the minimum retrieval amount of 0.001 BTC that is used for the configuration parameter retrieve_btc_min_amount. The RBF flag is set on every transaction to ensure that they can be updated if necessary.

Transactions with min_confirmations=6 confirmations or more are considered finalized. The ckBTC minter stores information about finalized transactions forever.

Know Your Transaction & Fees

Before UTXOs are accepted, they undergo a know-your-transaction (KYT) check where information about the UTXO is sent to a KYT canister, which interacts with KYT service providers such as Chainalysis, to check if the UTXO is “clean” using HTTPS outcalls.

In a similar fashion, there is a KYT check of the destination address when attempting to transfer bitcoins out of the ckBTC minter.

Both of these KYT checks incur fees, charged in ckBTC:

  • The KYT fee is subtracted from the amount to be minted and the amount transferred out.
  • The KYT fee is 2000 satoshi per KYT check.

The KYT canister supports multiple KYT access key providers, called maintainers, each having a principal ID and (secret) access key to the external KYT service. Since the maintainers pay for their subscriptions, they must be remunerated. To this end, the ckBTC minter maintains a mapping of maintainers to the owed amount, which is the number of KYT checks that were performed with the respective maintainer’s credentials times the KYT fee of 2000 satoshi.

Each successful response from the KYT canister contains the principal ID of the maintainer so that the ckBTC minter can update the owed amount correctly. Once every 24 hours, the ckBTC minter pays out the owed amounts in ckBTC to all maintainers.

Converting BTC to ckBTC

In this section, the process to convert BTC to ckBTC is explained, making use of the ckBTC minter and ckBTC ledger endpoints.

The first step is for the user to determine the Bitcoin address where the user is supposed to transfer bitcoin for the minting process by calling the get_btc_address endpoint. Next, the user transfers the desired BTC amount to this Bitcoin address.

Once the transaction has min_confirmations=6 confirmations, the user notifies the ckBTC minter to update the balance of the user's account on the ckBTC ledger by calling the update_balance function. The ckBTC minter uses the bitcoin_get_utxos endpoint to retrieve the current list of UTXOs for the Bitcoin address associated with the user. If there are new UTXOs, the ckBTC minter performs the KYT check for the newly discovered UTXOs and then, if the checks are successful, issues a minting transaction to the ckBTC ledger per UTXO, minting the value of the UTXO minus the KYT fee into the user’s account.

Technical Details

As mentioned above, a call to the update_balance endpoint triggers a call to the Bitcoin canister to get UTXOs for the Bitcoin address associated with the given principal-subaccount pair. Let R denote the set of returned UTXOs. The following pseudo-code illustrates how the UTXOs are processed:

for utxo in new_utxos(R):        // R = set of received UTXOs in the get_utxos call
    if utxo.value >= kyt_fee:
        if utxo in checked_utxos:
            (uuid, state) = checked_utxos.get(utxo)
        else:
            (uuid, state, kyt_provider) = kyt_canister.check(utxo).await?
            if state == clean:
                checked_utxos.set(utxo, (uuid, clean))
                owed_kyt_fee[kyt_provider] += KYT_FEE
        if state == clean:
            ckbtc_ledger.mint(utxo.value-KYT_FEE, recipient_account, uuid).await?
            available_utxos.add(utxo)
            checked_utxos.remove(utxo)
        else:
            add_to_quarantine_list(utxo)
    else:
        add_to_ignore_list(utxo)
return response with UTXO statuses

The function new_utxos extracts the newly discovered UTXOs from R. Details about this function are provided further below.

A UTXO is considered if its value is at least kyt_fee. UTXOs with a value lower than the KYT fee are added to an ignore list. The additional state checked_utxos is maintained to remember that a UTXO was checked if the state is clean. Once the corresponding amount of ckBTC has been minted, this state can be removed again. If the UTXO is tainted, it is moved to a quarantine list instead.

The function new_utxos filters out all UTXOs in the ignore list, the quarantine list, and the set available_utxos, as well as the UTXOs in any used_utxos list of SubmittedBtcTransaction structs. By contrast, the UTXOs in checked_utxos are not filtered.

Note that the implementation uses the map utxos_state_addresses instead of the set available_utxos. For each address, the map contains all UTXOs, including UTXOs already used in outgoing transactions. It is therefore not necessary to parse all SubmittedBtcTransaction structs when using the map because UTXOs that have been used in transactions are already considered.

UTXOs in the ignore list and quarantine list remain there indefinitely. Mechanisms to enable the owner to transfer the funds in these UTXOs back out may be added in the future.

Converting ckBTC to BTC

The process to convert ckBTC to BTC consists of the following steps:

  1. Transfer request: The user makes the desired ckBTC amount available to the ckBTC minter and requests a conversion. The destination Bitcoin address undergoes a KYT check. If the check is successful, the request is accepted and put into a queue.
  2. Submission: The ckBTC minter periodically attempts to submit transactions for validated transfer requests.
  3. Finalization: The ckBTC minter periodically checks which transactions went through and finalizes these transactions.
  4. Resubmission: The ckBTC minter can resubmit a transaction that has been pending for at least one day with a higher fee.

The individual parts are discussed in greater detail in the following sections.

There are two flows to convert ckBTC to BTC. The newer, recommended flow is based on the ICRC-2 standard and requires the user to allow the ckBTC minter to withdraw the desired amount from a user-controlled account by calling icrc2_approve on the ckBTC ledger. Subsequently, the user can call the retrieve_btc_with_approval endpoint to inform the ckBTC minter about the withdrawal intent. In addition to specifying the withdrawal amount, the Bitcoin address where the withdrawn funds are to be sent must be specified as well.

The ckBTC minter performs a KYT check against the targeted Bitcoin address. If the check is successful, the ckBTC minter deducts the KYT fee from the amount to be retrieved and puts the corresponding retrieval request into a queue and checks the status of the queue on a timer.

If the oldest request has been in the queue for at least 10 minutes or at least 20 retrieval requests have been accumulated, the ckBTC minter creates a single Bitcoin transaction to serve up to 100 retrieval requests as follows:

  1. It selects available UTXOs with a total sum of at least the sum in the retrieval requests.
  2. It constructs a Bitcoin transaction with the selected UTXOs as inputs and an output for each retrieval request plus an additional output for the ckBTC minter’s fee and the change.
  3. It uses the Bitcoin canister’s fee API to determine an appropriate fee for the transaction, using the median fee rate.
  4. It distributes the fee evenly among all outputs other than the output for the ckBTC minter’s fee plus change.
  5. For each input of the transaction, the ckBTC minter invokes the threshold ECDSA functionality (calling the sign_with_ecdsa function) to obtain the required signatures and puts them into the transaction.
  6. Lastly, it sends the Bitcoin transaction by invoking the bitcoin_end_transaction function of the Bitcoin integration API.

The BTC retrieval process is depicted in the following figure.

Process of converting ckBTC to BTC.

Note that the amounts in the transfer to the withdrawal account and the retrieval request need not be the same. The retrieve_btc_status_v2 endpoint can be used to query the current status of a retrieval request.

The other, older mechanism, which is not based on ICRC-2, is summarized here briefly for the sake of completeness. Since the ckBTC minter can only burn ckBTC in an account that it controls, the first step is to transfer the amount to be retrieved to the owner-specific withdrawal account under the ckBTC minter’s control. After the user has transferred the desired ckBTC amount to the withdrawal account, the user can call the retrieve_btc endpoint, specifying the withdrawal amount and the destination Bitcoin address. The ckBTC minter will then attempt to burn the specified ckBTC amount in the withdrawal account and, if the KYT check succeeds and the destination address is found to be clean, record the retrieval request, which is handled on a timer as before.

The advantage of the ICRC-2-based flow is that the ckBTC amount stays with the user until a request is made to retrieve BTC, i.e., the risk that the funds get stuck in the withdrawal account is removed.

Technical Details

As mentioned above, the first step is to approve the ckBTC minter to withdraw the desired ckBTC amount from (one of) the user's accounts. To this end, the user calls icrc2_approve on the ckBTC ledger. The required parameters are spender and amount but there are also several optional parameters such as from_subaccount.

Subsequently, the user can call retrieve_btc_with_approval on the ckBTC minter with parameters address, specifying the Bitcoin address that should receive the retrieved bitcoins, and amount (plus, optionally, from_subaccount), which causes the ckBTC minter to attempt to transfer the specified amount from the user's account to the minting account. As defined in ICRC-1, transferring tokens to the minting account constitutes a burn operation.

Note that specifying an amount to be retrieved smaller than the minimum retrieval amount (retrieve_btc_min_amount) results in an immediate rejection of the request.

If the burn operation fails, the retrieval process is aborted and an error is returned to the user. If the ckBTC tokens are burned successfully, the ckBTC minter performs a KYT check against the Bitcoin address where funds are supposed to be sent. If this check fails, a task is created internally to reimburse the burned amount to the user and an error is returned. On the other hand, if the KYT check returns a result, the steps depend on the classification of the Bitcoin address: if the address is clean, a task to transfer the amount minus the KYT fee to the destination address is created and the user receives the signal that the request was accepted in the form of the block index of the burn operation on the ckBTC ledger. If the address is considered tainted, a task to reimburse the amount minus the KYT fee is created and a corresponding error message is returned to the user.

The following pseudo-code illustrates how the retrieve_btc_with_approval endpoint works, given the parameters amount and btc_address.

assert(max(retrieve_btc_min_amount, KYT_FEE) <= amount)
index = ckbtc_ledger.icrc2_transfer_from(user_account, minting_account, amount).await?
(uuid, state, kyt_provider) = kyt_canister.check(btc_address).await
if uuid = None:
    create_reimbursement(amount, index, btc_address, uuid)
    return Error("Failed to perform KYT check")
else:
    owed_kyt_fee[kyt_provider] += KYT_FEE
    if state == clean:    
        create_request(amount-KYT_FEE, index, btc_address, uuid)
        return index
    else:
        create_reimbursement(amount-KYT_FEE, index, btc_address, uuid)
        return Error("Tainted destination address")

For each recorded retrieval request, the ckBTC minter stores the following data:

  • block_index: The block index of the burn operation used to burn the ckBTC. Since the block index is unique, it is used as the request ID.
  • amount: The total amount of tokens to retrieve. This amount must be at least the minimum retrieval amount as defined above.
  • address: The address where the bitcoins will be sent.
  • received_at: The timestamp when the request was received.


Pseudo-code for the older retrieve_btc endpoint whose retrieval flow is based on the concept of withdrawal accounts is shown here, also requiring the parameters amount and btc_address.

assert(amount >= max(retrieve_btc_min_amount, kyt_fee))
    assert(ckbtc_ledger.balance_of(withdrawal_account).await? >= amount)
    (uuid, state, kyt_provider) = kyt_canister.check(btc_address).await?

if state == clean:
    index = ckbtc_ledger.icrc1_transfer(withdrawal_account, minting_account, amount).await?
    owed_kyt_fee[kyt_provider] += kyt_fee
    create_request(amount-kyt_fee, index, btc_address, uuid)
    return index
 else:
    index = ckbtc_ledger.burn(kyt_fee, withdrawal_account).await?
    owed_kyt_fee[kyt_provider] += kyt_fee
    return Error("Tainted destination address", index)

Note that while the retrieve_btc endpoint achieves the same result as retrieve_btc_with_approval, it works quite differently internally. For example, the KYT check is performed before the ckBTC tokens are burned. Further note that if the KYT check succeeds but the burn transaction fails (regardless of the state of the KYT response), no KYT fee is charged and the request is rejected, which implies that a subsequent request with the same parameters will result in another call to the KYT canister.

Submission

The ckBTC minter uses the timer functionality to initiate Bitcoin transfers. The following steps are carried out periodically:

  1. Check if there is at least one request that is 10 minutes old or there are at least 20 requests in the pending-requests queue. If not, stop.
  2. Update the balance of the ckBTC minter’s main BTC address (the P2WPKH address derived from its public key with an empty derivation path) using the Bitcoin integration’s bitcoin_get_utxos function. Newly discovered UTXOs are added to the set available_utxos.
  3. Determine the total amount of bitcoins available, which is the sum of all bitcoins in available_utxos.
  4. Call the transfer function with the next batch of requests that can be served given the total amount of available bitcoins. A transaction is created, setting the transaction ID for each request in the batch, and sent to the Bitcoin network.
  5. Every request in this batch is then moved to the unconfirmed-transfers queue.


As evident from the steps outlined above, the transfer function can handle multiple requests at the same time. Handling multiple requests in a single transaction has several advantages over sending individual transactions:

  1. Requests can possibly be served more quickly, especially if the ckBTC minter must wait for change to return to its main BTC address.
  2. As the fee for the non-input bytes is shared, the fee per request is slightly lower.
  3. Serving multiple requests at the same time makes denial-of-service attacks where an attacker attempts to keep the pool of usable UTXOs empty with many small requests harder.

Given this set of requests, the next step is to select UTXOs for the transaction.

Since UTXOs are always spent entirely, the difference between the sum of bitcoins in the spent UTXOs and the requested amount minus the Bitcoin miner fee must be transferred to a new UTXO as well. As mentioned before, the ckBTC minter uses its main BTC address to accumulate change.

The transfer function performs the following steps:

  1. Determine the target t of bitcoins that must be transferred out to handle all requests in the given batch.
  2. Select UTXOs for the transaction from the set available_utxos.
  3. Build the Bitcoin transaction and compute the Bitcoin miner fee based on current Bitcoin fees using the median fee rate of the return value of bitcoin_get_current_fee_percentiles and the (virtual) size of the transaction. The ckBTC minter fee is 246*in + 7*out + 52 satoshi, where in and out denote the number of transaction inputs and outputs, respectively. Note that the fee is split evenly among the handled retrieval requests, deducting the same fraction of the total fee from each output that is not returning change and the ckBTC minter fee to the ckBTC minter.
  4. Sign every input using the threshold ECDSA interface.
  5. Submit the transaction using the bitcoin_send_transaction endpoint.
  6. Create a transaction record in the form of a SubmittedBtcTransaction struct. The UTXOs selected for this transaction are moved from the set available_utxos to the used_utxos field in the SubmittedBtcTransaction struct.

The following UTXO selection algorithm, in pseudo-code, is used:

// t = target, A = available_utxos, k = # outputs
// Pre-condition: sum(A) >= t
fn select_utxos(t, A, k)

    fn greedy(t, A):
        if t  0 or |A| = 0: return {}
        m := max(A)    // The UTXO with the largest value
        if m.value < t:
            return {m}  greedy(t-m.value, A \ {m})
        else:
            return min({a  A | a.value  t})

    S := greedy(t, A)
    if |A| > UTXOS_COUNT_THRESHOLD:
        A := A \ S
        a := min(A)
        while a  Ø and |S| < k:
            S := S  {a}
        	A := A \ {a}
    else:
        A := A \ S
    return S

The algorithm has the following properties. If there are at most UTXOS_COUNT_THRESHOLD (currently set to 1,000) UTXOs, the algorithm greedily chooses the smallest number of UTXOs possible for the given target. If a single UTXO suffices, it uses the UTXO that results in the smallest change.

If there are more than UTXOS_COUNT_THRESHOLD UTXOs, the UTXOs with the smallest values are added to the greedy solution until the number of inputs k matches the output that the transaction produces. Note that a transaction with k outputs handles k-1 retrieval requests as there is always one output that returns the change and fee to the ckBTC minter.

Once the transaction is sent, the requests are moved to the unconfirmed-transfers queue.

Finalization

The ckBTC minter uses the timer mechanism to determine the status of sent transactions as well. Specifically, the ckBTC minter periodically wakes up and checks the state of the requests in the unconfirmed-transfers queue.The ckBTC minter checks the UTXOs of its main account to determine which transactions have sufficiently many confirmations. Concretely, If an output returning the ckBTC fee and change is discovered, the corresponding transaction is considered final and is discarded.

Resubmission

It is possible that it takes a long time for a transaction to be included in a block. If fees increase significantly for some time, a transaction may even be stuck for a long time or dropped entirely. While the ckBTC minter uses a reasonable fee, it may still be necessary to issue a transaction again because burned ckBTC are never returned and UTXOs are never freed and are thus stuck when the transaction spending these UTXOs is stuck.

The ckBTC minter resubmits a transaction that has not been confirmed within 24 hours.

If a transaction is replaced, the new transaction uses the same UTXOs as the original transaction but the fee is increased. In other words, the transaction is identical except that the outputs for each user is reduced due to the increased fee. The new fee is the sum of the old transaction fee plus the size of the transaction (in vbytes) times the minimum relay fee of 1 satoshi/vbyte plus the ckBTC minter fee again because the ckBTC minter must acquire new signatures and send the new transaction to the Bitcoin canister.

Fees

The ckBTC canisters run on an application subnet and must be self-sustainable. Rather than charging cycles for the endpoints, the ckBTC minter accumulates a surplus of BTC over time. In the future, the ckBTC minter will mint ckBTC to get the total ckBTC supply and the BTC amount under the ckBTC minter's control to match. The ckBTC minter can then trade these extra ckBTC tokens for cycles to charge both the ckBTC minter and ckBTC ledger.

There is a growing surplus of BTC because it receives the ckBTC transaction fees in the subaccount 0xfee as mentioned before. Moreover, it collects a fee when bitcoins are retrieved. The formula for the ckBTC minter fee when calling retrieve_btc is determined as follows:

  • Under the conservative assumption that 1 BTC = 10,000 XDR, 1 billion cycles corresponds to 10 satoshi (because 1 trillion cycles corresponds to 1 XDR).
  • The cost to obtain a single EDCSA signature is approximately 21.54 billion cycles on a 28-node subnet, whereas sending a Bitcoin transaction costs 5 billion cycles plus 20 million cycles per byte.

Given these numbers, the cost to sign and send a transaction with in inputs and out outputs is

21.54b*in + 5b + tx_size*20m cycles
< 21.54b*in + 5b + (149*in + 35*out + 10)*20m cycles
< 24.52b*in +0.7b*out + 5.2b cycles
< 246*in + 7*out + 52 satoshi.

The formula 246*in + 7*out + 52 is used to determine the ckBTC minter’s fee in satoshi. Since every transaction has at least one input and one output, the fee is at least 305 satoshi.

This conservative pricing strategy is used to subsidize the other endpoints, which are free of charge. Moreover, while the retrieve_btc endpoint is relatively expensive, the fee is typically still lower than the Bitcoin miner fee.

As mentioned above, there is also a KYT fee (currently 2000 satoshi) when converting BTC to ckBTC and vice versa.

ckBTC Minter API

The ckBTC minter provides the following endpoints:

  • get_btc_address: Returns a specific Bitcoin address that the caller can use to obtain ckBTC by sending BTC to this address.
  • update_balance: Instructs the ckBTC minter to check the balance of a Bitcoin address and mint ckBTC into the account of the owner.
  • estimate_withdrawal_fee: Returns a current estimate for the fee to be paid when retrieving a certain BTC amount.
  • get_deposit_fee: Returns the fee charged when minting ckBTC. This fee currently corresponds to kyt_fee.
  • get_withdrawal_account: Returns a specific ckBTC account where the owner must transfer ckBTC before being able to retrieve BTC.
  • retrieve_btc_with_approval: Instructs the ckBTC minter to burn a certain ckBTC amount and send the corresponding BTC amount, minus fees, to a provided Bitcoin address.
  • retrieve_btc: Serves the same purpose as retrieve_btc_with_approval but requires the user to first deposit ckBTC in a specific subaccount of the ckBTC minter.
  • retrieve_btc_status_v2: Returns the status of a previous retrieve_btc or retrieve_btc_with_approval call.
  • retrieve_btc_status_v2_by_account: Provides the status of all recent retrieve_btc or retrieve_btc_with_approval calls associated with the provided account.
  • retrieve_btc_status: Serves the same purpose as retrieve_btc_status_v2 but returns less status information. This endpoint is considered deprecated.
  • get_minter_info: Returns information about the ckBTC minter itself.
  • get_events: Returns a set of events at the ckBTC minter.

The endpoints are discussed in more detail in the ckBTC developer documentation.

See also