Covenants, also known as spending constraints, is the name given to hypothetical bitcoin scripts that, when attached to UTXOs, would constrain the way these can be spent, for example restricting the addresses where such coins can be sent.
Covenants which replicate themselves into the UTXOs where they are spent to are called recursive.
Generalized covenants would enable a ton of new applications for scripts, such as:
- Scripts with unbounded length (including arbitrary sized multisigs)
- Turing complete contracts
- Transaction-level MAST (a really coarse-grained version of MAST that would split code paths at the script/transaction level)
- Drivechain-like two-way pegs
Several more use cases are described on the OP_CTV page.
Note: Not all the covenants described in this document are generalized ones, as several are heavily restricted, therefore the use-cases that each covenant provides are not all the same.
Because I spent a whole month researching ways to create covenants with the current capabilities of Bitcoin's Script and, after thinking I had found a beakthrough, I discovered that the same system I had thought about had already been described a few months ago in one of the thousands of posts of the bitcoin mailing list. The goal of this website is to provide information on bitcoin covenants so that other people don't end up wasting their time like I did.
In other words:
Those who don't know history are doomed to repeat it
Bitcoin Cash and the Liquid sidechain have the capabilities needed for building generalized covenants.
Bitcoin scripts can't use generalized covenants but it's possible to build a very restricted kind of covenants using trusted setups.
Nevertheless, there's several proposals for protocol upgrades that would enable more flexible covenants (yet not general) for Bitcoin, the most active ones being SIGHASH_ANYPREVOUT/NOINPUT and OP_CHECKTEMPLATEVERIFY.
One of the earliest covenant proposals, this paper describes a new opcode to be added to the Bitcoin protocol, this new opcode takes three arguments (index, value, pattern)
and returns true if and only if the following conditions holdfor the index
-th output of the transaction that is trying to spend the UTXO:
- The amount of bitcoin spent on that output is equal to
value
- The script attached to the output is equal to
pattern
except for the parts ofpattern
that contain placeholders for public keys and hashes.
A technically-wrong but exemplary implementation of the opcode in python would be as follows:
def CheckOutputVerify(tx, index, value, pattern):
output = tx.outputs[index]
if value != 0:
if output.value != value:
return False
if pattern != "":
sanitizedPattern = pattern.replace("PubKeyPlaceholder", "00000000").replace("HashPlaceholder", "00000000")
mask = replaceLocationsOfPlaceholdersWithZeroes("1"*len(sanitizedPattern), ["PubKeyPlaceholder", "HashPlaceholder"])
if (output.script & mask) != sanitizedPattern:
return False
return True
Note: This implementation is wrong due to the fact that the binary operations are replaced with string operations, which are themselves wrong in lots of ways (a mask of bytes is not constructed as a string of 1's...) but as a pseudocode example it helps get the concept across
This opcode would enable all kind of covenants, but nowadays it has been superseded by other systems and the probability of it ever getting deployed is null.
This opcode, which was proposed as an extension to tapscript and aims at being as simple as possible, just serializes the outputs of a transaction, hashes the result twice and compares it with an argument:
def CheckOutputsHashVerify(tx, outputsHash):
if sha256(sha256(tx.outputs)) == outputsHash:
return True
else:
return False
Something uncommon about this opcode is the fact that it's argument is provided in Script after OP_CHECKOUTPUTSHASHVERIFY, not before. This behaviour is completely different from other opcodes', which take arguments from the stack, in order to prevent the output hashes from being constructed in Script.
The proposal was superseded by OP_CHECKTEMPLATEVERIFY, which modifies the behaviour to enforce non-malleability in transactions that spend UTXOs encumbered by the covenant and changes the deployment scheme to the classic NOP-replacement instead of a tapscrip extension.
See the BIP for more details.
Previously known as OP_SECURETHEBAG, this is a new version of OP_CHECKOUTPUTSHASHVERIFY that enforces non-malleability of transactions by including several other parts of the transaction into the hash, whereas OP_CHECKOUTPUTSHASHVERIFY only included the outputs.
Specifically, this opcode puts together the following fields:
- Version bits
- nLockTime
- Input's scriptSig
- Number of inputs
- nSequence
- Number of outputs
- Outputs
- Index of the input being verified currently
serializes them together, hashes them and compares the hash with an element in the stack, verifying the transaction if the values are equal.
For more information, see BIP119 and the website dedicated to it.
The following is a short explanation on Schnorr signatures, the signatures used in Bitcoin are not of this type but they are conceptually similar:
The main idea behind this signatures is that if you have a point
With that in mind, we can take a number
With these values, anyone else can verify that such signature is valid by checking that the equality
The main idea behind this category of covenants (signature based) is that, while transaction information is not directly available inside Script, it is used to construct the value
At it's heart, this opcode is really simple, it just takes a message, a public key and a signature and checks if the signature is valid. The interesting bit is that you can use this to compare any transaction built inside Script to the transaction that triggered the call and check if they are equal, therefore getting access to all the transaction data that is included inside the hash in OP_CHECKSIG signatures.
More specifically, this would work by having the verification script (ScriptPubKey) of a UTXO construct (or verify) a serialized transaction, making sure that certain properties are met, such as the outputs being equal to ones defined previously for example, then hash such transaction and run OP_CHECKSIGFROMSTACK on the resulting hash and the values
If that signature is valid, then OP_CHECKSIG would be run with the same values except for the hash, that is the values
For more information, see the paper and article about it.
Note: This opcode has been implemented inside Elements, the blockchain layer upon which Blockstream's Liquid sidechain is built. Furthermore, an opcode very similar to it called OP_CHECKDATASIG has been implemented and deployed in Bitcoin Cash.
A kind of somewhat restricted covenants can be created if SIGHASH_NOINPUT or ANYPREVOUTS is deployed. These covenants are based on creating several transactions beforehand and then constructing public keys for which only a single signature can be computed, making any money sent to that public key only spendable through the transactions defined before.
In other words, someone could create a covenant using the following protocol:
- Create a transaction
$tx$ spending some funds - Pick a number
$s$ and a point$K$ in a deterministic and verifiable way (could also be constants) - Calculate a public key by solving for
$P$ in the equation$sG = K - hash(tx)P$ , that is computing$P = hash(x)^{-1}(K - sG)$ . - Send funds to an address where they can only be spent with a signature of
$P$ .
Because the private key associated with
Now, the huge problem with this scheme is that to construct the transaction
SIGHASH_NOINPUT solves this problem by allowing
Check out the bitcoin-dev post where this scheme was initially described for more details.
Another similar scheme that trades the dependency on SIGHASH_NOINPUT for a dependency on any opcode that enables comparison against a part of a word on the stack (eg: OP_AND, OP_SUBSTR, OP_CAT...), while allowing the use of generalized covenants, is based on directly constructing the signature for an approved transaction, inside script:
- Inside script, obtain a transaction
$tx$ which verifies all the properties that the covenant enforces - Inside script, compute
$hash(tx)+1$ and then apply OP_CHECKSIG using$-G$ as the public key ($P$ ),$G$ as the nonce ($K$ ) and the value that we just computed as$s$ . An alternative interpretation of this is that the private key$p$ is set to$-1$ and the nonce$k$ to$1$ . - If OP_CHECKSIG succeeds the transaction being spent is the same as
$tx$
They point of this scheme is that when OP_CHECKSIG is applied, it results in the equation
You may wonder why do we use
A different system to generate restricted covenants which can be used in the current Bitcoin protocol at the expense of requiring a trusted setup is one based on one-time keys, the main idea being that you can create a new private key, use it to sign a set of transactions and then destroy the key while keeping the signed transactions, therefore making it so the address associated with the key will only ever be able to spend it's funds through these transactions.
This idea can be extended to multi-sigature setups where a trusted setup would be organized to build an n-of-n multisig along with all the transactions needed for the protocol, afterwards each of the participants of that protocol should delete their respective keys and, if at least one of the participants does so, it will be impossible to construct any new signatures for that multisig, therefore creating effective covenants. That said, if none of the signatures is destroyed and all the players cooperate, it should be possible to create new arbitrary signatures, destroying the whole point of the system. Due to this problem, this scheme requires trust in at least one of the participants of the trusted setup.
This text is open source (MIT licensed), and available on GitHub. Contributions are welcome.