ECDSA cryptographic signature library (pure python)
—
Elliptic Curve Diffie-Hellman (ECDH) key agreement protocol implementation for secure shared secret generation between parties. ECDH allows two parties to establish a shared secret over an insecure channel by exchanging public keys and combining them with their private keys.
The ECDH class provides a complete implementation of the elliptic curve Diffie-Hellman key agreement protocol.
class ECDH:
def __init__(self, curve=None, private_key=None, public_key=None):
"""
Initialize ECDH instance.
Parameters:
- curve: Curve object, elliptic curve to use
- private_key: SigningKey object, existing private key
- public_key: VerifyingKey object, existing public key
"""
def set_curve(self, key_curve):
"""
Set the elliptic curve for ECDH operations.
Parameters:
- key_curve: Curve object, curve to use for operations
"""def generate_private_key(self):
"""
Generate a new random private key and return corresponding public key.
Returns:
VerifyingKey object, the generated public key
Raises:
NoCurveError: if curve is not set
"""
def load_private_key(self, private_key):
"""
Load private key from SigningKey object.
Parameters:
- private_key: SigningKey object
Returns:
VerifyingKey object, corresponding public key
Raises:
InvalidCurveError: if key curve doesn't match set curve
"""
def load_private_key_bytes(self, private_key):
"""
Load private key from raw byte string.
Parameters:
- private_key: bytes, raw private key
Returns:
VerifyingKey object, corresponding public key
Raises:
NoCurveError: if curve is not set
"""
def load_private_key_der(self, private_key_der):
"""
Load private key from DER-encoded bytes.
Parameters:
- private_key_der: bytes, DER-encoded private key
Returns:
VerifyingKey object, corresponding public key
"""
def load_private_key_pem(self, private_key_pem):
"""
Load private key from PEM-encoded string or bytes.
Parameters:
- private_key_pem: str or bytes, PEM-encoded private key
Returns:
VerifyingKey object, corresponding public key
"""
def get_public_key(self):
"""
Get the current public key.
Returns:
VerifyingKey object, current public key
Raises:
NoKeyError: if no private key is loaded
"""def load_received_public_key(self, public_key):
"""
Load the other party's public key from VerifyingKey object.
Parameters:
- public_key: VerifyingKey object
Raises:
InvalidCurveError: if key curve doesn't match set curve
"""
def load_received_public_key_bytes(self, public_key_str, valid_encodings=None):
"""
Load the other party's public key from raw bytes.
Parameters:
- public_key_str: bytes, raw public key
- valid_encodings: list of str, acceptable point encodings or None
Raises:
NoCurveError: if curve is not set
"""
def load_received_public_key_der(self, public_key_der):
"""
Load the other party's public key from DER-encoded bytes.
Parameters:
- public_key_der: bytes, DER-encoded public key
"""
def load_received_public_key_pem(self, public_key_pem):
"""
Load the other party's public key from PEM-encoded string or bytes.
Parameters:
- public_key_pem: str or bytes, PEM-encoded public key
"""def generate_sharedsecret(self):
"""
Generate shared secret as integer.
Returns:
int, shared secret value
Raises:
NoKeyError: if private key or received public key not loaded
InvalidSharedSecretError: if computed secret is invalid (point at infinity)
"""
def generate_sharedsecret_bytes(self):
"""
Generate shared secret as byte string.
Returns:
bytes, shared secret as raw bytes
Raises:
NoKeyError: if private key or received public key not loaded
InvalidSharedSecretError: if computed secret is invalid (point at infinity)
"""class NoKeyError(Exception):
"""Raised when a required key is not set but needed for operation."""
class NoCurveError(Exception):
"""Raised when curve is not set but needed for operation."""
class InvalidCurveError(Exception):
"""Raised when public and private keys use different curves."""
class InvalidSharedSecretError(Exception):
"""Raised when shared secret computation results in point at infinity."""from ecdsa import ECDH, NIST256p
# Party A setup
alice = ECDH(curve=NIST256p)
alice_public_key = alice.generate_private_key()
# Party B setup
bob = ECDH(curve=NIST256p)
bob_public_key = bob.generate_private_key()
# Exchange public keys (over insecure channel)
alice.load_received_public_key(bob_public_key)
bob.load_received_public_key(alice_public_key)
# Both parties compute the same shared secret
alice_secret = alice.generate_sharedsecret_bytes()
bob_secret = bob.generate_sharedsecret_bytes()
assert alice_secret == bob_secret
print(f"Shared secret established: {alice_secret.hex()}")from ecdsa import ECDH, SigningKey, SECP256k1
# Use existing private keys
alice_private = SigningKey.generate(curve=SECP256k1)
bob_private = SigningKey.generate(curve=SECP256k1)
# Setup ECDH instances with existing keys
alice_ecdh = ECDH(curve=SECP256k1)
alice_public = alice_ecdh.load_private_key(alice_private)
bob_ecdh = ECDH(curve=SECP256k1)
bob_public = bob_ecdh.load_private_key(bob_private)
# Exchange public keys
alice_ecdh.load_received_public_key(bob_public)
bob_ecdh.load_received_public_key(alice_public)
# Generate shared secrets
alice_secret = alice_ecdh.generate_sharedsecret()
bob_secret = bob_ecdh.generate_sharedsecret()
assert alice_secret == bob_secretfrom ecdsa import ECDH, NIST384p
# Alice generates key pair and exports public key
alice = ECDH(curve=NIST384p)
alice_public_key = alice.generate_private_key()
alice_public_pem = alice_public_key.to_pem()
# Bob generates key pair and exports public key
bob = ECDH(curve=NIST384p)
bob_public_key = bob.generate_private_key()
bob_public_pem = bob_public_key.to_pem()
# Exchange serialized public keys (e.g., over network)
# Alice loads Bob's public key from PEM
alice.load_received_public_key_pem(bob_public_pem)
# Bob loads Alice's public key from PEM
bob.load_received_public_key_pem(alice_public_pem)
# Both parties generate the same shared secret
alice_secret = alice.generate_sharedsecret_bytes()
bob_secret = bob.generate_sharedsecret_bytes()
assert alice_secret == bob_secret
print(f"ECDH key exchange completed successfully")from ecdsa import ECDH, NIST256p, NoKeyError, NoCurveError, InvalidCurveError
try:
# Attempt to generate key without setting curve
ecdh = ECDH()
public_key = ecdh.generate_private_key() # Raises NoCurveError
except NoCurveError:
print("Must set curve before generating keys")
try:
# Attempt to generate shared secret without loading received key
ecdh = ECDH(curve=NIST256p)
ecdh.generate_private_key()
secret = ecdh.generate_sharedsecret() # Raises NoKeyError
except NoKeyError:
print("Must load received public key before generating shared secret")
try:
# Attempt to use keys from different curves
from ecdsa import SECP256k1
alice = ECDH(curve=NIST256p)
alice_public = alice.generate_private_key()
bob = ECDH(curve=SECP256k1)
bob_public = bob.generate_private_key()
alice.load_received_public_key(bob_public) # Raises InvalidCurveError
except InvalidCurveError:
print("Both parties must use the same curve")from ecdsa import ECDH, SECP256k1, Ed25519, BRAINPOOLP384r1
# Bitcoin's secp256k1 curve
bitcoin_ecdh = ECDH(curve=SECP256k1)
bitcoin_public = bitcoin_ecdh.generate_private_key()
# Edwards curve (note: Ed25519 typically used for EdDSA, not ECDH)
# Standard ECDH is usually done with Weierstrass curves
edwards_ecdh = ECDH(curve=Ed25519)
edwards_public = edwards_ecdh.generate_private_key()
# Brainpool curve
brainpool_ecdh = ECDH(curve=BRAINPOOLP384r1)
brainpool_public = brainpool_ecdh.generate_private_key()
print(f"Generated keys for different curves:")
print(f"- Bitcoin secp256k1: {len(bitcoin_public.to_string())} bytes")
print(f"- Edwards Ed25519: {len(edwards_public.to_string())} bytes")
print(f"- Brainpool P384r1: {len(brainpool_public.to_string())} bytes")Install with Tessl CLI
npx tessl i tessl/pypi-ecdsa