Guide: Signing & Verification

libcrypto provides two layers of signing:

  1. Raw ECDSA (signing) — sign an arbitrary 32-byte hash. You control the hashing.
  2. Message signing (messages) — Ethereum (EIP-191) and Bitcoin signed-message standards, handled end to end.

Both are deterministic (RFC 6979) and self-contained: no external cryptography package is used.

Raw ECDSA

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

key = 0xC0FFEE...  # your private key integer in [1, N-1]
digest = hashlib.sha256(b"payload").digest()

sig = sign(key, digest)                 # canonical low-S by default
pub = private_key_to_public_key(key)

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

# Serialize for transport
der = sig.to_der()                      # strict DER
compact = sig.to_compact()              # 64-byte r||s

Custom nonce hash

RFC 6979 uses SHA-256 by default. To use a different HMAC hash (rarely needed):

import hashlib
sig = sign(key, digest, hash_cls=hashlib.sha512)

Low-S / canonical form

Bitcoin and EVM chains require the s value to be in the lower half of the curve order to prevent signature malleability. This is the default. Disable it only when you have a specific reason:

sig = sign(key, digest, canonical=False)

Ethereum message signing (EIP-191)

from libcrypto import (
    sign_message_ethereum, recover_address_ethereum, verify_message_ethereum,
)

key = 1
signature = sign_message_ethereum(key, "Hello, world!")   # 65 bytes: r||s||v

signer = recover_address_ethereum("Hello, world!", signature)
assert verify_message_ethereum(signer, "Hello, world!", signature)

The digest matches ethers.hashMessage / MetaMask personal_sign, and v is 27 or 28.

Bitcoin message signing

from libcrypto import (
    sign_message_bitcoin, recover_address_bitcoin, verify_message_bitcoin,
)

key = 1
signature = sign_message_bitcoin(key, "Hello, world!", compressed=True)  # Base64

signer = recover_address_bitcoin("Hello, world!", signature)
assert verify_message_bitcoin(signer, "Hello, world!", signature)

The output format matches Bitcoin Core's signmessage / verifymessage.

Choosing the right layer

Need Use
Sign a transaction hash or custom payload signing.sign
Prove control of an Ethereum address messages.sign_message_ethereum
Prove control of a Bitcoin address messages.sign_message_bitcoin
Recover the signer of an existing signature recover_public_key / recover_address_*