Conceptual model:
10. The Signature Hash (sighash) Algorithm
What is a Signature Hash?
The signature hash (sighash) is the data that is actually signed by the private key during transaction creation. It commits a signature to specific aspects of the transaction, ensuring that the signature authorizes exactly what was intended.
# Conceptual model:
sighash_preimage = serialize(transaction_data, sighash_flags)
z = SHA256(SHA256(sighash_preimage))
signature = ECDSA_sign(private_key, z)
The sighash type is a 1-byte value appended to the DER-encoded signature. It tells the verifier how to reconstruct the sighash preimage for verification.
Sighash Type Values
# Standard sighash types
SIGHASH_ALL = 0x01 # Signs all inputs and outputs
SIGHASH_NONE = 0x02 # Signs all inputs, no outputs
SIGHASH_SINGLE = 0x03 # Signs all inputs, one output
# Modifier (OR with above)
SIGHASH_ANYONECANPAY = 0x80 # Only signs this input (not others)
# Combined values
SIGHASH_ALL | ANYONECANPAY = 0x81
SIGHASH_NONE | ANYONECANPAY = 0x82
SIGHASH_SINGLE | ANYONECANPAY = 0x83
The Legacy Sighash Algorithm (Pre-SegWit)
def get_signature_hash(tx, input_index, script_code, sighash_type):
"""
Compute sighash for legacy (non-SegWit) transactions.
"""
# Make a copy to modify
tx_copy = copy_transaction(tx)
# Clear all input scripts
for i, inp in enumerate(tx_copy.inputs):
inp.script = b''
# Set the current input's script
tx_copy.inputs[input_index].script = script_code
# Apply sighash_type modifications
base_type = sighash_type & 0x1f
anyone_can_pay = bool(sighash_type & 0x80)
if base_type == SIGHASH_NONE:
tx_copy.outputs = []
for i, inp in enumerate(tx_copy.inputs):
if i != input_index:
inp.sequence = 0
elif base_type == SIGHASH_SINGLE:
# Only keep output at same index as input
tx_copy.outputs = tx_copy.outputs[:input_index + 1]
# Set outputs before index to "null" (-1 value, empty script)
for i in range(input_index):
tx_copy.outputs[i].value = 0xFFFFFFFFFFFFFFFF # -1
tx_copy.outputs[i].script = b''
for i, inp in enumerate(tx_copy.inputs):
if i != input_index:
inp.sequence = 0
if anyone_can_pay:
tx_copy.inputs = [tx_copy.inputs[input_index]]
# Serialize and hash
preimage = tx_copy.serialize() + struct.pack('<I', sighash_type)
return hashlib.sha256(hashlib.sha256(preimage).digest()).digest()
The Sighash Bug: SIGHASH_SINGLE with More Inputs than Outputs
There is a known quirk in the legacy sighash algorithm: when SIGHASH_SINGLE is used and the input index exceeds the number of outputs, the hash value defaults to 0x01 (the integer 1):
# The bug (simplified):
if base_type == SIGHASH_SINGLE and input_index >= len(tx.outputs):
# This is a protocol quirk — returns hash of "1" as 256-bit little-endian
return b'\x01' + b'\x00' * 31
# Because 1 in little-endian 32 bytes is:
# 0100000000000000000000000000000000000000000000000000000000000000
This means any private key can "sign" this transaction with SIGHASH_SINGLE when its input has no corresponding output — a significant quirk that has been exploited in practice.
BIP143 SegWit Sighash Algorithm
BIP143 completely redesigned the sighash algorithm for SegWit inputs. Key improvements:
def segwit_sighash(tx, input_index, script_code, value, sighash_type):
"""
BIP143 sighash for SegWit v0 inputs.
Commits to input amounts — hardware wallets can verify they're not overpaying fees.
"""
anyone_can_pay = bool(sighash_type & SIGHASH_ANYONECANPAY)
base_type = sighash_type & 0x1f
# Compute hashPrevouts
if not anyone_can_pay:
outpoints = b''.join(inp.outpoint.serialize() for inp in tx.inputs)
hash_prevouts = double_sha256(outpoints)
else:
hash_prevouts = b'\x00' * 32
# Compute hashSequence
if not anyone_can_pay and base_type not in (SIGHASH_SINGLE, SIGHASH_NONE):
sequences = b''.join(struct.pack('<I', inp.sequence) for inp in tx.inputs)
hash_sequence = double_sha256(sequences)
else:
hash_sequence = b'\x00' * 32
# Compute hashOutputs
if base_type not in (SIGHASH_SINGLE, SIGHASH_NONE):
outputs = b''.join(out.serialize() for out in tx.outputs)
hash_outputs = double_sha256(outputs)
elif base_type == SIGHASH_SINGLE and input_index < len(tx.outputs):
hash_outputs = double_sha256(tx.outputs[input_index].serialize())
else:
hash_outputs = b'\x00' * 32
# Assemble preimage
preimage = (
struct.pack('<I', tx.version)
+ hash_prevouts
+ hash_sequence
+ tx.inputs[input_index].outpoint.serialize()
+ encode_script(script_code)
+ struct.pack('<Q', value) # ← Input amount committed here
+ struct.pack('<I', tx.inputs[input_index].sequence)
+ hash_outputs
+ struct.pack('<I', tx.locktime)
+ struct.pack('<I', sighash_type)
)
return double_sha256(preimage)
TeachMeBitcoin is an ad-free, open-source educational repository curated by a passionate team of Bitcoin researchers and educators for public benefit. If you found our articles helpful, please consider supporting our hosting and ongoing content updates with a clean donation: