Covenants in Elements Alpha
Elements

Covenants in Elements Alpha

noc@blockstream.com

Bitcoin lets users choose rules to control how their funds can be spent and have them automatically enforced by the network. This programmatic facility enables users to set their own policies, from simple ones like multisignature to complex ones like trustless exchange. However, some policies do not currently have an obvious expression in the Bitcoin system. In this post we’ll talk about one such example, covenants, and describe how they can be implemented with a small extension to the Bitcoin script language that already exists in Elements.

A financial covenant is an agreement that restricts how funds (typically loans) may be used. The Bitcoin scripting language can be programmed to allow authorization schemes restricting who is allowed to create transactions spending particular funds. However, any party that is able to satisfy the authorization requirements is allowed to create any transaction with those funds. With covenants, one can create scripts that, not only enforce authorization schemes, but can also restrict the sorts of transactions that the funds can be spend with. For example, we can use the Möser-Eyal-Sirer vault to time lock transactions and detect malicious access to one’s funds.

Like Bitcoin, the Elements Alpha script language does not have direct access to the transaction data. Transaction data is only accessible indirectly through the OP_CHECKSIG and OP_CHECKSIGVERIFY operations. These operations take an elliptic curve public key and digital signature as inputs along with a signature hash type. They compute a double SHA-256 hash of the transaction data, modified in accordance with the specified signature hash type, and then perform a digital signature verification of the doubly hashed message data with the digital signature and public key. If the operation is successful, OP_CHECKSIG returns a value of 1 on the stack, otherwise it returns 0.

Elements Alpha’s new OP_CHECKSIGFROMSTACK and OP_CHECKSIGFROMSTACKVERIFY operations takes three inputs: an elliptic curve public key, a message, and a digital signature. The operations perform a single SHA-256 hash of the message and then perform a digital signature verification of the hashed message data with the digital signature and public key. If the operation is successful, OP_CHECKSIGFROMSTACK returns a value of 1 on the stack, otherwise it returns 0.

The trick to creating a covenant in Elements Alpha is using the fact that a successful OP_CHECKSIG operation means that the public key and digital signature together form a commitment to the transaction data. If the same public key and digital signature are used in a successful OP_CHECKSIGFROMSTACK operation then the message passed into OP_CHECKSIGFROMSTACKmust be the same as the transaction data. Using OP_CHECKSIG and OP_CHECKSIGFROMSTACK together, we create a cryptographic guarantee that the message passed to OP_CHECKSIGFROMSTACK is the signed transaction data.

Covenant Construction

Consider the following script:

  1. OP_OVER OP_SHA256 <pubKey>
  2. 2 OP_PICK 1 OP_CAT OP_OVER
  3. OP_CHECKSIGVERIFY
  4. OP_CHECKSIGFROMSTACKVERIFY

Suppose two items are initially on the stack.

<signature>
<sigTransactionData>

After step 1, we have hashed the <sigTransactionData> once with SHA-256, and pushed a public key onto the stack.

<pubKey>
<sha256(sigTransactionData)>
<signature>
<sigTransactionData>

After step 2, we have copied the signature and public key onto the front of the stack. The signature has byte 0x01 appended to it. This is the code for SIGHASH_ALL hash type.  It specifies that the signed transaction data contain all inputs and outputs.

<pubKey>
<signature;SIGHASH_ALL>
<pubKey>
<sha256(sigTransactionData)>
<signature>
<sigTransactionData>

The OP_CHECKSIGVERIFY of step 3 checks that a valid signature has been provided and proves that the transaction has been authorized with the private key.

<pubKey>
<sha256(sigTransactionData)>
<signature>
<sigTransactionData>

The OP_CHECKSIGFROMSTACKVERIFY of step 4 uses the same pubKey and signature. This operation performs a another SHA-256 and does a digital signature validation on the doubly hashed <sigTransactionData>. Because we are using exactly the same pubKey and signature that we used for the previous OP_CHECKSIGVERIFY operation, this OP_CHECKSIGFROMSTACKVERIFY can only succeed if <sigTransactionData> is identical to message that was checked during step 3’s OP_CHECKSIGVERIFY operation.

<sigTransactionData>

At this point in the script we are assured that <sigTransactionData> is an exact copy of the signed transaction data. With full access to the transaction data we can add further script operations to enforce a covenant. We can restrict the number of outputs. We can restrict the values of the outputs. We can restrict the scripts of the outputs.

This technique for covenants comes with some limitations. In Elements Alpha script, each stack item is limited to 520 bytes. This means that we can only create covenants for <sigTransactionData> that is within this limit. However, we can create interesting covenants within this limit.

Recursive Covenants

In this section we will build a trivial recursive covenant. This covenant will restrict transactions to only ones that spend to a single output whose output script is the same as the input script. This covenant is a toy, but illustrates the basic recipe for building a recursive covenant. Later we will build more interesting covenants.

Rather than putting the whole signed transaction data on the stack and parsing it, this script will assemble the transaction data from pieces. The script than can impose conditions on each piece to enforce the covenant. It is important the enforce that each piece of data is of a known fixed size, otherwise the script could be attacked by passing extra long or extra short pieces causing the script to think it is parsing one field of the transaction data when it is actually parsing another.

Below is the scriptPubKey for our trivial covenant.

  1. <0x0100000001>
  2. OP_SWAP OP_SIZE 36 OP_NUMEQUALVERIFY OP_CAT
  3. <0x00> OP_CAT
  4. OP_SWAP OP_SIZE 32 OP_NUMEQUALVERIFY OP_CAT
  5. <0x00005f> OP_CAT
  6. 2 OP_PICK OP_SIZE 95 OP_NUMEQUALVERIFY OP_CAT
  7. <0xffffffff0100> OP_CAT
  8. OP_SWAP OP_SIZE 32 OP_NUMEQUALVERIFY OP_CAT
  9. <0x0000> OP_HASH256 OP_CAT
  10. <0x17a914> OP_CAT
  11. OP_SWAP OP_HASH160 OP_CAT
  12. <0x870000000001000000> OP_CAT
  13. OP_SHA256
  14. 1 OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT
  15. OP_DUP
  16. 2 OP_ROLL 3 OP_PICK
  17. OP_CHECKSIGFROMSTACKVERIFY
  18. 1 OP_CAT OP_SWAP
  19. OP_CHECKSIG

The corresponding scriptSig to spend it is

  1. <recoveredPubKey>
  2. <script>
  3. <valueOut>
  4. <valueIn>
  5. <outPoint>

which initializes the stack in reverse order:

<outPoint>
<valueIn>
<valueOut>
<script>
<recoveredPubKey>

The first several steps of the scriptPubKey reconstructs the signed transaction data inputs provided in the scriptSig. The script verifies that each piece of the transaction data is the correct size.

After evaluating the script up to step 12, the stack looks like

<0x0100000001;outPoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash(0x0000);17a914;hash160(script);870000000001000000>
<recoveredPubKey>

The top value on the stack is the signed transaction data for an Elements Alpha transaction with one input and one output. The format is a little different from the signed transaction data for Bitcoin. Notice that both the input values and output values are given in the signed transaction data. The <hash(0x0000)> data is part of Elements Alpha’s confidential transactions system. For covenants, we will be working with non-confidential transactions, so we fill that part of the data with a trivial value. You may recognize <0x17a914> as the prefix for P2SH transactions. Here we are specifying the output must be a P2SH address for <script>. Notice that <script> appears on both the input side and the output side. Recall that the input’s scriptPubKey replaces the scriptSig in the signed transaction data. Because the same <script> appears on both sides, this transaction moves its funds back to the same scriptPubKey that it started with.

Step 13, OP_SHA256, hashes this transaction data once with SHA-256.

<sha256(0x0100000001;outpoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash(0x0000);17a914;hash160(script);870000000001000000)>
<recoveredPubKey>

Step 14 puts the value <0x01...01> onto the stack. This value is the byte 0x01 repeated 64 times. Elements Alpha use Schnorr signatures which have exactly 64 bytes in them. The value <0x01...01> is used as a fixed digital signature. Instead of the signature being provided as a script input, the elliptic curve public key is provided as an input instead. The OP_DUP of step 15 duplicated this fixed signature, leaving the stack as follows.

<fixedSignature>
<fixedSignature>
<sha256(0x0100000001;outpoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash(0x0000);17a914;hash160(script);870000000001000000)>
<recoveredPubKey>

Now we enforce the covenant by using OP_CHECKSIGFROMSTACK with the fixed signature, transaction-data, and a public key, and using OP_CHECKSIG with the same signature and public key. Step 16 places these items in the appropriate place on the stack

<recoveredPubKey>
<sha256(0x0100000001;outpoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash(0x0000);17a914;hash160(script);870000000001000000)>
<fixedSignature>
<fixedSignature>
<recoveredPubKey>

Step 17 calls OP_CHECKSIGFROMSTACKVERIFY. A <recoveredPubKey> value such that OP_CHECKSIGFROMSTACKVERIFY will succeed can be computed from the fixed signature and the message by using elliptic curve public key recovery.

<fixedSignature>
<recoveredPubKey>

Step 18 appends 0x01, which is the code for SIGHASH_ALL, to the fixed signature, and puts the data in the correct order on the stack.

<recoveredPubKey>
<fixedSignature;SIGHASH_ALL>

Finally OP_CHECKSIG from step 19 verifies that the transaction we created on the stack earlier matches the actual transaction data of the current transaction. Our covenant is complete.

Notice that we use a fixed signature and public key recovery for the values passed into the signature checking operations. The script does no authentication; anyone can preform the public key recovery procedure. Because of the covenant, users cannot directly take the funds; they can only be spend funds back into the same covenant. That makes this script effectively autonomous. This script can be executed repeatedly until all the funds held in the covenant are bleed off into fees.

You can find a live example of this script in transaction 11fa503ff49a22afa99c498ce7375827b0f1a8761f002928c168705b433059df on the Elements Alpha network.

The Möser-Eyal-Sirer vault

In this section, we will construct a covenant that implements the Möser-Eyal-Sirer vault.

Funds locked by a Möser-Eyal-Sirer vault are accessible by one of two keys: a hot key, intended to reside on-line, and a cold key that is intended to be kept safely off-line and only used for recovery purposes. Under normal circumstances, the hot key is used to create a transaction that spends coins from the vault, but whether the hot key or the cold key is used, funds spent from the vault must first pass through a time lock that holds the funds for some fixed period of time, for example, 24 hours. The idea being that if a malicious party gets hold of the hot key, they must publicly broadcast this time-locked transaction on the blockchain before they can take ownership of the funds. This gives the vault owner 24-hours to detect that their funds are being moved by a malicious party and recover those funds. This is where the cold key come in.

During the 24-hour time lock period, the script allows the funds to be redirected to another address using the cold key. However, even when the cold key is used to redirect the funds, that transaction must pass through another 24 hour time lock. The cold key can be used to redirect the funds again during this second time lock, and so on. The idea here is that even if the malicious party gets hold of the cold key, they still cannot get access to the funds. The owner and the malicious party can continue to use the cold key to redirect the time locked funds back and forth between addresses. As long as they both remain active the funds will remain caught in this vault loop indefinitely.

A Möser-Eyal-Sirer vault is composed of two scripts.  The first script is the main vault script that locks the funds with the hot and cold keys. The second script contains the 24-hour time lock which allows the cold key to be used to redirect the funds. Because this second script can redirect the funds back to the same 24-hour time locked script, we call this the script the vault loop script.

Main Vault Script

To store funds in the vault we need a main vault script that allows either the hot key or the cold key to authorize a sending the funds to a P2SH address, but only by going through the vault loop script.

Below is the scriptPubKey for the main vault.

  1. OP_SIZE 4 OP_NUMEQUALVERIFY
  2. 1 OP_CAT
  3. OP_SWAP OP_SIZE 36 OP_NUMEQUALVERIFY OP_CAT
  4. <0x00> OP_CAT
  5. 3 OP_PICK OP_SIZE 32 OP_NUMEQUALVERIFY OP_CAT
  6. <0x0000fd4f01> OP_CAT
  7. OP_SWAP OP_SIZE 335 OP_NUMEQUALVERIFY OP_CAT
  8. OP_SWAP OP_SIZE 4 OP_NUMEQUALVERIFY OP_CAT
  9. <0x0100> OP_CAT
  10. OP_SWAP OP_CAT
  11. <0x0000> OP_HASH256 OP_CAT
  12. <0x17a914> OP_CAT
  13. <0x14>
  14. OP_ROT OP_SIZE 20 OP_NUMEQUALVERIFY OP_CAT
  15. <vaultLoopScript> OP_CAT
  16. OP_HASH160 OP_CAT
  17. <0x87> OP_CAT
  18. OP_SWAP OP_SIZE 4 OP_NUMEQUALVERIFY OP_CAT
  19. <0x83000000> OP_CAT
  20. OP_SHA256 OP_SWAP
  21. OP_DUP OP_IF
  22. 1 OP_EQUALVERIFY
  23. <coldPubKey>
  24. OP_ELSE
  25. 0 OP_EQUALVERIFY
  26. <hotPubKey>
  27. OP_ENDIF
  28. OP_3DUP OP_CHECKSIGFROMSTACKVERIFY
  29. OP_NIP OP_SWAP
  30. <0x83> OP_CAT OP_SWAP OP_CHECKSIG

It can be spent by the following scriptSig

  1. <signature>
  2. 0 | 1
  3. <lockTimeCode>
  4. <p2shTarget>
  5. <value>
  6. <sequenceNumberCode>
  7. <mainVaultScript>
  8. <outPoint>
  9. <version>

which initializes the stack in reverse order:

<version>
<outPoint>
<mainVaultScript>
<sequenceNumberCode>
<value>
<p2shTarget>
<lockTimeCode>
0 | 1
<signature>

This covenant is more flexible than the trivial recursive covenant described above. It takes more transaction pieces as inputs such as the version number and lock time. The second last item, which is either a 0 or 1 indicates that the signature is for the hot key from the on-line wallet, or the cold key, in off-line storage. Either key can be used to sign the transaction.

Steps 1 through 19 construct the transaction on the stack leaving the stack in the following state.

<version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash(0x0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode;83000000>
0 | 1
<signature>

The transaction’s output script contains <vaultLoopScript> which is the vault loop script that we will define in the next section to enforce the time-lock. The operation immediately before the <vaultLoopScript> fixes the <p2shTarget> that the vault loop script will use when it is redeemed after the time-lock.

Notice that we have appended 0x83000000 to the end of the transaction data. This is the code for requesting SIGHASH_SINGLE|SIGHASH_ANYONECANPAY signed transaction data. This covenant only restricts the first input and the first output. The other inputs and outputs can be anything.

The <value> appears in both the input and the output so the all the funds must be sent through the vault loop script. This means that none of the funds in the vault can be spent on fees. A second input to the transaction will be required to cover the transaction fee.

Steps 21 through 23 pushes the either the hot or cold public key onto the stack

<hotPubKey> | <coldPubKey>
<sha256(version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash(0x0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode;83000000)>
<signature>

Then the last steps run OP_CHECKSIGFROMSTACKVERIFY and OP_CHECKSIG, which enforces the covenant. The last OP_CHECKSIG also verifies that this transaction has been signed with the selected key.

Vault Loop Script

The vault loop script is responsible for enforcing the time-lock and allow for recovery of funds using the cold key.

Below is the scriptPubKey for a 24-hour time locked vault loop.

  1. <p2shTarget>
  2. OP_SWAP OP_SIZE 4 OP_NUMEQUALVERIFY
  3. 1 OP_CAT
  4. OP_ROT OP_SIZE 36 OP_NUMEQUALVERIFY OP_CAT
  5. <0x00> OP_CAT
  6. 3 OP_PICK OP_SIZE 32 OP_NUMEQUALVERIFY OP_CAT
  7. <0x0000ba14> OP_CAT
  8. OP_OVER OP_CAT
  9. 4 OP_PICK OP_SIZE 165 OP_NUMEQUALVERIFY OP_CAT
  10. OP_ROT OP_SIZE 4 OP_NUMEQUALVERIFY OP_CAT
  11. <0x0100> OP_CAT
  12. OP_ROT OP_CAT
  13. <0x0000> OP_HASH256 OP_CAT
  14. <0x17a914> OP_CAT
  15. OP_DEPTH 6 OP_EQUAL OP_IF
  16. OP_NIP
  17. <0x14>
  18. 3 OP_ROLL OP_SIZE 20 OP_NUMEQUALVERIFY OP_CAT
  19. OP_ROT OP_CAT
  20. OP_HASH160
  21. OP_CAT
  22. <coldPubKey>
  23. OP_ROT
  24. OP_ELSE
  25. <24-hours> OP_CHECKSEQUENCEVERIFY OP_DROP
  26. OP_SWAP OP_CAT
  27. OP_NIP
  28. OP_SWAP
  29. 1 OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT
  30. OP_ENDIF
  31. OP_DUP
  32. 3 OP_ROLL
  33. <0x87> OP_CAT
  34. 4 OP_ROLL OP_SIZE 4 OP_NUMEQUALVERIFY OP_CAT
  35. <0x83000000> OP_CAT
  36. OP_SHA256 3 OP_PICK OP_CHECKSIGFROMSTACKVERIFY
  37. <0x83> OP_CAT OP_SWAP OP_CHECKSIG

The vault loop script is parameterized by

  • <p2shTarget>, which is the P2SH that this transaction can be spent to after the time lock is expired.  We saw that this value was set by the main vault script.
  • <coldPubKey>, which is the public key for the cold key which allows the transaction to be redirected to an alternative output before the time lock expires.
  • <24-hours>, which is the time that is passed to OP_CHECKSEQUENCEVERIFY that determines how long the time lock lasts.  Users can configure their vault’s time lock to an appropriate value for them.

The vault loop can be redeemed in two ways. Under normal conditions the transaction can send the funds to the <p2shTarget> address fixed by the scriptPubKey after the time-lock is expired. The following scriptSig is used in this case.

  1. <lockTimeCode>
  2. <recoveredPubKey>
  3. <vaultLoopScript>
  4. <value>
  5. <sequenceNumberCode>
  6. <outPoint>
  7. <version>

This initializes the stack in reverse order

<version>
<outPoint>
<sequenceNumberCode>
<value>
<vaultLoopScript>
<recoveredPubKey>
<lockTimeCode>

Steps 1 through 14 of the scriptPubKey constructs the initial segment of the transaction on the stack, bringing the stack to the following state.

<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash(0x0000);17a914>
<p2shTarget>
<vaultLoopScript>
<recoveredPubKey>
<lockTimeCode>

Step 15 checks the stack size. Because the stack size is 5 rather than 6, the scripts executes the OP_ELSE branch. In this branch the script checks that the 24-hour lock time has expired. The script appends the <p2shTarget> to the transaction under construction and, after some stack fiddling, it pushes the same fixed signature onto the stack that we used in the trivial recursive covenant.

<fixedSignature>
<recoveredPubKey>
<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash(0x0000);17a914;p2shTarget>
<lockTimeCode>

The script finishes constructing the transaction after step 22.

<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash(0x0000);17a914;p2shTarget;87;lockTimeCode;83000000>
<fixedSignature>
<fixedSignature>
<recoveredPubKey>

Like in the main vault script, we are using SIGHASH_SINGLE|SIGHASH_ANYONECANPAY so that other inputs can provide the necessary transaction fee.

Step 23 runs OP_CHECKSIGFROMSTACKVERIFY with the fixed signature and the recovered public key. If successful, the stack is left in the following state.

<fixedSignature>
<recoveredPubKey>

Finally, Step 24 appends 0x83, our chosen signature hash type, to the <fixedSignature> and does the OP_CHECKSIG, completing the covenant.

Again, we use public key recovery to compute the necessary <recoveredPubKey> input, making this redemption process autonomous. Any party can create this transaction, be it the sender of the funds, the receiver of the funds, or any third party. However, the covenant ensures that the funds can only be sent to the <p2shTarget> that has already been fixed in the scriptPubKey.

To change the <p2shTarget>, the cold key can be used to create the <signature> in the following scriptSig.

  1. <lockTimeCode>
  2. <signature>
  3. <p2shNewTarget>
  4. <vaultLoopScript>
  5. <value>
  6. <sequenceNumberCode>
  7. <outPoint>
  8. <version>

This scriptSig initializes the stack in reverse order.

<version>
<outPoint>
<sequenceNumberCode>
<value>
<vaultLoopScript>
<p2shNewTarget>
<signature>
<lockTimeCode>

The first 14 steps for this case proceed as before, leaving the stack in the following state.

<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash(0x0000);17a914>
<p2shTarget>
<vaultLoopScript>
<p2shNewTarget>
<signature>
<lockTimeCode>

Now when step 15 checks the stack depth; it is equal to 6, so the script executes the first branch. Steps 15.1 to 15.5 construct a P2SH value.

<hash160(0x14;p2shNewTarget;vaultLoopScript)>
<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash(0x0000);17a914>
<signature>
<lockTimeCode>

This script is the same <vaultLoopScript> from the input, except <p2shNewTarget> replaces the <p2shTarget> as the first operation of the script.

Steps 15.6 through 15.8 sets the output script to this new P2SH value and pushes the cold public key onto the stack.

<signature>
<coldPubKey>
<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash(0x0000);17a914;hash160(0x14;p2shNewTarget;vaultLoopScript)>
<lockTimeCode>

Finally we proceed as before.  Steps 19 through 22 complete the construction of the transaction.

<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash(0x0000);17a914;hash160(0x14;p2shNewTarget;vaultLoopScript);87;lockTimeCode;83000000>
<signature>
<signature>
<coldPubKey>

The rest of the steps run OP_CHECKSIGFROMSTACKVERIFY and OP_CHECKSIG to enforce the covenant.

This time the transaction’s output script is the same vault loop script but with a new P2SH target. Notice also that the OP_CHECKSIG step is using the cold public key instead of a recovered public key. This means that the OP_CHECKSIG step not only enforces our covenant, but also validates that the private key for the cold key has been used to authorize this transaction. This process effectively resets the clock on the vault loop, requiring another 24 hour period before a transaction can be made to the new P2SH target.

That concludes our implementation of Möser-Eyal-Sirer vaults. You can find live examples of these scripts in the on the Elements Alpha network.

One can make variations of these vault scripts. One could allow the main vault to spend only part of the funds to the P2SH target and require the remaining funds be returned to the main vault script. This would preclude using the SIGHASH_SINGLE hash type, and would likely exceed the stack item size restriction.

The scripts developed here are specific to the Elements Alpha sidechain’s transaction format, but the general design will work for other blockchains. In particular, a new segregated witness Bitcoin script version could soft-fork in the OP_CAT and OP_CHECKSIGFROMSTACKVERIFY operations, both of which have many other uses. This would enable this style of covenants in Bitcoin itself.

Möser-Eyal-Sirer vaults are only one application of covenants, and there are endless other possible applications. Covenants open a whole new avenue for exploring what programmable money could be. Eventually, operations specific to supporting covenants are likely to be developed, but until then, we can all experiment with covenants in Elements Alpha today.

Acknowledgments

Thanks to Greg Maxwell who first suggested to me that it might be possible to use OP_CHECKSIGFROMSTACK to enforce a covenant. Thanks to Andrew Poelstra for his technical review of this post.

Appendix A: Traces of the Vault Scripts

Below we trace the execution of the trivial recursive covenant, starting from a stack initialized with the scriptSig.

ScriptPubKey Stack
  <outPoint>
<valueIn>
<valueOut>
<script>
<recoveredPubKey>
<0x0100000001> <0x0100000001>
<outPoint>
<valueIn>
<valueOut>
<script>
<recoveredPubKey>
OP_SWAP OP_SIZE 36 OP_NUMEQUALVERIFY OP_CAT <0x0100000001;outPoint>
<valueIn>
<valueOut>
<script>
<recoveredPubKey>
<0x00> OP_CAT <0x0100000001;outPoint;00>
<valueIn>
<valueOut>
<script>
<recoveredPubKey>
OP_SWAP OP_SIZE 32 OP_NUMEQUALVERIFY OP_CAT <0x0100000001;outPoint;00;valueIn>
<valueOut>
<script>
<recoveredPubKey>
<0x00005f> OP_CAT <0x0100000001;outPoint;00;valueIn;00005f>
<valueOut>
<script>
<recoveredPubKey>
2 OP_PICK OP_SIZE 95 OP_NUMEQUALVERIFY OP_CAT <0x0100000001;outPoint;00;valueIn;00005f;script>
<valueOut>
<script>
<recoveredPubKey>
<0xffffffff0100> OP_CAT <0x0100000001;outPoint;00;valueIn;00005f;script;ffffffff0100>
<valueOut>
<script>
<recoveredPubKey>
OP_SWAP OP_SIZE 32 OP_NUMEQUALVERIFY OP_CAT <0x0100000001;outPoint;00;valueIn;00005f;script;ffffffff0100;valueOut>
<script>
<recoveredPubKey>
<0x0000> OP_HASH256 OP_CAT <0x0100000001;outPoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash256(0x0000)>
<script>
<recoveredPubKey>
<0x17a914> OP_CAT <0x0100000001;outPoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash256(0x0000);17a914>
<script>
<recoveredPubKey>
OP_SWAP OP_HASH160 OP_CAT <0x0100000001;outPoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash256(0x0000);17a914;hash160(script)>
<recoveredPubKey>
<0x870000000001000000> OP_CAT <0x0100000001;outPoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash256(0x0000);17a914;hash160(script);870000000001000000>
<recoveredPubKey>
OP_SHA256 <sha256(0x0100000001;outPoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash256(0x0000);17a914;hash160(script);870000000001000000)>
<recoveredPubKey>
1 OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT <fixedSignature>
<sha256(0x0100000001;outPoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash256(0x0000);17a914;hash160(script);870000000001000000)>
<recoveredPubKey>
OP_DUP <fixedSignature>
<fixedSignature>
<sha256(0x0100000001;outPoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash256(0x0000);17a914;hash160(script);870000000001000000)>
<recoveredPubKey>
2 OP_ROLL 3 OP_PICK <recoveredPubKey>
<sha256(0x0100000001;outPoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash256(0x0000);17a914;hash160(script);870000000001000000)>
<fixedSignature>
<fixedSignature>
<recoveredPubKey>
OP_CHECKSIGFROMSTACKVERIFY <fixedSignature>
<recoveredPubKey>
1 OP_CAT OP_SWAP <recoveredPubKey>
<fixedSignature;SIGHASH_ALL>
OP_CHECKSIG 1

Main Vault Script Execution Trace

Below we trace the execution of the main vault script, starting from a stack initialized with the scriptSig.

ScriptPubKey Stack
  <version>
<outPoint>
<mainVaultScript>
<sequenceNumberCode>
<value>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
OP_SIZE 4 OP_NUMEQUALVERIFY <version>
<outPoint>
<mainVaultScript>
<sequenceNumberCode>
<value>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
1 OP_CAT <version;01>
<outPoint>
<mainVaultScript>
<sequenceNumberCode>
<value>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
OP_SWAP OP_SIZE 36 OP_NUMEQUALVERIFY OP_CAT <version;01;outPoint>
<mainVaultScript>
<sequenceNumberCode>
<value>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
<0x00> OP_CAT <version;01;outPoint;00>
<mainVaultScript>
<sequenceNumberCode>
<value>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
3 OP_PICK OP_SIZE 32 OP_NUMEQUALVERIFY OP_CAT <version;01;outPoint;00;value>
<mainVaultScript>
<sequenceNumberCode>
<value>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
<0x0000fd4f01> OP_CAT <version;01;outPoint;00;value;0000fd4f01>
<mainVaultScript>
<sequenceNumberCode>
<value>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
OP_SWAP OP_SIZE 335 OP_NUMEQUALVERIFY OP_CAT <version;01;outPoint;00;value;0000fd4f01;mainVaultScript>
<sequenceNumberCode>
<value>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
OP_SWAP OP_SIZE 4 OP_NUMEQUALVERIFY OP_CAT <version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode>
<value>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
<0x0100> OP_CAT <version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100>
<value>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
OP_SWAP OP_CAT <version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
<0x0000> OP_HASH256 OP_CAT <version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000)>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
<0x17a914> OP_CAT <version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
<0x14> <0x14>
<version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914>
<p2shTarget>
<lockTimeCode>
(0|1)
<signature>
OP_ROT OP_SIZE 20 OP_NUMEQUALVERIFY OP_CAT <0x14;p2shTarget>
<version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914>
<lockTimeCode>
(0|1)
<signature>
<vaultLoopScript> OP_CAT <0x14;p2shTarget;vaultLoopScript>
<version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914>
<lockTimeCode>
(0|1)
<signature>
OP_HASH160 OP_CAT <version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript)>
<lockTimeCode>
(0|1)
<signature>
<0x87> OP_CAT <version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87>
<lockTimeCode>
(0|1)
<signature>
OP_SWAP OP_SIZE 4 OP_NUMEQUALVERIFY OP_CAT <version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode>
(0|1)
<signature>
<0x83000000> OP_CAT <version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode;83000000>
(0|1)
<signature>
OP_SHA256 OP_SWAP (0|1)
<sha256(version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode;83000000)>
<signature>
OP_DUP OP_IF 1
<sha256(version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode;83000000)>
<signature>
1 OP_EQUALVERIFY <sha256(version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode;83000000)>
<signature>
<coldPubKey> <coldPubKey>
<sha256(version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode;83000000)>
<signature>
OP_ELSE 0
<sha256(version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode;83000000)>
<signature>
0 OP_EQUALVERIFY <sha256(version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode;83000000)>
<signature>
<hotPubKey> <hotPubKey>
<sha256(version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode;83000000)>
<signature>
OP_ENDIF <pubKey>
<sha256(version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode;83000000)>
<signature>
OP_3DUP OP_CHECKSIGFROMSTACKVERIFY <pubKey>
<sha256(version;01;outPoint;00;value;0000fd4f01;mainVaultScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(0x14;p2shTarget;vaultLoopScript);87;lockTimeCode;83000000)>
<signature>
OP_NIP OP_SWAP <signature>
<pubKey>
<0x83> OP_CAT OP_SWAP OP_CHECKSIG 1

Vault Loop Script Execution Trace

Below we trace the execution of the vault loop script, starting from a stack initialized with the scriptSig.

ScriptPubKey Stack
  <version>
<outPoint>
<sequenceNumberCode>
<value>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
<p2shTarget> <p2shTarget>
<version>
<outPoint>
<sequenceNumberCode>
<value>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
OP_SWAP OP_SIZE 4 OP_NUMEQUALVERIFY <version>
<p2shTarget>
<outPoint>
<sequenceNumberCode>
<value>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
1 OP_CAT <version;01>
<p2shTarget>
<outPoint>
<sequenceNumberCode>
<value>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
OP_ROT OP_SIZE 36 OP_NUMEQUALVERIFY OP_CAT <version;01;outPoint>
<p2shTarget>
<sequenceNumberCode>
<value>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
<0x00> OP_CAT <version;01;outPoint;00>
<p2shTarget>
<sequenceNumberCode>
<value>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
OP_3 OP_PICK OP_SIZE 32 OP_NUMEQUALVERIFY OP_CAT <version;01;outPoint;00;value>
<p2shTarget>
<sequenceNumberCode>
<value>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
<0x0000ba14> OP_CAT <version;01;outPoint;00;value;0000ba14>
<p2shTarget>
<sequenceNumberCode>
<value>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
OP_OVER OP_CAT <version;01;outPoint;00;value;0000ba14;p2shTarget>
<p2shTarget>
<sequenceNumberCode>
<value>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
4 OP_PICK OP_SIZE 165 OP_NUMEQUALVERIFY OP_CAT <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript>
<p2shTarget>
<sequenceNumberCode>
<value>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
OP_ROT OP_SIZE 4 OP_NUMEQUALVERIFY OP_CAT <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode>
<p2shTarget>
<value>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
<0x0100> OP_CAT <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100>
<p2shTarget>
<value>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
OP_ROT OP_CAT <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value>
<p2shTarget>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
<0x0000> OP_HASH256 OP_CAT <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000)>
<p2shTarget>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
<0x17a914> OP_CAT <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914>
<p2shTarget>
<vaultLoopScript>
(<p2shNewTarget> <signature>|<recoveredPubKey>)
<lockTimeCode>
OP_DEPTH 6 OP_EQUAL OP_IF <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914>
<p2shTarget>
<vaultLoopScript>
<p2shNewTarget>
<signature>
<lockTimeCode>
OP_NIP <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914>
<vaultLoopScript>
<p2shNewTarget>
<signature>
<lockTimeCode>
<0x14> <0x14>
<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914>
<vaultLoopScript>
<p2shNewTarget>
<signature>
<lockTimeCode>
3 OP_ROLL OP_SIZE 20 OP_NUMEQUALVERIFY OP_CAT <0x14;p2shNewTarget>
<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914>
<vaultLoopScript>
<signature>
<lockTimeCode>
OP_ROT OP_CAT <0x14;p2shNewTarget;vaultLoopScript>
<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914>
<signature>
<lockTimeCode>
OP_HASH160 OP_CAT <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(14;p2shNewTarget;vaultLoopScript)>
<signature>
<lockTimeCode>
<coldPubKey> <coldPubKey>
<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(14;p2shNewTarget;vaultLoopScript)>
<signature>
<lockTimeCode>
OP_ROT <signature>
<coldPubKey>
<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;hash160(14;p2shNewTarget;vaultLoopScript)>
<lockTimeCode>
OP_ELSE <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914>
<p2shTarget>
<vaultLoopScript>
<recoveredPubKey>
<lockTimeCode>
<24-hours> OP_CHECKSEQUENCEVERIFY OP_DROP <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914>
<p2shTarget>
<vaultLoopScript>
<recoveredPubKey>
<lockTimeCode>
OP_SWAP OP_CAT <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;p2shTarget>
<vaultLoopScript>
<recoveredPubKey>
<lockTimeCode>
OP_NIP <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;p2shTarget>
<recoveredPubKey>
<lockTimeCode>
OP_SWAP <recoveredPubKey>
<version;02;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;p2shTarget>
<lockTimeCode>
1 OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT <fixedSignature>
<recoveredPubKey>
<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;p2shTarget>
<lockTimeCode>
OP_ENDIF <sig>
<pubKey>
<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;p2sh>
<lockTimeCode>
OP_DUP <sig>
<sig>
<pubKey>
<version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;p2sh>
<lockTimeCode>
3 OP_ROLL <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;p2sh>
<sig>
<sig>
<pubKey>
<lockTimeCode>
<0x87> OP_CAT <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;p2sh;87>
<sig>
<sig>
<pubKey>
<lockTimeCode>
4 OP_ROLL OP_SIZE 4 OP_NUMEQUALVERIFY OP_CAT <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;p2sh;87;lockTimeCode>
<sig>
<sig>
<pubKey>
<0x83000000> OP_CAT <version;01;outPoint;00;value;0000ba14;p2shTarget;vaultLoopScript;sequenceNumberCode;0100;value;hash256(0000);17a914;p2sh;87;lockTimeCode;83000000>
<sig>
<sig>
<pubKey>
OP_SHA256 3 OP_PICK OP_CHECKSIGFROMSTACKVERIFY <sig>
<pubKey>
<0x83> OP_CAT OP_SWAP OP_CHECKSIG 1

If you have specific preferences, please, mark the topic(s) you would like to read: