CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-ecdsa

ECDSA cryptographic signature library (pure python)

Pending
Overview
Eval results
Files

encoding.mddocs/

Encoding & Decoding Utilities

Comprehensive encoding and decoding utilities for digital signatures, cryptographic keys, and ASN.1 structures. The ecdsa library provides multiple encoding formats to ensure compatibility with different systems and standards including DER, PEM, SSH, and raw binary formats.

Capabilities

Signature Encoding Functions

Functions to encode ECDSA signature pairs (r, s) into various standard formats.

def sigencode_string(r, s, order):
    """
    Encode signature as raw concatenated byte string.

    Parameters:
    - r: int, signature r component
    - s: int, signature s component  
    - order: int, curve order for length calculation

    Returns:
    bytes, concatenated r||s as fixed-length byte string
    """

def sigencode_strings(r, s, order):
    """
    Encode signature components as separate byte strings.

    Parameters:
    - r: int, signature r component
    - s: int, signature s component
    - order: int, curve order for length calculation

    Returns:
    tuple[bytes, bytes], (r_bytes, s_bytes) as separate strings
    """

def sigencode_der(r, s, order):
    """
    Encode signature in DER ASN.1 format.

    Parameters:
    - r: int, signature r component
    - s: int, signature s component
    - order: int, curve order (unused but kept for API consistency)

    Returns:
    bytes, DER-encoded ASN.1 SEQUENCE containing r and s INTEGERs
    """

def sigencode_string_canonize(r, s, order):
    """
    Encode signature as canonical raw byte string (low-s form).

    Parameters:
    - r: int, signature r component
    - s: int, signature s component
    - order: int, curve order for canonicalization

    Returns:
    bytes, canonical concatenated r||s with s in low form
    """

def sigencode_strings_canonize(r, s, order):
    """
    Encode signature components as canonical separate byte strings.

    Parameters:
    - r: int, signature r component
    - s: int, signature s component
    - order: int, curve order for canonicalization

    Returns:
    tuple[bytes, bytes], canonical (r_bytes, s_bytes) with low-s
    """

def sigencode_der_canonize(r, s, order):
    """
    Encode signature in canonical DER format (low-s form).

    Parameters:
    - r: int, signature r component
    - s: int, signature s component
    - order: int, curve order for canonicalization

    Returns:
    bytes, canonical DER-encoded signature with low-s
    """

Signature Decoding Functions

Functions to decode signatures from various formats back to (r, s) integer pairs.

def sigdecode_string(signature, order):
    """
    Decode signature from raw concatenated byte string.

    Parameters:
    - signature: bytes, concatenated r||s byte string
    - order: int, curve order for length calculation

    Returns:
    tuple[int, int], (r, s) signature components

    Raises:
    ValueError: if signature length is incorrect
    """

def sigdecode_strings(rs_strings, order):
    """
    Decode signature from separate byte string tuple.

    Parameters:
    - rs_strings: tuple[bytes, bytes], (r_bytes, s_bytes)
    - order: int, curve order for validation

    Returns:
    tuple[int, int], (r, s) signature components
    """

def sigdecode_der(sig_der, order):
    """
    Decode signature from DER ASN.1 format.

    Parameters:
    - sig_der: bytes, DER-encoded ASN.1 signature
    - order: int, curve order for validation

    Returns:
    tuple[int, int], (r, s) signature components

    Raises:
    UnexpectedDER: if DER structure is invalid
    """

Number and String Conversion Utilities

Low-level utilities for converting between integers and byte strings with proper length handling.

def number_to_string(num, order):
    """
    Convert integer to fixed-length byte string based on curve order.

    Parameters:
    - num: int, number to convert
    - order: int, curve order determining output length

    Returns:
    bytes, big-endian byte string with length based on order
    """

def number_to_string_crop(num, order):
    """
    Convert integer to minimal-length byte string.

    Parameters:
    - num: int, number to convert
    - order: int, curve order for validation

    Returns:
    bytes, minimal big-endian byte string (no leading zeros)
    """

def string_to_number(string):
    """
    Convert byte string to integer.

    Parameters:
    - string: bytes, big-endian byte string

    Returns:
    int, converted number
    """

def string_to_number_fixedlen(string, order):
    """
    Convert byte string to integer with length validation.

    Parameters:
    - string: bytes, big-endian byte string
    - order: int, curve order for length validation

    Returns:
    int, converted number

    Raises:
    ValueError: if string length doesn't match expected length
    """

DER ASN.1 Encoding Functions

Functions for encoding data structures in Distinguished Encoding Rules (DER) format.

def encode_sequence(*encoded_pieces):
    """
    Encode ASN.1 SEQUENCE containing multiple elements.

    Parameters:
    - encoded_pieces: bytes, already DER-encoded elements

    Returns:
    bytes, DER-encoded SEQUENCE
    """

def encode_integer(r):
    """
    Encode integer as ASN.1 INTEGER.

    Parameters:
    - r: int, integer to encode

    Returns:
    bytes, DER-encoded INTEGER
    """

def encode_octet_string(s):
    """
    Encode byte string as ASN.1 OCTET STRING.

    Parameters:
    - s: bytes, byte string to encode

    Returns:
    bytes, DER-encoded OCTET STRING
    """

def encode_bitstring(s, unused=0):
    """
    Encode byte string as ASN.1 BIT STRING.

    Parameters:
    - s: bytes, byte string to encode
    - unused: int, number of unused bits in last byte (0-7)

    Returns:
    bytes, DER-encoded BIT STRING
    """

def encode_oid(first, second, *pieces):
    """
    Encode ASN.1 OBJECT IDENTIFIER from components.

    Parameters:
    - first: int, first OID component
    - second: int, second OID component
    - pieces: int, additional OID components

    Returns:
    bytes, DER-encoded OBJECT IDENTIFIER
    """

def encode_constructed(tag, value):
    """
    Encode constructed ASN.1 type with custom tag.

    Parameters:
    - tag: int, ASN.1 tag number
    - value: bytes, content to encode

    Returns:
    bytes, DER-encoded constructed type
    """

def encode_implicit(tag, value, cls="context-specific"):
    """
    Encode implicit ASN.1 tag.

    Parameters:
    - tag: int, tag number
    - value: bytes, content to encode  
    - cls: str, tag class ("context-specific", "application", etc.)

    Returns:
    bytes, DER-encoded implicit tag
    """

DER ASN.1 Decoding Functions

Functions for decoding DER-encoded ASN.1 structures.

def remove_sequence(string):
    """
    Decode ASN.1 SEQUENCE and return content.

    Parameters:
    - string: bytes, DER-encoded data starting with SEQUENCE

    Returns:
    tuple[bytes, bytes], (sequence_content, remaining_data)

    Raises:
    UnexpectedDER: if not a valid SEQUENCE
    """

def remove_integer(string):
    """
    Decode ASN.1 INTEGER and return value.

    Parameters:
    - string: bytes, DER-encoded data starting with INTEGER

    Returns:
    tuple[int, bytes], (integer_value, remaining_data)

    Raises:
    UnexpectedDER: if not a valid INTEGER
    """

def remove_octet_string(string):
    """
    Decode ASN.1 OCTET STRING and return content.

    Parameters:
    - string: bytes, DER-encoded data starting with OCTET STRING

    Returns:
    tuple[bytes, bytes], (octet_string_content, remaining_data)
    """

def remove_bitstring(string, expect_unused=0):
    """
    Decode ASN.1 BIT STRING and return content.

    Parameters:
    - string: bytes, DER-encoded data starting with BIT STRING
    - expect_unused: int, expected number of unused bits

    Returns:
    tuple[bytes, bytes], (bitstring_content, remaining_data)

    Raises:
    UnexpectedDER: if unused bits don't match expected
    """

def remove_object(string):
    """
    Decode ASN.1 OBJECT IDENTIFIER and return OID tuple.

    Parameters:
    - string: bytes, DER-encoded data starting with OBJECT IDENTIFIER

    Returns:
    tuple[tuple[int, ...], bytes], (oid_tuple, remaining_data)
    """

def remove_constructed(string):
    """
    Decode constructed ASN.1 type.

    Parameters:
    - string: bytes, DER-encoded constructed type

    Returns:
    tuple[int, bytes, bytes], (tag, content, remaining_data)
    """

DER/PEM Conversion Utilities

Functions for converting between DER binary format and PEM text format.

def encode_length(l):
    """
    Encode ASN.1 length field.

    Parameters:
    - l: int, length to encode

    Returns:
    bytes, DER-encoded length
    """

def read_length(string):
    """
    Decode ASN.1 length field.

    Parameters:
    - string: bytes, DER data starting with length field

    Returns:
    tuple[int, bytes], (length, remaining_data)
    """

def is_sequence(string):
    """
    Check if data starts with ASN.1 SEQUENCE.

    Parameters:
    - string: bytes, DER-encoded data

    Returns:
    bool, True if starts with SEQUENCE tag
    """

def topem(der, name):
    """
    Convert DER to PEM format.

    Parameters:
    - der: bytes, DER-encoded data
    - name: str, PEM label (e.g., "PRIVATE KEY", "PUBLIC KEY")

    Returns:
    str, PEM-formatted string with headers and base64 encoding
    """

def unpem(pem):
    """
    Convert PEM to DER format.

    Parameters:
    - pem: str or bytes, PEM-formatted data

    Returns:
    bytes, DER-encoded data (base64 decoded, headers removed)
    """

Object Identifiers and Constants

Standard ASN.1 Object Identifiers used in elliptic curve cryptography.

oid_ecPublicKey: tuple       # (1, 2, 840, 10045, 2, 1) - EC public key algorithm
encoded_oid_ecPublicKey: bytes  # DER-encoded version of above
oid_ecDH: tuple             # (1, 3, 132, 1, 12) - ECDH algorithm  
oid_ecMQV: tuple            # (1, 3, 132, 1, 13) - ECMQV algorithm

Random Number Generation

Utilities for cryptographically secure random number generation.

class PRNG:
    """Pseudorandom number generator for deterministic signatures."""

def randrange(order, entropy=None):
    """
    Generate cryptographically secure random number in range [1, order-1].

    Parameters:
    - order: int, upper bound (exclusive)
    - entropy: callable or None, entropy source (default: os.urandom)

    Returns:
    int, random number in specified range
    """

Exception Classes

class UnexpectedDER(Exception):
    """Raised when DER encoding or decoding encounters invalid structure."""

Usage Examples

Signature Encoding and Decoding

from ecdsa import SigningKey, NIST256p
from ecdsa.util import sigencode_der, sigdecode_der, sigencode_string, sigdecode_string

# Generate key and sign data
sk = SigningKey.generate(curve=NIST256p)
vk = sk.verifying_key
message = b"Test message for encoding"

# Sign with different encodings
sig_raw = sk.sign(message, sigencode=sigencode_string)
sig_der = sk.sign(message, sigencode=sigencode_der)

print(f"Raw signature length: {len(sig_raw)} bytes")
print(f"DER signature length: {len(sig_der)} bytes")

# Verify with corresponding decodings
vk.verify(sig_raw, message, sigdecode=sigdecode_string)
vk.verify(sig_der, message, sigdecode=sigdecode_der)

# Decode signatures to (r, s) pairs
r_raw, s_raw = sigdecode_string(sig_raw, sk.curve.order)
r_der, s_der = sigdecode_der(sig_der, sk.curve.order)

print(f"Raw signature r: {r_raw}")
print(f"DER signature r: {r_der}")
print(f"Signatures match: {r_raw == r_der and s_raw == s_der}")

Working with DER/ASN.1 Structures

from ecdsa.der import encode_sequence, encode_integer, remove_sequence, remove_integer

# Create DER-encoded sequence containing two integers
r, s = 12345, 67890
der_r = encode_integer(r)
der_s = encode_integer(s)
der_sequence = encode_sequence(der_r, der_s)

print(f"DER sequence: {der_sequence.hex()}")

# Decode the sequence
sequence_content, remaining = remove_sequence(der_sequence)
decoded_r, content_after_r = remove_integer(sequence_content)
decoded_s, final_remaining = remove_integer(content_after_r)

print(f"Decoded r: {decoded_r}, s: {decoded_s}")
print(f"Original r: {r}, s: {s}")
print(f"Match: {decoded_r == r and decoded_s == s}")

PEM/DER Conversion

from ecdsa import SigningKey, NIST256p
from ecdsa.der import topem, unpem

# Generate key and export as DER
sk = SigningKey.generate(curve=NIST256p)
der_key = sk.to_der()

# Convert DER to PEM
pem_key = topem(der_key, "EC PRIVATE KEY")
print("PEM format:")
print(pem_key.decode())

# Convert PEM back to DER
der_from_pem = unpem(pem_key)
print(f"DER roundtrip successful: {der_key == der_from_pem}")

# Also works with public keys
vk = sk.verifying_key
der_pubkey = vk.to_der()
pem_pubkey = topem(der_pubkey, "PUBLIC KEY")
der_pubkey_roundtrip = unpem(pem_pubkey)
print(f"Public key roundtrip successful: {der_pubkey == der_pubkey_roundtrip}")

Number and String Conversions

from ecdsa.util import number_to_string, string_to_number, number_to_string_crop
from ecdsa import NIST256p

# Work with curve order for proper length calculation
order = NIST256p.order
test_number = 0x1234567890abcdef

# Convert number to fixed-length string
fixed_bytes = number_to_string(test_number, order)
minimal_bytes = number_to_string_crop(test_number, order)

print(f"Original number: 0x{test_number:x}")
print(f"Fixed length: {len(fixed_bytes)} bytes - {fixed_bytes.hex()}")
print(f"Minimal length: {len(minimal_bytes)} bytes - {minimal_bytes.hex()}")

# Convert back to numbers
recovered_fixed = string_to_number(fixed_bytes)
recovered_minimal = string_to_number(minimal_bytes)

print(f"Recovered from fixed: 0x{recovered_fixed:x}")
print(f"Recovered from minimal: 0x{recovered_minimal:x}")
print(f"All match: {test_number == recovered_fixed == recovered_minimal}")

Canonical Signatures (Low-S Form)

from ecdsa import SigningKey, SECP256k1
from ecdsa.util import sigencode_der_canonize, sigdecode_der

# Bitcoin uses canonical signatures (low-s form)
sk = SigningKey.generate(curve=SECP256k1)
message = b"Bitcoin transaction data"

# Generate canonical signature
canonical_sig = sk.sign(message, sigencode=sigencode_der_canonize)

# Decode and verify s is in low form
r, s = sigdecode_der(canonical_sig, sk.curve.order)
print(f"Signature r: {r}")
print(f"Signature s: {s}")
print(f"s is canonical (low): {s <= sk.curve.order // 2}")

# Verify signature
vk = sk.verifying_key
vk.verify(canonical_sig, message, sigdecode=sigdecode_der)
print("Canonical signature verified successfully")

Advanced DER Operations

from ecdsa.der import (
    encode_oid, remove_object, encode_octet_string, remove_octet_string,
    encode_bitstring, remove_bitstring
)

# Work with Object Identifiers
secp256k1_oid = (1, 3, 132, 0, 10)  # secp256k1 curve OID
encoded_oid = encode_oid(*secp256k1_oid)
decoded_oid, remaining = remove_object(encoded_oid)

print(f"Original OID: {secp256k1_oid}")
print(f"Decoded OID: {decoded_oid}")
print(f"OID match: {secp256k1_oid == decoded_oid}")

# Work with octet strings
test_data = b"Secret key material"
encoded_octets = encode_octet_string(test_data)
decoded_octets, remaining = remove_octet_string(encoded_octets)

print(f"Octet string roundtrip: {test_data == decoded_octets}")

# Work with bit strings (for public key encoding)
pubkey_data = b"\x04" + b"x" * 64  # Uncompressed public key format
encoded_bits = encode_bitstring(pubkey_data, unused=0)
decoded_bits, remaining = remove_bitstring(encoded_bits, expect_unused=0)

print(f"Bit string roundtrip: {pubkey_data == decoded_bits}")

Install with Tessl CLI

npx tessl i tessl/pypi-ecdsa

docs

curves.md

ecdh.md

eddsa.md

encoding.md

index.md

keys-signatures.md

mathematical-functions.md

tile.json