TeachMeBitcoin

Schnorr signature format (BIP 340):

From TeachMeBitcoin, the free encyclopedia Reading time: 3 min

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
☕ Help support TeachMeBitcoin

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:

Ethereum: 0x578417C51783663D8A6A811B3544E1f779D39A85
Bitcoin: bc1q77k9e95rn669kpzyjr8ke9w95zhk7pa5s63qzz
Solana: 4ycT2ayqeMucixj3wS8Ay8Tq9NRDYRPKYbj3UGESyQ4J
Address copied to clipboard!