Participants are encouraged to review the notes from the session on
PR18401 in particular, since we’ll be covering a lot of the same
topics this week.
Notes
The transaction digest that is used in signature creation and verification
is calculated from different parts of the transaction. There are extensive
notes covering this topic from our session on
PR18401.
This week, we’ll review the implementation of the Taproot transaction hashing
algorithm. We won’t look at full signature validation. It may be useful to
check out just up to the commit Implement Taproot signature hashing (BIP 341).
Specifically, we’ll be looking at the SignatureHashSchnorr() function in
src/script/interpreter.cpp.
It’s probably most instructive to compare the new SignatureHashSchnorr()
function to the SignatureHash() function immediately below, which
calculates the signature hash for segwit v0 and legacy transactions.
Questions
SignatureHashSchnorr() is a templated function. Why? What types T is
the template function instantiated with? Hint: look at how the existing
SignatureHash() function is called.
SignatureHashSchnorr() is passed a PrecomputedTransactionData* argument.
What data is stored in this structure? Why?
SignatureHashSchnorr() is passed a hash_type argument. How many valid
hash types are there? The hash_type parameter is split into output_type
and input_type here:
How many valid values are there for output_type and input_type?
Just like SignatureHash(), the new function creates a local CHashWriter
object. CHashWriter is a stream-like object. Other objects can be
serialized into it (using the << operator), and at the end,
CHashWriter::GetHash() is called to retrieve the digest.
One difference from SignatureHash() is that the CHashWriter is
copy-constructed from HasherTapSighash. How is that object constructed, and
what’s the difference from a regular CHashWriter object?
If the hash_type does not have the ANYONECANPAY flag, certain parts of
the transaction are added to the CHashWriter. What are those elements, and
how is that different from SignatureHash()?
A spend_type byte is added the CHashWriter. What are the component parts
of spend_type, and what do they indicate? Hint: refer back to BIP 341 and
BIP 342.
If the hash_type indicates SIGHASH_SINGLE, there’s a check here:
<jnewbery> normal reminder: there are no stupid questions. We're all here to learn, so please speak up if something isn't clear to you. You'll be helping others too!
<jnewbery> other reminder: you can ask questions at any time. You don't have to ask to ask. I'll guide the discussion with some prepared questions, but feel free to jump in at any point with your question
<jnewbery> First question: SignatureHashSchnorr() is a templated function. Why? What types T is the template function instantiated with? Hint: look at how the existing SignatureHash() function is called.
<jnewbery> troygiorshev pinheadmz willcl_ark fjahr: correct! We can call it with either a CTransaction or CMutableTransaction, just like SignatureHash()
<sipa> it may also enable having different container structures for tx that are more efficient (e.g. no separate allocation for all the inputs, outputs, scripts, ...)
<jnewbery> willcl_ark: I think those are actually all cached for all witness transactions. It's just that m_spent_amounts_hash and m_spent_scripts_hash are only used if the tx is taproot
<sipa> if your question is why did BIP143's authors chose that option: it was aiming to change as little as possible compared to pre-segwit sighashing, iirc
<jnewbery> this is what it looked like previously: ttps://github.com/bitcoin-core-review-club/bitcoin/commit/41d08f5d77f52bec0e31bb081d85fff2d67d0467#diff-be2905e2f5218ecdbe4e55637dac75f3R1312-R1331
<jnewbery> Just like SignatureHash(), the new function creates a local CHashWriter object. CHashWriter is a stream-like object. Other objects can be serialized into it (using the << operator), and at the end, CHashWriter::GetHash() is called to retrieve the digest.
<jnewbery> One difference from SignatureHash() is that the CHashWriter is copy-constructed from HasherTapSighash. How is that object constructed, and what’s the difference from a regular CHashWriter object?
<pinheadmz> but adding 0x01 at the end of the sig is not valid right? if you want sighash all you just leave it as a 64 byte sig with no explciit type?
<sipa> pinheadmz: you can do either; have a 64-byte one with implicit sighash 0, or explicitly make a 65-byte sig with hashtype 1; their semantics are the same... but the signature will still differ because the hash commits to the actual hashtype value
<jnewbery> pinheadmz fjahr felixweiss: great stuff. If anyone is confused by the words 'midstate' or 'block' in this context, look up the SHA256 or merkle-damgard hash construction wikipedia pages
<jnewbery> next question: If the hash_type does not have the ANYONECANPAY flag, certain parts of the transaction are added to the CHashWriter. What are those elements, and how is that different from SignatureHash()?
<pinheadmz> ok sure, so currently you can trick a hardware wallet into signing two separate transactions each with two inputs (say a big value and a small value input) then, create a new TX with just the two high value inputs, making the input to the tx unexpectedly large. the output value doesnt change, so the wallet loses a big fee
<sipa> troygiorshev: static just makes the variable inaccessible outside the compilation unit, and const prevents code from accidentally modifying the cached value; neither changes performance, they just prevent things we don't want to happen
<pinheadmz> although now that im trying to explain, it seems like sighash single must be used for the attack so the bip341 updated couldnt prevent that ?
<jnewbery> it's been known about for some time, and adding a new segwit version allows us to resolve that class of issues by introducing a new sighash algorithm
<jnewbery> Next question: A spend_type byte is added the CHashWriter. What are the component parts of spend_type, and what do they indicate? Hint: refer back to BIP 341 and BIP 342.
<jnewbery> "spend_type (1): equal to (ext_flag * 2) + annex_present, where annex_present is 0 if no annex is present, or 1 otherwise (the original witness stack has two or more witness elements, and the first byte of the last element is 0x50)"
<jnewbery> hmmm I'm not sure about the part about OP_CODESEPARATOR. I know that people have tried to use that in protocols like tumblebit to allow different signing paths
<jnewbery> the idea behind SIGHASH_SINGLE is that only the output with the corresponding index to the input being spent is included in the hash. Obviously for that to work, there needs to be an output with the same index as the input being signed