2024.02 Vol.1

Bit of covenants

A covenant is a loose term in bitcoin-land because there are a handful of proposals out there which all satisfy some level of a covenant. The most succinct description I have seen is from Anthony Towns.

“The most useful definition of covenant is that it’s when the scriptPubKey of a UTXO restricts the scriptPubKey in the output(s) of a tx spending that UTXO”.

Time to muddle that up.

Covenants limit how bitcoin is moved, not just how it is locked. So spending a UTXO goes from “hey, I own this private key” to “hey, I own this private key and I am spending the bitcoin as specified”. An example of a covenant could be something like “outputs of the spending transaction must be exactly 100,000 sats”.

This is a large departure from the original Script model which generally focuses on how coins are locked. There are some light covenant aspects like the OP_CHECKLOCKTIME opcode, which inspects the spending transaction to ensure its locktime is old enough. But these are very scoped, limited covenants. Next generation off-chain contracts will probably require more expressive covenants. Things like flow control where a user could limit any transaction paying out of an address to a certain number of coins. Or vaults, where a user could require coins to first move to staging address and wait for some time before moving to any other address. A use case I am very curious about is joinpools which could horizontally scale the ownership of a UTXO, maybe making the last mile of lightning onboarding less painful for new users.

With any new feature proposal for bitcoin, the complexity concerns need to be weighed. Covenants are powerful and power can be kinda scary. They might be too powerful, maybe there is a lurking bug that breaks all bitcoin. One of the original concerns with covenants is that they could be used to whitelist/blacklist coins, some sort of “coins can only be sent to these X addresses” breaking fungibility. This would definitely be possible, but I don’t think it is very relevant since there is already a much easier way to implement this in bitcoin: a simple multisig address where one party (e.g. evil government) would only sign a transaction if it is sending to a “whitelisted” address. I think the covenant version is “scarier” since it is covered by consensus, so more locked-in. But it would be more expensive to operate, so no reason an authority figure would do it that way over the existing multisig implementation.

A more interesting worry for covenants is recursive covenants. A covenant implementation’s power correlates with how many transaction fields it can introspect. As mentioned above, there are some existing op codes with limited covenant power since they can introspect one field (e.g. locktime) of a spending transaction. On the other end of the spectrum would be an opcode which could introspect every field of a transaction. Closer to this end of the spectrum it becomes possible for a covenant to enforce that coins are spent to an output with the same covenant it is in. Coins can end up in an infinite recursive covenant, oh no!

But it should be pointed out that recursive versus non-recursive covenants are not that different from a scary perspective. Someone could create a covenant output which restricts the spending output to a huge (like, thousands) chain of transactions. Not much difference from a user’s perspective and they probably just shouldn’t lock their coins in such a fashion.

With that said, how exactly are recursive covenants created? Modern UTXO output scripts are public keys that are a form of a hash commitment (so I am ignoring old school pre-P2SH outputs). A commitment requires the data be known when the covenant output script is created. For a non-recursive covenant, this can be an equivalence check of the hash since the transaction or chain of transactions is known at the time the covenant is created (e.g. the spending transaction must output to this existing address X). A recursive covenant by definition has a loop to itself, but the transaction is not known yet, so there is a cycle which makes it impossible to make a commitment. A recursive covenant requires deeper introspection into the output script, it needs to be able to enforce the requirement “this script conforms to a certain template”. The looser template requirement allows there to be some dynamic data per transaction. It is also much more expressive, I can think of a lot of different ways one might want to analyze an output over just a hash equivalence. But if the template matches the current covenant’s template, then it is possible that it is an infinite recursion loop.

One pattern to get this level of introspection is to require the covenant spender to put a copy of the output script on the witness stack. This way the covenant can analyze the script and enforce it does what it needs to do. But it would then have to hash that script and compare it to the actual output’s scriptPubKey to confirm that the provided script is the same one in the output. At this time though, I don’t think there are any script op codes that can create a taproot public key since the tweaking is 256-bit arithmetic, we would need to softfork something in like Rusty Russell’s OP_KEYADDTWEAK idea.

But anyways, I am not concerned about the “scary” features of covenants. They appear to already be a part of bitcoin in one way or another. As long as they don’t KO validating nodes, and just rely on users to not shoot themselves in the foot, I think we are OK.

Assuming some form of covenants lands in Script, things really get interesting when offchain contract patterns take advantage of layering covenants together. I think that is when the primitive really shows its power.