Skip to content

Instantly share code, notes, and snippets.

@0xBEEFCAF3
Last active October 7, 2024 17:43
Show Gist options
  • Save 0xBEEFCAF3/fcc28c0f1ca8f9ede9e5037f03760d34 to your computer and use it in GitHub Desktop.
Save 0xBEEFCAF3/fcc28c0f1ca8f9ede9e5037f03760d34 to your computer and use it in GitHub Desktop.
Emulating CSFS using CAT

Emulating Check Sig From Stack (OP_CSFS) using OP_CAT

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.

Some Background

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.

Preface

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.

Our goal

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"

Building Block: Authenticated Covenants

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.

$Sig_0 = (G, s)$ where $s = H(tag | G | G | M) + 1$. This implies that $d = k = 1$.

$Sig_1 = (R, s)$ where $s = H(tag | R | P | M) + k$. Where $R = k * G$ and $P = d * G$.

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".

Building Block: Arbituary Messages

What's left for us is to validate that the second signature ($Sig_1$) not only authenticates the covenant's structure but also signs an arbitrary message. Using our CAT covenant techniques, we can extract the value of an output that Alice has signed over.

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:

  1. The first output is an OP_RETURN with a 32-byte value.
  2. The second output pays Bob.

We then require the spender to provide an additional item on the witness stack: $M$. In our script, we can "pull" the 32-byte value from output 0 and compare it against SHA256(M). If $M$ from the witness stack matches the 32-byte hash in the OP_RETURN, we can confirm that Alice has signed over $M$.

Additional Ideas

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.

Acknowledgements

Thank you to Dan Gould for looking over this work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment