def sec(self, compressed=True): """returns the binary version of the SEC format""" if compressed: if not self.csec: serialized = ffi.new("unsigned char [33]") output_len = ffi.new("size_t *", 33) if not lib.secp256k1_ec_pubkey_serialize( GLOBAL_CTX, serialized, output_len, self.c, lib.SECP256K1_EC_COMPRESSED, ): raise RuntimeError("libsecp256k1 serialize error") self.csec = bytes(ffi.buffer(serialized, 33)) return self.csec else: if not self.usec: serialized = ffi.new("unsigned char [65]") output_len = ffi.new("size_t *", 65) if not lib.secp256k1_ec_pubkey_serialize( GLOBAL_CTX, serialized, output_len, self.c, lib.SECP256K1_EC_UNCOMPRESSED, ): raise RuntimeError("libsecp256k1 serialize error") self.usec = bytes(ffi.buffer(serialized, 65)) return self.usec
def der(self): if not self.der_cache: der = ffi.new("unsigned char[72]") der_length = ffi.new("size_t *", 72) if not lib.secp256k1_ecdsa_signature_serialize_der( GLOBAL_CTX, der, der_length, self.c): raise RuntimeError("libsecp256k1 der serialize error") self.der_cache = bytes(ffi.buffer(der, der_length[0])) return self.der_cache
def bip340(self): # returns the binary version of BIP340 pubkey xonly_key = ffi.new("secp256k1_xonly_pubkey *") if not lib.secp256k1_xonly_pubkey_from_pubkey(GLOBAL_CTX, xonly_key, ffi.NULL, self.c): raise RuntimeError("libsecp256k1 xonly pubkey error") output32 = ffi.new("unsigned char [32]") if not lib.secp256k1_xonly_pubkey_serialize(GLOBAL_CTX, output32, xonly_key): raise RuntimeError("libsecp256k1 xonly serialize error") return bytes(ffi.buffer(output32, 32))
def verify_schnorr(self, msg, sig): xonly_key = ffi.new("secp256k1_xonly_pubkey *") if not lib.secp256k1_xonly_pubkey_from_pubkey(GLOBAL_CTX, xonly_key, ffi.NULL, self.c): raise RuntimeError("libsecp256k1 xonly pubkey error") return lib.secp256k1_schnorrsig_verify(GLOBAL_CTX, sig.raw, msg, len(msg), xonly_key)
def __rmul__(self, coefficient): coef = coefficient % N new_key = ffi.new("secp256k1_pubkey *") s = self.sec(compressed=False) if not lib.secp256k1_ec_pubkey_parse(GLOBAL_CTX, new_key, s, len(s)): raise RuntimeError("libsecp256k1 parse error") if not lib.secp256k1_ec_pubkey_tweak_mul(GLOBAL_CTX, new_key, int_to_big_endian(coef, 32)): raise RuntimeError("libsecp256k1 multiplication error") serialized = ffi.new("unsigned char [65]") output_len = ffi.new("size_t *", 65) if not lib.secp256k1_ec_pubkey_serialize( GLOBAL_CTX, serialized, output_len, new_key, lib.SECP256K1_EC_UNCOMPRESSED): raise RuntimeError("libsecp256k1 serialization error") return self.__class__(usec=bytes(serialized))
def __add__(self, scalar): """Multiplies scalar by generator, adds result to current point""" coef = scalar % N new_key = ffi.new("secp256k1_pubkey *") s = self.sec(compressed=False) if not lib.secp256k1_ec_pubkey_parse(GLOBAL_CTX, new_key, s, len(s)): raise RuntimeError("libsecp256k1 parse error") if not lib.secp256k1_ec_pubkey_tweak_add(GLOBAL_CTX, new_key, int_to_big_endian(coef, 32)): raise RuntimeError("libsecp256k1 add error") serialized = ffi.new("unsigned char [65]") output_len = ffi.new("size_t *", 65) if not lib.secp256k1_ec_pubkey_serialize( GLOBAL_CTX, serialized, output_len, new_key, lib.SECP256K1_EC_UNCOMPRESSED): raise RuntimeError("libsecp256k1 serialize error") return self.__class__(usec=bytes(serialized))
def sign_schnorr(self, msg, aux): if len(msg) != 32: raise ValueError("msg needs to be 32 bytes") if len(aux) != 32: raise ValueError("aux needs to be 32 bytes") # per libsecp256k1 documentation, this helps against side-channel attacks if not lib.secp256k1_context_randomize( GLOBAL_CTX, secrets.token_bytes(32), ): raise RuntimeError("libsecp256k1 context randomization error") keypair = ffi.new("secp256k1_keypair *") if not lib.secp256k1_keypair_create( GLOBAL_CTX, keypair, int_to_big_endian(self.secret, 32)): raise RuntimeError("libsecp256k1 keypair creation problem") raw_sig = ffi.new("unsigned char [64]") if not lib.secp256k1_schnorrsig_sign(GLOBAL_CTX, raw_sig, msg, keypair, aux): raise RuntimeError("libsecp256k1 schnorr signing problem") return SchnorrSignature(bytes(ffi.buffer(raw_sig, 64)))
def __init__(self, der=None, c=None): if der: self.der_cache = der self.c = ffi.new("secp256k1_ecdsa_signature *") if not lib.secp256k1_ecdsa_signature_parse_der( GLOBAL_CTX, self.c, der, len(der)): raise RuntimeError(f"badly formatted signature {der.hex()}") elif c: self.c = c self.der_cache = None else: raise RuntimeError("need der or c object")
def tagged_hash(tag, msg): result = ffi.new("unsigned char [32]") tag_length = len(tag) msg_length = len(msg) if not lib.secp256k1_tagged_sha256( GLOBAL_CTX, result, tag, tag_length, msg, msg_length, ): raise RuntimeError("libsecp256k1 tagged hash problem") return bytes(ffi.buffer(result, 32))
def __init__(self, raw): self.raw = raw if len(raw) != 64: raise ValueError("signature should be 64 bytes") # check that the sig's R is valid if big_endian_to_int(raw[:32]) == 0: raise AssertionError("R should not be zero") xonly_key = ffi.new("secp256k1_xonly_pubkey *") if not lib.secp256k1_xonly_pubkey_parse(GLOBAL_CTX, xonly_key, raw[:32]): raise ValueError(f"libsecp256k1 invalid R {raw[:32].hex()}") s = big_endian_to_int(raw[32:]) if s >= N: raise ValueError(f"{s:x} is greater than or equal to {N:x}")
def combine(cls, points): c_pubkeys = [] for point in points: new_key = ffi.new("secp256k1_pubkey *") s = point.sec(compressed=False) if not lib.secp256k1_ec_pubkey_parse(GLOBAL_CTX, new_key, s, len(s)): raise RuntimeError("libsecp256k1 parse error") c_pubkeys.append(new_key) sum_pub_key = ffi.new("secp256k1_pubkey *") if not lib.secp256k1_ec_pubkey_combine(GLOBAL_CTX, sum_pub_key, c_pubkeys, len(c_pubkeys)): raise RuntimeError("libsecp256k1 combine error") serialized = ffi.new("unsigned char [65]") output_len = ffi.new("size_t *", 65) if not lib.secp256k1_ec_pubkey_serialize( GLOBAL_CTX, serialized, output_len, sum_pub_key, lib.SECP256K1_EC_UNCOMPRESSED, ): raise RuntimeError("libsecp256k1 serialization error") return cls(usec=bytes(serialized))
def sign(self, z): # per libsecp256k1 documentation, this helps against side-channel attacks if not lib.secp256k1_context_randomize( GLOBAL_CTX, secrets.token_bytes(32), ): raise RuntimeError("libsecp256k1 context randomization error") secret = int_to_big_endian(self.secret, 32) msg = int_to_big_endian(z, 32) csig = ffi.new("secp256k1_ecdsa_signature *") if not lib.secp256k1_ecdsa_sign(GLOBAL_CTX, csig, msg, secret, ffi.NULL, ffi.NULL): raise RuntimeError("libsecp256k1 ecdsa signing problem") sig = Signature(c=csig) if not self.point.verify(z, sig): raise RuntimeError("generated signature doesn't verify") return sig
def __init__(self, csec=None, usec=None): if usec: self.usec = usec self.csec = None sec_cache = usec self.parity = usec[-1] & 1 elif csec: self.csec = csec self.usec = None sec_cache = csec self.parity = csec[0] - 2 else: raise RuntimeError("need a serialization") self.c = ffi.new("secp256k1_pubkey *") if not lib.secp256k1_ec_pubkey_parse(GLOBAL_CTX, self.c, sec_cache, len(sec_cache)): raise ValueError("libsecp256k1 produced error")