Covenants are a construction to allow introspection: a transaction output can place conditions on the transaction which spends it (beyond the specific “must provide a valid signature of itself and a particular pubkey”).

This power extends script in useful ways, such as allow creation of force spending paths (such as vaults which force spending delays), and rebindable inputs (such as required for Lightning “state fixup” proposals, aka LN-Symmetry). But when we discuss specific proposals (such as OP_TX, OP_TXHASH or OP_CHECKTEMPLATEVERIFY) it’s been difficult to nail down the exact trade-offs made for each one. So I want to describe the landscape (or taxonomy) which we can use to categorize and assess covenants.

The Simplest Covenant (Which Doesn’t Quite Work!)

Firstly, consider the simplest covenant: OP_TXIDVERIFY. This would check the txid of the spending transaction is equal to the given txid. This is easy to implement both in existing script (replacing OP_NOP3), and in tapscript. It doesn’t actually work, since it makes a commitment circle (the txid contains the txid of the input, which contains the txid…; thanks Jeremy Rubin), but it’s a useful thought experiment.

Fully Complete Covenants

Now, consider the most complete covenant proposal: OP_TX. The idea is to push some specified field of the spending transaction onto the stack. For efficiency, it would take a bitmap to push multiple fields at once and (because we don’t have OP_CAT) have an option to concatenate them all as push them as one element. There are details here which matter, such as how primitive Bitcoin script is when dealing with numbers, and stack space limits for large transactions, but the idea is simple.

This allows you to do things like “output amount must be > 100000 sats”.

Equality Covenants

On the spectrum from simplest to most complete, is Russell O’Connor’s OP_TXHASH (which I generalized into the OP_TX proposal), which takes a bitmap from the stack and hashes those fields together, then pushes the resulting hash onto the stack. Of course, you can have multiple OP_IF branches allowing different equalities, but only a handful: you’ll run out of scripts space quite fast. With OP_CAT you could extend this further to assemble a template to compare against at runtime, but we don’t have that so I’ll ignore that for now.

This allows for simple equality tests, such as “output amount must be 100000 sats”.

OP_CHECKTEMPLATEVERIFY Covenant

OP_CHECKTEMPLATEVERIFY is a further restriction on basic equality covenants: it’s like OP_TXHASH with a fixed bitmap. It’s an opinionated subset though, which makes it more powerful than OP_TXIDVERIFY (and usable!): in particular it doesn’t commit to inputs at all (except the input number), but commits to all the outputs; this means you can theoretically add fees and still match, but you can’t have a change output. It’s also usable outside tapscript, since it’s written in the old “don’t-touch-the-stack” script soft-fork style.

Taproot Allows Us To Design, Then Restrict

Designing in the second half of 2023, I think it’s reasonable to assume covenants are only relevant inside taproot.

This means we have the ability to easily limit it in a way which can be unlimited in stages later via future soft-forks:

  • If we only allow certain bits to be set, and otherwise treat it as OP_SUCCESS, we can trim its ability today, and softfork in new bits later without needing a new opcode.
  • Similarly, we can also restrict it to a static analyzable set by requiring it to immediately follow a PUSH operation, otherwise degrade to OP_SUCCESS.

As an example, let’s turn OP_TX into OP_TXIDVERIFY. We only define one bit: OP_TX_BIT_TXID. That bit means “push the txid on the stack”, and if anything else is set, OP_TX is interpreted as OP_SUCCESS:

01 OP_TX_BIT_TXID OP_TX <txid> OP_EQUALVERIFY

Similarly, if we want OP_CHECKTEMPLATEVERIFY, we require the following OP_TX bits to be defined:

  1. OP_TX_BIT_COMBINE (meaning to concatenate onto one stack element)
  2. OP_TX_BIT_NVERSION
  3. OP_TX_BIT_NLOCKTIME
  4. OP_TX_BIT_SCRIPTSIG_HASHES
  5. OP_TX_BIT_INPUT_INDEX
  6. OP_TX_BIT_NSEQUENCES
  7. OP_TX_BIT_NUM_INPUTS
  8. OP_TX_BIT_OUTPUTS_AMOUNT
  9. OP_TX_BIT_OUTPUTS_SCRIPT
  10. OP_TX_BIT_NUM_OUTPUTS

i.e: (assuming they’re assigned bits from 0 to 10)

02 b1111111111 OP_TX OP_SHA256 <hash> OP_EQUALVERIFY

There are some differences in how fields are hashed, and perhaps their order, but these are cosmetic not functional differences.

Extending this in future simply means defining other fields, and what combinations are allowed.

The Recursion Distraction

There are many ways we can argue about how to clip covenants’ wings. But I want to address (and dismiss) one specifically: the idea of restricting recursive covenants which restrict all future descendants.

Any covenant system listed here can restrict outputs. That means I can require that the spending transaction spend to a spending transaction that spends to a spending transaction that spends to…. 100 million transactions later… an output to me. You could prevent this by requiring that any covenant-spending tx itself is not allowed to use covenants at all, but that adds complexity and reduces usefulness.

Mathematically, there’s a difference between being able to restrict transactions to arbitrary depth and to infinite depth. Nobody else cares: either way, there are far better ways to render your coins useless than placing them in a giant chain or loop.

Covenants by the Back Door

I wrote a previous post on Covenants via Signatures which noted that signatures with BIP-118 can be used to make covenants. This is not a neat design, it’s more like “we have a jackhammer, we can use it to knock in a nail”. On the covenant spectrum, it’s an Equality Covenant between OP_TXHASH and OP_CHECKTEMPLATEVERIFY, in that it can be used with several different field bitmaps, according to the SIGHASH flags used on the signature.

Introspection Is Not All We Want

It’s worth noting that Bitcoin’s OP_CHECKSIG (and family) do three things:

  1. Assemble parts of the current transaction.
  2. Hash it.
  3. Check the hash is signed with a given key.

OP_TX implements the first, and we already have various OP_SHA256 and similar operations for the second. OP_TXHASH and OP_CHECKTEMPLATEVERIFY combine the first two.

It’s logical to want a separate operation for the third one, hence the proposal to be able to check a signature signs a given hash: OP_CHECKSIGFROMSTACK. This would let you simulate any OP_CHECKSIG variation (depending on what OP_TX/OP_TXHASH flags were enabled):

02 <flags> OP_TX OP_SHA256 <pubkey> OP_CHECKSIGFROMSTACK

Summary

We should enable ANYPREVOUT. This will enable LN-symmetry which makes Lightning simpler (and thus more robust!), which has already been implemented. It will also enable covenants, though with a weird requirement for a signature-in-output, which makes them less efficient than they could be, but enables real uses and experimentation to inform future soft forks.

For future covenant soft forks, we should look at complete designs like OP_TX, then clip their wings as desired so we can enable the full functionality later. This may well end up looking like OP_CHECKTEMPLATEVERIFY!

Meanwhile, Greg Sanders, who both refined the OP_VAULT proposal and implemented LN-Symmetry (nee Eltoo), expressed the opinion that we’re fast approaching the edge of Bitcoin Script usability, and he now was firmly of the opinion that a soft fork to introduce Simplicity would be better. Perhaps that will happen instead of OP_TX or the like?