Elliptic curve crypto in python including secp256k1, alt_bn128, and bls12_381
—
Bitcoin's elliptic curve operations providing ECDSA (Elliptic Curve Digital Signature Algorithm) functionality, key generation, and signature recovery. This is the same curve used in Bitcoin and Ethereum for transaction signatures.
# Curve parameters
P: int # Field modulus: 2^256 - 2^32 - 977
N: int # Curve order: 115792089237316195423570985008687907852837564279074904382605163141518161494337
A: int # Curve coefficient a: 0
B: int # Curve coefficient b: 7
G: Tuple[int, int] # Generator point coordinatesConvert between private keys and public keys, and perform basic key validation.
def privtopub(privkey: bytes) -> Tuple[int, int]:
"""
Convert a private key to its corresponding public key point.
Args:
privkey (bytes): 32-byte private key
Returns:
Tuple[int, int]: Public key as (x, y) coordinates
"""Sign message hashes using ECDSA with deterministic k-value generation according to RFC 6979.
def ecdsa_raw_sign(msghash: bytes, priv: bytes) -> Tuple[int, int, int]:
"""
Sign a message hash using ECDSA.
Args:
msghash (bytes): 32-byte hash of the message to sign
priv (bytes): 32-byte private key
Returns:
Tuple[int, int, int]: (v, r, s) signature components where:
- v: recovery parameter (27 or 28)
- r: signature r component
- s: signature s component
"""Recover the public key from a signature and message hash, enabling verification without storing public keys.
def ecdsa_raw_recover(msghash: bytes, vrs: Tuple[int, int, int]) -> Tuple[int, int]:
"""
Recover the public key from an ECDSA signature.
Args:
msghash (bytes): 32-byte hash of the original message
vrs (Tuple[int, int, int]): (v, r, s) signature components
Returns:
Tuple[int, int]: Recovered public key as (x, y) coordinates
"""Low-level elliptic curve point operations for custom cryptographic protocols.
def multiply(a: Tuple[int, int], n: int) -> Tuple[int, int]:
"""
Multiply a point by a scalar (point * scalar).
Args:
a (Tuple[int, int]): Point as (x, y) coordinates
n (int): Scalar multiplier
Returns:
Tuple[int, int]: Resulting point coordinates
"""
def add(a: Tuple[int, int], b: Tuple[int, int]) -> Tuple[int, int]:
"""
Add two elliptic curve points.
Args:
a (Tuple[int, int]): First point as (x, y) coordinates
b (Tuple[int, int]): Second point as (x, y) coordinates
Returns:
Tuple[int, int]: Sum of the two points
"""Internal operations for advanced use cases and custom implementations.
def to_jacobian(p: Tuple[int, int]) -> Tuple[int, int, int]:
"""Convert point from affine to Jacobian coordinates."""
def from_jacobian(p: Tuple[int, int, int]) -> Tuple[int, int]:
"""Convert point from Jacobian to affine coordinates."""
def jacobian_double(p: Tuple[int, int, int]) -> Tuple[int, int, int]:
"""Double a point in Jacobian coordinates."""
def jacobian_add(p: Tuple[int, int, int], q: Tuple[int, int, int]) -> Tuple[int, int, int]:
"""Add two points in Jacobian coordinates."""
def jacobian_multiply(a: Tuple[int, int, int], n: int) -> Tuple[int, int, int]:
"""Multiply a point by scalar in Jacobian coordinates."""
def inv(a: int, n: int) -> int:
"""Modular inverse using extended Euclidean algorithm."""
def bytes_to_int(x: bytes) -> int:
"""Convert bytes to integer."""
def deterministic_generate_k(msghash: bytes, priv: bytes) -> int:
"""Generate deterministic k value for ECDSA signing (RFC 6979)."""from py_ecc.secp256k1 import privtopub, ecdsa_raw_sign, ecdsa_raw_recover
import os
import hashlib
# Generate a random private key
private_key = os.urandom(32)
print(f"Private key: {private_key.hex()}")
# Derive public key
public_key = privtopub(private_key)
print(f"Public key: ({public_key[0]}, {public_key[1]})")
# Sign a message
message = b"Hello, secp256k1!"
message_hash = hashlib.sha256(message).digest()
v, r, s = ecdsa_raw_sign(message_hash, private_key)
print(f"Signature: v={v}, r={r}, s={s}")
# Recover public key from signature
recovered_pubkey = ecdsa_raw_recover(message_hash, (v, r, s))
assert recovered_pubkey == public_key
print("Signature verification successful!")from py_ecc.secp256k1 import G, multiply, add
# Generate some points
point1 = multiply(G, 123) # 123 * G
point2 = multiply(G, 456) # 456 * G
# Add points
sum_point = add(point1, point2)
# This should equal (123 + 456) * G
expected = multiply(G, 123 + 456)
assert sum_point == expected
print("Point arithmetic verified!")from py_ecc.secp256k1 import privtopub
import hashlib
def pubkey_to_address(pubkey):
"""Convert secp256k1 public key to Bitcoin address format."""
# Compress public key
x, y = pubkey
if y % 2 == 0:
compressed = b'\x02' + x.to_bytes(32, 'big')
else:
compressed = b'\x03' + x.to_bytes(32, 'big')
# Hash with SHA256 then RIPEMD160
sha256_hash = hashlib.sha256(compressed).digest()
ripemd160 = hashlib.new('ripemd160', sha256_hash).digest()
return ripemd160
# Example usage
private_key = b'\x01' * 32 # Don't use this in production!
public_key = privtopub(private_key)
address_hash = pubkey_to_address(public_key)
print(f"Address hash: {address_hash.hex()}")from py_ecc.secp256k1 import ecdsa_raw_sign, ecdsa_raw_recover
import hashlib
def verify_signature(message_hash, signature, expected_pubkey):
"""Manually verify an ECDSA signature."""
try:
recovered_pubkey = ecdsa_raw_recover(message_hash, signature)
return recovered_pubkey == expected_pubkey
except:
return False
# Example
private_key = b'\x12' * 32
public_key = privtopub(private_key)
message = b"Test message"
message_hash = hashlib.sha256(message).digest()
# Sign
signature = ecdsa_raw_sign(message_hash, private_key)
# Verify
is_valid = verify_signature(message_hash, signature, public_key)
print(f"Signature valid: {is_valid}")from typing import Tuple
# Point representations
PlainPoint2D = Tuple[int, int] # Affine coordinates (x, y)
PlainPoint3D = Tuple[int, int, int] # Jacobian coordinates (x, y, z)The secp256k1 module functions may raise various exceptions for invalid inputs:
ValueError: For invalid point coordinates or parametersZeroDivisionError: For degenerate cases in point operationsAlways validate inputs when working with untrusted data:
from py_ecc.secp256k1 import privtopub, ecdsa_raw_recover
try:
# Validate private key range
if not (1 <= int.from_bytes(private_key, 'big') < N):
raise ValueError("Private key out of range")
public_key = privtopub(private_key)
recovered = ecdsa_raw_recover(message_hash, signature)
except ValueError as e:
print(f"Invalid input: {e}")
except Exception as e:
print(f"Cryptographic error: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-py-ecc