|
|
@ -1,4 +1,5 @@ |
|
|
|
from coincurve.context import GLOBAL_CONTEXT |
|
|
|
from coincurve.utils import bytes_to_int, int_to_bytes, sha256 |
|
|
|
from ._libsecp256k1 import ffi, lib |
|
|
|
|
|
|
|
MAX_SIG_LENGTH = 72 |
|
|
@ -28,6 +29,51 @@ def der_to_cdata(der, context=GLOBAL_CONTEXT): |
|
|
|
return cdata |
|
|
|
|
|
|
|
|
|
|
|
def recover(message, recover_sig, hasher=sha256, context=GLOBAL_CONTEXT): |
|
|
|
msg_hash = hasher(message) |
|
|
|
if len(msg_hash) != 32: |
|
|
|
raise ValueError('Message hash must be 32 bytes long.') |
|
|
|
pubkey = ffi.new('secp256k1_pubkey *') |
|
|
|
|
|
|
|
recovered = lib.secp256k1_ecdsa_recover( |
|
|
|
context.ctx, pubkey, recover_sig, msg_hash |
|
|
|
) |
|
|
|
if recovered: |
|
|
|
return pubkey |
|
|
|
raise Exception('failed to recover ECDSA public key') |
|
|
|
|
|
|
|
|
|
|
|
def serialize_recoverable(recover_sig, context=GLOBAL_CONTEXT): |
|
|
|
output = ffi.new('unsigned char[%d]' % CDATA_SIG_LENGTH) |
|
|
|
recid = ffi.new('int *') |
|
|
|
|
|
|
|
lib.secp256k1_ecdsa_recoverable_signature_serialize_compact( |
|
|
|
context.ctx, output, recid, recover_sig |
|
|
|
) |
|
|
|
|
|
|
|
return bytes(ffi.buffer(output, CDATA_SIG_LENGTH)) + int_to_bytes(recid[0]) |
|
|
|
|
|
|
|
|
|
|
|
def deserialize_recoverable(serialized, context=GLOBAL_CONTEXT): |
|
|
|
if len(serialized) != 65: |
|
|
|
raise ValueError("Serialized signature must be 65 bytes long.") |
|
|
|
|
|
|
|
ser_sig, rec_id = serialized[:64], bytes_to_int(serialized[64:]) |
|
|
|
|
|
|
|
if not 0 <= rec_id <= 3: |
|
|
|
raise ValueError("Invalid recovery id.") |
|
|
|
|
|
|
|
recover_sig = ffi.new('secp256k1_ecdsa_recoverable_signature *') |
|
|
|
|
|
|
|
parsed = lib.secp256k1_ecdsa_recoverable_signature_parse_compact( |
|
|
|
context.ctx, recover_sig, ser_sig, rec_id |
|
|
|
) |
|
|
|
if not parsed: |
|
|
|
raise ValueError('Failed to parse recoverable signature.') |
|
|
|
|
|
|
|
return recover_sig |
|
|
|
|
|
|
|
|
|
|
|
""" |
|
|
|
Warning: |
|
|
|
The functions below may change and are not tested! |
|
|
@ -77,45 +123,6 @@ def signature_normalize(raw_sig, context=GLOBAL_CONTEXT): |
|
|
|
return not not res, sigout |
|
|
|
|
|
|
|
|
|
|
|
def recover(msg, recover_sig, context=GLOBAL_CONTEXT): |
|
|
|
pubkey = ffi.new('secp256k1_pubkey *') |
|
|
|
|
|
|
|
recovered = lib.secp256k1_ecdsa_recover( |
|
|
|
context.ctx, pubkey, recover_sig, msg |
|
|
|
) |
|
|
|
if recovered: |
|
|
|
return pubkey |
|
|
|
raise Exception('failed to recover ECDSA public key') |
|
|
|
|
|
|
|
|
|
|
|
def recoverable_to_der(recover_sig, context=GLOBAL_CONTEXT): |
|
|
|
output = ffi.new('unsigned char[%d]' % CDATA_SIG_LENGTH) |
|
|
|
recid = ffi.new('int *') |
|
|
|
|
|
|
|
lib.secp256k1_ecdsa_recoverable_signature_serialize_compact( |
|
|
|
context.ctx, output, recid, recover_sig |
|
|
|
) |
|
|
|
|
|
|
|
return bytes(ffi.buffer(output, CDATA_SIG_LENGTH)), recid[0] |
|
|
|
|
|
|
|
|
|
|
|
def der_to_recoverable(ser_sig, rec_id, context=GLOBAL_CONTEXT): |
|
|
|
if rec_id < 0 or rec_id > 3: |
|
|
|
raise Exception("invalid rec_id") |
|
|
|
if len(ser_sig) != 64: |
|
|
|
raise Exception("invalid signature length") |
|
|
|
|
|
|
|
recover_sig = ffi.new('secp256k1_ecdsa_recoverable_signature *') |
|
|
|
|
|
|
|
parsed = lib.secp256k1_ecdsa_recoverable_signature_parse_compact( |
|
|
|
context.ctx, recover_sig, ser_sig, rec_id |
|
|
|
) |
|
|
|
if parsed: |
|
|
|
return recover_sig |
|
|
|
else: |
|
|
|
raise Exception('failed to parse ECDSA compact sig') |
|
|
|
|
|
|
|
|
|
|
|
def recoverable_convert(recover_sig, context=GLOBAL_CONTEXT): |
|
|
|
normal_sig = ffi.new('secp256k1_ecdsa_signature *') |
|
|
|
|
|
|
|