It would be simple to hard fail at import time if any of the interesting libsecp modules are missing, as it was done before this commit. However, some Linux distros (atm current ubuntu lts, 22.04) lack new enough libsecp. Also, for now, we don't use the schnorr APIs yet anyway. Until we start to rely on them more, it is feasible to only require them when they are used. I am hoping we will be able to revert this commit later though, to keep things simple.
195 lines
8.4 KiB
Python
195 lines
8.4 KiB
Python
# Copyright (c) 2013-2018 Richard Kiss
|
|
# Copyright (c) 2018-2024 The Electrum developers
|
|
# Distributed under the MIT software license, see the accompanying
|
|
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
|
|
#
|
|
# Originally based on pycoin:
|
|
# https://github.com/richardkiss/pycoin/blob/01b1787ed902df23f99a55deb00d8cd076a906fe/pycoin/ecdsa/native/secp256k1.py
|
|
|
|
import os
|
|
import sys
|
|
import traceback
|
|
import ctypes
|
|
from ctypes import (
|
|
byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer,
|
|
CFUNCTYPE, POINTER, cast
|
|
)
|
|
|
|
from .logging import get_logger
|
|
|
|
|
|
_logger = get_logger(__name__)
|
|
|
|
|
|
SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1)
|
|
SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0)
|
|
SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1)
|
|
# /** The higher bits contain the actual data. Do not use directly. */
|
|
SECP256K1_FLAGS_BIT_CONTEXT_VERIFY = (1 << 8)
|
|
SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9)
|
|
SECP256K1_FLAGS_BIT_COMPRESSION = (1 << 8)
|
|
|
|
# /** Flags to pass to secp256k1_context_create. */
|
|
SECP256K1_CONTEXT_VERIFY = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
|
|
SECP256K1_CONTEXT_SIGN = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
|
|
SECP256K1_CONTEXT_NONE = (SECP256K1_FLAGS_TYPE_CONTEXT)
|
|
|
|
SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION)
|
|
SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION)
|
|
|
|
|
|
class LibModuleMissing(Exception): pass
|
|
|
|
|
|
def load_library():
|
|
global HAS_SCHNORR
|
|
|
|
# note: for a mapping between bitcoin-core/secp256k1 git tags and .so.V libtool version numbers,
|
|
# see https://github.com/bitcoin-core/secp256k1/pull/1055#issuecomment-1227505189
|
|
tested_libversions = [2, 1, 0, ] # try latest version first
|
|
libnames = []
|
|
if sys.platform == 'darwin':
|
|
for v in tested_libversions:
|
|
libnames.append(f"libsecp256k1.{v}.dylib")
|
|
elif sys.platform in ('windows', 'win32'):
|
|
for v in tested_libversions:
|
|
libnames.append(f"libsecp256k1-{v}.dll")
|
|
elif 'ANDROID_DATA' in os.environ:
|
|
libnames = ['libsecp256k1.so', ] # don't care about version number. we built w/e is available.
|
|
else: # desktop Linux and similar
|
|
for v in tested_libversions:
|
|
libnames.append(f"libsecp256k1.so.{v}")
|
|
# maybe we could fall back to trying "any" version? maybe guarded with an env var?
|
|
#libnames.append(f"libsecp256k1.so")
|
|
library_paths = []
|
|
for libname in libnames: # try local files in repo dir first
|
|
library_paths.append(os.path.join(os.path.dirname(__file__), libname))
|
|
for libname in libnames:
|
|
library_paths.append(libname)
|
|
|
|
exceptions = []
|
|
secp256k1 = None
|
|
for libpath in library_paths:
|
|
try:
|
|
secp256k1 = ctypes.cdll.LoadLibrary(libpath)
|
|
except BaseException as e:
|
|
exceptions.append(e)
|
|
else:
|
|
break
|
|
if not secp256k1:
|
|
_logger.error(f'libsecp256k1 library failed to load. exceptions: {repr(exceptions)}')
|
|
return None
|
|
|
|
try:
|
|
secp256k1.secp256k1_context_create.argtypes = [c_uint]
|
|
secp256k1.secp256k1_context_create.restype = c_void_p
|
|
|
|
secp256k1.secp256k1_context_randomize.argtypes = [c_void_p, c_char_p]
|
|
secp256k1.secp256k1_context_randomize.restype = c_int
|
|
|
|
secp256k1.secp256k1_ec_pubkey_create.argtypes = [c_void_p, c_void_p, c_char_p]
|
|
secp256k1.secp256k1_ec_pubkey_create.restype = c_int
|
|
|
|
secp256k1.secp256k1_ecdsa_sign.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p, c_void_p, c_void_p]
|
|
secp256k1.secp256k1_ecdsa_sign.restype = c_int
|
|
|
|
secp256k1.secp256k1_ecdsa_verify.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p]
|
|
secp256k1.secp256k1_ecdsa_verify.restype = c_int
|
|
|
|
secp256k1.secp256k1_ec_pubkey_parse.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t]
|
|
secp256k1.secp256k1_ec_pubkey_parse.restype = c_int
|
|
|
|
secp256k1.secp256k1_ec_pubkey_serialize.argtypes = [c_void_p, c_char_p, c_void_p, c_char_p, c_uint]
|
|
secp256k1.secp256k1_ec_pubkey_serialize.restype = c_int
|
|
|
|
secp256k1.secp256k1_ecdsa_signature_parse_compact.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
secp256k1.secp256k1_ecdsa_signature_parse_compact.restype = c_int
|
|
|
|
secp256k1.secp256k1_ecdsa_signature_normalize.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
secp256k1.secp256k1_ecdsa_signature_normalize.restype = c_int
|
|
|
|
secp256k1.secp256k1_ecdsa_signature_serialize_compact.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
secp256k1.secp256k1_ecdsa_signature_serialize_compact.restype = c_int
|
|
|
|
secp256k1.secp256k1_ecdsa_signature_parse_der.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t]
|
|
secp256k1.secp256k1_ecdsa_signature_parse_der.restype = c_int
|
|
|
|
secp256k1.secp256k1_ecdsa_signature_serialize_der.argtypes = [c_void_p, c_char_p, c_void_p, c_char_p]
|
|
secp256k1.secp256k1_ecdsa_signature_serialize_der.restype = c_int
|
|
|
|
secp256k1.secp256k1_ec_pubkey_tweak_mul.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
secp256k1.secp256k1_ec_pubkey_tweak_mul.restype = c_int
|
|
|
|
secp256k1.secp256k1_ec_pubkey_combine.argtypes = [c_void_p, c_char_p, c_void_p, c_size_t]
|
|
secp256k1.secp256k1_ec_pubkey_combine.restype = c_int
|
|
|
|
# --enable-module-recovery
|
|
try:
|
|
secp256k1.secp256k1_ecdsa_recover.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p]
|
|
secp256k1.secp256k1_ecdsa_recover.restype = c_int
|
|
|
|
secp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact.argtypes = [c_void_p, c_char_p, c_char_p, c_int]
|
|
secp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact.restype = c_int
|
|
except (OSError, AttributeError):
|
|
raise LibModuleMissing('libsecp256k1 library found but it was built '
|
|
'without required module (--enable-module-recovery)')
|
|
|
|
# --enable-module-schnorrsig
|
|
try:
|
|
secp256k1.secp256k1_schnorrsig_sign32.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p, c_char_p]
|
|
secp256k1.secp256k1_schnorrsig_sign32.restype = c_int
|
|
|
|
secp256k1.secp256k1_schnorrsig_verify.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t, c_char_p]
|
|
secp256k1.secp256k1_schnorrsig_verify.restype = c_int
|
|
except (OSError, AttributeError):
|
|
_logger.warning(f"libsecp256k1 library found but it was built without desired module (--enable-module-schnorrsig)")
|
|
HAS_SCHNORR = False
|
|
# raise LibModuleMissing('libsecp256k1 library found but it was built '
|
|
# 'without required module (--enable-module-schnorrsig)')
|
|
|
|
# --enable-module-extrakeys
|
|
try:
|
|
secp256k1.secp256k1_xonly_pubkey_parse.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
secp256k1.secp256k1_xonly_pubkey_parse.restype = c_int
|
|
|
|
secp256k1.secp256k1_xonly_pubkey_serialize.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
secp256k1.secp256k1_xonly_pubkey_serialize.restype = c_int
|
|
|
|
secp256k1.secp256k1_keypair_create.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
secp256k1.secp256k1_keypair_create.restype = c_int
|
|
except (OSError, AttributeError):
|
|
_logger.warning(f"libsecp256k1 library found but it was built without desired module (--enable-module-extrakeys)")
|
|
HAS_SCHNORR = False
|
|
# raise LibModuleMissing('libsecp256k1 library found but it was built '
|
|
# 'without required module (--enable-module-extrakeys)')
|
|
|
|
secp256k1.ctx = secp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY)
|
|
ret = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32))
|
|
if not ret:
|
|
_logger.error('secp256k1_context_randomize failed')
|
|
return None
|
|
|
|
return secp256k1
|
|
except (OSError, AttributeError) as e:
|
|
_logger.error(f'libsecp256k1 library was found and loaded but there was an error when using it: {repr(e)}')
|
|
return None
|
|
|
|
|
|
_libsecp256k1 = None
|
|
HAS_SCHNORR = True
|
|
try:
|
|
_libsecp256k1 = load_library()
|
|
except BaseException as e:
|
|
_logger.error(f'failed to load libsecp256k1: {repr(e)}')
|
|
|
|
|
|
if _libsecp256k1 is None:
|
|
# hard fail:
|
|
raise ImportError("Failed to load libsecp256k1")
|
|
|
|
|
|
def version_info() -> dict:
|
|
return {
|
|
"libsecp256k1.path": _libsecp256k1._name if _libsecp256k1 else None,
|
|
}
|