2

I'm having trouble manually recreating the taproot sighash from the example below.

I used btcdeb to pass in an input transaction (with a P2TR output), and a valid tx that spends from it. In the logs it shows the components of the sighash message, but when I concatenate them and hash it with sha256 I'm not getting the same sighash. What am I missing?

Also, what is the epoch? I've tried with and without this value and neither worked.

$ btcdeb --txin='02000000000101c7eb56c02553b5fe29c16b458960ba21be0ef1fb020972fde15324b1f725f0480000000000fdffffff027c9c181e010000002251204a0cd40b4984a107334966b266bdc8134bcba9f23f039ed9ddc3f99d6c99c03fa048ed0b000000002251204f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa024730440220077e71f9276149475ecb27e68374712f43e0af6f61ca0e0c9a74483c07bd08d902206f0befdcbc047b0a9a59eff49e011f81644ba0e179a6fa4a90bb19f7765d165c012103d31501fa2dd221b0d40eead3961afd65bdfa986f2577327d1400fa82691cc69965000000' --tx='02000000000101d0a3e1a6c730355abefa376f9b5d3ca9325b153475becc8402e3b4a98d393ed20100000000ffffffff0280d1f00800000000160014fc7250a211deddc70ee5a2738de5f07817351cef80f0fa0200000000160014531260aa2a199e228c537dfa42c82bea2c7c1f4d01404d22f94e5d1787eecf712b14b0c8fb57242633616d50ca5b77f31dad9ad43d0a6786b1e5467dc20494a08e5862821834675f91f897f0e568b417430b07ba100b00000000'
btcdeb 0.4.22 -- type `btcdeb -h` for start up options
LOG: sighash signing segwit taproot
notice: btcdeb has gotten quieter; use --verbose if necessary (this message is temporary)
input tx index = 0; tx input vout = 1; value = 200100000
got witness stack of size 1
34 bytes (v0=P2WSH, v1=taproot/tapscript)
valid script
- generating prevout hash from 1 ins
[+] COutPoint(d23e398da9, 1)
note: there is a for-clarity preamble (use --verbose for details)
2 op script loaded. type `help` for usage information
script                                                           |                                                             stack 
-----------------------------------------------------------------+-------------------------------------------------------------------
4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa | 4d22f94e5d1787eecf712b14b0c8fb57242633616d50ca5b77f31dad9ad43d0...
OP_CHECKSIG                                                      | 
#0000 4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa
btcdeb> step
        <> PUSH stack 4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa
script                                                           |                                                             stack 
-----------------------------------------------------------------+-------------------------------------------------------------------
OP_CHECKSIG                                                      |   4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa
                                                                 | 4d22f94e5d1787eecf712b14b0c8fb57242633616d50ca5b77f31dad9ad43d0...
#0001 OP_CHECKSIG
btcdeb> step
EvalChecksig() sigversion=2
GenericTransactionSignatureChecker::CheckSchnorrSignature(64 len sig, 32 len pubkey, sigversion=2)
  sig         = 4d22f94e5d1787eecf712b14b0c8fb57242633616d50ca5b77f31dad9ad43d0a6786b1e5467dc20494a08e5862821834675f91f897f0e568b417430b07ba100b
  pub key     = 4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa
SignatureHashSchnorr(in_pos=0, hash_type=00)
- taproot sighash
 << epoch
#001 00
 << hash type
#001 00
 << tx_to.nVersion
#004 02000000
 << tx_to.nLockTime
#004 00000000
input type != anyone can pay:
 << cache prevouts single hash
#032 e186179664b38e58723ba1ac8d5915f97023f6afc2f2be94459fe9868cf953eb
 << cache spent amounts single hash
#032 646b3ee05dcdae994f2d99d94a878d1ad40c3f4b536b76a6a730b4e28b29c385
 << cache spent scripts single hash
#032 e00e0a53937928e143c165df361096d6f607d8fe35aa63f4adfe2e73b56cd324
 << cache sequences single hash
#032 ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e
output type == sighash_all
 << cache outputs single hash
#032 5ac2b429009184588bf78341f4692a02303a6a3110c2f1a914f3dba9cea091cb
 << spend type
#001 00
 << in_pos
#004 00000000
- schnorr sighash = 1eaaf5f3228e6aa087cf9620ac86f8bc5458261b074b7cb6d78c20f358d6fc1f
  pubkey.VerifySchnorrSignature(sig=4d22f94e5d1787eecf712b14b0c8fb57242633616d50ca5b77f31dad9ad43d0a6786b1e5467dc20494a08e5862821834675f91f897f0e568b417430b07ba100b, sighash=1eaaf5f3228e6aa087cf9620ac86f8bc5458261b074b7cb6d78c20f358d6fc1f):
  result: success

And here is my python script to try and create the same sighash:

import hashlib

sig_msg = (
    "00" # epoch
    + "00" # hash_type
    + "02000000" # nVersion
    + "00000000" # nLockTime
    + "e186179664b38e58723ba1ac8d5915f97023f6afc2f2be94459fe9868cf953eb" # sha_prevouts
    + "646b3ee05dcdae994f2d99d94a878d1ad40c3f4b536b76a6a730b4e28b29c385" # sha_amounts
    + "e00e0a53937928e143c165df361096d6f607d8fe35aa63f4adfe2e73b56cd324" # sha_scriptpubkeys
    + "ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e" # sha_sequences
    + "5ac2b429009184588bf78341f4692a02303a6a3110c2f1a914f3dba9cea091cb" # sha_outputs
    + "00" # spend_type
    + "00000000" # input_index
)

sig_msg = bytes.fromhex(sig_msg)

hashlib.sha256(sig_msg).digest().hex()
# returns 4c1f7ef4d927d34de60599d334fe26cfb03090c5672e01019bf5da54b9cb5446

2 Answers 2

2

I believe you're missing the fact that the hash function used in BIP341/BIP342 is not SHA256 directly, but a tagged hash "hashTapSighash()".

From the BIP:

hashtag(x) notation to refer to SHA256(SHA256(tag) || SHA256(tag) || x).

So you have to prefix your message with SHA256("TapSighash") twice.

1
1

From Pieter Wuille's answer, here's what the correct hash function from BIP340 should be:

import hashlib

def tagged_hash(tag: str, msg: bytes) -> bytes:
    tag_hash = hashlib.sha256(tag.encode()).digest()
    return hashlib.sha256(tag_hash + tag_hash + msg).digest()

sig_msg = (
    "00" # epoch
    + "00" # hash_type
    + "02000000" # nVersion
    + "00000000" # nLockTime
    + "e186179664b38e58723ba1ac8d5915f97023f6afc2f2be94459fe9868cf953eb" # sha_prevouts
    + "646b3ee05dcdae994f2d99d94a878d1ad40c3f4b536b76a6a730b4e28b29c385" # sha_amounts
    + "e00e0a53937928e143c165df361096d6f607d8fe35aa63f4adfe2e73b56cd324" # sha_scriptpubkeys
    + "ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e" # sha_sequences
    + "5ac2b429009184588bf78341f4692a02303a6a3110c2f1a914f3dba9cea091cb" # sha_outputs
    + "00" # spend_type
    + "00000000" # input_index
)

sig_msg = bytes.fromhex(sig_msg)

tagged_hash("TapSighash", sig_msg)

# returns 1ffcd658f3208cd7b67c4b071b265854bcf886ac2096cf87a06a8e22f3f5aa1e
# which is the same as btcdeb (little endian)

Also, an explanation for the sighash epoch can be found here in BIP341.

Why is the input to hashTapSighash prefixed with 0x00? This prefix is called the sighash epoch, and allows reusing the hashTapSighash tagged hash in future signature algorithms that make invasive changes to how hashing is performed (as opposed to the ext_flag mechanism that is used for incremental extensions). An alternative is having them use a different tag, but supporting a growing number of tags may become undesirable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.