Files
beast-trader/dashboard/venv/lib/python3.12/site-packages/coincurve/der.py

271 lines
8.4 KiB
Python

"""
Minimal, dependency-free ASN.1/DER encoder & decoder for secp256k1 EC private keys.
This module implements just enough DER encoding/decoding to support:
1. Outputting a DER-encoded PKCS#8 EC private key (with an embedded ECPrivateKey per RFC 5915)
2. Reading such a DER-encoded EC private key
Only the following ASN.1 types are supported:
- INTEGER
- BIT STRING
- OCTET STRING
- OBJECT IDENTIFIER
- SEQUENCE
- Context-specific EXPLICIT tags (for the optional public key)
The expected DER structure is as follows:
PrivateKeyInfo ::= SEQUENCE {
version INTEGER, -- must be 0
privateKeyAlgorithm SEQUENCE {
algorithm OBJECT IDENTIFIER, -- id-ecPublicKey (1.2.840.10045.2.1)
parameters OBJECT IDENTIFIER -- secp256k1 (1.3.132.0.10)
},
privateKey OCTET STRING -- DER encoding of ECPrivateKey
}
ECPrivateKey ::= SEQUENCE {
version INTEGER, -- must be 1
privateKey OCTET STRING, -- the secret bytes
publicKey [1] EXPLICIT BIT STRING OPTIONAL -- uncompressed public key
}
"""
from __future__ import annotations
from coincurve.utils import int_to_bytes
# ASN.1 DER tag bytes
INTEGER_TAG = 0x02
BIT_STRING_TAG = 0x03
OCTET_STRING_TAG = 0x04
OBJECT_IDENTIFIER_TAG = 0x06
SEQUENCE_TAG = 0x30
# OIDs
EC_PUBKEY_OID = bytes([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01]) # 1.2.840.10045.2.1 (ecPublicKey)
SECP256K1_OID = bytes([0x2B, 0x81, 0x04, 0x00, 0x0A]) # 1.3.132.0.10 (secp256k1)
# Pre-computed structures
VERSION_INTEGER_ZERO = bytes([INTEGER_TAG, 0x01, 0x00]) # INTEGER 0
VERSION_INTEGER_ONE = bytes([INTEGER_TAG, 0x01, 0x01]) # INTEGER 1
EC_ALGORITHM_IDENTIFIER = bytes([
SEQUENCE_TAG,
16,
OBJECT_IDENTIFIER_TAG,
len(EC_PUBKEY_OID),
*EC_PUBKEY_OID,
OBJECT_IDENTIFIER_TAG,
len(SECP256K1_OID),
*SECP256K1_OID,
])
def encode_length(length: int) -> bytes:
"""Encode a length in DER format."""
# Short form
if length < 128: # noqa: PLR2004
return bytes([length])
# Long form
length_bytes = int_to_bytes(length)
return bytes([0x80 | len(length_bytes)]) + length_bytes
def encode_octet_string(value: bytes) -> bytes:
"""Encode an OCTET STRING in DER format."""
length_bytes = encode_length(len(value))
length_bytes_len = len(length_bytes)
result = bytearray(1 + length_bytes_len + len(value))
result[0] = OCTET_STRING_TAG
result[1 : 1 + length_bytes_len] = length_bytes
result[1 + length_bytes_len :] = value
return bytes(result)
def encode_bit_string(value: bytes, unused_bits: int = 0) -> bytes:
"""Encode a BIT STRING in DER format."""
length_bytes = encode_length(len(value) + 1)
length_bytes_len = len(length_bytes)
result = bytearray(1 + length_bytes_len + 1 + len(value))
result[0] = BIT_STRING_TAG
result[1 : 1 + length_bytes_len] = length_bytes
result[1 + length_bytes_len] = unused_bits
result[1 + length_bytes_len + 1 :] = value
return bytes(result)
def encode_der(private_key: bytes, public_key: bytes | None = None) -> bytes:
"""
Encode an EC private key in DER format (PKCS#8/RFC 5208).
Optimized for secp256k1 keys.
Parameters:
private_key: The private key as bytes (32 bytes for secp256k1)
public_key: The public key as bytes (65 bytes uncompressed for secp256k1, starting with 0x04)
Returns:
The DER-encoded private key
"""
# EC private key contains version(1) + octet string + optional pubkey
ec_key_buffer = bytearray(VERSION_INTEGER_ONE)
# Add private key as octet string
private_key_os = encode_octet_string(private_key)
ec_key_buffer.extend(private_key_os)
# Add public key if provided (optional)
if public_key is not None:
public_key_bs = encode_bit_string(public_key)
pubkey_len = len(public_key_bs)
ec_key_buffer.append(0xA1) # context-specific [1] constructed
ec_key_buffer.extend(encode_length(pubkey_len))
ec_key_buffer.extend(public_key_bs)
# Wrap EC private key in sequence
ec_key_seq = bytearray([SEQUENCE_TAG])
ec_key_seq.extend(encode_length(len(ec_key_buffer)))
ec_key_seq.extend(ec_key_buffer)
# Wrap in octet string for outer structure
ec_key_os = encode_octet_string(ec_key_seq)
# Build the outer PKCS#8 structure
result = bytearray([SEQUENCE_TAG])
# Calculate total length: version(3) + alg_id(18) + octet_string(len)
outer_len = 3 + len(EC_ALGORITHM_IDENTIFIER) + len(ec_key_os)
result.extend(encode_length(outer_len))
# Version 0
result.extend(VERSION_INTEGER_ZERO)
# Algorithm identifier (pre-computed)
result.extend(EC_ALGORITHM_IDENTIFIER)
# EC key wrapped in octet string
result.extend(ec_key_os)
return bytes(result)
def decode_length(data: bytes, offset: int) -> tuple[int, int]:
"""
Decode a DER length field.
Parameters:
data: The DER-encoded data
offset: The current offset in the data
Returns:
Tuple of (length, new_offset)
"""
length_byte = data[offset]
offset += 1
# Short form
if length_byte < 128: # noqa: PLR2004
return length_byte, offset
# Long form
num_length_bytes = length_byte & 0x7F
length = 0
for _ in range(num_length_bytes):
length = (length << 8) | data[offset]
offset += 1
return length, offset
def decode_der(der_data: bytes) -> bytes:
"""
Decode a DER-encoded EC private key to extract the private key secret.
Optimized for secp256k1 keys.
Parameters:
der_data: The DER-encoded private key in PKCS#8 format
Returns:
The private key secret as bytes
"""
# Quick validation for performance
if len(der_data) < 34 or der_data[0] != SEQUENCE_TAG: # noqa: PLR2004
msg = "Invalid DER: not a valid PKCS#8 structure"
raise ValueError(msg)
# Skip outer sequence tag and length
offset = 1
_, offset = decode_length(der_data, offset)
# Skip version INTEGER (should be 0)
if der_data[offset] != INTEGER_TAG:
msg = "Invalid DER: expected INTEGER tag for version"
raise ValueError(msg)
offset += 1
version_len, offset = decode_length(der_data, offset)
offset += version_len # Skip version value
# Validate algorithm identifier is for EC
if der_data[offset] != SEQUENCE_TAG:
msg = "Invalid DER: expected SEQUENCE tag for algorithm"
raise ValueError(msg)
offset += 1
alg_len, offset = decode_length(der_data, offset)
alg_end = offset + alg_len # Store the end position of algorithm identifier
# Check if first OID is EC
if der_data[offset] != OBJECT_IDENTIFIER_TAG:
msg = "Invalid DER: expected OBJECT IDENTIFIER tag"
raise ValueError(msg)
offset += 1
oid_len, offset = decode_length(der_data, offset)
algorithm_oid = der_data[offset : offset + oid_len]
# Check if it's an EC key
if oid_len != len(EC_PUBKEY_OID) or algorithm_oid != EC_PUBKEY_OID:
msg = "Not an EC private key"
raise ValueError(msg)
# Skip to the end of algorithm identifier section
offset = alg_end
# Extract private key octet string
if der_data[offset] != OCTET_STRING_TAG:
msg = "Invalid DER: expected OCTET STRING for private key"
raise ValueError(msg)
offset += 1
priv_len, offset = decode_length(der_data, offset)
# Parse EC private key structure
ec_data = der_data[offset : offset + priv_len]
# Verify EC structure starts with sequence
if len(ec_data) < 2 or ec_data[0] != SEQUENCE_TAG: # noqa: PLR2004
msg = "Invalid EC key format: missing sequence"
raise ValueError(msg)
# Skip sequence tag and length
ec_offset = 1
_, ec_offset = decode_length(ec_data, ec_offset)
# Skip version INTEGER (should be 1)
if ec_data[ec_offset] != INTEGER_TAG:
msg = "Invalid EC key format: missing version"
raise ValueError(msg)
ec_offset += 1
ec_ver_len, ec_offset = decode_length(ec_data, ec_offset)
ec_offset += ec_ver_len # Skip version value
# Get private key octet string
if ec_data[ec_offset] != OCTET_STRING_TAG:
msg = "Invalid DER: expected OCTET STRING for EC private key"
raise ValueError(msg)
ec_offset += 1
key_len, ec_offset = decode_length(ec_data, ec_offset)
# Extract private key
return ec_data[ec_offset : ec_offset + key_len]