Both of these verify correctly for the same message and public key:
23. Low-S Signature Requirement (BIP 66)
The Problem: Signature Malleability
Bitcoin's ECDSA signatures have a mathematical property: for any valid signature (r, s), the signature (r, n - s) is also valid, where n is the order of the secp256k1 group. This means every ECDSA signature has two valid forms — the original (r, s) and the "negated-S" version (r, n-s).
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
# Both of these verify correctly for the same message and public key:
(r, s) → valid ECDSA signature
(r, n - s) → also valid ECDSA signature
Why This Matters for Transaction IDs
In legacy transactions (pre-SegWit), the scriptSig is part of the transaction data used to compute the TXID. Since the scriptSig contains the DER-encoded signature, swapping s for n-s changes the DER encoding, which changes the TXID:
Original transaction:
TXID_1 = HASH256(version + inputs_with_sig_s + outputs + locktime)
Malleable transaction (same content, different sig):
TXID_2 = HASH256(version + inputs_with_sig_(n-s) + outputs + locktime)
TXID_1 ≠ TXID_2 but both are valid and spend the same UTXO!
This was exploited in the MtGox exchange hack and created problems for payment channels and other protocols that relied on TXID stability.
BIP 66: Strict DER Encoding
BIP 66 (activated July 2015) introduced strict DER encoding rules, which among other requirements mandated that the s value must be in the "lower half" of the group order:
# BIP 66 Low-S requirement:
s <= n/2
# Where:
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
n/2 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
# If s > n/2, normalize it:
s_normalized = n - s (which is < n/2)
Why n/2 is the Cutoff
The group of valid s values is [1, n-1]. For every s > n/2, there exists a "smaller" valid signature n-s < n/2. The Low-S rule picks the canonical representative from each pair, eliminating ambiguity:
# Before BIP 66:
Both (r, s) and (r, n-s) are acceptable → 2 valid signatures per key/message pair
# After BIP 66 (Low-S rule):
Only (r, s) where s <= n/2 is acceptable → 1 valid signature per key/message pair
Implementing Low-S Enforcement
SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
HALF_ORDER = SECP256K1_ORDER // 2
def normalize_signature(r: int, s: int):
"""Ensure signature uses low-S form."""
if s > HALF_ORDER:
s = SECP256K1_ORDER - s
return r, s
def is_low_s(s: int) -> bool:
return s <= HALF_ORDER
def verify_bip66_signature(sig_bytes: bytes) -> bool:
"""Verify signature is valid DER and uses low-S."""
r, s = parse_der_signature(sig_bytes[:-1]) # strip sighash byte
if not is_low_s(s):
raise ScriptError("BIP 66 violation: high-S signature")
return True
BIP 66 Enforcement Levels
Non-standard: High-S signatures are non-standard (won't be relayed by default)
SCRIPT_VERIFY_LOW_S: Consensus enforcement flag (activated at BIP 66 activation)
# In Bitcoin Core:
# "Low-S" is enforced as a consensus rule since block 363,724 (July 2015)
# Prior transactions with high-S remain valid (they were mined under old rules)
SegWit and TXID Malleability
SegWit (BIP 141, activated August 2017) solves signature malleability more fundamentally by separating witness data (including signatures) from the transaction data used to compute the TXID:
Legacy TXID:
txid = HASH256(version + vin + vout + locktime)
→ scriptSig (with signature) is inside vin → TXID is malleable
SegWit TXID (wtxid for witness, txid for non-witness):
txid = HASH256(version + vin_without_witness + vout + locktime)
wtxid = HASH256(version + marker + flag + vin + vout + witness + locktime)
→ txid does NOT include signature → TXID is not malleable even with high-S
Schnorr Signatures: No Malleability
BIP 340 Schnorr signatures don't have the s-negation malleability issue because:
# Schnorr signature (R, s):
# R is a curve point (represented as x-coordinate, 32 bytes)
# s is a scalar mod n
# There is no (R, n-s) equivalent that also verifies
# The verification equation: s*G = R + H(R||P||m)*P
# Negating s gives: -s*G = -(R + H(R||P||m)*P)
# Which would verify against -P (a different public key)
# So high-S Schnorr signatures simply don't verify → no malleability
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: