libcrypto.signing

Deterministic ECDSA over secp256k1 with RFC 6979 nonces, canonical low-S signatures, public-key recovery, and DER/compact serialization. Implemented entirely with the Python standard library (hmac, hashlib) and internal secp256k1 primitives — no external cryptography package.

Functions

sign(private_key, message_hash, *, hash_cls=hashlib.sha256, canonical=True) -> Signature

Create a deterministic signature for a pre-computed message_hash (for example sha256(...) or keccak256(...)).

  • private_key: integer in [1, N-1].
  • hash_cls: hash used for the RFC 6979 HMAC nonce (default SHA-256).
  • canonical: when True, normalize to low-S (required by Bitcoin and EVM chains). The recovery id is adjusted accordingly.

Raises SignatureError for out-of-range keys or non-bytes input.

verify(public_key, message_hash, signature) -> bool

Verify a Signature against a public key and message hash. Returns False for invalid signatures rather than raising.

recover_public_key(message_hash, signature, recovery_id=None, *, compressed=True) -> bytes

Recover the signer's public key. recovery_id defaults to signature.recovery_id.

class Signature

Member Description
r, s Signature scalars in [1, N-1].
recovery_id Recovery id 0..3, or None.
is_canonical True when s is in the lower half of the order.
to_compact() -> bytes 64-byte r \|\| s.
from_compact(data, recovery_id=None) Parse a 64-byte compact signature.
to_der() -> bytes Strict DER encoding.
from_der(data, recovery_id=None) Parse a DER signature (rejects negative and non-minimal integers).

Exceptions

Exception Meaning
SignatureError Invalid key, scalar, recovery id, or serialization (subclass of ValueError).

Example

import hashlib
from libcrypto import sign, verify, recover_public_key
from libcrypto.secp256k1 import private_key_to_public_key

key = 1
digest = hashlib.sha256(b"hello").digest()

sig = sign(key, digest)
pub = private_key_to_public_key(key, compressed=True)

assert verify(pub, digest, sig)
assert recover_public_key(digest, sig) == pub

RFC 6979 test vector

import hashlib
from libcrypto import sign

msg = b"Everything should be made as simple as possible, but not simpler."
sig = sign(1, hashlib.sha256(msg).digest())
assert sig.r == 0x33a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c9
assert sig.s == 0x6f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262

sign operates on a hash, not a raw message. For chain-specific message formats, use messages.