From b76f390a94e62a031c2391aa53ab61934002730f Mon Sep 17 00:00:00 2001 From: Oren Date: Sun, 28 Dec 2025 22:47:00 +0200 Subject: [PATCH 01/13] new bip: timelock recovery storage format --- ...timelock-recovery-storage-format.mediawiki | 225 ++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 bip-timelock-recovery-storage-format.mediawiki diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki new file mode 100644 index 0000000000..fe811c41f8 --- /dev/null +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -0,0 +1,225 @@ +
+  BIP: ?
+  Layer: Applications
+  Title: Timelock-Recovery storage format
+  Author: Oren 
+  Status: Draft
+  Type: Process
+  Assigned: ?
+  License: BSD-2-Clause
+
+ +== 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 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. + +== 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 it should alert the user that the recovery-plan has been triggered, giving them a limited time to prevent the majority of the funds from moving to the secondary wallets. +* ''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]]. + +Both transactions are expected to have an nVersion of at least 2, and an +nLocktime not higher than the current block height. +Both transactions should be non-malleable, as defined in [[bip-0062.mediawiki|BIP-62]]. + +=== 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: + + +n_sequence = (1 << 22) | round(cancellation_period_days * 24 * 60 * 60 / 512) + + +Users should be notified that the cancellation-period is not guaranteed to be exact (due to miners' +manipulation of block-timestamps). + +Less than 2 days of cancellation-period and partial-days are not supported, as they are not useful. + +More than 388 days of cancellation-period will overflow the 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 10,000 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. +* 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. +* 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. +* 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: + +const checksumData = new TextEncoder().encode( + JSON.stringify(Object.entries(recoveryPlanJson).sort()), +); +const checksum = new Uint8Array(await crypto.subtle.digest('SHA-256', checksumData)); +const checksumHex = Array.from(checksum).map(b => b.toString(16).padStart(2, '0')).join('').slice(0, 8); + + +Checksum hex string should be at least 8 characters long. Wallets may choose to use a longer +checksum. + +== Rationale == + +The JSON object will contain the raw transactions, in addition to other information - some of +which could technically be extracted from the raw transactions. This is intentional, to let +frontend UIs display the plan before uploading it to any service, without the need for +complicated parsing in the frontend. + +Backend services that receive the JSON object for monitoring/execution are expected to validate +that the information is consistent with the raw transactions. + +Also, if some wallet apps did not implement the specifications correctly, the services could +write custom code based on the 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: + + +{ + "kind": "timelock-recovery-plan", + "id": "exported-692452189b301b561ed57cbe", + "name": "Recovery Plan ac300e72-7612-497e-96b0-df2fdeda59ea", + "description": "RITREK APP 1.1.0: Trezor Account #1", + "created_at": "2025-11-24T12:39:53.532Z", + "plugin_version": "1.0.1", + "wallet_version": "1.0.1", + "wallet_name": "RITREK Service", + "wallet_kind": "RITREK BACKEND", + "timelock_days": 2, + "anchor_amount_sats": 600, + "anchor_addresses": [ + "bc1qnda6x2gxdh3yujd2zjpsd7qzx3awxmlaf9wwlk" + ], + "alert_address": "bc1qj0f9sjenwyjs0u7mlgvptjp05z3syzq7mru3ep", + "alert_inputs": [ + "a265a485df4c6417019b91379257eb387bceeda96f7bb6311794b8ed358cf104:0", + "2f621c2151f33173983133cbc1000e3b603b8a18423b0379feffe8513171d5d3:0" + ], + "alert_tx": "0200000000010204F18C35EDB8941731B67B6FA9EDCE7B38EB579237919B0117644CDF85A465A20000000000FDFFFFFFD3D5713151E8FFFE79033B42188A3B603B0E00C1CB3331987331F351211C622F0000000000FDFFFFFF0258020000000000001600149B7BA329066DE24E49AA148306F802347AE36FFD205600000000000016001493D2584B33712507F3DBFA1815C82FA0A302081E02483045022100DCDBAE77C35EB4A0B3ED0DE5484206AB6B07041BE99B2BBAF0243C125916523C0220396959C3C52B2B1F9E472AEEE7C5D9540531B131C3221DE942754C6D0941397D012103C08FF3ADBA14B742646572BCA6F07AEB910666FB28E4DDDC40E33755E7C869D30248304502210089084472FDA3CF82D6ABC11BF1A5E77C9B423617C8B840F58C02746035B3BA6302203942AA1FA13F952F49FB114D48130A9AAF70151E7D09036D15734DB1F41A8B6001210397064EDED7DAD7D662290DC2847E87C5C27DA8865B89DDB58FDE9A006BA7DB3900000000", + "alert_txid": "f1413fedadaf30697820bcd8f6a393fcc73ea00a15bea3253f89d5658690d2f7", + "alert_fee": 231, + "alert_weight": 834, + "recovery_tx": "02000000000101F7D2908665D5893F25A3BE150AA03EC7FC93A3F6D8BC20786930AFADED3F41F101000000005201400001A6550000000000001600149B7BA329066DE24E49AA148306F802347AE36FFD0247304402204AFF87C2127F5697F300C6522067A8D5E5290CA8D140D2E5BCEF4A36606C5FE5022056673BEC5BB459DFFBD4D266EE95AEF0D701383ED80BD433A02C3C486A826D76012102774DBCD59F2D08EFF718BC09972ADC609FBC31C26B551B3E4EA30A1D43EEDB9700000000", + "recovery_txid": "bc304610e8f282036345e87163d4cba5b16488a3bf2e4d738379d7bda3a0bca3", + "recovery_fee": 122, + "recovery_weight": 437, + "recovery_outputs": [ + [ + "bc1qnda6x2gxdh3yujd2zjpsd7qzx3awxmlaf9wwlk", + 21926, + "My Backup Wallet" + ] + ], + "metadata": "sig:825d6b3858c175c7fc16da3134030e095c4f9089c3c89722247eeedc08a7ef4f", + "checksum": "92f8b3da" +} + + +== Copyright == + +This document is licensed under the 2-clause BSD license. From ccb0dd838e80ef6d9ed01554e0e9a58cc51b3507 Mon Sep 17 00:00:00 2001 From: Oren Date: Fri, 2 Jan 2026 21:26:26 +0200 Subject: [PATCH 02/13] Comparison with Script-Based Wallets --- ...timelock-recovery-storage-format.mediawiki | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki index fe811c41f8..14bdf5583c 100644 --- a/bip-timelock-recovery-storage-format.mediawiki +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -39,6 +39,27 @@ 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_VAULT ([[bip-0345.mediawiki|BIP-345]]), 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: From f1fe10e84723c9ff046311992870fcdc34ee20e3 Mon Sep 17 00:00:00 2001 From: Oren <115847146+oren-z0@users.noreply.github.com> Date: Tue, 27 Jan 2026 22:02:45 +0200 Subject: [PATCH 03/13] Type is Specification Co-authored-by: Mark "Murch" Erhardt --- bip-timelock-recovery-storage-format.mediawiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki index 14bdf5583c..6855fe5867 100644 --- a/bip-timelock-recovery-storage-format.mediawiki +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -4,7 +4,7 @@ Title: Timelock-Recovery storage format Author: Oren Status: Draft - Type: Process + Type: Specification Assigned: ? License: BSD-2-Clause From a581d404d76066a1ccaefac0941ad3b95c86a59e Mon Sep 17 00:00:00 2001 From: Oren <115847146+oren-z0@users.noreply.github.com> Date: Tue, 27 Jan 2026 22:03:10 +0200 Subject: [PATCH 04/13] Change Authors to a single Author Co-authored-by: Mark "Murch" Erhardt --- bip-timelock-recovery-storage-format.mediawiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki index 6855fe5867..693c655b33 100644 --- a/bip-timelock-recovery-storage-format.mediawiki +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -2,7 +2,7 @@ BIP: ? Layer: Applications Title: Timelock-Recovery storage format - Author: Oren + Authors: Oren Status: Draft Type: Specification Assigned: ? From fe83fc5a16f3821ef93a2bb4270c75258eab9132 Mon Sep 17 00:00:00 2001 From: Oren Date: Tue, 27 Jan 2026 23:08:42 +0200 Subject: [PATCH 05/13] Replace OP_VAULT mention with OP_CHECKCONTRACTVERIFY --- bip-timelock-recovery-storage-format.mediawiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki index 693c655b33..f8317088d1 100644 --- a/bip-timelock-recovery-storage-format.mediawiki +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -57,7 +57,7 @@ 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_VAULT ([[bip-0345.mediawiki|BIP-345]]), 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. +* 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 == From f5743f58dfa0a09863ee0709bb73226cba4f7c53 Mon Sep 17 00:00:00 2001 From: Oren Date: Wed, 28 Jan 2026 00:03:00 +0200 Subject: [PATCH 06/13] Only the Alert Transaction needs to be non-malleable --- bip-timelock-recovery-storage-format.mediawiki | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki index f8317088d1..01166a3fc8 100644 --- a/bip-timelock-recovery-storage-format.mediawiki +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -67,9 +67,14 @@ 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 it should alert the user that the recovery-plan has been triggered, giving them a limited time to prevent the majority of the funds from moving to the secondary wallets. * ''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]]. -Both transactions are expected to have an nVersion of at least 2, and an -nLocktime not higher than the current block height. -Both transactions should be non-malleable, as defined in [[bip-0062.mediawiki|BIP-62]]. +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. === nSequence calculation === From 76bca314e29efbbfbe0eece49f271dd0c12ed99a Mon Sep 17 00:00:00 2001 From: Oren Date: Wed, 28 Jan 2026 00:23:56 +0200 Subject: [PATCH 07/13] Adding discussion link --- bip-timelock-recovery-storage-format.mediawiki | 1 + 1 file changed, 1 insertion(+) diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki index 01166a3fc8..bb74c1c3f4 100644 --- a/bip-timelock-recovery-storage-format.mediawiki +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -5,6 +5,7 @@ Authors: Oren Status: Draft Type: Specification + Discussion: https://groups.google.com/g/bitcoindev/c/K1NpJp9_BYk Assigned: ? License: BSD-2-Clause From 4cbfb1e02474d781ee8a2117ab7e661bda3e05b3 Mon Sep 17 00:00:00 2001 From: Oren Date: Wed, 28 Jan 2026 00:29:57 +0200 Subject: [PATCH 08/13] limiting the transactions weight This is important in order to prevent users from creating recovery-plans that are hard to propagate. --- bip-timelock-recovery-storage-format.mediawiki | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki index bb74c1c3f4..dac8a0dce2 100644 --- a/bip-timelock-recovery-storage-format.mediawiki +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -120,11 +120,11 @@ The JSON object will have the following fields: * 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. +* 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. +* 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. From a18dd7373bdb168e7398a59561eadb7148285202 Mon Sep 17 00:00:00 2001 From: Oren Date: Wed, 28 Jan 2026 15:23:44 +0200 Subject: [PATCH 09/13] Explain anchor-addresses --- bip-timelock-recovery-storage-format.mediawiki | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki index dac8a0dce2..3790377888 100644 --- a/bip-timelock-recovery-storage-format.mediawiki +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -77,6 +77,12 @@ 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 +particapents' 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. From 2396392a629ac0ca6c1229d4e09d8272485a62f0 Mon Sep 17 00:00:00 2001 From: Oren <115847146+oren-z0@users.noreply.github.com> Date: Thu, 29 Jan 2026 23:36:46 +0200 Subject: [PATCH 10/13] fix typo Co-authored-by: Mark "Murch" Erhardt --- bip-timelock-recovery-storage-format.mediawiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki index 3790377888..7cd8e493eb 100644 --- a/bip-timelock-recovery-storage-format.mediawiki +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -79,7 +79,7 @@ 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 -particapents' control (i.e. addresses from the secondary wallets). +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. From 5538fb42f72c5f2d9e1b0458e08c724a56aad99a Mon Sep 17 00:00:00 2001 From: Oren Date: Thu, 29 Jan 2026 23:38:25 +0200 Subject: [PATCH 11/13] add surname initial to author name --- bip-timelock-recovery-storage-format.mediawiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki index 7cd8e493eb..9d8ebb5883 100644 --- a/bip-timelock-recovery-storage-format.mediawiki +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -2,7 +2,7 @@ BIP: ? Layer: Applications Title: Timelock-Recovery storage format - Authors: Oren + Authors: Oren Z Status: Draft Type: Specification Discussion: https://groups.google.com/g/bitcoindev/c/K1NpJp9_BYk From 38acc9dd31da71e819ba63fa62ec7ce5efeac5f2 Mon Sep 17 00:00:00 2001 From: Oren Date: Fri, 30 Jan 2026 00:04:25 +0200 Subject: [PATCH 12/13] Explain unintentional initiation of rrecovery-plan. --- bip-timelock-recovery-storage-format.mediawiki | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki index 9d8ebb5883..46230ed1b1 100644 --- a/bip-timelock-recovery-storage-format.mediawiki +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -65,9 +65,17 @@ pre-signed transactions: 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 it should alert the user that the recovery-plan has been triggered, giving them a limited time to prevent the majority of the funds from moving to the secondary wallets. +* ''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 From dc80bae422fea0246324eab9ccd7057274c3d15d Mon Sep 17 00:00:00 2001 From: Oren Date: Fri, 30 Jan 2026 14:18:52 +0200 Subject: [PATCH 13/13] limit alert_inputs length to 2439 --- bip-timelock-recovery-storage-format.mediawiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bip-timelock-recovery-storage-format.mediawiki b/bip-timelock-recovery-storage-format.mediawiki index 46230ed1b1..b3f5f589b9 100644 --- a/bip-timelock-recovery-storage-format.mediawiki +++ b/bip-timelock-recovery-storage-format.mediawiki @@ -130,7 +130,7 @@ The JSON object will have the following fields: * 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 10,000 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. +* 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.