§ 05 · Transactions
Sighash
What exactly a signature signs
On this page 7 sections
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 sighashSighash: The exact message a signature signs: a tagged hash of the transaction's parts. A flag picks which parts are covered; Pearl's wallet signs all of them..
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.
What exactly is signed?
A byte string called SigMsg, hashed with the tag “TapSighash”:
For a key-path spend with the default type, SigMsg is these fields, in order:
| 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:
| 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 |
-
Hash the message
TaggedHash(“TapSighash”, those 175 bytes) =59a251aaae100a854337adb465d5841f078132f7c28ef1634bc363965e26b58f. -
Take the signature
The witness holds4686c701…a86ba2, 64 bytes, so the type is the default. -
Verify
BIP-340 verification of that signature, that hash and the key Q4b09fcc4…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 hashTapLeaf hash: The tagged hash of one script in a Taproot or P2MR script tree: TaggedHash("TapLeaf", leaf version ‖ length ‖ script). With a single script, it is the Merkle root. 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:
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.