def _cdata_signature_to_der(signature): '''Return a DER-serialized signature; bytes of length at most 72.''' size = ffi.new('size_t *', MAX_SIG_LENGTH) data = ffi.new(f'unsigned char [{MAX_SIG_LENGTH}]') result = lib.secp256k1_ecdsa_signature_serialize_der(CONTEXT, data, size, signature) # Failure should never happen - MAX_SIG_LENGTH bytes is always enough assert result return bytes(ffi.buffer(data, size[0]))
def to_bytes(self, *, compressed=None): '''Serialize a PublicKey to bytes.''' if (self._compressed if compressed is None else compressed): length, flag = 33, EC_COMPRESSED else: length, flag = 65, EC_UNCOMPRESSED result = ffi.new(f'unsigned char [{length}]') rlength = ffi.new('size_t *', length) lib.secp256k1_ec_pubkey_serialize(CONTEXT, result, rlength, self._public_key, flag) return bytes(ffi.buffer(result, length))
def der_signature_to_compact(der_sig): '''Returns 64 bytes representing r and s as concatenated 32-byte big-endian numbers.''' cdata_sig = _der_signature_to_cdata(der_sig) compact_sig = ffi.new('unsigned char [64]') lib.secp256k1_ecdsa_signature_serialize_compact(CONTEXT, compact_sig, cdata_sig) return bytes(ffi.buffer(compact_sig))
def public_key_from_recoverable_signature(recoverable_sig, msg_hash): cdata_recsig = _cdata_recsig(recoverable_sig) public_key = ffi.new('secp256k1_pubkey *') if not lib.secp256k1_ecdsa_recover(CONTEXT, public_key, cdata_recsig, msg_hash): raise InvalidSignatureError('invalid recoverable signature') return public_key
def compact_signature_to_der(compact_sig): if not (isinstance(compact_sig, bytes) and len(compact_sig) == 64): raise InvalidSignatureError('compact signature must be 64 bytes') cdata_sig = ffi.new('secp256k1_ecdsa_signature *') lib.secp256k1_ecdsa_signature_parse_compact(CONTEXT, cdata_sig, compact_sig) return _cdata_signature_to_der(cdata_sig)
def verify_recoverable_signature(recoverable_sig, msg_hash, public_key): # Convert a 65-byte recoverable sig to a CDATA one cdata_sig = ffi.new('secp256k1_ecdsa_signature *') cdata_recsig = _cdata_recsig(recoverable_sig) # Always succeeds lib.secp256k1_ecdsa_recoverable_signature_convert(CONTEXT, cdata_sig, cdata_recsig) return bool(lib.secp256k1_ecdsa_verify(CONTEXT, cdata_sig, msg_hash, public_key))
def multiply(self, value): '''Return a new PrivateKey instance multiplying value by our secret.''' secret = ffi.new('unsigned char [32]', self._secret) if not lib.secp256k1_ec_privkey_tweak_mul(CONTEXT, secret, _to_32_bytes(value)): raise ValueError('value or result out of range') return PrivateKey(secret, self._compressed, self._coin)
def from_bytes(cls, data): '''Construct a PublicKey from its serialized bytes. data should be bytes of length 33 or 65.''' public_key = ffi.new('secp256k1_pubkey *') if not lib.secp256k1_ec_pubkey_parse(CONTEXT, public_key, data, len(data)): raise ValueError('invalid public key') return cls(public_key, len(data) == 33)
def add(self, value): '''Return a new PublicKey instance formed by adding value*G to this one. Preserves compressed / uncompressed serialization.''' public_key = ffi.new('secp256k1_pubkey *', self._public_key[0]) if not lib.secp256k1_ec_pubkey_tweak_add(CONTEXT, public_key, _to_32_bytes(value)): raise ValueError('value or result out of range') return PublicKey(public_key, self._compressed, self._network)
def _secp256k1_public_key(self): '''Construct a wrapped secp256k1 PublicKey.''' public_key = ffi.new('secp256k1_pubkey *') created = lib.secp256k1_ec_pubkey_create(CONTEXT, public_key, self._secret) # Only possible if client code has mucked with the private key's internals if not created: raise RuntimeError('invalid private key') return public_key
def sign_recoverable(msg_hash, secret): '''Sign a message hash and return a 65-byte recoverable signature. This is a 64-byte compact signature with a recovery ID byte appended; and from which the public key can be immediately recovered. ''' rec_signature = ffi.new('secp256k1_ecdsa_recoverable_signature *') if not lib.secp256k1_ecdsa_sign_recoverable( CONTEXT, rec_signature, msg_hash, secret, ffi.NULL, ffi.NULL): raise ValueError('invalid private key') # Serialize its as a 65-byte compact recoverable signature output = ffi.new(f'unsigned char [{CDATA_SIG_LENGTH}]') recid = ffi.new('int *') lib.secp256k1_ecdsa_recoverable_signature_serialize_compact( CONTEXT, output, recid, rec_signature) # recid is 0, 1, 2 or 3. return bytes(ffi.buffer(output, CDATA_SIG_LENGTH)) + bytes([recid[0]])
def multiply(self, value): '''Return a new PublicKey instance formed by multiplying this one by value (i.e. adding it to itself value times). Preserves compressed / uncompressed serialization. ''' public_key = ffi.new('secp256k1_pubkey *', self._public_key[0]) if not lib.secp256k1_ec_pubkey_tweak_mul(CONTEXT, public_key, _to_32_bytes(value)): raise ValueError('value or result out of range') return PublicKey(public_key, self._compressed, self._network)
def compact_signature_to_der(compact_sig, raise_on_overflow=False): '''If R or S are too large (>= the curve order) returns a der signature with both set to zero, unless raise_on_overflow is True. ''' if not (isinstance(compact_sig, bytes) and len(compact_sig) == 64): raise InvalidSignature('compact signature must be 64 bytes') cdata_sig = ffi.new('secp256k1_ecdsa_signature *') overflow = not lib.secp256k1_ecdsa_signature_parse_compact(CONTEXT, cdata_sig, compact_sig) if overflow and raise_on_overflow: raise InvalidSignature('R or S value overflows') return _cdata_signature_to_der(cdata_sig)
def _cdata_recsig(recoverable_sig): if len(recoverable_sig) != 65: raise InvalidSignatureError('invalid recoverable signature') cdata_recsig = ffi.new('secp256k1_ecdsa_recoverable_signature *') recid = recoverable_sig[-1] if not 0 <= recid <= 3: raise InvalidSignatureError('invalid recoverable signature') if not lib.secp256k1_ecdsa_recoverable_signature_parse_compact( CONTEXT, cdata_recsig, recoverable_sig, recid): raise InvalidSignatureError('invalid recoverable signature') return cdata_recsig
def combine_keys(cls, public_keys): '''Return a new PublicKey equal to the sum of the given PublicKeys. The result takes its default compressed and network attributes from the first public key. ''' lib_keys = [pk._public_key for pk in public_keys] if not lib_keys: raise ValueError('no public keys to combine') public_key = ffi.new('secp256k1_pubkey *') if not lib.secp256k1_ec_pubkey_combine(CONTEXT, public_key, lib_keys, len(lib_keys)): raise ValueError('the sum of the public keys is invalid') return cls(public_key, public_keys[0]._compressed, public_keys[0]._network)
def _der_signature_to_cdata(der_sig): cdata_sig = ffi.new('secp256k1_ecdsa_signature *') if not lib.secp256k1_ecdsa_signature_parse_der(CONTEXT, cdata_sig, der_sig, len(der_sig)): raise InvalidSignatureError('invalid DER-encoded signature') return cdata_sig
def sign_der(msg_hash, secret): cdata_sig = ffi.new('secp256k1_ecdsa_signature *') if not lib.secp256k1_ecdsa_sign(CONTEXT, cdata_sig, msg_hash, secret, ffi.NULL, ffi.NULL): raise ValueError('invalid private key') return _cdata_signature_to_der(cdata_sig)