On November 2018, Tendo Pein published a brilliant article on spending constraints with OP_CHECKDATASIG (updated version). By using OP_CHECKDATASIG ( OP_CDS ) and OP_CHECKSIG a script can verify it has received a subset of the content of the spending transaction (a sighash preimage). This enables an output script to apply rules to the spending transaction, effectively creating a covenant.

Recently I've seen claims that OP_CDS is not required to have this capability, like in this tweet from @sinoTrinity. I asked around and u/-mr-word- briefly explained to me what he calls "the checksig trick".

Then I found a solution that could have been used on BCH since May 2018, as it doesn't require any bignum operation. Here's an example on mainnet.

To simulate OP_CDS in script (i.e. to verify a signature), we need bignum math and complex elliptic curve operations. However, @sinoTrinity's solution on BSV is completely different: instead of checking one user-provided signature twice, it actually creates the signature in script and check it with OP_CHECKSIG . A successful signature check means the user provided the right sighash preimage, just like we have with OP_CDS on BCH. This approach requires bignum operations, but can be made to completely avoid elliptic curve operations.

An ECDSA signature is a pair (r,s) calculated as:

r=x coordinate of (k \times G) , where k is a nonce, G is the generator point, \times is the EC point multiplication operation.

s=k^(-1)*(sighash + r*d) mod n , where d is the private key and n is the order of G .

The solution on BSV uses bignum operations. It basically follows these steps:

Calculate sighash i.e. hash the sighash preimage. Convert it to little-endian. Calculate s=k^(-1)*(sighash + r*d) mod n . k^(-1) , r , d and n are constants in the contract. Adjust s to guarantee a low-s (a consensus rule on BCH and BSV). Convert it to big-endian. Concatenate r and s to create a valid DER encoded signature. OP_CHECKSIG it with a pre-computed public key derived from d .

A user spending from this contract has to provide only the sighash preimage in the scriptSig and it executes only one signature verification (SigOp). In contrast, on BCH using OP_CDS the user is required to provide the sighash preimage, a signature and a public key and it uses 2 SigOps.

The choice of parameters ( k and d ) used in this contract seemed random. Since any valid choice suffices, why not choose numbers that facilitates the computation?

By choosing k=1 and d=r^(-1) , step 3 becomes s=(sighash + 1) mod n . We can safely ignore the mod n operation, since the low-s restriction on step 4 automatically covers it. Because we are avoiding bignum operations, we just skip step 4 and restrict sighash so that sighash<n/2 . Fortunately, it's quite easy to malleate the spending transaction to generate a new sighash, for instance by changing nSequence or nLockTime . Any sighash has a probability close to 50% to become a low-s signature, so this shouldn't be much work.

The computation is now reduced to s=sighash + 1 . It's not as easy as it seems, because we must convert endianness twice and potentially have to propagate a carry over many bytes. We could avoid the carry part by including another restriction on sighash, but there's an easier way: choosing d=r^(-1)*0x01...00 (256bit scalar) . This avoids the endianness conversions and carry propagation by simply restricting the said sighash constraint a little more: now sighash's MSB must be <0x7e . We need to malleate the transaction ~2 times, in expected value, to find an adequate sighash to spend this covenant contract.

The unlocking script then becomes:

OP_HASH256 OP_1 OP_SPLIT OP_SWAP OP_BIN2NUM OP_1ADD OP_SWAP OP_CAT [3044022079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980220] OP_SWAP OP_CAT [c1] OP_CAT [02b405d7f0322a89d0f9f3a98e6f938fdc1c969a8d1382a2bf66a71ae74a1e83b0] OP_CHECKSIG[VERIFY]

It uses 87 bytes and only one SigOp. It can be used anywhere one needs to verify that the correct sighash preimage was provided. This solution makes it really easy to trigger smart contracts: no ecdsa or even hash code needed! If it fails to broadcast with a "Non-canonical DER signature" error, just malleate the transaction and try again.

I tested it on mainnet with no extra spending restrictions.

Choose k=x=1 and this 63-bytes script also does the trick:

OP_HASH256 [79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798] OP_2 OP_OVER OP_CAT OP_2DUP OP_CAT OP_3 OP_ROLL OP_CAT OP_SHA256 [1f] OP_SPLIT [00] OP_CAT OP_BIN2NUM OP_1ADD OP_1 OP_SPLIT OP_DROP OP_CAT OP_SWAP OP_TOALTSTACK OP_CAT [c1] OP_CAT OP_FROMALTSTACK OP_CHECKSIG[VERIFY]

The good news is that we need to malleate the transaction only once every 256 spends, in average. Also, add an OP_CODESEPARATOR and this script can be useful for covenants optimizing for smaller scriptSigs, even smaller than using OP_CDS .

Here's a sample transaction on mainnet.

So, it seems we could have had covenants smart contracts since May 2018. Is this approach useful? What are your thoughts?

Lead image by edar from Pixabay.