diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki new file mode 100644 index 0000000000..b3f5f589b9 --- /dev/null +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -0,0 +1,266 @@ +
+ BIP: ? + Layer: Applications + Title: Timelock-Recovery storage format + Authors: Oren Z+ +== Abstract == + +This document proposes a standard format for saving timelock-recovery plans, to allow different +wallets to generate them, and different services to monitor/execute them. + +== Motivation == + +Pre-signed transactions are one way to create a recovery-plan, for use in case of seed loss or +inheritance. +The most common example is a single pre-signed transaction with an+ Status: Draft + Type: Specification + Discussion: https://groups.google.com/g/bitcoindev/c/K1NpJp9_BYk + Assigned: ? + License: BSD-2-Clause +
nLocktime set to a
+future date, as explained in [[bip-0065.mediawiki|BIP-65]].
+One limitation of this approach is that in the happy-flow scenario, when the seed is not lost,
+and the nLocktime is about to be reached, the user must access their wallet and spend
+one of its UTXOs - in order to revoke the pre-signed transaction and prevent it from being able to
+move the funds with no cancellation period.
+This could be frustrating, for example, for users that split their seed over multiple geographic
+locations.
+
+''Timelock-Recovery plans'' are a way to pre-sign a pair of transactions that eventually move the
+funds to one or more secondary wallets - with a special nSequence relative-locktime
+in the second transaction, so that the user always has a cancellation-period.
+
+Executing and monitoring a ''Timelock-Recovery plan'' thus requires more than broadcasting and
+monitoring a single transaction. It also requires mechanisms for accelerating the first
+transaction (which does not move most funds to the secondary wallet), for checking whether
+the relative-timelock has passed, and a more nuanced handling of reorgs.
+
+This BIP proposes a standard format for exporting ''Timelock-Recovery plans'' from the wallet that
+generated them, and importing them into apps/services for monitoring/execution.
+
+=== Comparison with Script-Based Wallets ===
+
+Script-based wallets are another way to create recovery mechanisms, and can use absolute and
+relative locktimes using OP_CHECKLOCKTIMEVERIFY ([[bip-0065.mediawiki|BIP-65]]) and
+OP_CHECKSEQUENCEVERIFY ([[bip-0112.mediawiki|BIP-112]]).
+For example, we can build a script that allows one main key to spend the funds at any time,
+and a secondary key to spend the funds only in transactions with nLocktime above a certain
+date/block-height, or only in transactions with nSequence above a certain relative
+time-gap/number-of-blocks.
+This makes the secondary key useful only after an absolute date/block-height, or after
+a relative time since the funds were received (each UTXO independently).
+This approach does have some advantages over pre-signed transactions, for example the
+recovery-mechanism automatically applies to new funds received into the wallet.
+
+However, script-based wallets have some disadvantages over a sequence of
+pre-signed transactions:
+
+* Script-based wallets are harder to implement correctly by hardware wallets, and harder to backup properly (i.e. users may forget to backup wallet-descriptors even for basic multisig wallets).
+* As of the time of writing, scripts can limit when secondary-keys can be used, but not how they can be used: if the user doesn't touch the wallets' UTXOs for long-enough time, the secondary key will eventually become useable and could move the funds anywhere. This is true whether we measure the time in absolute terms (OP_CHECKLOCKTIMEVERIFY) or relative terms compared to when the wallets' UTXOs were created (OP_CHECKSEQUENCEVERIFY). This means that even in the happy-flow scenario of an untouched wallet, where no recovery is needed, the user must periodically "renew" the recovery-mechanism by spending the UTXO to a new wallet/address. This may be inconvenient in ultra-cold-storage scenarios (i.e. multisig with main keys hidden in different geographic locations). New opcode suggestions, such as OP_CHECKTEMPLATEVERIFY ([[bip-0119.mediawiki|BIP-119]]) and OP_CHECKCONTRACTVERIFY ([[bip-0443.mediawiki|BIP-443]]), discuss possible recovery-mechanisms in which in order for a secondary key to have full control over the funds, some onchain operations must be performed, with a required time-gap between them - giving the user enough time to revoke the whole process and move the funds elsewhere (assuming they still have the main key and the recovery-mechanism was triggered unintentionally). However, these suggestions are still in the discussion phase and even if ever implemented, their adoption may be slow.
+* New Bitcoiners today typically don't think of such recovery-mechanisms in advance, and start with a P2WPKH wallet. They can pre-sign transactions with this wallet, but to utilize script-based features they would need to create a new wallet and move the funds there - an operation that might seem intimidating for large amounts.
+
+== Specification ==
+
+A ''Timelock-Recovery plan'' consists of two transactions:
+
+* ''Alert Transaction'': A mostly-consolidation transaction that keeps most funds in the original wallet, except for a fee and a small fixed amount that goes to ''anchor-addresses'' - addresses which can be used to accelerate the ''Alert Transaction'' via CPFP. The majority of funds should remain on the original wallet, in a new previously-unused address which we call the ''alert-address''. We use the term ''Alert Transaction'' because monitoring the blockchain and looking for it should alert the user that the recovery-plan has been initiated (intentionally, unintentionally or maliciously).
+* ''Recovery Transaction'': The transaction that moves the funds from the alert-address UTXO from the ''Alert Transaction'' to one or more addresses of secondary wallets (each may receive a different amount). This transaction should have a special nSequence relative-locktime according to the size of cancellation-period requested by the user, following the rules of [[bip-0068.mediawiki|BIP-68]].
+
+With a reliable tool to monitor the blockchain for the ''Alert Transaction''
+or the ''Alert Address'', the user can safely store online backups of the recovery plan's
+JSON file (or, even without a tool, by checking the blockchain manually from time to time).
+If the presigned transactions leak and the ''Alert Transaction'' is broadcast
+unintentionally, the user has the cancellation period (expected to be at least a
+few days) to prevent most funds from moving by sending them to a new address, thereby
+invalidating the ''Recovery Transaction''.
+
+It is important that the ''Alert Transaction'' will be non-malleable (e.g. by using
+[[bip-0140.mediawiki|BIP-140]]).
+If a malleable ''Alert Transaction'' is used, a malicious miner could replace the
+''Alert Transaction'' with a similar transaction with a different txid,
+making the ''Recovery Transaction'' invalid (pointing to a non-existent UTXO).
+
+The nLocktime of both transactions should not be higher than the current
+block height.
+
+The ''anchor-addresses'' mentioned above, which are used for CPFP acceleration, could possibly
+be P2A addresses (described in [[bip-0433.mediawiki|BIP-433]]), or other addresses under the
+participants' control (i.e. addresses from the secondary wallets).
+As of the time of writing, P2A is not widely adopted, and less-technical users may
+struggle using them for CPFP acceleration - so we currently recommend using regular addresses.
+
+=== nSequence calculation ===
+
+Users will specify the cancellation-period in whole days between 2-388.
+
+Following [[bip-0068.mediawiki|BIP-68]], the nSequence can represent a timespan in
+units of 512 seconds, when bit (1 << 22) is set. An example calculation is provided below:
+
+nSequence field bits
+allocated for the relative-locktime, and is not supported.
+
+=== JSON format ===
+
+For simplicity, this BIP proposes that a ''Timelock-Recovery plan'' will be saved as a JSON
+object.
+
+The JSON object will have the following fields:
+
+* kind (mandatory): must be "timelock-recovery-plan".
+* id (mandatory): a non-empty string of up to 100 characters, to represent the plan uniquely (i.e. a UUID, or a server generated ID).
+* name (optional): a name for the plan, decided by the user. A string of up to 200 characters.
+* description (optional): a description for the plan, decided by the user. A string of up to 10,000 characters.
+* created_at (mandatory): an ISO 8601 timestamp of the plan creation time, including timezone offset ('Z' if the timezone is UTC).
+* plugin_version (mandatory): The version of the plugin that generated the plan. A string of up to 100 characters.
+* wallet_version (mandatory): The version of the wallet that generated the plan. A string of up to 100 characters.
+* wallet_name (mandatory): The human-readable name of the wallet app that generated the plan. A string of up to 100 characters.
+* wallet_kind (mandatory): The internal name of the wallet app that generated the plan. A string of up to 100 characters.
+* timelock_days (mandatory): The cancellation period in whole days. A number between 2 and 388.
+* anchor_amount_sats (mandatory): The amount in satoshis sent to each anchor address in the Alert Transaction. We recommend using 600 sats, which is above the dust limit.
+* anchor_addresses (mandatory): An array of up to 10,000 Bitcoin addresses that receive the anchor amount in the Alert Transaction. Each address is a string of up to 100 characters.
+* alert_address (mandatory): The Bitcoin address (mainnet) that receives the majority of funds in the Alert Transaction. A string of up to 100 characters.
+* alert_inputs (mandatory): An array of up to 2439 inputs spent by the Alert Transaction. Each input is a string in the format "txid:vout" where txid is a 64-character lowercase hexadecimal string and vout is a decimal number of up to 6 digits. The maximal length of 2439 is calculated from a standard transaction of 400,000 wu where each input contains at least 41 bytes.
+* alert_tx (mandatory): The raw Alert Transaction in uppercase hexadecimal format. A string of up to 800,000 characters.
+* alert_txid (mandatory): The transaction ID of the Alert Transaction. A 64-character lowercase hexadecimal string.
+* alert_fee (mandatory): The total fee paid by the Alert Transaction in satoshis. A non-negative integer.
+* alert_weight (mandatory): The weight of the Alert Transaction. A positive integer, not higher than 400,000.
+* recovery_tx (mandatory): The raw Recovery Transaction in uppercase hexadecimal format. A string of up to 800,000 characters.
+* recovery_txid (mandatory): The transaction ID of the Recovery Transaction. A 64-character lowercase hexadecimal string.
+* recovery_fee (mandatory): The total fee paid by the Recovery Transaction in satoshis. A non-negative integer.
+* recovery_weight (mandatory): The weight of the Recovery Transaction. A positive integer, not higher than 400,000.
+* recovery_outputs (mandatory): An array of up to 10,000 outputs from the Recovery Transaction. Each output is a tuple containing: [address, amount_sats, label?] where:
+** address is a mandatory Bitcoin address string (up to 100 characters).
+** amount_sats is a mandatory positive integer representing the amount in satoshis.
+** label is an optional string of up to 200 characters.
+* metadata (optional): A string of up to 10,000 characters for additional metadata, for example a digital-signature.
+* checksum (mandatory): A checksum for verifying the integrity of the plan. A string of 8 to 64 characters.
+
+=== Checksum Calculation ===
+Notice that besides the top-level JSON object, all the internal values are either primitive or
+arrays.
+This is intentional, so a conversion of the values to JSON strings will be deterministic.
+
+The checksum is calculated by converting the top-level JSON object to an array of
+[key, value] pairs, sorting the array, stringifying, calculating the
+SHA256 hash of the result in lowercase hexadecimal format, and taking a prefix of at least 8
+characters.
+
+For example:
+wallet_kind, wallet_version and
+plugin_version fields.
+
+Servers may decide to put more restrictions on JSON objects, for example to refuse
+storing very large transactions.
+
+Notice that the raw transactions (alert_tx and recovery_tx) are expected
+to be in uppercase hexadecimal format.
+This is useful for frontend UIs to display them as QR codes, which are more compact when using
+uppercase-only alphanumeric characters.
+
+=== Monitoring Timelock-Recovery Plans ===
+
+Checking whether the Alert Transaction is valid is trivial, via the
+testmempoolaccept RPC call in bitcoin core 0.17+.
+
+However, checking whether the Recovery Transaction is valid is more complex,
+since it depends on a UTXO created by the Alert Transaction.
+
+The testmempoolaccept RPC can receive a list of transactions in which the later
+transactions may depend on earlier transactions - however in our case the
+Recovery Transaction has an nSequence relative-locktime, and therefore
+calling testmempoolaccept 'alert-tx' 'recovery-tx' will fail, claiming that the
+Alert Transaction UTXO is not confirmed (and the required time window has not passed).
+
+We recommend services that want to verify the entire Timelock-Recovery plan to parse
+the Recovery Transaction and check its signatures manually, and reject complicated
+spending scripts. Discovering that the Recovery Transaction is invalid only at the
+time of execution, could lead to funds being locked forever.
+
+== Reference Implementation ==
+
+JSON files can be generated using the Timelock Recovery plugin on
+[https://electrum.org Electrum Wallet]:
+
+https://github.com/spesmilo/electrum/tree/master/electrum/plugins/timelock_recovery
+
+Demo Video: https://drive.google.com/file/d/10uXRouQbH1kz_HC14WnmRnYHa3gPZY8l/preview
+
+Example JSON file:
+
+