§ 05

§ 05 · Transactions

Sighash

What exactly a signature signs

A signature has to sign something. On Pearl it never signs the transaction’s bytes directly. It signs a 32-byte hash of a carefully chosen summary of the transaction, called the sighash.

What goes into that summary decides what can and can’t change after signing. Pearl uses Taproot’s recipe, BIP-341, unchanged in every case but one. This page rebuilds the summary for a real payment, checks its signature, and then shows the one case where Pearl does something different.

Try it

The sighash is a tagged hash. Pick the TapSighash tag to see how it starts.

Tagged hash

SHA-256 with a label baked in, so hashes for different jobs never collide

Tag

TapSighash: The digest a Taproot signature signs

  1. Hash the tag once

    t = SHA256(“TapSighash”)

    f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a031

  2. Put it in front of the message, twice

    t ‖ t ‖ message = 96 bytes

    f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a031f40a48df4b2a70c8b4924bf2654661ed3d95fd66a313eb87237597c628e4a03179be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798

    The two copies of t fill exactly one 64-byte SHA-256 block, so software can precompute it once per tag.

  3. Hash all of it once more

    tagged = SHA256(t ‖ t ‖ message)

    Tagged hash ✓ matches taggedHash()

    87c2b87e10305b92ab17b9eaa4f6f9d032cec56fe4e9ac707ea1dff9ca89657a

Same message, other tags

The same 32 bytes give an unrelated hash under each tag, and under plain SHA-256. That is the point: a hash made for one job can never be passed off as another.

plain SHA-256
132f39a9…70e51def
TapTweak
3cf5216d…32fa30e7
TapLeaf
94333e8b…760a3135
TapBranch
a4481a6e…b12f3de0
BIP0340/challenge
d75ca81e…781add6f
Runs in your browser · nothing is sent src/lib/hash.ts · BIP-340 tags, unchanged in Pearl

What exactly is signed?

A byte string called SigMsg, hashed with the tag “TapSighash”:

sighash=TaggedHashTapSighash(𝟶𝚡𝟶𝟶∥SigMsg)\text{sighash} = \text{TaggedHash}_{\text{TapSighash}}\big(\texttt{0x00} \parallel \text{SigMsg}\big)

For a key-path spend with the default type, SigMsg is these fields, in order:

The signed message for a key-path spend, SIGHASH_DEFAULT (175 bytes with the 0x00 prefix)
Field Size Description
epoch 1 00. Always zero; room for future versions.
hash_type 1 The sighash type: 00 for default.
nVersion 4 Transaction version.
nLockTime 4 Transaction locktime.
sha_prevouts 32 SHA-256 of every input's outpoint (txid ‖ index).
sha_amounts 32 SHA-256 of every spent amount (8 bytes each).
sha_scriptpubkeys 32 SHA-256 of every spent locking script, each with its length.
sha_sequences 32 SHA-256 of every input's sequence.
sha_outputs 32 SHA-256 of all outputs, serialized as in the transaction.
spend_type 1 2 × (script path?) + (annex present?). 00 for a plain key-path spend.
input_index 4 Which input this signature is for.

Note what’s covered that the transaction itself doesn’t contain: the amounts and locking scripts of the coins being spent. That’s deliberate. A wallet told false amounts would sign a message no node agrees with, and its signature would be worthless rather than dangerous.

Can we check it on a real payment?

Yes. Take the payment from block 130,825. Its single input spends 495,654,555 grains locked to prl1pfvyle3p7penvnezpg9awpa03ylfl0n30z3usygzvf8kt77wyra8suvzxy2, whose 32-byte key Q is 4b09fcc43e0e66c9e441417ae0f5f127d3f7ce2f147902204c49ecbf79c41f4f. Rebuilding the message:

SigMsg of the payment d919e02a…, input 0 175 bytes
00 00 01000000 00000000 8f5729f08d1f327250068d02475e3b6be5de608d4a1efeba2782ec3764989fc7 e9364e39201be1ad9ec8c1fa26a9a22fe8064b71b8015160fd39a80bdb24320a a709bfc18f57cfef423ef6c8146c37acce3e17beff7fb1b0bc893b8adb951a05 ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e 16b4a40f3bed23979427586444a80a4555167190193e59153a8d97d64be5218f 00 00000000
Field Bytes Value
epoch 1 @0 00
hash_type (default) 1 @1 00
nVersion 4 @2 version 1
nLockTime 4 @6 locktime 0
sha_prevouts 32 @10 SHA-256 of 59395570…53812133 ab020000
sha_amounts 32 @42 SHA-256 of 9b168b1d00000000 = 495,654,555 grains
sha_scriptpubkeys 32 @74 SHA-256 of 22 5120 4b09fcc4…c41f4f
sha_sequences 32 @106 SHA-256 of ffffffff
sha_outputs 32 @138 SHA-256 of both outputs
spend_type 1 @170 key path, no annex
input_index 4 @171 input 0
Computed with this site's library from the real transaction and the spent output. The signature in the transaction's witness verifies against its hash.
  1. Hash the message

    TaggedHash(“TapSighash”, those 175 bytes) = 59a251aaae100a854337adb465d5841f078132f7c28ef1634bc363965e26b58f.
  2. Take the signature

    The witness holds 4686c701…a86ba2, 64 bytes, so the type is the default.
  3. Verify

    BIP-340 verification of that signature, that hash and the key Q 4b09fcc4…c41f4f: valid.

So a real Pearl payment signs exactly the message BIP-341 describes. (We can’t tell which wallet made this signature. Pearl’s own signer picks its nonce with RFC 6979, so with it, signing the same message with the same key always gives the same 64 bytes.)

What are the sighash types?

The type is the 65th byte of a signature, or the default when there are only 64.

Type Name Signs
00 DEFAULT Everything, like ALL. Implied by a 64-byte signature; writing 00 explicitly is invalid.
01 ALL All inputs and all outputs.
02 NONE All inputs, no outputs: anyone may redirect the coins.
03 SINGLE All inputs and only the output at this input’s index. Invalid if there is no such output.
81, 82, 83 … | ANYONECANPAY As above, but only this one input: others may add inputs.

Pearl’s wallet always signs with the default. The others exist for special contracts and were tested on Pearl: 65-byte signatures with 01 and 83 pass, an explicit 00 fails (“invalid sig len: 65”), and an undefined type such as 04 fails.

What changes for a script-path spend?

Three fields are appended, and spend_type becomes 2 (or 3 with an annex):

Field Size Contents
tapleaf_hash 32 The TapLeaf hash of the script being run
key_version 1 00
codesep_pos 4 Position of the last executed OP_CODESEPARATOR, or ffffffff

That ties a signature to one script: it can’t be replayed under a different leaf of the same tree. OP_CHECKXMSSSIG signs this same message, always with the default type.

What is the P2MR quirk?

The one place Pearl departs from the BIPs, and it matters for anyone writing P2MR signing code.

To save work, Pearl’s node precomputes the hashes in SigMsg once per transaction. It computes sha_amounts and sha_scriptpubkeys only if some input spends a Taproot (P2TR) output:

node/txscript/hashcache.go Go
if IsPayToTaproot(prevOut.PkScript) {
	hasV1Inputs = true
} else {
	// Otherwise, we'll assume we need the v0 sighash midstate.
	hasV0Inputs = true
}
…
if hasV1Inputs {
	sigHashes.HashInputAmountsV1 = calcHashInputAmounts(tx, inputFetcher)
	sigHashes.HashInputScriptsV1 = calcHashInputScripts(tx, inputFetcher)
}

A P2MR input isn’t a P2TR input, so in a transaction where every input is P2MR, both fields stay at 32 zero bytes. Validation uses the same cache, so on Pearl the signed message really does contain zeros there.

Verified on mainnet

The 2-of-2 P2MR spend in block 130,822 (982089ac…945f) has one P2MR input. Rebuilding both versions of its message:

Message sha_amounts, sha_scriptpubkeys Sighash Both signatures
Pearl (as validated) 32 zero bytes each 0f7cb7d490c225b74223312ab8f862ca4a8a5d617a30714dbb138c78b89dd382 valid
Textbook BIP-341/342 real hashes 826d8ce588435c2655f3edcf2a08ceef76d6be1fc4bae0f54a7a6c1f3c683d8b invalid

So real P2MR wallets on Pearl already sign the zeroed message. What it means:

  • Signatures in all-P2MR transactions don’t commit to the spent amounts or scripts. The protection described above, against being lied to about amounts, is missing there.
  • A textbook BIP-341 / BIP-360 signer is rejected. Its signature fails (“signature not empty on failed checksig”).
  • Mixed transactions are normal. As soon as one input spends a P2TR output, every input’s message uses the real values.

Whether this is intended isn’t documented. Changing it now would change which P2MR signatures are valid, which is a consensus change. It was still present on Pearl’s main branch on 2026-10-20.

Is there any older sighash?

No. Pearl has no ECDSA in transactions and no legacy sighash. The BIP-143 (segwit v0) sighash function is still compiled into the node, but nothing calls it. Every signature on Pearl signs a TapSighash.

The codex

Suggested

Up and down arrows move through the results, Enter opens one, Escape closes. Anywhere on the site, slash or Command K opens this search, and the square brackets [ and ] go to the previous and next page.