Schnorr signature format (BIP 340):
22. Signature Encoding (DER Format) in Scripts
Overview
Bitcoin uses Distinguished Encoding Rules (DER) format to encode ECDSA signatures in scripts. DER is a subset of Basic Encoding Rules (BER) from the ASN.1 standard. A DER-encoded Bitcoin signature consists of the (r, s) pair from ECDSA, plus a trailing sighash type byte.
DER Encoding Structure
Full signature = DER_encoding + sighash_type_byte
DER structure:
┌─────────────────────────────────────────────────────┐
│ 0x30 (compound type: SEQUENCE) │
│ <length> (total length of what follows) │
│ 0x02 (integer type: INTEGER) │
│ <r_length> (length of r value) │
│ <r_bytes> (r value, big-endian, possibly padded)│
│ 0x02 (integer type: INTEGER) │
│ <s_length> (length of s value) │
│ <s_bytes> (s value, big-endian, possibly padded)│
└─────────────────────────────────────────────────────┘
+ 0x01 (or 0x02, 0x03, etc.) — sighash type
Detailed Byte Layout Example
3044
02 20
45b0cfc220ceec5b7c1c62c97d7c3d3b2e12cef4cf57de36f8dac14b6d40b76
02 20
0b6e9f9fde90edfb9ac2cee9f9b5a1dcfe0ec1c83f76becc47aaa7fbd5e7c89
01
Breaking it down:
30 = SEQUENCE tag
44 = 68 bytes follow (total length of r + s encoding)
02 = INTEGER tag (r)
20 = 32 bytes of r follow
45b0...76 = r value (32 bytes)
02 = INTEGER tag (s)
20 = 32 bytes of s follow
0b6e...89 = s value (32 bytes)
01 = SIGHASH_ALL
Padding Rules
DER integers are signed (they can represent negative numbers). Since r and s are always positive in Bitcoin ECDSA, if the most significant bit of the value is 1, a 0x00 byte must be prepended to indicate the value is positive:
def encode_der_integer(value_bytes: bytes) -> bytes:
"""Encode a positive integer in DER format."""
# Remove leading zero bytes
value_bytes = value_bytes.lstrip(b'\x00')
# If high bit is set, add 0x00 prefix to indicate positive number
if value_bytes[0] & 0x80:
value_bytes = b'\x00' + value_bytes
return bytes([0x02, len(value_bytes)]) + value_bytes
def encode_der_signature(r: int, s: int) -> bytes:
r_bytes = r.to_bytes(32, 'big')
s_bytes = s.to_bytes(32, 'big')
r_encoded = encode_der_integer(r_bytes)
s_encoded = encode_der_integer(s_bytes)
inner = r_encoded + s_encoded
return bytes([0x30, len(inner)]) + inner
Signature Length Variation
Because of the possible 0x00 padding for r and s, DER signatures vary in length:
Minimum length: 8 + 31 + 31 = 70 bytes (r and s both < 0x80 high bit, stripped zeros)
Standard length: 70-72 bytes (most common)
Maximum length: 8 + 33 + 33 = 74 bytes (both r and s require 0x00 prefix)
With sighash byte appended: 71-73 bytes (most common range)
Parsing a DER Signature
def parse_der_signature(sig_bytes: bytes):
"""Parse a DER-encoded signature and return (r, s) integers."""
assert sig_bytes[0] == 0x30, "Not a SEQUENCE"
total_len = sig_bytes[1]
assert len(sig_bytes) == total_len + 2, "Length mismatch"
# Parse r
assert sig_bytes[2] == 0x02, "Expected INTEGER for r"
r_len = sig_bytes[3]
r_bytes = sig_bytes[4:4 + r_len]
r = int.from_bytes(r_bytes, 'big')
# Parse s
s_start = 4 + r_len
assert sig_bytes[s_start] == 0x02, "Expected INTEGER for s"
s_len = sig_bytes[s_start + 1]
s_bytes = sig_bytes[s_start + 2:s_start + 2 + s_len]
s = int.from_bytes(s_bytes, 'big')
return r, s
Schnorr vs DER Encoding
Taproot uses Schnorr signatures with a much simpler encoding:
# Schnorr signature format (BIP 340):
<r_bytes (32)> + <s_bytes (32)> = 64 bytes fixed size
# With non-default sighash type:
<r_bytes (32)> + <s_bytes (32)> + <sighash_byte (1)> = 65 bytes
# No DER encoding, no variable length, no padding needed
# r is represented as the x-coordinate only (32 bytes)
# s is always exactly 32 bytes
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: