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.
In `build-electrum-git.sh`, the `-w` CLI arg is removed: it was apparently ignored as we are using a .spec file,
and pyinstaller 5.0+ is now raising a hard error (see https://github.com/pyinstaller/pyinstaller/issues/6660).
```
option(s) not allowed:
--console/--nowindowed/--windowed/--noconsole
makespec options not valid when a .spec file is given
```
-----
In ecc_fast.py, we don't sys.exit() anymore as pyinstaller 5.0+ tries to import electrum during the Analysis phase.
see https://github.com/pyinstaller/pyinstaller/pull/6171
```
57912 INFO: Looking for dynamic libraries
1746 INFO: gettext setting initial language to None
1932 ERROR: libsecp256k1 library failed to load. exceptions: [FileNotFoundError("Could not find module 'C:\\python3\\lib\\site-packages\\electrum\\libsecp256k1-2.dll' (or one of its depende
ncies). Try using the full path with constructor syntax."), FileNotFoundError("Could not find module 'C:\\python3\\lib\\site-packages\\electrum\\libsecp256k1-1.dll' (or one of its dependenc
ies). Try using the full path with constructor syntax."), FileNotFoundError("Could not find module 'C:\\python3\\lib\\site-packages\\electrum\\libsecp256k1-0.dll' (or one of its dependencie
s). Try using the full path with constructor syntax."), FileNotFoundError("Could not find module 'libsecp256k1-2.dll' (or one of its dependencies). Try using the full path with constructor
syntax."), FileNotFoundError("Could not find module 'libsecp256k1-1.dll' (or one of its dependencies). Try using the full path with constructor syntax."), FileNotFoundError("Could not find
module 'libsecp256k1-0.dll' (or one of its dependencies). Try using the full path with constructor syntax.")]
Traceback (most recent call last):
File "C:\python3\lib\runpy.py", line 196, in _run_module_as_main
return _run_code(code, main_globals, None,
File "C:\python3\lib\runpy.py", line 86, in _run_code
exec(code, run_globals)
File "C:\python3\scripts\pyinstaller.exe\__main__.py", line 7, in <module>
File "C:\python3\lib\site-packages\PyInstaller\__main__.py", line 194, in _console_script_run
run()
File "C:\python3\lib\site-packages\PyInstaller\__main__.py", line 180, in run
run_build(pyi_config, spec_file, **vars(args))
File "C:\python3\lib\site-packages\PyInstaller\__main__.py", line 61, in run_build
PyInstaller.building.build_main.main(pyi_config, spec_file, **kwargs)
File "C:\python3\lib\site-packages\PyInstaller\building\build_main.py", line 1006, in main
build(specfile, distpath, workpath, clean_build)
File "C:\python3\lib\site-packages\PyInstaller\building\build_main.py", line 928, in build
exec(code, spec_namespace)
File "deterministic.spec", line 55, in <module>
a = Analysis([home+'run_electrum',
File "C:\python3\lib\site-packages\PyInstaller\building\build_main.py", line 428, in __init__
self.__postinit__()
File "C:\python3\lib\site-packages\PyInstaller\building\datastruct.py", line 184, in __postinit__
self.assemble()
File "C:\python3\lib\site-packages\PyInstaller\building\build_main.py", line 736, in assemble
isolated.call(find_binary_dependencies, self.binaries, self.binding_redirects, collected_packages)
File "C:\python3\lib\site-packages\PyInstaller\isolated\_parent.py", line 372, in call
return isolated.call(function, *args, **kwargs)
File "C:\python3\lib\site-packages\PyInstaller\isolated\_parent.py", line 302, in call
raise RuntimeError(f"Child process call to {function.__name__}() failed with:\n" + output)
RuntimeError: Child process call to find_binary_dependencies() failed with:
File "C:\python3\lib\site-packages\PyInstaller\isolated\_child.py", line 63, in run_next_command
output = function(*args, **kwargs)
File "C:\python3\lib\site-packages\PyInstaller\building\build_main.py", line 177, in find_binary_dependencies
__import__(package)
File "C:\python3\lib\site-packages\electrum\__init__.py", line 20, in <module>
from .wallet import Wallet
File "C:\python3\lib\site-packages\electrum\wallet.py", line 53, in <module>
from .bip32 import BIP32Node, convert_bip32_intpath_to_strpath, convert_bip32_strpath_to_intpath
File "C:\python3\lib\site-packages\electrum\bip32.py", line 11, in <module>
from . import constants
File "C:\python3\lib\site-packages\electrum\constants.py", line 30, in <module>
from . import bitcoin
File "C:\python3\lib\site-packages\electrum\bitcoin.py", line 35, in <module>
from . import ecc
File "C:\python3\lib\site-packages\electrum\ecc.py", line 39, in <module>
from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED
File "C:\python3\lib\site-packages\electrum\ecc_fast.py", line 151, in <module>
sys.exit(f"Error: Failed to load libsecp256k1.")
SystemExit: Error: Failed to load libsecp256k1.
🗯 ERROR: build-electrum-git failed
```
Also, the -OO flag is removed from wine python, for similar reasons:
pyinstaller imports electrum, and in electrum/__init__.py, we raise
if -O is used: 9b1fb0e5fe/electrum/__init__.py (L40)
this replaces https://github.com/spesmilo/electrum/pull/8320
see f6bef03c0a/CHANGELOG.md
I am not yet sure how this will look like going forward, but unless there will
be lots of libsecp256k1 releases with ~invisible harmless ABI changes, I think
conceptually this is the right approach.
note: low R grinding would not have to be duplicated if we trusted the caller
to have done it already (as is the case with the classes in ecc.py), and if
we propagated the choice of "random_k" as part of the nonce_function passed
to libsecp256k1 (which is not currently done)
time taken to add points changes to around 35% of what it was with python-ecdsa
-----
# benchmark runs before:
> python3.7-64 ..\wspace\201909_libsecp256k1_point_addition\bench.py
time taken: 3.7693 seconds
> python3.7-64 ..\wspace\201909_libsecp256k1_point_addition\bench.py
time taken: 3.8123 seconds
> python3.7-64 ..\wspace\201909_libsecp256k1_point_addition\bench.py
time taken: 3.7937 seconds
# benchmark runs after:
> python3.7-64 ..\wspace\201909_libsecp256k1_point_addition\bench.py
time taken: 1.3127 seconds
> python3.7-64 ..\wspace\201909_libsecp256k1_point_addition\bench.py
time taken: 1.3000 seconds
> python3.7-64 ..\wspace\201909_libsecp256k1_point_addition\bench.py
time taken: 1.3128 seconds
-----
# benchmark script:
import os
import time
from electrum.ecc import generator
from electrum.crypto import sha256
rand_bytes = os.urandom(32)
#rand_bytes = bytes.fromhex('d3d88983b91ee6dfd546ccf89b9a1ffb23b01bf2eef322c2808cb3d951a3c116')
point_pairs = []
for i in range(30000):
rand_bytes = sha256(rand_bytes)
rand_int = int.from_bytes(rand_bytes, "big")
a = generator() * rand_int
rand_bytes = sha256(rand_bytes)
rand_int = int.from_bytes(rand_bytes, "big")
b = generator() * rand_int
point_pairs.append((a,b))
t0 = time.time()
for a, b in point_pairs:
c = a + b
t = time.time() - t0
print(f"time taken: {t:.4f} seconds")