Bitcoin OP_CAT Use Cases Series #1: Covenants

Ordinal lock: trustless on-chain Ordinal sales

sCrypt
6 min readMay 16, 2024

The OP_CAT opcode has potential to enhance the flexibility and functionality of Bitcoin, if reactivated. It allows developers to create impressive new features such as covenants. In the first of a series, we demonstrate a trustless sale of an Ordinal NFT using a layer-1 covenants made possible by re-enabling OP_CAT, without intermediaries.

OP_CAT

OP_CAT stands for “concatenate” in Bitcoin’s scripting language. It was originally included in the scripting language but was disabled due to concerns over potential vulnerabilities. OP_CAT allows concatenation of two strings on the stack.

It has been reactivated on Bitcoin Cash and SV since 2018 and has been used extensively to significantly enhance scripting capabilities while maintaining security. A few prominent use cases are:

On BTC, the reintroduction of OP_CAT has garnered interest lately. It has been assigned the Bitcoin Improvement Proposal (BIP) number 347, marking the first step towards potentially implementing this long-discussed software upgrade.

sCrypt vs Script

Our series will explore a wide range of use cases of OP_CAT. All of them will be implemented in sCrypt and open sourced, for anyone to independently try and verify on signet, where OP_CAT has been activated.

Up till now, most discussions on OP_CAT on have been hypothetical and theoretical. One primary reason for the dearth of practical demonstrations is the immense difficulty of reasoning about and coding in Bitcoin Script, which is a low-level assembly language.

With sCrypt, a Typescript-based domain specific language (DSL), developers can write Bitcoin smart contracts leveraging OP_CAT directly in TypeScript, one of the world’s most popular programming languages, used by millions of developers daily. sCrypt contracts are then compiled into Bitcoin Script.

We demonstrate sCrypt makes previously complex and intractable Script constructions as easy as developing Web2 applications, accelerating OP_CAT-enabled innovations. These empirical experiments provide data points and insights to the ongoing OP_CAT discourse.

Covenants

In the context of Bitcoin, a covenant refers to a mechanism that can impose constraints on how future transactions involving a specific set of coins can be spent. Covenants are essentially restrictions or rules embedded within Bitcoin transactions, designed to control the conditions under which the coins can be transferred or spent. For example, they can restrict the addresses to which the coins can be sent, or require certain scripts to be included in future transactions.

One straightforward way to add covenants is to introduce new opcode such as OP_CHECKTEMPLATEVERIFY (CTV) in BIP 119. Alternatively, it turns out covenants can be implemented by using a trick if OP_CAT were reactivated.

We first wrote about the introspection trick in 2020 using ECDSA signatures, which we called OP_PUSH_TX and its optimal variant.

Later on, it was written by Andrew Poelstra in 2021 and extended to Schnorr signatures.

Covenants Using Schnorr Signatures

BTC’s Taproot upgrade introduced Schnorr signatures (see BIP-340). The signature scheme uses the same underlying elliptic curve as in ECDSA.

Suppose we want to sign using the key pair (x, P = xG). We generate an ephemeral key pair (k, R = kG). The signature is defined as the tuple (R, s), where

s = k + xe

e is a hash of three things concatenated: our public key P’s x-coordinate, the x-coordinate of ephemeral R, and our transaction data/preimage.

e = H(Rx || Px || transaction)

If we choose k = 1 and x = 1, we can simplify the equation:

s = 1 + e

By constructing such a signature within script and passing it to an OP_CHECKSIG, we can constrain chunks of the transaction data. In short, we get a working covenant with OP_CAT, which allows calculating the equation in script. You can read more about this trick in the aforementioned articles.

Ordinal Locks

A seller transfers her ordinal NFT into a covenant smart contract (called an ordinal lock) in a taproot output that can only be redeemed if the spending transaction pays the seller the asking price. Traditional Ordinals marketplaces based on Partially Signed Bitcoin Transactions (PSBTs) generally do not share these PSBT listings with each other. Ordinal lock can potentially create a global shared on-chain order book with more liquidity and transparency, which is public for everyone to access.

Implementation

The signature equation above requires us to add 1 to a 256-bit integer. BTC currently only allows arithmetic in integers of 32 bits long, so we need to find a workaround.

Because e is derived from our transaction data, we can malleate the transaction until it hashed to a value of e that ends with 0x01 (or any value less than 0xFF) as we did in optimal PUSHTX. This takes 256 tries on average, which is instantaneous on modern hardware. For this example, we iterate on the value of nLockTime in the transaction, starting from 0, which won’t have an effect on the transaction’s validity/finality.

Once we have a value ending with 0x01, we can simply cut off the last byte and append 0x02 to it within the script to arrive at the correct value for s.

Let us implement a simple function:

If the code execution succeeds, it leaves us with a verified preimage of e, i.e., variable shPreimage. Line 4 enforeces e ending with 0x01.

We then retrieve the hashOutputs field from shPreimage, which allows us to constrain the spending transactions’ outputs.

In the above code, we label the constrained output the payment output. This ensures that the transaction MUST contain this output exactly as specified by the contract in order to be valid. For the seller, this means the blockchain guarantees them a payout of the listing price if there is a buyer willing to purchase the ordinal.

Additionally, we constrain the SigHash type to SIGHASH_ALL, to avoid preimage spoofing.

A single run results in the following transactions:

  • Listing Transaction ID:
  • Purchase Transaction ID:
Lots of OP_CATs

Full code is at https://github.com/sCrypt-Inc/scrypt-btc-ord-listing.

In addition to the covenant mechanism, the Pay-to-Taproot (P2TR) output can also commit to an alternative leaf script that simply checks for the seller’s signature, plus a time lock. This allows the seller to cancel/withdraw the listing.

Script version

To show the efficiency of sCrypt, we also implemented the same contract in Script at https://github.com/sCrypt-Inc/btc-ordinal-listing.

// Gx = 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
// e must end with 0x01. Mutate TX until it does. Should take 256 tries on average.
// e' = slice(e, 0, -1)
// s = 1 + e
inputs: <e'>
tapscript: <Gx> OP_2DUP OP_SWAP <0x02> OP_CAT OP_CAT OP_SWAP OP_CHECKSIGVERIFY

If the above script execution succeeds, it leaves us with a verified e’ on the stack.

Let’s extend the script to also get the sighash along with its transaction preimage via input and make it validate the correct value of e:

inputs: <preimage> <sigHash> <e'>
tapscript: OP_OVER <tagHash || tagHash || Gx || Gx> OP_SWAP OP_CAT
OP_SHA256 OP_OVER OP_1 OP_CAT OP_EQUALVERIFY
<Gx> OP_DUP OP_2 OP_ROLL OP_2 OP_CAT OP_CAT OP_SWAP OP_CHECKSIGVERIFY
OP_OVER OP_SHA256 OP_EQUALVERIFY

Finally, let’s also constrain the transaction preimage’s second output and their sighash type.

inputs: 
<preimage_txVer || preimage_nLockTime>
<preimage_hashPrevouts || preimage_hashSpentAmounts>
<preimage_hashSpentScripts || preimage_hashSequences>
<preimage_spendType || preimage_inputNumber || preimage_tapleafHash || preimage_keyVersion || preiamge_codeSeparator>
<preimage_ordDestOutput>
<preimage_changeOutput>
<sigHash> <e'>
tapscript:
OP_OVER <tagHash || tagHash || Gx || Gx> OP_SWAP OP_CAT
OP_SHA256 OP_OVER OP_1 OP_CAT OP_EQUALVERIFY
<Gx> OP_DUP OP_2 OP_ROLL OP_2 OP_CAT OP_CAT OP_SWAP OP_CHECKSIGVERIFY
OP_TOALTSTACK // Move <sigHash> to alt stack
<preimage_paymentOutput>
OP_SWAP OP_CAT OP_CAT OP_SHA256 // Assemble and hash outputs
OP_SWAP
OP_CAT
OP_CAT
OP_CAT
OP_CAT
OP_SIZE <0xd200> OP_EQUALVERIFY // Check preimage size so far to avoid sighash flag in wrong place
// Add sighash type, epoch value and tweak prefixes to preimage. 0x00 - SIGHASH_ALL, 0x00 - epoch, 0xf40a...031 - tweak prefix
<0xf40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a031f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a0310000>
OP_SWAP OP_CAT
OP_SHA256 OP_FROMALTSTACK OP_EQUAL // Hash assembled preimage and compare against sighash

Compared to the sCrypt implementation, this version is tremendously more challenging to construct and understand.

--

--

sCrypt
sCrypt

Written by sCrypt

sCrypt (https://scrypt.io) is a web3 development platform specialized in UTXO-blockchains like Bitcoin

No responses yet