I read on bitcoin-dev that CHECKSIGFROMSTACKVERIFY (CHECKDATASIG in BCash) can be used to emulate new sighash flags. How would that work, concretely? As an example, please provide an example update transaction based on figure 4 of the Eltoo paper, or aj's simplified lightning (that doesn't work with watchtowers).
-
You also need OP_CAT or OP_SUBSTR. If you are still interested in this question, I can answer this in detail– sanket1729Jan 28, 2021 at 18:14
-
@sanket1729, Yes, I am still interested! Thanks! Looking forward to reading your answer.– Janus TroelsenJan 28, 2021 at 20:49
-
1Sorry for the super long delay, I finally have an answer for you. This may be a lot to take in, so let me know if something needs more clarification.– sanket1729Jul 22, 2021 at 8:46
1 Answer
Eltoo relies on a primitive SIGHASH_ANYPREVOUT
or SIGHASH_NOINPUT
(hereafter referred as APO) BPI118, constructing Eltoo from APO would be replacing the CHECKSIG
figure 4 of Eltoo paper with a modified script.
Most of this answer is based on a blog post by Russell O'Connor from this link(which goes into other covenant constructions too). For completeness, let us look at the basics of transaction introspection and later hop into how to construct APO
. The answer is for Segwit Signature Hash and the ECDSA signature algorithm. Unfortunately, extending this to taproot requires additional opcodes/other changes which I will explain at the end of the answer.
OP_CHECKSIGFROMSTACK
:(hereafterCSFS
) There is no formal proposal for this opcode yet, but most likely this would work as follows: pop 1) a pubkeypk
, 2) a messagem
, and a 3) digital signaturesig
. The operation would perform the verify the sig as ECSDA.Verify(pk, sha256(m), sig) and push the result onto the stack top.- OP_CAT: Pop two items from stack
top
andnext_top
and pushnext_top || top
onto the stack where||
denotes the byte string concatenation. I will be assuming aSIGHASH_ALL
for the below construction, but the construction can be used for sighash type.
First we will look at basic transaction introspection using OP_CAT
and OP_CSFS
:
Consider the following script:
1. OP_OVER OP_SHA256 <pubKey>
2. 2 OP_PICK 1 OP_CAT OP_OVER
3. OP_CHECKSIGVERIFY
4. OP_CHECKSIGFROMSTACKVERIFY
with the witness stack
<sig>
<sig_msg>
Where <fixed_pk>
is public whose secret key is known to everyone. Say, the generator G
whose secret key is 1
. <sig_msg>
denotes transition digest being signed.
At the end of Step 1, the stack is
<pubKey>
<sha256(<sig_msg>)>
<signature>
<<sig_msg>>
At the end of step 2, the stack contents are
<pubKey>
<sig;SIGHASH_ALL>
<pubKey>
<sha256(sig_msg)>
<sig>
<sig_msg>
Note that 1
is translated as SIGHASH_ALL
and catted to sig
. This creates a regular bitcoin signature which we can verify using regular OP_CHECKSIG
. After Step 3, the stack contents are
<pubKey>
<sha256(sig_msg)>
<sig>
<sig_msg>
Which would call OP_CHECKSIGFROMSTACKVERIFY
to leave only <sig_msg>
onto the stack. The OP_CHECKSIGFROMSTACKVERIFY of step 4 uses the same pubKey and signature. This operation performs another SHA-256(double sha256 is required for segwit sighash algorithm) and does a digital signature validation on the doubly hashed <sig_msg>. Note that because we are using exactly the same <pubKey>
and <sig>
that we used for the previous OP_CHECKSIGVERIFY
operation, the current OP_CHECKSIGFROMSTACKVERIFY
can only succeed if <sig_msg> is identical to the message that was checked during step 3’s OP_CHECKSIGVERIFY operation. At this point, we are certain that the <sig_msg>
on the stack top is the same as the signed transaction message.
Note that since the private key to <fixed_pk>
is known, anyone can create this signature, but this script would only succeed if the <sig_msg>
is correctly specified as mentioned above.
Onto ANYPREVOUT:
Anyprevout essentially is substituting some things from the regular signature hash messages and replacing those with 0's. In detail, we can now split our sig_msg
into multiple parts as defined in BIP143 using OP_SUBSTR
. It is also possible to use OP_CAT
to initially supply the split up the message and then CAT it together.
Now that we have signature hash message as per BIP143 on the stack, we manipulate it to make certain fields 0's. In particular, we make items 2-4 0.
2. hashPrevouts (32-byte hash)
3. hashSequence (32-byte hash)
4. outpoint (32-byte hash + 4-byte little endian)
We take a OP_DUP OP_SUSTR 0 <4>
to get item 1, version. onto the stack. and similarly, obtain a concatenation of items 5-10 onto the stack. Finally, we CAT them together by <ver> <[0; (32+ 32 + (32 +4))]>
<items 5.10>` to get a new sighash which we use for Eltoo update key.
Let introspect_script
=
OP_OVER OP_SHA256 <pubKey> 2 OP_PICK 1 OP_CAT OP_OVER OP_CHECKSIGVERIFY OP_CHECKSIGFROMSTACKVERIFY
as we discussed above. And let manipulate_txdata_script
be the script to modify the stack contents from BIP143 sighash message to get a new message which sets the respective items to 0s
. Then our final substitute for OP_CHECKSIGANYPREVOUT
would be the following script.
final_script(eltoo_pk) = <introspect_script> <manipulate_txdata_script> <eltoo_pk> OP_CSFS
Satisfying this would require the following onto the initial witness stack.
<sig_from_fixed_key>
<sig_msg>
<sig_from_eltoo_update_priv_key>
Lastly, the eltoo script would now look like:
OP_IF
10 OP_CSV
2 A(s,i) B(s,i) 2 OP_CHECKMULTISIGVERIFY
OP_ELSE
<Si + 1> OP_CHECKLOCKTIMEVERIFY
final_script(Au) final_script(Bu)
OP_ENDIF
Note how the two of two multi-sig is split into two parts.
Taproot Signature Hash:
The same technique does not work for Taproot Signature Hash algorithm(see BIP341) and Schnorr signatures (see BIP340) because of tagged hashes. At a high level, because of tags, the size of these messages is more than 520 bytes which is currently the maximum supported by bitcoin network. It is possible to overcome this limitation by adding 1)streaming SHA256
opcodes, 2) by raising the per stack element limit, Or 3) adding support for tagged hashes by new opcodes.