diff --git a/.github/scripts/build-windows-wheels.sh b/.github/scripts/build-windows-wheels.sh deleted file mode 100755 index 32f9082a8..000000000 --- a/.github/scripts/build-windows-wheels.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -set -ex - -build_dll() { - ./autogen.sh - ./configure --host=$1 --enable-module-recovery --enable-experimental --enable-module-ecdh --enable-module-extrakeys --enable-module-schnorrsig --enable-benchmark=no --enable-tests=no --enable-exhaustive-tests=no --enable-static --disable-dependency-tracking --with-pic - make -} - -sudo apt-get install -y mingw-w64 -sudo apt-get -f install - -mkdir .hidden -cp * .hidden -R -mv .hidden/src/coincurve/_windows_libsecp256k1.py .hidden/src/coincurve/_libsecp256k1.py -mv .hidden ../clean - -cd .. - -curl -sLO "https://github.com/bitcoin-core/secp256k1/archive/$COINCURVE_UPSTREAM_REF.tar.gz" -tar -xzf "$COINCURVE_UPSTREAM_REF.tar.gz" -mv "secp256k1-$COINCURVE_UPSTREAM_REF" secp256k1 - -mv secp256k1 64bit -cp 64bit 32bit -R - -cd 64bit -build_dll x86_64-w64-mingw32 -# As the API changes, the -x.dll will change to -y.dll, so we use a wildcard -mv .libs/libsecp256k1-?.dll ../clean/src/coincurve/libsecp256k1.dll -cd ../clean -python -m build --wheel -C="--build-option=--plat-name win_amd64" -rm src/coincurve/libsecp256k1.dll -rm -rf build/temp.* - -cd ../32bit -build_dll i686-w64-mingw32 -# As the API changes, the -x.dll will change to -y.dll, so we use a wildcard -mv .libs/libsecp256k1-?.dll ../clean/src/coincurve/libsecp256k1.dll -cd ../clean -python -m build --wheel -C="--build-option--plat-name win32" - -mv dist/* ../coincurve/dist -cd ../coincurve diff --git a/.github/scripts/install-macos-build-deps.sh b/.github/scripts/install-macos-build-deps.sh index 5f9482305..ec3029b27 100755 --- a/.github/scripts/install-macos-build-deps.sh +++ b/.github/scripts/install-macos-build-deps.sh @@ -8,7 +8,7 @@ brew update brew outdated openssl || brew upgrade openssl # Install packages needed to build lib-secp256k1 -for pkg in automake libtool pkg-config; do +for pkg in pkg-config; do brew list $pkg > /dev/null || brew install $pkg brew outdated --quiet $pkg || brew upgrade $pkg done diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9150b38cc..61b257ce9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,11 +17,14 @@ concurrency: env: COINCURVE_UPSTREAM_REF: 1ad5185cd42c0636104129fcc9f6a4bf9c67cc40 COINCURVE_IGNORE_SYSTEM_LIB: '1' - CIBW_PROJECT_REQUIRES_PYTHON: ">=3.8" - CIBW_BEFORE_ALL_MACOS: ./.github/scripts/install-macos-build-deps.sh + # Only 'SHARED' is recognized, any other string means 'not SHARED' + COINCURVE_SECP256K1_BUILD: 'STATIC' CIBW_ENVIRONMENT_PASS_LINUX: > COINCURVE_UPSTREAM_REF COINCURVE_IGNORE_SYSTEM_LIB + COINCURVE_SECP256K1_BUILD + CIBW_PROJECT_REQUIRES_PYTHON: '>=3.8' + CIBW_BEFORE_ALL_MACOS: ./.github/scripts/install-macos-build-deps.sh CIBW_TEST_REQUIRES: pytest pytest-benchmark CIBW_TEST_COMMAND: > python -c @@ -113,20 +116,16 @@ jobs: if-no-files-found: error macos-wheels-arm: - name: Build macOS wheels for ARM + name: Build macOS wheels for ARM (Native) needs: - test - runs-on: macos-latest + runs-on: macos-14 steps: - uses: actions/checkout@v4 - name: Build wheels uses: pypa/cibuildwheel@v2.17 - env: - CIBW_ARCHS_MACOS: arm64 - COINCURVE_CROSS_HOST: aarch64-apple-darwin - CFLAGS: -target arm64-apple-macos11 - uses: actions/upload-artifact@v4 with: @@ -134,8 +133,81 @@ jobs: path: wheelhouse/*.whl if-no-files-found: error - windows-wheels-and-sdist: - name: Build Windows wheels and source distribution + windows-wheels-x86_64: + name: Build Windows wheels AMD64 + needs: + - test + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Build wheels + uses: pypa/cibuildwheel@v2.17 + env: + CIBW_ARCHS_WINDOWS: 'AMD64' + CIBW_BEFORE_ALL: choco install -y --no-progress --no-color cmake>=3.28 pkgconfiglite + + - uses: actions/upload-artifact@v4 + with: + name: artifact-windows-wheels-x86_64 + path: wheelhouse/*.whl + if-no-files-found: error + + windows-wheels-x86: + name: Build Windows wheels x86 + needs: + - test + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Build wheels + uses: pypa/cibuildwheel@v2.17 + env: + COINCURVE_CROSS_HOST: 'x86' + CIBW_ARCHS_WINDOWS: 'x86' + CIBW_BEFORE_ALL: choco install -y --no-progress --no-color cmake>=3.28 pkgconfiglite + + - uses: actions/upload-artifact@v4 + with: + name: artifact-windows-wheels-x86 + path: wheelhouse/*.whl + if-no-files-found: error + + windows-wheels-arm: + name: Build Windows wheels for ARM64 + needs: + - test + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.17 + env: + COINCURVE_CROSS_HOST: 'arm64' + CIBW_ARCHS_WINDOWS: 'ARM64' + CIBW_BEFORE_ALL: choco install -y --no-progress --no-color cmake>=3.28 pkgconfiglite + + - uses: actions/upload-artifact@v4 + with: + name: artifact-windows-wheels-arm + path: wheelhouse/*.whl + if-no-files-found: error + + sdist: + name: Build source distribution needs: - test runs-on: ubuntu-latest @@ -149,12 +221,9 @@ jobs: - name: Build source distribution run: python -m build --sdist - - name: Build Windows wheels - run: ./.github/scripts/build-windows-wheels.sh - - uses: actions/upload-artifact@v4 with: - name: artifact-windows-wheels-and-sdist + name: artifact-sdist path: dist/* if-no-files-found: error @@ -193,7 +262,10 @@ jobs: - linux-wheels-standard - macos-wheels-x86_64 - macos-wheels-arm - - windows-wheels-and-sdist + - windows-wheels-x86_64 + - windows-wheels-x86 + - windows-wheels-arm + - sdist - linux-wheels-arm runs-on: ubuntu-latest if: > diff --git a/_cffi_build/build.py b/_cffi_build/build.py index f478fb58a..3d95a9ced 100644 --- a/_cffi_build/build.py +++ b/_cffi_build/build.py @@ -1,36 +1,77 @@ +import argparse +import logging import os from collections import namedtuple +from typing import List from cffi import FFI +logging.basicConfig(level=logging.INFO) + here = os.path.dirname(os.path.abspath(__file__)) Source = namedtuple('Source', ('h', 'include')) -def _mk_ffi(sources, name='_libsecp256k1', **kwargs): +def gather_sources_from_directory(directory: str) -> List[Source]: + """ + Gather source files from a given directory. + + :param directory: The directory where source files are located. + :return: A list of Source namedtuples. + """ + sources = [] + for filename in os.listdir(directory): + if filename.endswith('.h'): + include_line = f'#include <{filename}>' + sources.append(Source(filename, include_line)) + return sorted(sources) + + +define_static_lib = """ +#if defined(_WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) +# define SECP256K1_STATIC 1 +#endif +""" + + +def mk_ffi(sources: List[Source], + static_lib: bool = False, + name: str = '_libsecp256k1') -> FFI: + """ + Create an FFI object. + + :param sources: A list of Source namedtuples. + :param static_lib: Whether to generate a static lib in Windows. + :param name: The name of the FFI object. + :return: An FFI object. + """ _ffi = FFI() - code = [] + code = [define_static_lib] if static_lib else [] + logging.info(f' Static {static_lib}...') for source in sources: with open(os.path.join(here, source.h)) as h: - _ffi.cdef(h.read()) + logging.info(f' Including {source.h}...') + c_header = h.read().replace('SECP256K1_API', '') + _ffi.cdef(c_header) + code.append(source.include) code.append('#define PY_USE_BUNDLED') - _ffi.set_source(name, '\n'.join(code), **kwargs) + _ffi.set_source(name, '\n'.join(code)) return _ffi -modules = [ - Source('secp256k1.h', '#include '), - Source('secp256k1_ecdh.h', '#include '), - Source('secp256k1_extrakeys.h', '#include '), - Source('secp256k1_recovery.h', '#include '), - Source('secp256k1_schnorrsig.h', '#include '), - Source('secp256k1_ellswift.h', '#include '), - Source('secp256k1_preallocated.h', '#include '), -] +if __name__ == '__main__': + logging.info('Starting CFFI build process...') + parser = argparse.ArgumentParser(description='Generate C code using CFFI.') + parser.add_argument('c_file', help='Generated C code filename.') + parser.add_argument('static_lib', help='Generate static lib in Windows.', default='0', type=str) + args = parser.parse_args() -ffi = _mk_ffi(modules, libraries=['secp256k1']) + modules = gather_sources_from_directory(here) + ffi = mk_ffi(modules, args.static_lib == '1') + ffi.emit_c_code(args.c_file) + logging.info(f' Generated C code: {args.c_file}') diff --git a/_cffi_build/build_shared.py b/_cffi_build/build_shared.py deleted file mode 100644 index 4ad89d38e..000000000 --- a/_cffi_build/build_shared.py +++ /dev/null @@ -1,84 +0,0 @@ -import argparse -import logging -import os -from collections import namedtuple -from typing import List - -from cffi import FFI - -logging.basicConfig(level=logging.INFO) - -here = os.path.dirname(os.path.abspath(__file__)) - -Source = namedtuple('Source', ('h', 'include')) - - -def gather_sources_from_directory(directory: str) -> List[Source]: - """ - Gather source files from a given directory. - - :param directory: The directory where source files are located. - :return: A list of Source namedtuples. - """ - sources = [] - for filename in os.listdir(directory): - if filename.endswith('.h'): - include_line = f'#include <{filename}>' - sources.append(Source(filename, include_line)) - return sorted(sources) - - -define_static_lib = """ -#if defined(_WIN32) -# define SECP256K1_STATIC 1 -# define SECP256K1_API extern __declspec(dllexport) -#endif -""" - -define_shared_lib = """ -#if defined(_WIN32) -# define SECP256K1_API extern __declspec(dllimport) -#endif -""" - - -def mk_ffi(sources: List[Source], - static_lib: str = '0', - name: str = '_libsecp256k1') -> FFI: - """ - Create an FFI object. - - :param sources: A list of Source namedtuples. - :param libraries: A list of libraries to link against. - :param static_lib: Whether to generate a static lib in Windows. - :param name: The name of the FFI object. - :return: An FFI object. - """ - _ffi = FFI() - code = [define_static_lib] if static_lib == '1' else [define_shared_lib] - - for source in sources: - with open(os.path.join(here, source.h)) as h: - logging.info(f' Including {source.h}...') - c_header = h.read().replace('SECP256K1_API', '') - _ffi.cdef(c_header) - - code.append(source.include) - - code.append('#define PY_USE_BUNDLED') - _ffi.set_source(name, '\n'.join(code)) - - return _ffi - - -if __name__ == '__main__': - logging.info('Starting CFFI build process...') - parser = argparse.ArgumentParser(description='Generate C code using CFFI.') - parser.add_argument('c_file', help='Generated C code filename.') - parser.add_argument('static_lib', help='Generate static lib in Windows.', default=False) - args = parser.parse_args() - - modules = gather_sources_from_directory(here) - ffi = mk_ffi(modules, args.static_lib) - ffi.emit_c_code(args.c_file) - logging.info(f' Generated C code: {args.c_file}') diff --git a/setup.py b/setup.py index 5bbe1338a..e8be50b66 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -import os +import logging import os.path import platform import sys @@ -16,11 +16,7 @@ PATH.insert(0, join(COINCURVE_ROOT_DIR, 'setup_tools')) sys.path.append(os.path.abspath(os.path.dirname(__file__))) -from setup_tools.support import detect_dll, has_system_lib # noqa: E402 - -BUILDING_FOR_WINDOWS = detect_dll(COINCURVE_ROOT_DIR) - -MAKE = 'gmake' if platform.system() in ['FreeBSD', 'OpenBSD'] else 'make' +from setup_tools.support import has_system_lib # noqa: E402 # IMPORTANT: keep in sync with .github/workflows/build.yml # @@ -29,79 +25,49 @@ LIB_TARBALL_URL = f'https://github.com/bitcoin-core/secp256k1/archive/{UPSTREAM_REF}.tar.gz' +LIB_NAME = 'libsecp256k1' +PKG_NAME = 'coincurve' + +# Helpers for compilation instructions +# Cross-compile for Windows/ARM64, Linux/ARM64, Darwin/ARM64, Windows/x86 (GitHub) +X_HOST = os.getenv('COINCURVE_CROSS_HOST') + +SYSTEM = platform.system() # supported: Windows, Linux, Darwin +MACHINE = platform.machine() # supported: AMD64, x86_64 + +logging.info(f'Building for {SYSTEM}:{MACHINE} with {X_HOST = }') +SECP256K1_BUILD = os.getenv('COINCURVE_SECP256K1_BUILD') or 'STATIC' +SECP256K1_IGNORE_EXT_LIB = os.getenv('COINCURVE_IGNORE_SYSTEM_LIB') + + +class Distribution(_Distribution): + def has_c_libraries(self): + return not has_system_lib() + def main(): from setup_tools.commands import BdistWheel, EggInfo, Sdist, Develop - from setup_tools.build_clib import BuildClib - from setup_tools.build_ext import BuildCFFIForSharedLib, BuildExt - from setup_tools.support import call_pkg_config - - if has_system_lib(): - - class Distribution(_Distribution): - def has_c_libraries(self): - return not has_system_lib() - - extension = Extension( - name='coincurve._libsecp256k1', - sources=[os.path.join('src/coincurve', '_libsecp256k1.c')], - # ABI?: py_limited_api=True, - ) - - extension.extra_compile_args = [call_pkg_config(['--cflags-only-I'], 'libsecp256k1')] - extension.extra_link_args = [ - call_pkg_config(['--libs-only-L'], 'libsecp256k1'), - call_pkg_config(['--libs-only-l'], 'libsecp256k1'), - ] - - if os.name == 'nt' or sys.platform == 'win32': - # Apparently, the linker on Windows interprets -lxxx as xxx.lib, not libxxx.lib - for i, v in enumerate(extension.__dict__.get('extra_link_args')): - extension.__dict__['extra_link_args'][i] = v.replace('-L', '/LIBPATH:') - - if v.startswith('-l'): - v = v.replace('-l', 'lib') - extension.__dict__['extra_link_args'][i] = f'{v}.lib' - - setup_kwargs = dict( - ext_modules=[extension], - cmdclass={ - 'build_clib': BuildClib, - 'build_ext': BuildCFFIForSharedLib, - 'develop': Develop, - 'egg_info': EggInfo, - 'sdist': Sdist, - 'bdist_wheel': BdistWheel, - }, - ) - - else: - if BUILDING_FOR_WINDOWS: - - class Distribution(_Distribution): - def is_pure(self): - return False - - setup_kwargs = {} - - else: - - class Distribution(_Distribution): - def has_c_libraries(self): - return not has_system_lib() - - setup_kwargs = dict( - ext_package='coincurve', - cffi_modules=['_cffi_build/build.py:ffi'], - cmdclass={ - 'build_clib': BuildClib, - 'build_ext': BuildExt, - 'develop': Develop, - 'egg_info': EggInfo, - 'sdist': Sdist, - 'bdist_wheel': BdistWheel, - }, - ) + from setup_tools.build_clib import BuildClibWithCMake + from setup_tools.build_ext import BuildExtensionFromCFFI + + extension = Extension( + name='coincurve._libsecp256k1', + sources=['_c_file_for_extension.c'], + py_limited_api=False, + extra_compile_args=['/d2FH4-'] if SYSTEM == 'Windows' else [], + ) + + setup_kwargs = dict( + ext_modules=[extension], + cmdclass={ + 'build_clib': None if has_system_lib() else BuildClibWithCMake, + 'build_ext': BuildExtensionFromCFFI, + 'develop': Develop, + 'egg_info': EggInfo, + 'sdist': Sdist, + 'bdist_wheel': BdistWheel, + }, + ) setup( distclass=Distribution, diff --git a/setup_tools/build_clib.py b/setup_tools/build_clib.py index 0b0dd6da9..65022e3be 100644 --- a/setup_tools/build_clib.py +++ b/setup_tools/build_clib.py @@ -1,121 +1,144 @@ -import errno import logging import os +import shutil import subprocess from setuptools.command import build_clib +from setup import LIB_NAME, MACHINE, SECP256K1_BUILD, SYSTEM, X_HOST +from setup_tools.support import ( + absolute, + call_pkg_config, + define_secp256k1_local_lib_info, + download_library, + has_system_lib, +) -class BuildClib(build_clib.build_clib): - def initialize_options(self): - super().initialize_options() - self.build_flags = None - def finalize_options(self): - super().finalize_options() - if self.build_flags is None: - self.build_flags = {'include_dirs': [], 'library_dirs': [], 'define': []} +class BuildClibWithCMake(build_clib.build_clib): + title = 'SECP256K1 C library build' - def get_source_files(self): - from support import absolute, has_system_lib + def __init__(self, dist): + super().__init__(dist) + self.pkgconfig_dir = None + + self._cwd = None + self._lib_src = None + self._install_dir = None + self._install_lib_dir = None + def get_source_files(self): # Ensure library has been downloaded (sdist might have been skipped) if not has_system_lib(): - from setup_tools.support import download_library - download_library(self) - return [ - absolute(os.path.join(root, filename)) - for root, _, filenames in os.walk(absolute('libsecp256k1')) - for filename in filenames - ] - - def build_libraries(self, libraries): - raise Exception('build_libraries') - - def check_library_list(self, libraries): - raise Exception('check_library_list') - - def get_library_names(self): - from support import build_flags - - return build_flags('libsecp256k1', 'l', os.path.abspath(self.build_temp)) + # This seems to create issues in MANIFEST.in + return [f for _, _, fs in os.walk(absolute(LIB_NAME)) for f in fs] def run(self): - from support import absolute, build_flags, has_system_lib - - from setup import MAKE - if has_system_lib(): logging.info('Using system library') return - build_temp = os.path.abspath(self.build_temp) + logging.info(self.title) + self.bc_set_dirs_download() + self.bc_prepare_build(self._install_lib_dir, self.build_temp, self._lib_src) try: - os.makedirs(build_temp) - except OSError as e: - if e.errno != errno.EEXIST: - raise - - if not os.path.exists(absolute('libsecp256k1')): - # library needs to be downloaded + os.chdir(self.build_temp) + subprocess.check_call(self.bc_build_command()) # noqa S603 + finally: + os.chdir(self._cwd) + + # Register lib installation path + self.bc_update_pkg_config_path() + + def bc_set_dirs_download(self): + self._cwd = os.getcwd() + os.makedirs(self.build_temp, exist_ok=True) + self._install_dir = self.build_temp.replace('temp', 'lib') + + # Install path + # SHARED: lib/coincurve -> path/lib.xxx/coincurve/path # included in coincurve wheel + # STATIC: x_lib/libsecp256k1 -> path/x_lib.xxx/libsecp256k1/path # NOT included in coincurve wheel + lib, inst_dir = define_secp256k1_local_lib_info() + self._install_lib_dir = os.path.join(self._install_dir.replace('lib', inst_dir), lib) + + self._lib_src = os.path.join(self._cwd, LIB_NAME) + if not os.path.exists(self._lib_src): self.get_source_files() - if not os.path.exists(absolute('libsecp256k1/configure')): - # configure script hasn't been generated yet - autogen = absolute('libsecp256k1/autogen.sh') - os.chmod(absolute(autogen), 0o700) - subprocess.check_call([autogen], cwd=absolute('libsecp256k1')) # noqa S603 - - for filename in [ - 'libsecp256k1/configure', - 'libsecp256k1/build-aux/compile', - 'libsecp256k1/build-aux/config.guess', - 'libsecp256k1/build-aux/config.sub', - 'libsecp256k1/build-aux/depcomp', - 'libsecp256k1/build-aux/install-sh', - 'libsecp256k1/build-aux/missing', - 'libsecp256k1/build-aux/test-driver', - ]: - try: - os.chmod(absolute(filename), 0o700) - except OSError as e: - # some of these files might not exist depending on autoconf version - if e.errno != errno.ENOENT: - # If the error isn't 'No such file or directory' something - # else is wrong and we want to know about it - raise - - cmd = [ - absolute('libsecp256k1/configure'), - '--disable-shared', - '--enable-static', - '--disable-dependency-tracking', - '--with-pic', - '--enable-module-extrakeys', - '--enable-module-recovery', - '--enable-module-schnorrsig', - '--prefix', - os.path.abspath(self.build_clib), - '--enable-experimental', - '--enable-module-ecdh', - '--enable-benchmark=no', - '--enable-tests=no', - '--enable-exhaustive-tests=no', + def bc_update_pkg_config_path(self): + self.pkgconfig_dir = [os.path.join(self._install_lib_dir, n, 'pkgconfig') for n in ['lib', 'lib64']] + self.pkgconfig_dir.append(os.getenv('PKG_CONFIG_PATH', '')) + os.environ['PKG_CONFIG_PATH'] = os.pathsep.join(self.pkgconfig_dir) + call_pkg_config(['--exists'], LIB_NAME) + + @staticmethod + def _generator(msvc): + if '2017' in str(msvc): + return 'Visual Studio 15 2017' + if '2019' in str(msvc): + return 'Visual Studio 16 2019' + if '2022' in str(msvc): + return 'Visual Studio 17 2022' + + @staticmethod + def bc_prepare_build(install_lib_dir, build_temp, lib_src): + cmake_args = [ + '-DCMAKE_BUILD_TYPE=Release', + f'-DCMAKE_INSTALL_PREFIX={install_lib_dir}', + f'-DCMAKE_C_FLAGS={"-fPIC" if SECP256K1_BUILD != "SHARED" and SYSTEM != "Windows" else ""}', + f'-DSECP256K1_DISABLE_SHARED={"OFF" if SECP256K1_BUILD == "SHARED" else "ON"}', + '-DSECP256K1_BUILD_BENCHMARK=OFF', + '-DSECP256K1_BUILD_TESTS=OFF', + '-DSECP256K1_ENABLE_MODULE_ECDH=ON', + '-DSECP256K1_ENABLE_MODULE_RECOVERY=ON', + '-DSECP256K1_ENABLE_MODULE_SCHNORRSIG=ON', + '-DSECP256K1_ENABLE_MODULE_EXTRAKEYS=ON', ] - if 'COINCURVE_CROSS_HOST' in os.environ: - cmd.append(f"--host={os.environ['COINCURVE_CROSS_HOST']}") - logging.debug(f"Running configure: {' '.join(cmd)}") - subprocess.check_call(cmd, cwd=build_temp) # noqa S603 - - subprocess.check_call([MAKE], cwd=build_temp) # noqa S603 - subprocess.check_call([MAKE, 'check'], cwd=build_temp) # noqa S603 - subprocess.check_call([MAKE, 'install'], cwd=build_temp) # noqa S603 - - self.build_flags['include_dirs'].extend(build_flags('libsecp256k1', 'I', build_temp)) - self.build_flags['library_dirs'].extend(build_flags('libsecp256k1', 'L', build_temp)) - if not has_system_lib(): - self.build_flags['define'].append(('CFFI_ENABLE_RECOVERY', None)) + # Windows (more complex) + if SYSTEM == 'Windows': + vswhere = shutil.which('vswhere') + cmd = [vswhere, '-latest', '-find', 'MSBuild\\**\\Bin\\MSBuild.exe'] + msvc = subprocess.check_output(cmd).strip().decode('utf-8') # noqa S603 + + # For windows x86/x64, select the correct architecture + arch = 'x64' if MACHINE == 'AMD64' else 'Win32' # Native + + if X_HOST is not None: + logging.info(f'Cross-compiling on {SYSTEM}:{MACHINE} for {X_HOST}') + if X_HOST in ['arm64', 'ARM64', 'x86']: + arch = 'Win32' if X_HOST in ['x86'] else 'arm64' + else: + raise NotImplementedError(f'Unsupported architecture: {X_HOST}') + + # Place the DLL directly in the package directory + cmake_args.append('-DCMAKE_INSTALL_BINDIR=.') + cmake_args.extend(['-G', BuildClibWithCMake._generator(msvc), f'-A{arch}']) + + elif SYSTEM == 'Darwin': + if X_HOST is None: + cmake_args.append(f'-DCMAKE_OSX_ARCHITECTURES={MACHINE}') # Native + else: + logging.info(f'Cross-compiling on {SYSTEM}:{MACHINE} for {X_HOST}') + if X_HOST in ['armv7', 'armv7s', 'arm64', 'arm64e']: + cmake_args.append(f'-DCMAKE_OSX_ARCHITECTURES={X_HOST}') + elif X_HOST is not None: + if X_HOST not in [ + 'arm-linux-gnueabihf', + 'x86_64-w64-mingw32', + ]: + raise NotImplementedError(f'Unsupported architecture: {X_HOST}') + + logging.info(f'Cross-compiling on {SYSTEM}:{MACHINE} for {X_HOST}') + cmake_args.append(f'-DCMAKE_TOOLCHAIN_FILE=../cmake/{X_HOST}.toolchain.cmake') + + logging.info(' Configure CMake') + subprocess.check_call(['cmake', '-S', lib_src, '-B', build_temp, *cmake_args]) # noqa S603 + + @staticmethod + def bc_build_command(): + logging.info(' Install with CMake') + return ['cmake', '--build', '.', '--target', 'install', '--config', 'Release', '--clean-first'] diff --git a/setup_tools/build_ext.py b/setup_tools/build_ext.py index 28eb931eb..172fbf4c6 100644 --- a/setup_tools/build_ext.py +++ b/setup_tools/build_ext.py @@ -1,28 +1,71 @@ import os -import subprocess import sys from setuptools.command import build_ext +from setup import LIB_NAME +from setup_tools.linkers import SharedLinker, StaticLinker +from setup_tools.support import build_flags, has_system_lib, subprocess_run -class BuildExt(build_ext.build_ext): - def run(self): - if self.distribution.has_c_libraries(): - _build_clib = self.get_finalized_command('build_clib') - self.include_dirs.append(os.path.join(_build_clib.build_clib, 'include')) - self.include_dirs.extend(_build_clib.build_flags['include_dirs']) - self.library_dirs.insert(0, os.path.join(_build_clib.build_clib, 'lib')) - self.library_dirs.extend(_build_clib.build_flags['library_dirs']) +class BuildExtensionFromCFFI(build_ext.build_ext): + from setup import SECP256K1_BUILD - self.define = _build_clib.build_flags['define'] + static_lib = True if SECP256K1_BUILD == 'STATIC' else False - return super().run() + def update_link_args(self, libraries, libraries_dirs, extra_link_args): + if self.static_lib: + StaticLinker.update_link_args(self.compiler, libraries, libraries_dirs, extra_link_args) + else: + SharedLinker.update_link_args(self.compiler, libraries, libraries_dirs, extra_link_args) + def create_c_files(self, ext): + # Construct C-file from CFFI + build_script = os.path.join('_cffi_build', 'build.py') + for i, c_file in enumerate(ext.sources): + os.makedirs(self.build_temp, exist_ok=True) + c_file = os.path.join(self.build_temp, os.path.basename(c_file)) + # This puts c-file a temp location (and not in the coincurve src directory) + ext.sources[i] = c_file + cmd = [sys.executable, build_script, c_file, '1' if self.static_lib else '0'] + subprocess_run(cmd) -class BuildCFFIForSharedLib(build_ext.build_ext): - def build_extensions(self): - build_script = os.path.join('_cffi_build', 'build_shared.py') - c_file = self.extensions[0].sources[0] - subprocess.run([sys.executable, build_script, c_file, '0'], shell=False, check=True) # noqa S603 - super().build_extensions() + def build_extension(self, ext): + # Construct C-file from CFFI + self.create_c_files(ext) + + # Enforce API interface + ext.py_limited_api = False + + c_lib_pkg = None + if not has_system_lib(): + # Find pkgconfig file for locally built library + if (pkg_dirs := self.get_finalized_command('build_clib')) is None: + raise RuntimeError( + f'No C-lib command when System lib is {has_system_lib() = }. ' + 'The build system failed to trigger the build_clib.' + ) + + c_lib_pkg = next( + (d for d in pkg_dirs.pkgconfig_dir if os.path.isfile(os.path.join(d, f'{LIB_NAME}.pc'))), None + ) + + if c_lib_pkg is None: + raise RuntimeError( + f'pkgconfig file not found: {LIB_NAME}.pc in : {pkg_dirs}.' + f'\nSystem lib is {has_system_lib() = }. ' + 'Please check that the library was properly built.' + ) + + # PKG_CONFIG_PATH is updated by build_clib if built locally, + # however, it would not work for a step-by-step build, thus we specify the lib path + ext.extra_compile_args.extend([f'-I{build_flags(LIB_NAME, "I", c_lib_pkg)[0]}']) + ext.library_dirs.extend(build_flags(LIB_NAME, 'L', c_lib_pkg)) + + libraries = build_flags(LIB_NAME, 'l', c_lib_pkg) + + # We do not set ext.libraries, this would add the default link instruction + # Instead, we use extra_link_args to customize the link command + self.update_link_args(libraries, ext.library_dirs, ext.extra_link_args) + + super().build_extension(ext) diff --git a/setup_tools/linkers.py b/setup_tools/linkers.py new file mode 100644 index 000000000..87e375077 --- /dev/null +++ b/setup_tools/linkers.py @@ -0,0 +1,61 @@ +import os +import sys + +from setup_tools.support import has_system_lib + + +class SharedLinker: + @staticmethod + def update_link_args(compiler, libraries, libraries_dirs, extra_link_args): + if compiler.__class__.__name__ == 'UnixCCompiler': + extra_link_args.extend([f'-l{lib}' for lib in libraries]) + if has_system_lib(): + # This requires to add DYLD_LIBRARY_PATH to the environment + # When repairing the wheel on MacOS (linux not tested yet) + extra_link_args.extend([f'-L{lib}' for lib in libraries_dirs]) + elif sys.platform == 'darwin': + extra_link_args.extend(['-Wl,-rpath,@loader_path/lib']) + else: + extra_link_args.extend( + [ + '-Wl,-rpath,$ORIGIN/lib', + '-Wl,-rpath,$ORIGIN/lib64', + ] + ) + elif compiler.__class__.__name__ == 'MSVCCompiler': + for ld in libraries_dirs: + ld = ld.replace('/', '\\') + for lib in libraries: + lib_file = os.path.join(ld, f'lib{lib}.lib') + lib_path = [f'/LIBPATH:{ld}', f'lib{lib}.lib'] + if os.path.exists(lib_file): + extra_link_args.extend(lib_path) + else: + raise NotImplementedError(f'Unsupported compiler: {compiler.__class__.__name__}') + + +class StaticLinker: + @staticmethod + def update_link_args(compiler, libraries, libraries_dirs, extra_link_args): + if compiler.__class__.__name__ == 'UnixCCompiler': + # It is possible that the library was compiled without fPIC option + for lib in libraries: + # On MacOS the mix static/dynamic option is different + # It requires a -force_load option for each library + if sys.platform == 'darwin': + for lib_dir in libraries_dirs: + if os.path.exists(os.path.join(lib_dir, f'lib{lib}.a')): + extra_link_args.extend(['-Wl,-force_load', os.path.join(lib_dir, f'lib{lib}.a')]) + break + else: + extra_link_args.extend(['-Wl,-Bstatic', f'-l{lib}', '-Wl,-Bdynamic']) + + elif compiler.__class__.__name__ == 'MSVCCompiler': + for ld in libraries_dirs: + ld = ld.replace('/', '\\') + for lib in libraries: + lib_file = os.path.join(ld, f'lib{lib}.lib') + if os.path.exists(lib_file): + extra_link_args.append(lib_file) + else: + raise NotImplementedError(f'Unsupported compiler: {compiler.__class__.__name__}') diff --git a/setup_tools/support.py b/setup_tools/support.py index 2689b25a3..838a6a8b3 100644 --- a/setup_tools/support.py +++ b/setup_tools/support.py @@ -1,10 +1,8 @@ -import glob import logging import os import shutil import subprocess import tarfile -from contextlib import suppress from io import BytesIO @@ -17,17 +15,7 @@ def absolute(*paths): def build_flags(library, type_, path): """Return separated build flags from pkg-config output""" - - pkg_config_path = [path] - if 'PKG_CONFIG_PATH' in os.environ: - pkg_config_path.append(os.environ['PKG_CONFIG_PATH']) - if 'LIB_DIR' in os.environ: - pkg_config_path.append(os.environ['LIB_DIR']) - pkg_config_path.append(os.path.join(os.environ['LIB_DIR'], 'pkgconfig')) - - # Update PKG_CONFIG_PATH, it may be needed in later stages - new_path = str(os.pathsep).join(pkg_config_path) - os.environ['PKG_CONFIG_PATH'] = new_path + os.pathsep + os.environ.get('PKG_CONFIG_PATH', '') + update_pkg_config_path(path) options = {'I': '--cflags-only-I', 'L': '--libs-only-L', 'l': '--libs-only-l'} flags = call_pkg_config([options[type_]], library) @@ -37,25 +25,19 @@ def build_flags(library, type_, path): def _find_lib(): - if 'COINCURVE_IGNORE_SYSTEM_LIB' in os.environ: + if os.getenv('COINCURVE_IGNORE_SYSTEM_LIB', '1') == '1': return False - from cffi import FFI + update_pkg_config_path() try: - options = ['--cflags-only-I'] - includes = call_pkg_config(options, 'libsecp256k1') - - return os.path.exists(os.path.join(includes[2:], 'secp256k1_ecdh.h')) + lib_dir = call_pkg_config(['--libs-only-L'], 'libsecp256k1') + return verify_system_lib(lib_dir[2:].strip()) except (OSError, subprocess.CalledProcessError): - if 'LIB_DIR' in os.environ: - for path in glob.glob(os.path.join(os.environ['LIB_DIR'], '*secp256k1*')): - with suppress(OSError): - FFI().dlopen(path) - return True - # We couldn't locate libsecp256k1, so we'll use the bundled one - return False + from ctypes.util import find_library + + return bool(find_library('secp256k1')) _has_system_lib = None @@ -68,10 +50,6 @@ def has_system_lib(): return _has_system_lib -def detect_dll(root_dir: str): - return any(fn.endswith('.dll') for fn in os.listdir(os.path.join(root_dir))) - - def subprocess_run(cmd, *, debug=False): try: result = subprocess.run(cmd, capture_output=True, text=True, check=True) # noqa S603 @@ -135,3 +113,76 @@ def download_library(command): raise SystemExit('Unable to download secp256k1 library: %s') from e except ImportError as e: raise SystemExit('Unable to download secp256k1 library: %s') from e + + +def define_secp256k1_local_lib_info(): + """ + Define the library name and the installation directory + The purpose is to automatically include the shared library in the package and + prevent inclusion the static library. This is probably hacky, but it works. + """ + from setup import LIB_NAME, PKG_NAME, SECP256K1_BUILD + + if SECP256K1_BUILD == 'SHARED': + return PKG_NAME, 'lib' + return LIB_NAME, 'x_lib' + + +def update_pkg_config_path(path=None): + """Updates the PKG_CONFIG_PATH environment variable to include the given path.""" + pkg_config_paths = [path, os.getenv('PKG_CONFIG_PATH', '').strip('"')] + + if cpf := os.getenv('CONDA_PREFIX'): + conda_paths = [os.path.join(cpf, sbd, 'pkgconfig') for sbd in ('lib', 'lib64', os.path.join('Library', 'lib'))] + pkg_config_paths.extend([p for p in conda_paths if os.path.isdir(p)]) + + if lbd := os.getenv('LIB_DIR'): + pkg_config_paths.append(os.path.join(lbd, 'pkgconfig')) + + # Update environment + os.environ['PKG_CONFIG_PATH'] = os.pathsep.join([p for p in pkg_config_paths if p is not None]) + + +def verify_system_lib(lib_dir): + """Verifies that the system library is installed and of the expected type.""" + import ctypes + import platform + from ctypes.util import find_library + from pathlib import Path + + LIB_NAME = 'libsecp256k1' # noqa N806 + PKG_NAME = 'coincurve' # noqa N806 + SECP256K1_BUILD = os.getenv('COINCURVE_SECP256K1_BUILD') or 'STATIC' # noqa N806 + SYSTEM = platform.system() # noqa N806 + + def load_library(lib): + try: + return ctypes.CDLL(lib) + except OSError: + return None + + logging.warning(f'find_library: {find_library(LIB_NAME[3:])}') + lib_dir = Path(lib_dir).with_name('bin') if SYSTEM == 'Windows' else Path(lib_dir) + lib_ext = '.dll' if SYSTEM == 'Windows' else '.[sd][oy]*' + logging.warning(f'dir: {lib_dir}') + logging.warning(f'patt: *{LIB_NAME[3:]}{lib_ext}') + l_dyn = list(lib_dir.glob(f'*{LIB_NAME[3:]}*{lib_ext}')) + + # Evaluates the dynamic libraries found, + logging.warning(f'Found libraries: {l_dyn}') + dyn_lib = next((lib for lib in l_dyn if load_library(lib) is not None), False) + + found = any((dyn_lib and SECP256K1_BUILD == 'SHARED', not dyn_lib and SECP256K1_BUILD != 'SHARED')) + if not found: + logging.warning( + f'WARNING: {LIB_NAME} is installed, but it is not the expected type. ' + f'Please ensure that the {SECP256K1_BUILD} library is installed.' + ) + + if dyn_lib: + lib_base = dyn_lib.stem + # Update coincurve._secp256k1_library_info + info_file = Path(PKG_NAME, '_secp256k1_library_info.py') + info_file.write_text(f"SECP256K1_LIBRARY_NAME = '{lib_base}'\nSECP256K1_LIBRARY_TYPE = 'EXTERNAL'\n") + + return found diff --git a/src/coincurve/_windows_libsecp256k1.py b/src/coincurve/_windows_libsecp256k1.py deleted file mode 100644 index 49a324a26..000000000 --- a/src/coincurve/_windows_libsecp256k1.py +++ /dev/null @@ -1,420 +0,0 @@ -import os - -from cffi import FFI - -BASE_DEFINITIONS = """ -typedef struct secp256k1_context_struct secp256k1_context; -typedef struct secp256k1_scratch_space_struct secp256k1_scratch_space; - -typedef struct { - unsigned char data[64]; -} secp256k1_pubkey; - -typedef struct { - unsigned char data[64]; -} secp256k1_ecdsa_signature; - -typedef int (*secp256k1_nonce_function)( - unsigned char *nonce32, - const unsigned char *msg32, - const unsigned char *key32, - const unsigned char *algo16, - void *data, - unsigned int attempt -); - -#define SECP256K1_CONTEXT_NONE 1 -#define SECP256K1_EC_COMPRESSED 258 -#define SECP256K1_EC_UNCOMPRESSED 2 -#define SECP256K1_TAG_PUBKEY_EVEN 2 -#define SECP256K1_TAG_PUBKEY_ODD 3 -#define SECP256K1_TAG_PUBKEY_UNCOMPRESSED 4 -#define SECP256K1_TAG_PUBKEY_HYBRID_EVEN 6 -#define SECP256K1_TAG_PUBKEY_HYBRID_ODD 7 - -extern const secp256k1_context *secp256k1_context_static; -extern void secp256k1_selftest(void); -extern secp256k1_context *secp256k1_context_create( - unsigned int flags -); -extern secp256k1_context *secp256k1_context_clone( - const secp256k1_context *ctx -); -extern void secp256k1_context_destroy( - secp256k1_context *ctx -); -extern void secp256k1_context_set_illegal_callback( - secp256k1_context *ctx, - void (*fun)(const char *message, void *data), - const void *data -); -extern void secp256k1_context_set_error_callback( - secp256k1_context *ctx, - void (*fun)(const char *message, void *data), - const void *data -); -extern secp256k1_scratch_space *secp256k1_scratch_space_create( - const secp256k1_context *ctx, - size_t size -); -extern void secp256k1_scratch_space_destroy( - const secp256k1_context *ctx, - secp256k1_scratch_space *scratch -); -extern int secp256k1_ec_pubkey_parse( - const secp256k1_context *ctx, - secp256k1_pubkey *pubkey, - const unsigned char *input, - size_t inputlen -); -extern int secp256k1_ec_pubkey_serialize( - const secp256k1_context *ctx, - unsigned char *output, - size_t *outputlen, - const secp256k1_pubkey *pubkey, - unsigned int flags -); -extern int secp256k1_ec_pubkey_cmp( - const secp256k1_context *ctx, - const secp256k1_pubkey *pubkey1, - const secp256k1_pubkey *pubkey2 -); -extern int secp256k1_ecdsa_signature_parse_compact( - const secp256k1_context *ctx, - secp256k1_ecdsa_signature *sig, - const unsigned char *input64 -); -extern int secp256k1_ecdsa_signature_parse_der( - const secp256k1_context *ctx, - secp256k1_ecdsa_signature *sig, - const unsigned char *input, - size_t inputlen -); -extern int secp256k1_ecdsa_signature_serialize_der( - const secp256k1_context *ctx, - unsigned char *output, - size_t *outputlen, - const secp256k1_ecdsa_signature *sig -); -extern int secp256k1_ecdsa_signature_serialize_compact( - const secp256k1_context *ctx, - unsigned char *output64, - const secp256k1_ecdsa_signature *sig -); -extern int secp256k1_ecdsa_verify( - const secp256k1_context *ctx, - const secp256k1_ecdsa_signature *sig, - const unsigned char *msghash32, - const secp256k1_pubkey *pubkey -); -extern int secp256k1_ecdsa_signature_normalize( - const secp256k1_context *ctx, - secp256k1_ecdsa_signature *sigout, - const secp256k1_ecdsa_signature *sigin -); - -extern const secp256k1_nonce_function secp256k1_nonce_function_rfc6979; - -extern const secp256k1_nonce_function secp256k1_nonce_function_default; - -extern int secp256k1_ecdsa_sign( - const secp256k1_context *ctx, - secp256k1_ecdsa_signature *sig, - const unsigned char *msghash32, - const unsigned char *seckey, - secp256k1_nonce_function noncefp, - const void *ndata -); -extern int secp256k1_ec_seckey_verify( - const secp256k1_context *ctx, - const unsigned char *seckey -); -extern int secp256k1_ec_pubkey_create( - const secp256k1_context *ctx, - secp256k1_pubkey *pubkey, - const unsigned char *seckey -); -extern int secp256k1_ec_seckey_negate( - const secp256k1_context *ctx, - unsigned char *seckey -); -extern int secp256k1_ec_pubkey_negate( - const secp256k1_context *ctx, - secp256k1_pubkey *pubkey -); -extern int secp256k1_ec_seckey_tweak_add( - const secp256k1_context *ctx, - unsigned char *seckey, - const unsigned char *tweak32 -); -extern int secp256k1_ec_pubkey_tweak_add( - const secp256k1_context *ctx, - secp256k1_pubkey *pubkey, - const unsigned char *tweak32 -); -extern int secp256k1_ec_seckey_tweak_mul( - const secp256k1_context *ctx, - unsigned char *seckey, - const unsigned char *tweak32 -); -extern int secp256k1_ec_pubkey_tweak_mul( - const secp256k1_context *ctx, - secp256k1_pubkey *pubkey, - const unsigned char *tweak32 -); -extern int secp256k1_context_randomize( - secp256k1_context *ctx, - const unsigned char *seed32 -); -extern int secp256k1_ec_pubkey_combine( - const secp256k1_context *ctx, - secp256k1_pubkey *out, - const secp256k1_pubkey * const *ins, - size_t n -); -extern int secp256k1_tagged_sha256( - const secp256k1_context *ctx, - unsigned char *hash32, - const unsigned char *tag, - size_t taglen, - const unsigned char *msg, - size_t msglen -); -""" - -EXTRAKEYS_DEFINITIONS = """ -typedef struct { - unsigned char data[64]; -} secp256k1_xonly_pubkey; -typedef struct { - unsigned char data[96]; -} secp256k1_keypair; -extern int secp256k1_xonly_pubkey_parse( - const secp256k1_context *ctx, - secp256k1_xonly_pubkey *pubkey, - const unsigned char *input32 -); -extern int secp256k1_xonly_pubkey_serialize( - const secp256k1_context *ctx, - unsigned char *output32, - const secp256k1_xonly_pubkey *pubkey -); -extern int secp256k1_xonly_pubkey_cmp( - const secp256k1_context *ctx, - const secp256k1_xonly_pubkey *pk1, - const secp256k1_xonly_pubkey *pk2 -); -extern int secp256k1_xonly_pubkey_from_pubkey( - const secp256k1_context *ctx, - secp256k1_xonly_pubkey *xonly_pubkey, - int *pk_parity, - const secp256k1_pubkey *pubkey -); -extern int secp256k1_xonly_pubkey_tweak_add( - const secp256k1_context *ctx, - secp256k1_pubkey *output_pubkey, - const secp256k1_xonly_pubkey *internal_pubkey, - const unsigned char *tweak32 -); -extern int secp256k1_xonly_pubkey_tweak_add_check( - const secp256k1_context *ctx, - const unsigned char *tweaked_pubkey32, - int tweaked_pk_parity, - const secp256k1_xonly_pubkey *internal_pubkey, - const unsigned char *tweak32 -); -extern int secp256k1_keypair_create( - const secp256k1_context *ctx, - secp256k1_keypair *keypair, - const unsigned char *seckey -); -extern int secp256k1_keypair_sec( - const secp256k1_context *ctx, - unsigned char *seckey, - const secp256k1_keypair *keypair -); -extern int secp256k1_keypair_pub( - const secp256k1_context *ctx, - secp256k1_pubkey *pubkey, - const secp256k1_keypair *keypair -); -extern int secp256k1_keypair_xonly_pub( - const secp256k1_context *ctx, - secp256k1_xonly_pubkey *pubkey, - int *pk_parity, - const secp256k1_keypair *keypair -); -extern int secp256k1_keypair_xonly_tweak_add( - const secp256k1_context *ctx, - secp256k1_keypair *keypair, - const unsigned char *tweak32 -); -""" - -RECOVERY_DEFINITIONS = """ -typedef struct { - unsigned char data[65]; -} secp256k1_ecdsa_recoverable_signature; -extern int secp256k1_ecdsa_recoverable_signature_parse_compact( - const secp256k1_context *ctx, - secp256k1_ecdsa_recoverable_signature *sig, - const unsigned char *input64, - int recid -); -extern int secp256k1_ecdsa_recoverable_signature_convert( - const secp256k1_context *ctx, - secp256k1_ecdsa_signature *sig, - const secp256k1_ecdsa_recoverable_signature *sigin -); -extern int secp256k1_ecdsa_recoverable_signature_serialize_compact( - const secp256k1_context *ctx, - unsigned char *output64, - int *recid, - const secp256k1_ecdsa_recoverable_signature *sig -); -extern int secp256k1_ecdsa_sign_recoverable( - const secp256k1_context *ctx, - secp256k1_ecdsa_recoverable_signature *sig, - const unsigned char *msghash32, - const unsigned char *seckey, - secp256k1_nonce_function noncefp, - const void *ndata -); -extern int secp256k1_ecdsa_recover( - const secp256k1_context *ctx, - secp256k1_pubkey *pubkey, - const secp256k1_ecdsa_recoverable_signature *sig, - const unsigned char *msghash32 -); -""" - -SCHNORRSIG_DEFINITIONS = """ -typedef int (*secp256k1_nonce_function_hardened)( - unsigned char *nonce32, - const unsigned char *msg, - size_t msglen, - const unsigned char *key32, - const unsigned char *xonly_pk32, - const unsigned char *algo, - size_t algolen, - void *data -); -extern const secp256k1_nonce_function_hardened secp256k1_nonce_function_bip340; -typedef struct { - unsigned char magic[4]; - secp256k1_nonce_function_hardened noncefp; - void *ndata; -} secp256k1_schnorrsig_extraparams; -extern int secp256k1_schnorrsig_sign32( - const secp256k1_context *ctx, - unsigned char *sig64, - const unsigned char *msg32, - const secp256k1_keypair *keypair, - const unsigned char *aux_rand32 -); -extern int secp256k1_schnorrsig_sign_custom( - const secp256k1_context *ctx, - unsigned char *sig64, - const unsigned char *msg, - size_t msglen, - const secp256k1_keypair *keypair, - secp256k1_schnorrsig_extraparams *extraparams -); -extern int secp256k1_schnorrsig_verify( - const secp256k1_context *ctx, - const unsigned char *sig64, - const unsigned char *msg, - size_t msglen, - const secp256k1_xonly_pubkey *pubkey -); -""" - -ECDH_DEFINITIONS = """ -typedef int (*secp256k1_ecdh_hash_function)( - unsigned char *output, - const unsigned char *x32, - const unsigned char *y32, - void *data -); -extern const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_sha256; -extern const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_default; -extern int secp256k1_ecdh( - const secp256k1_context *ctx, - unsigned char *output, - const secp256k1_pubkey *pubkey, - const unsigned char *seckey, - secp256k1_ecdh_hash_function hashfp, - void *data -); -""" - -ELLSWIFT_DEFINITIONS = """ -typedef int (*secp256k1_ellswift_xdh_hash_function)( - unsigned char *output, - const unsigned char *x32, - const unsigned char *ell_a64, - const unsigned char *ell_b64, - void *data -); -extern const secp256k1_ellswift_xdh_hash_function secp256k1_ellswift_xdh_hash_function_prefix; -extern const secp256k1_ellswift_xdh_hash_function secp256k1_ellswift_xdh_hash_function_bip324; -extern int secp256k1_ellswift_encode( - const secp256k1_context *ctx, - unsigned char *ell64, - const secp256k1_pubkey *pubkey, - const unsigned char *rnd32 -); -extern int secp256k1_ellswift_decode( - const secp256k1_context *ctx, - secp256k1_pubkey *pubkey, - const unsigned char *ell64 -); -extern int secp256k1_ellswift_create( - const secp256k1_context *ctx, - unsigned char *ell64, - const unsigned char *seckey32, - const unsigned char *auxrnd32 -); -extern int secp256k1_ellswift_xdh( - const secp256k1_context *ctx, - unsigned char *output, - const unsigned char *ell_a64, - const unsigned char *ell_b64, - const unsigned char *seckey32, - int party, - secp256k1_ellswift_xdh_hash_function hashfp, - void *data -); -""" - -PREALLOCATED_DEFINITIONS = """ -extern size_t secp256k1_context_preallocated_size( - unsigned int flags -); -extern secp256k1_context *secp256k1_context_preallocated_create( - void *prealloc, - unsigned int flags -); -extern size_t secp256k1_context_preallocated_clone_size( - const secp256k1_context *ctx -); -extern secp256k1_context *secp256k1_context_preallocated_clone( - const secp256k1_context *ctx, - void *prealloc -); -extern void secp256k1_context_preallocated_destroy( - secp256k1_context *ctx -); -""" - -ffi = FFI() - -ffi.cdef(BASE_DEFINITIONS) -ffi.cdef(EXTRAKEYS_DEFINITIONS) -ffi.cdef(RECOVERY_DEFINITIONS) -ffi.cdef(SCHNORRSIG_DEFINITIONS) -ffi.cdef(ECDH_DEFINITIONS) - -here = os.path.dirname(os.path.abspath(__file__)) -lib = ffi.dlopen(os.path.join(here, 'libsecp256k1.dll')) -