Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Add fallback bundled libsecp256k1
This commit adds the ability to install this wrapper without the need to have a prior installation of libsecp256k1 on one's system. This works as follows: - During 'sdist': If the directory 'libsecp256k1' doesn't exist in the source directory it is downloaded from the location specified by the `LIB_TARBALL_URL` constant in setup.py and extracted into a directory called 'libsecp256k1' To upgrade to a newer version of the bundled libsecp256k1 source simply delete the 'libsecp256k1' directory and update the `LIB_TARBALL_URL` to point to a newer commit. - During 'install': If an existing (system) installation of libsecp256k1 is found (either in the default library locations or in the location pointed to by the environment variable `LIB_DIR`) it is used as before. Due to the way the way cffi modules are implemented it is necessary to perform this detection in the cffi build module '_cffi_build/build.py' as well as in 'setup.py'. For that reason some utility functions have been moved into a 'setup_support.py' module which is imported from both. If however no existing installation can be found the bundled source code is used to build a library locally that will be statically linked into the CFFI extension. By default only the 'recovery' module will be enabled in this bundled version as it is the only one not considered to be 'experimental' by the libsecp256k1 authors. It is possible to override this and enable all modules by setting the environment variable `SECP_BUNDLED_EXPERIMENTAL`. Additionally there are some small other optimizations: - The source code has been moved into a package so that site-packages top-level doesn't get cluttered with the cffi generated source files. - The cffi build script has been moved to a '_cffi_build' directory since it is not needed at runtime. - The C-definitions used by cffi have been extracted into their own files inside the '_cffi_build' directory to make the build script more readable.
- Loading branch information
Showing
18 changed files
with
861 additions
and
371 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,4 @@ _libsecp256k1* | |
.cache/ | ||
*.egg | ||
htmlcov | ||
libsecp256k1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
include setup_support.py | ||
recursive-include _cffi_build *.py *.h | ||
graft libsecp256k1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import os | ||
import sys | ||
from collections import namedtuple | ||
from itertools import combinations | ||
|
||
from cffi import FFI, VerificationError | ||
|
||
sys.path.append(os.path.abspath(os.path.dirname(__file__))) | ||
from setup_support import has_system_lib, redirect, workdir, absolute | ||
|
||
Source = namedtuple('Source', ('h', 'include')) | ||
|
||
|
||
class Break(Exception): | ||
pass | ||
|
||
|
||
def _mk_ffi(sources, name="_libsecp256k1", bundled=True, **kwargs): | ||
ffi = FFI() | ||
code = [] | ||
if 'INCLUDE_DIR' in os.environ: | ||
kwargs['include_dirs'] = [absolute(os.environ['INCLUDE_DIR'])] | ||
if 'LIB_DIR' in os.environ: | ||
kwargs['library_dirs'] = [absolute(os.environ['LIB_DIR'])] | ||
for source in sources: | ||
with open(source.h, 'rt') as h: | ||
ffi.cdef(h.read()) | ||
code.append(source.include) | ||
if bundled: | ||
code.append("#define PY_USE_BUNDLED") | ||
ffi.set_source(name, "\n".join(code), **kwargs) | ||
return ffi | ||
|
||
|
||
_base = [Source(absolute("_cffi_build/secp256k1.h"), "#include <secp256k1.h>", )] | ||
|
||
_modules = { | ||
'ecdh': Source(absolute("_cffi_build/secp256k1_ecdh.h"), "#include <secp256k1_ecdh.h>", ), | ||
'recovery': Source(absolute("_cffi_build/secp256k1_recovery.h"), "#include <secp256k1_recovery.h>", ), | ||
'schnorr': Source(absolute("_cffi_build/secp256k1_schnorr.h"), "#include <secp256k1_schnorr.h>", ), | ||
} | ||
|
||
|
||
ffi = None | ||
|
||
# The following is used to detect whether the library is already installed on | ||
# the system (and if so which modules are enabled) or if we will use the | ||
# bundled one. | ||
if has_system_lib(): | ||
_available = [] | ||
try: | ||
# try all combinations of optional modules that could be enabled | ||
# works downwards from most enabled modules to fewest | ||
for l in range(len(_modules), -1, -1): | ||
for combination in combinations(_modules.items(), l): | ||
try: | ||
_test_ffi = _mk_ffi( | ||
_base + [item[1] for item in combination], | ||
name="_testcompile", | ||
bundled=False, | ||
libraries=['secp256k1'] | ||
) | ||
with redirect(sys.stderr, os.devnull), workdir(): | ||
_test_ffi.compile() | ||
_available = combination | ||
raise Break() | ||
except VerificationError as ex: | ||
pass | ||
except Break: | ||
ffi = _mk_ffi( | ||
_base + [i[1] for i in _available], | ||
bundled=False, | ||
libraries=['secp256k1'] | ||
) | ||
print("Using system libsecp256k1 with modules: {}".format( | ||
", ".join(i[0] for i in _available)) | ||
) | ||
else: | ||
# We didn't find any functioning combination of modules | ||
# Normally this shouldn't happen but just in case we will fall back | ||
# to the bundled library | ||
print("Installed libsecp256k1 is unusable falling back to bundled version.") | ||
|
||
if ffi is None: | ||
# Library is not installed - use bundled one | ||
print("Using bundled libsecp256k1") | ||
|
||
# By default we only build with recovery enabled since the other modules | ||
# are experimental | ||
if os.environ.get('SECP_BUNDLED_EXPERIMENTAL'): | ||
ffi = _mk_ffi(_base + list(_modules.values()), libraries=['secp256k1']) | ||
else: | ||
ffi = _mk_ffi(_base + [_modules['recovery']], libraries=['secp256k1']) |
Oops, something went wrong.