OP_CAT enables the construction of covenants within Bitcoin script, albeit in a somewhat hacky manner. It requires the spender to place transaction elements on the stack, concatenate all the transaction elements, and then trick OP_CHECKSIG into verifying these elements. The spender accomplishes this by using a signature where both the private key and private nonce are set to 1.
I refere to this hack as the Poelstra trick and you can find more information here Understanding the Poelstra trick is the hard part of learning how to build a variant CSFS.
Elements Project's OP_CHECKSIGFROMSTACK (CSFS) verifies an ECDSA signature sig
over an arbitrary message m
against a public key pk
. Unlike Bitcoin's existing signature-checking opcodes, such as OP_CHECKSIG, which derive the message from the transaction executing the opcode, OP_CHECKSIGFROMSTACK reads an arbitrary message and any arbitrary public key.
Element's style CSFS allows you to provide any public key and signature combination. As you will see with our variant, you can only verify a message if you also can spend the UTXO.
We want to create a script that evaluates to this
if check_sig_from_stack(m, sig, pk) {
spend(bob_address)
}
where m
could be any arbiturary message such as "foo" or "the Boston Celtics will be the 2024 NBA champions"
One common misconception (or atleast one that I had) was that if you perform the Poelstra trick then you lose the normal CHECK_SIG operation that most bitcoin UTXOs execute. i.e you can only constrain a UTXO with a covenant or signature verification but not both. This does not have to be case. The spender can set up a CAT covenant with a certain set of restrictions and then provide a typical signature which signs over the typical SIGHASH.
In total we are left with two signatures.
A simplified witness stack would look like:
user provided tx_components
OP_CAT, OP_CAT ... OP_CAT
sha256x2
sig0
G
OP_CHECKSIG
sig1
OP_CHECKSIGVERIFY
As a consequence of providing a signature over the tx sighash we know that the owner of the private key d
is infact the one that set up covenants. Hence "authenticated covenants".
What's left for us is to validate that the second signature (
Let's set up a transaction with one input and two outputs. The input is the CAT-authenticated covenant UTXO. The first output is an OP_RETURN with a 32-byte hash of a message (M) (not to be confused with the transaction sighash, which we will denote as little (m)). The second output spends the remaining amount to Bob, minus a fee. The covenant must enforce the following conditions:
- The first output is an OP_RETURN with a 32-byte value.
- The second output pays Bob.
We then require the spender to provide an additional item on the witness stack:
You can naively use checkmultisig instead of checksig for a multiparty CSFS operation. This operation could be useful for multiparty oracle setups. In the future if we ever get a TX_HASH as a sighash flag you can consider how many parties can sign different data payloads in different outputs. Today with the sighash flags available this would not be possible.
In a seperate post I would like to demonstrate how we can use these tricks to emulate SIGHASH_NOINPUT / ANY_PREVOUT.
Thank you to Dan Gould for looking over this work!