== Introduction ==
This BIP proposes new tapscript opcodes, OP_VAULT and
OP_UNVAULT, that add consensus support for a specialized covenant.
These opcodes allow users to enforce a delay period before designated coins may
be spent to an arbitrary destination, with the exception of a prespecified
"recovery" path. At any time prior to final withdrawal, the coins can be spent to
the prespecified path.
=== Motivation ===
The hazard of custodying Bitcoin is well known. Users of Bitcoin must go to
significant effort to secure their private keys, and hope that once provisioned
their custody system does not yield to any number of evolving and
persistent threats. Users have little means to intervene once compromise is
detected. This proposal introduces a mechanism that significantly
mitigates the worst-case outcome of key compromise: coin loss.
Introducing a way to intervene during unexpected spends allows users to
incorporate highly secure key storage methods or unusual fallback strategies
that are only exercised in the worst case, and which may otherwise be
operationally prohibitive. The goal of this proposal is to make this strategy
usable for custodians of any size with minimal complication.
==== Example uses ====
An individual custodying Bitcoin uses the common "single signature and
passphrase" configuration with a hardware wallet. They are concerned about the
risk associated with relying on a single manufacturer for key management as
well as physical access to the hardware, so they generate a new key that is
highly secure, but would be impractical for daily use. For example the key
could be generated in some analog fashion, or on an old computer (with added
entropy) that is then destroyed, with the private key replicated only in paper
form. Or the key could be a 2-of-3 multisig using devices from different
manufacturers. Perhaps the key is geographically distributed.
This individual can use OP_VAULT to make use of the highly secure
key as the unlikely recovery path, while using their existing signing procedure
as the withdrawal trigger key, with a configured spend delay of 1 day. They can
run software on their mobile device that monitors the blockchain for spends of
the vault outpoints.
If the vaulted coins move in an unexpected way, the user can immediately sweep
them to the highly secure recovery path, but spending the coins on a daily
basis works in the same way it did prior to vaulting - aside from the spend
delay.
The recovery key could be any number of things: a 2-of-3 multisig with keys
that live on different devices, a 3-of-5 with socially distributed keys, a
Taproot construction that incorporates one of these methods along with a
time-delayed fallback to an "easier" recovery method, in case the highly secure
key winds up being ''too'' highly secure.
Institutional custodians of Bitcoin would likely use vaults in similar fashion.
===== Avoiding the $5 wrench attack =====
This proposal uniquely provides a solution to the
[https://web.archive.org/web/20230210123933/https://xkcd.com/538/ "$5 wrench attack."] By
setting the spend delay to, say, a week, and using as the recovery path a
script that enforces a longer relative timelock, the owner of the vault can
prove that he is unable to access its value immediately. To the author's
knowledge, this is the only way to configure this defense without rolling
timelocked coins for perpetuity or relying on a trusted third party.
== Goals ==
[[File:bip-VAULT/vaults-Basic.png|frame|center]]
Vaults in Bitcoin have been discussed formally since 2016
([http://fc16.ifca.ai/bitcoin/papers/MES16.pdf MES16]) or earlier. The value of
having a configurable delay period with recovery capability in light of an
unexpected spend has been widely recognized.
The only way to implement vaults given the existing consensus rules, aside from
[https://github.com/revault emulating vaults with large multisig
configurations], is to use presigned transactions created with a one-time-use
key. This approach was first demonstrated
[https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-April/017755.html in 2020].
Unfortunately, this approach has a number of practical shortcomings:
* generating and securely delete ephemeral keys, which are used to emulate the vault covenant, is required,
* amounts and withdrawal patterns must be precommitted to,
* there is a necessity to precommit to an address that the funds must pass through on their way to the final withdrawal target, which is likely only known at unvault time,
* the particular fee management technique or wallet must be decided upon vault creation,
* coin loss follows if a vault address is reused,
* the transaction data that represents the "bearer asset" of the vault must be stored for perpetuity else value is lost, and
* the vault creation ceremony must be performed each time a new balance is to be deposited.
The deployment of a "precomputed" covenant mechanism like
[https://github.com/bitcoin/bips/blob/master/bip-0119.mediawiki OP_CHECKTEMPLATEVERIFY] or
[https://github.com/bitcoin/bips/blob/master/bip-0118.mediawiki SIGHASH_ANYPREVOUT]
would both remove the necessity to use an ephemeral key, since the
covenant is enforced on-chain, and lessen the burden of sensitive data storage,
since the necessary transactions can be generated from a set of compact
parameters. This approach was demonstrated [https://github.com/jamesob/simple-ctv-vault in
2022].
However, the limitations of precomputation still apply: amounts,
destinations, and fee management are all fixed. Funds must flow through a fixed
intermediary to their final destination. Batch operations, which may be vital
for successful recovery during fee spikes or short spend delay, are not possible.
[[File:bip-VAULT/withdrawal-comparison.drawio.png|frame|center]]
Having a "general" covenant mechanism that can encode arbitrary transactional
state machines would allow us to solve these issues, but at the cost of complex
and large scripts that would probably be duplicated many times over in the
blockchain. The particular design and deployment timeline of such a general
framework is also uncertain. There are no sample vault implementations using
these means known to the author.
This proposal intends to address the problems outlined above by
providing a delay period/recovery path use with minimal transactional and
operational overhead using a specialized covenant.
The design goals of the proposal are:
* '''efficient reuse of an existing vault configuration.''''''Why does this support address reuse?''' The proposal doesn't rely on or encourage address reuse, but certain uses are unsafe if address reuse cannot be handled - for example, if a custodian gives its users a vault address to deposit to, it cannot enforce that those users make a single deposit for each address. A single vault configuration, whether the same literal scriptPubKey or not, should be able to “receive” multiple deposits.
* '''batched operations''' for recovery and withdrawal to allow managing multiple vault coins efficiently.
* '''unbounded partial withdrawals''', which allows users to withdrawal partial vault balances without having to perform the setup ceremony for a new vault.
* '''dynamic unvault targets''', which allow the proposed withdrawal target for a vault to be specified at withdrawal time rather than when the vault is first created. This would remove the need for a prespecified, intermediate wallet that only exists to route unvaulted funds to their desired destination.
* '''dynamic fee management''' that, like dynamic targets, defers the specification of fee rates and source to unvault time rather than vault creation time.
These goals are accompanied by basic safety considerations (e.g. not being
vulnerable to pinning) and a desire for concision, both in terms of the number
of outputs created as well as script sizes.
This proposal is designed to be compatible with any future sighash modes (e.g. SIGHASH_GROUP) or fee management strategies (e.g. [https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-September/018168.html transaction sponsors]) that may be introduced. Use of these opcodes will benefit from, but do not strictly rely on, future transaction versions (e.g. [https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2022-September/020937.html v3]) and [https://github.com/instagibbs/bips/blob/ephemeral_anchor/bip-ephemeralanchors.mediawik ephemeral anchors].
== Design ==
=== State machine ===
[[File:bip-VAULT/opvault-flow.drawio.png|frame|center]]
The vault has a number of stages, some of them optional:
* '''vault transaction''': encumbers some coins with an OP_VAULT script invocation.
* '''trigger transaction''': spends one or more OP_VAULT inputs into an OP_UNVAULT output which carries forward the same recovery and delay parameters, along with a commitment to the proposed withdrawal target outputs. This publicly broadcasts the intent to withdrawal to some specific set of outputs. This transaction may have an additional output which allocates some of the vault balance into a partial revault, which simply encumbers the revaulted portion of the value into the same scriptPubKey of the originating OP_VAULT output(s).
* '''withdrawal transaction''': spends OP_UNVAULT trigger inputs into a compatible set of final withdrawal outputs per the target hash, after the trigger inputs have matured per the spend delay. The only authorization for this spend (aside from the relative timelock) is the content hash of the withdrawal outputs.
* '''recovery transaction''': spends one or more OP_VAULT or OP_UNVAULT inputs, which can be done at any time prior to withdrawal confirmation, to the prespecified recovery path. This transaction can optionally require a witness satisfying a specified ''recovery authorization'' script, an optional scriptPubKey gating the initiation of recovery. The use of recovery authorization has certain trade-offs discussed later.
=== Parameters ===
The recovery parameters dictate both where funds can be swept to during a
recovery, and what kind of authorization (if any) is needed to initiate a
recovery. It is specified in the form
The first component commits to the destination that vault funds can be swept to
at any point prior to the finalization of a withdrawal.
The recovery scriptPubKey would usually correspond to a spending script that is
inconvenient to exercise but highly secure.
The second component, the recovery authorization scriptPubKey, is optional. It
is a raw scriptPubKey that, if specified, must be satisfied to allow the input
to be recovered. The benefit of using this parameter will be discussed later.
If this component is not given, the de facto "authorization" is the reveal of
the preimage, i.e. the recovery path.
Vaults which share the same recovery path can always be swept in batch operations,
which is an important practical aspect of managing large numbers of vaults.
The spend delay dictates the duration of blocks or time which must
elapse for the trigger OP_UNVAULT output to be claimable into the
withdrawal target outputs. Encoded as the least significant 23 bits of a
[https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki BIP-0068] style
relative locktime.
'''Trigger key'''
The trigger key, committed to with , is used to
authorize the ''trigger transaction'' - an on-chain declaration to attempt a
withdrawal to a certain set of target outputs.
This functions as the "normal" spending key, but if an attacker obtains access
to this key, the outcome is not catastrophic: any withdrawal attempt can be
interrupted (within the spend delay) and swept to the recovery path.
The trigger key can be an arbitrary scriptPubKey so long as it represents a
valid witness program. OP_VAULT outputs which have the same
recovery params and spend delay can be spent into the same
OP_UNVAULT output for a batched withdrawal process.
An arbitrary set of target withdrawal outputs that is specified as a parameter to OP_UNVAULT as a 32 byte tagged hash. The preimage is a list of destination output scriptPubKeys and amounts. If the trigger remains uncontested -- if it isn't swept to recovery before the spend delay elapses -- the vaulted funds may be spent into a compatible set of target outputs.
=== Fee management ===
A primary consideration of this proposal is how fee management is handled.
Providing dynamic fee management is critical to the operation of a vault, since
* precalculated fees are prone to making transactions unconfirmable by high fee environments, and
* a fee wallet that is prespecified might be compromised or lost before use.
But dynamic fee management can introduce
[https://bitcoinops.org/en/topics/transaction-pinning/ pinning vectors]. Care
has been taken to avoid unnecessarily introducing these vectors when using the new
content-based spending policies that this proposal introduces.
Originally, this proposal had a hard dependency on reformed transaction
nVersion=3 policies, including ephemeral anchors, but it has since been revised
to simply benefit from these changes in policy as well as other potential fee
management mechanisms.
== Specification ==
The tapscript opcodes OP_SUCCESS187 (0xbb) and
OP_SUCCESS188 (0xbc) are claimed to implement the
OP_VAULT and OP_UNVAULT rules, respectively.
=== OP_VAULT evaluation ===
==== Witness program ====
When evaluating OP_VAULT (OP_SUCCESS187,
0xbb), the witness program is pushed onto the stack for the
following result (stack shown top to bottom):
where
* is a 32 byte tagged hash of the scriptPubKey used to authorize the spend of this output into an OP_UNVAULT trigger output
** tagged_hash("VaultTriggerSPK", spk), per BIP-0340.
** If this value is not 32 bytes, script execution when spending this output MUST fail and terminate immediately.
** Because this parameter's scriptPubKey is committed to using a hash, witness version upgradeability for the trigger key is preserved.
* is a CScriptNum-encoded number (up to 4 bytes)
** It is interpreted as the least significant 23 bits of a [https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki BIP-0068] relative timelock.
** If this value does not decode to a valid CScriptNum, script execution when spending this output MUST fail and terminate immediately.
** If this value is less than 0, script execution when spending this output MUST fail and terminate immediately.
* is a variable length data push, consisting of two components:
*# a 32 byte tagged hash, the ''recovery sPK hash'', committing to the scriptPubKey which coins may be recovered to
*#* tagged_hash("VaultRecoverySPK", spk) from the [https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py BIP-0340 reference code].
*# 0 or more bytes that optionally specify a scriptPubKey that needs to be satisfied to authorize the recovery transaction.
*#* This optional parameter changes the allowable structure of recovery transactions.
** If is less than 32 bytes, script execution when spending this output MUST fail and terminate immediately.
** Because the recovery scriptPubKey is committed to with a hash, witness version upgradeability is preserved.
==== Check for recovery ====
After the witness program is parsed, it must be determined whether this input
is being spent towards a recovery. If an output in the spending transaction is
found whose scriptPubKey hashes to the recovery sPK hash (the
first component of ), the interpreter will
evaluate for recovery. Otherwise, the interpreter will evaluate assuming a withdrawal
is being triggered.
In pseudocode:
==== OP_VAULT evaluation for recovery spend ====
* If the recovery output does not have an nValue greater than this input's amount, the script MUST fail and terminate immediately.
* (Deferred) if the recovery output does not have an nValue equal to the sum of all OP_VAULT/OP_UNVAULT inputs with a corresponding recovery sPK hash, the transaction validation MUST fail.'''How do recovery transactions pay for fees?''' If the recovery is unauthorized, fees are attached either via CPFP with an ephemeral anchor or as inputs which are solely spent to fees (i.e. no change output). If the recovery is authorized, fees can be attached in any manner, e.g. unrelated inputs and outputs or CPFP via anchor.
** Note that in the draft implementation, this is facilitated by a "deferred check" which is queued by the script interpreter, but executed after the script interpreter has finished, in other validation code.'''Why does this proposal require a "deferred checks" framework for correct script evaluation?''' The deferred checks framework is an augmentation to execution of the Bitcoin script interpreter. Currently, the validity of each input is checked in an order-indepdendent manner across all inputs in a transaction. Because this proposal allows batching the spend of multiple vault inputs into a single recovery or withdrawal output, we need a mechanism to ensure that all expected values per output can be summed and then checked. This necessitates the introduction of an "aggregating" set of checks which can only be executed after each input's script is evaluated. Note that similar functionality would be required for batch input validation or cross-input signature aggregation.
* The script must FAIL (by policy, not consensus) and terminate immediately if neither'''Why are recovery transactions required to be replaceable?''' In the case of unauthorized recoveries, an attacker may attempt to pin recovery transactions by broadcasting a "rebundled" version with a low fee rate. Vault owners must be able to overcome this with replacement. In the case of authorized recovery, if an attacker steals the recovery authorization key, the attacker may try to pin the recovery transaction during theft. Requiring replaceability ensures that the owner can always raise the fee rate of the recovery transaction, even if they are RBF rule #3 griefed in the process.
*# the input is marked as opt-in replaceable by having an nSequence number less than 0xffffffff - 1, per [https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki BIP-0125], nor
*# the version of the recovery transaction has an nVersion equal to 3.
The stack may now have 0 or more elements. Any items on the stack will be used to verify the recovery authorization witness program, if any.
* If the ''recovery authorization sPK'' is not null:
** If VerifyWitnessProgram(, , ...) fails, the script MUST fail and terminate immediately.
** (This validates that the recovery has been authorized.)
* else (if the recovery is allowed to be unauthorized):
** If the spending transaction has more than two outputs, the script MUST fail and terminate immediately.
** If the spending transaction has two outputs, and the output not the recovery output is not an ephemeral anchor, the script MUST fail and terminate immediately.'''Why can unauthorized recoveries only process a single recovery path?''' Because there is no signature required for unauthorized recoveries, if additional outputs were allowed, someone observing a recovery in the mempool would be able to rebundle and broadcast the recovery with a lower fee rate.
(Note: the above rules imply that if all inputs have a recovery authorization sPK specified, the structure of the recovery transaction is "free form," and the only requirement is that for each OP_VAULT/OP_UNVAULT input, there exists a compatible ''recovery output'' which preserves its full nValue.)
If none of the conditions fail, a single true value (0x01) is left on the stack.
==== OP_VAULT evaluation for withdrawal trigger ====
Else, if it has been determined that the spend is not within a recovery
transaction, it must be evaluated for eligibility as a withdrawal trigger
spend.
===== Witness stack =====
There must be at least 4 items on the stack (shown top to bottom):
If the witness stack consists of fewer than four items the script MUST fail and
terminate immediately.
(Note: in practice, the witness stack will have included the other items necessary to reveal
a witness v1 (or greater) script-path spend, per [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#user-content-Constructing_and_spending_Taproot_outputs BIP-0341]. This is demonstrated in detail in [[#Transaction examples|the transaction examples section]].)
The items on the stack must be validated as follows:
* is a CScriptNum of up to 4 bytes.
** It indicates the vout index of this input's corresponding OP_UNVAULT output.
*** Validation rules for this output are described below.
** If this value does not decode as a valid CScriptNum value, the script MUST fail and terminate immediately.
** If this value does not correspond to a valid output in the spending transaction, the script MUST fail and terminate immediately.
* is a 32 byte data push.
** It is the hash of the proposed withdrawal target output set, defined by target_outputs_hash(outputs) below. Note that this value is duplicated here.'''Why, when spending an OP_VAULT output into a trigger, does the target hash need to be duplicated on the witness stack?''' The target hash exists in the OP_UNVAULT output script, likely behind a taproot pubkey. Its presence is required on the witness stack also so that the expected taproot pubkey for the OP_UNVAULT output can be constructed for comparison.
** If this value is not 32 bytes, the script MUST fail and terminate immediately.
* is a variable length data push.
** It must be the scriptPubKey that is the preimage to the specified in the spent OP_VAULT input.
** If this value does not tagged-hash to supplied by the OP_VAULT parameter, the script MUST fail and terminate immediately.
*** Verify tagged_hash("VaultTriggerSPK", ) ==
** If this value does not correspond to a valid witness program, the script MUST fail and terminate immediately.
* the remaining elements serve as the witness stack to satisfy the witness program.
** If VerifyWitnessProgram(, , ...) fails, the script MUST fail and terminate immediately.
** (This validates that the withdrawal trigger has been authorized.)
===== Transaction outputs validation =====
Once the contents of the witness stack have been parsed and validated, the transaction outputs must be checked.
First, we must check for a ''revault output'': an output in the trigger transaction whose
scriptPubKey exactly matches that of the OP_VAULT input being
spent. Its presence is optional.
For each vault input citing a particular , the output
located at vout[] (the "trigger output") must:
* have as its scriptPubKey a witness program version 1 with a single OP_UNVAULT tapscript, having the internal key lift_x(0x0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0), per the NUMS point mentioned in [https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#user-content-Design BIP-0341].'''Why must the OP_UNVAULT taproot use a NUMS point as its internal key?''' This ensures that an OP_UNVAULT trigger output is verifiable as expected. It also ensures that it is spendable only by the conditions of the vault.
** If the witness program has a version less than 1, the script MUST fail and terminate immediately.
** If the scriptPubKey of the output does not match the expected scriptPubKey, as computed by creating a taproot output using the cited NUMS point and a single tapscript spend condition of the form OP_UNVAULT, the script MUST fail and terminate immediately.
** Witness versions greater than 1 are allowed for upgradeability.
* If there does not exist a revault output in the transaction for this input:
** (deferred) the nValue of the trigger output must equal the sum of all vault inputs which cite it as their corresponding trigger output.
*** If these values are not equal, the script MUST fail and terminate immediately.
* else (if there does exist a revault output for this input):
** (deferred) the nValues of the trigger output and the revault output must sum to the sum of all vault inputs which both
*** cite this trigger output as the trigger-vout-idx and
*** have a scriptPubKey identical to the revault output's.
** If these values are not equal, the script MUST fail and terminate immediately.
If none of the conditions above results in a failure of the script interpreter, the
stack will consist of a single true value (0x01).
The above amount check can be expressed in Python as:
=== OP_UNVAULT evaulation ===
==== Witness program ====
When evaluating OP_UNVAULT (OP_SUCCESS188,
0xbc), the witness program is pushed onto the stack for the
following result (stack shown top to bottom):
where
* is validated exactly as described in [[#witness-program|the above OP_VAULT section]].
* is validated exactly as described in [[#witness-program|the above OP_VAULT section]].
* is a 32 byte data push.
** If it is not equal to 32 bytes, the script MUST fail and terminate immediately.
==== Check for recovery ====
A check is performed to determine if this input is being spent within the context of
a recovery transaction, exactly as in [[#check-for-recovery|the OP_VAULT evaluation described above]].
==== OP_UNVAULT evaluation for recovery spend ====
This is identical to the [[#op_vault-evaluation-for-recovery-spend|OP_VAULT case described above]].
==== OP_UNVAULT evaluation for withdrawal ====
When spending an OP_UNVAULT input into a withdrawal target, no witness stack is required.
* is used to check whether the withdrawal of the input has matured.
** If the input's relative timelock check, as described in [https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki BIP-0112] (using this value as "the top item on the stack") fails, the script MUST fail and terminate immediately.
*** The same CheckSequence() code path is used as for [https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki BIP-0112].
* The transaction outputs are then checked to verify that the withdrawal outputs are as expected.
** If target_outputs_hash(spending_tx.vout) != per the algorithm defined below, the script MUST fail and terminate immediately.