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.
Covenants using bignum
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)
, wherek
is a nonce,G
is the generator point,\times
is the EC point multiplication operation.s=k^(-1)*(sighash + r*d) mod n
, whered
is the private key andn
is the order ofG
.
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
andn
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
ands
to create a valid DER encoded signature.OP_CHECKSIG
it with a pre-computed public key derived fromd
.
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?
Covenants without bignum
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.
Can we use schnorr?
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?
Awesome article!
This is pretty cool, but awfully hard to comprehend (the technique, not the article) for an average programmer.
Personally, the hardest covenants I can understand and use are the CashScript's ones: https://twitter.com/RoscoKalis/status/1204765868950990848?s=19
I'm also guessing your approach takes a lot more of operations and therefore is more likely to hit the opcodes limit than OP_CDS one, right?