Compare commits

..

859 Commits

Author SHA1 Message Date
Vivek Teega
5d39cce124
Merge pull request #10 from bitspill/master
flo basic trezor support
2019-03-02 01:14:10 +05:30
Jeremiah Buddenhagen
5975c105d6 flo basic trezor support 2019-02-22 13:43:37 -08:00
Vivek Teega
9ab1aab6f8 Upstream Merge 2019-02-16 10:43:21 +05:30
SomberNight
0de954546a
test python version in main script
for better error text on old python

related: #5008, #5129
2019-02-15 22:06:10 +01:00
SomberNight
92bf409bf0
scripts: add "quick_start" to showcase some basic functionality 2019-02-15 21:14:08 +01:00
ThomasV
25a460f855 Merge branch 'network_walk' 2019-02-15 17:37:59 +01:00
ThomasV
52520490c5 improve network send_multiple_requests 2019-02-15 17:37:15 +01:00
SomberNight
5313591c28
synchronizer: disconnect from server if cannot deserialize txn 2019-02-15 17:22:24 +01:00
SomberNight
7b8114f865
synchronizer: allow server not finding txn sometimes
User has wallet file with history that includes some txid; corresponding
raw tx is not in the "transactions" dict in the file however.
When the synchronizer starts up, it requests this "missing" txn from
the server... but what if the server does not know about it?
Maybe it was reorged and is not in the new best chain,
and not even in mempool. This was not handled previously.

fix #5122
2019-02-14 20:54:55 +01:00
ThomasV
f60d1e7cb6 fix variable name in contrib/sign_version 2019-02-13 23:25:58 +01:00
ThomasV
c725c05947 update make_download 2019-02-13 23:19:34 +01:00
ThomasV
87c596fa1d prepare release 3.3.4 2019-02-13 22:23:30 +01:00
ThomasV
0e78e15fa4 update locale submodule 2019-02-13 22:20:56 +01:00
ThomasV
4e9dd679ab add contrib/sign_version 2019-02-13 21:46:48 +01:00
SomberNight
2867c2ef7a
update release notes 2019-02-13 20:23:54 +01:00
SomberNight
ec86850a2e
trezor: fix minor error handling issue
AttributeError: 'TrezorFailure' object has no attribute 'message'
2019-02-13 18:33:48 +01:00
SomberNight
e2eb051eed
keystore bip39: minor clean-up 2019-02-13 15:03:32 +01:00
ghost43
c8562f5362
network: reintroduce network.debug (#5093)
network.debug and interface.debug were removed during the asyncio-aiorpcx
network-rewrite.
2019-02-12 20:23:43 +01:00
SomberNight
086372f68a
wallet get_full_history: add from/to_height info to summary 2019-02-12 19:38:15 +01:00
SomberNight
019884a98b
network: follow-up 38ab7ee554 2019-02-12 19:23:58 +01:00
SomberNight
2174fc0676
cli history: add option to filter by block height 2019-02-12 18:38:35 +01:00
SomberNight
38ab7ee554
network: catch untrusted exceptions from server in public methods
and re-raise a wrapper exception (that retains the original exc in a field)

closes #5111
2019-02-12 17:02:15 +01:00
SomberNight
fd62ba874b
kivy: rm dead code 2019-02-11 20:22:03 +01:00
SomberNight
026448837f
no more "import *"
fixes #5101
fixes #5105
2019-02-11 20:21:24 +01:00
SomberNight
8072ad1ad9
network broadcast_transaction: make error text clearer 2019-02-11 16:36:01 +01:00
SomberNight
ebeed4736f
qt utxo_list: show full prevout_n in outpoint column
previously, if prevout_n was >=10, the ":" char or even digits were cut
2019-02-10 21:20:44 +01:00
SomberNight
c23b869d3c
qt MyTreeView subclasses: change "headers" from list to dict 2019-02-10 21:13:53 +01:00
SomberNight
5aafcb2875
qt MyTreeView subclasses: use IntEnum for columns 2019-02-10 21:00:08 +01:00
SomberNight
cd097d6bb8
qt history list: update_tx_mined_status was not updating 'date' for tx
fixes #5096
2019-02-10 06:36:58 +01:00
ThomasV
eb96d422f7 import version module 2019-02-09 12:15:46 +01:00
SomberNight
d78537c8c4
rerun freeze_packages 2019-02-08 19:19:02 +01:00
SomberNight
df9087970b
update block header checkpoints 2019-02-08 16:38:59 +01:00
SomberNight
699562c78d
bump libsecp256k1 version 2019-02-08 16:17:52 +01:00
ThomasV
01eaf0fe4e
Merge pull request #5052 from JeremyRand/utxolist-for-loop
Refactor for loop in UTXOList
2019-02-08 15:53:28 +01:00
ThomasV
b06b8753e6 fix #5088 2019-02-08 12:59:06 +01:00
ThomasV
1da1f0bfea fix #4984 2019-02-08 11:17:48 +01:00
ThomasV
8f4967f7d0 qt wizard: select_storage 2019-02-08 09:10:07 +01:00
ThomasV
beb9f63274 follow-up prev 2019-02-08 08:27:23 +01:00
ThomasV
58c2c15266 follow up 6fb974227b 2019-02-08 08:21:18 +01:00
SomberNight
fc72e661de
requirements: set min version for aiohttp 2019-02-08 02:24:44 +01:00
SomberNight
89bb49e117
mac build: install pinned pip and setuptools earlier
also add --no-use-pep517 option for pyinstaller (see 4b560250a6)
2019-02-07 20:19:07 +01:00
SomberNight
2c71b9da0c
icons: instead of symlinks, just mv "icons" dir
symlinks are really inconvenient on Windows
(when running from cloned source)

follow-up #5055
2019-02-07 20:01:52 +01:00
SomberNight
9beabc0311
fix prev: run make_locale before building android apk 2019-02-07 17:57:15 +01:00
SomberNight
ba08b2279d
kivy build: test and document that make_locale is to be run first 2019-02-07 16:45:09 +01:00
ThomasV
6fb974227b fix #5082 2019-02-07 13:56:11 +01:00
ThomasV
6ade5903dc network: fix send_multiple_requests 2019-02-07 13:30:14 +01:00
SomberNight
7bb3e5336a
trezor: PIN could not be disabled
fixes #5078
2019-02-06 15:50:57 +01:00
ThomasV
4ed8787433 remove 'util.py' from scripts 2019-02-05 20:33:50 +01:00
ThomasV
8f2a730b3b add more details values to history 2019-02-05 18:27:01 +01:00
SomberNight
d6986347e6
qt icons: update remaining QIcon() constructors
follow-up #5053
2019-02-05 00:59:09 +01:00
SomberNight
a5ddb42bfd
win/mac binaries: fix qt icons
follow-up #5055
2019-02-04 22:34:59 +01:00
SomberNight
2de7fd5466
wine build: small clean-up in prepare-wine.sh 2019-02-04 20:27:04 +01:00
SomberNight
001b815c18
wine build: upgrade wine, nsis, python
wine-specific hack no longer needed with new wine version
2019-02-04 20:27:04 +01:00
SomberNight
5a1778b7fe
start using util.resource_path 2019-02-04 20:27:03 +01:00
SomberNight
67d080b34a
mv qt update checker to its own file 2019-02-04 20:27:03 +01:00
SomberNight
6926b8b2d4
qt update checker: handle --offline 2019-02-04 20:27:02 +01:00
SomberNight
68cd37282e
qt: set default "window icon" (only visible on Windows) 2019-02-04 20:27:02 +01:00
SomberNight
9e58d56e6d
qt qrwindow: rm dead code 2019-02-04 20:27:01 +01:00
SomberNight
8412b53ed5
wizard: copy/restore storage when stepping through the wizard
When interacting with wizard, there is a single shared storage instance.
If you go down the tree of dialogs, press "back" a couple times, go
down another branch of dialogs, etc, there are side-effects on storage,
which are never undone.

fixes #5057
fixes #4496
2019-02-04 20:27:01 +01:00
SomberNight
9013f6d59e
wizard: make 'stack' private 2019-02-04 20:27:00 +01:00
SomberNight
bc2a421d87
network: clean up broadcast_transaction
Handle all exceptions in network.py, instead of in gui code.
Send some exceptions to crash reporter; previously gui code
would suppress them.
2019-02-04 20:27:00 +01:00
ThomasV
76ff2f53c5
Merge pull request #5042 from SomberNight/appimage
binaries for Linux: AppImage
2019-02-04 19:16:15 +01:00
SomberNight
66de511828
travis: build appimage for linux 2019-02-03 23:45:34 +01:00
SomberNight
a754f9fe10
initial commit for building AppImages for Linux x86_64 2019-02-03 23:45:30 +01:00
SomberNight
47b07f19b9
build: factor out some utilities to build_tools_util.sh 2019-02-03 23:44:34 +01:00
SomberNight
ca931f476f
fix android build: pin buildozer and pin kivy.
old p4a did not work with new buildozer. kivy master crashes.
kivy latest release has runtime issues (orientation was landscape).
these versions seem to work.
also updated dockerfile to more closely match p4a master.
2019-02-03 23:40:49 +01:00
SomberNight
43487910c7
qt network dialog: use intenum for columns 2019-02-03 20:04:33 +01:00
ThomasV
905e271db9 remove phishing server 2019-02-03 12:28:59 +01:00
SomberNight
52d602b6c1
network: fix get_servers to not modify default list 2019-02-02 20:10:12 +01:00
SomberNight
add3b36f32
build: replace remaining "python setup.py install" with "pip install" 2019-02-02 08:07:48 +01:00
ThomasV
819c3b81e3
Merge pull request #5055 from SomberNight/fix_qt_icons_when_pkg_installed
fix: qt icons not available when installed as python package
2019-02-02 06:26:47 +01:00
SomberNight
4fa87d8595
fix: qt icons not available when installed as python package
follow-up #5053
2019-02-01 23:32:24 +01:00
SomberNight
7ea01e9e91
qt inline icons: change mouse-over cursor 2019-02-01 21:57:18 +01:00
ghost43
7266ecc2b8
contrib/make_tgz: small improvements. (#5040) 2019-02-01 21:20:45 +01:00
ThomasV
f846d1d59a
Merge pull request #5039 from SomberNight/tx_version_bump_to_2
transaction: change default version to 2
2019-02-01 20:48:18 +01:00
ThomasV
f05aabd802
Merge pull request #5053 from SomberNight/qt_icons_file_rm
rm qt icons file
2019-02-01 20:47:16 +01:00
SomberNight
16bac5fd73
rm qt icons file
so we don't need pyrcc5, which is not deterministic,
and so we don't need the submodule for the icons

based on electrumsv/electrumsv@bf8802c2ea
2019-02-01 20:15:28 +01:00
SomberNight
185d02d9df
delete snap file
we don't distribute snaps; not sure if the file is useful for anyone
it's already not working because it references python 3.5
2019-02-01 19:46:20 +01:00
SomberNight
3ad6f738bd
util: rm hfu, cleaner bh2u 2019-02-01 19:02:02 +01:00
JeremyRand
8716bc8cfb
Refactor for loop in UTXOList
This refactor makes UTXOList somewhat easier to subclass.
2019-02-01 06:42:57 +00:00
SomberNight
7f3de8241c
qt/hww: temporarily bundle our own version of safetlib.qt.pinmatrix
until safetlib releases a new version that includes b1eab3dba4

closes #4960
2019-01-31 20:58:04 +01:00
SomberNight
e7d3fd32fb
contrib/freeze_packages.sh: should hard fail if there is an error
exceptions raised by find_restricted_dependencies.py were getting ignored
2019-01-31 19:44:04 +01:00
SomberNight
3ca1b710d6
build: use sha256sum instead of md5sum 2019-01-31 17:01:00 +01:00
SomberNight
d4967faf28
wine build: pin wine signing key. minor refactoring. 2019-01-31 16:37:27 +01:00
Vivek Teega
8310195453 Update authors & uncomment scrypt checking 2019-01-31 21:02:37 +05:30
Vivek Teega
cdaf874228 Fixing exchanges error 2019-01-31 20:30:57 +05:30
Vivek Teega
1a111b98d4 Minor fixes 2019-01-31 18:50:02 +05:30
SomberNight
c399693049
qt contact list: context menu fixups
fixes #5048
fixes #5049

follow-up 9cff42328d
2019-01-31 12:13:31 +01:00
SomberNight
9bbea9bf2f
wallet: implement wait_for_address_history_to_change API 2019-01-30 21:30:25 +01:00
Vivek Teega
0cd2f6e170 Merge remote-tracking branch 'upstream/master' into upstreamMerge 2019-01-31 00:41:06 +05:30
SomberNight
55e6830cfc
android build: update pinned python-for-android. use newer google NDK.
fixes #5045
2019-01-30 19:30:36 +01:00
Vivek Teega
e89ec0d1d5 GUI and unique identification for packaging 2019-01-30 13:38:16 +00:00
SomberNight
0f0cee422e
trezor and clones: sign tx version too 2019-01-30 12:03:52 +01:00
SomberNight
27299092df
hardware cmdline handler: print messages to stderr (take 2)
follow-up 5613f9b903

button_request should not call show_error as error dialogs in Qt block
the GUI thread.
2019-01-29 17:25:02 +01:00
ThomasV
0429fe5960
Merge pull request #5031 from spesmilo/daemon_error_forwarding
daemon: forward TypeError trace to client
2019-01-29 09:37:30 +01:00
Vivek Teega
eb418f1d3e Scrypt verification 2019-01-29 04:57:29 +00:00
Janus
2737744bfe daemon: forward TypeError trace to client 2019-01-29 02:07:10 +01:00
Vivek Teega
e28b8806ac Changes to FLO data scrollview and color of 'blocks' field 2019-01-28 18:35:47 +00:00
SomberNight
4c6379a936
linux sdist: try to exclude some more garbage files from release tarball 2019-01-28 18:15:05 +01:00
SomberNight
dcd3d6c3d1
linux sdist: rm dead file from MANIFEST.in 2019-01-28 17:36:47 +01:00
SomberNight
15bc097f55
gitignore: update old path 2019-01-28 17:35:52 +01:00
SomberNight
501e725a47
kivy readme: note about running on linux desktop
closes #5037
2019-01-28 15:26:46 +01:00
SomberNight
d820f9ad37
transaction: change default version to 2 2019-01-28 15:11:03 +01:00
duckartes
f9b0c66843 Update: README.rst (#5036)
Correct filename paths under Creating Binaries.
2019-01-27 18:15:37 +01:00
SomberNight
31c08db909
qt update notifications: make url clickable; prevent multiple dialogs 2019-01-27 14:42:37 +01:00
Vivek Teega
501371c001 FLO data input widget behavior 2019-01-27 18:50:07 +05:30
ThomasV
138c98d7d8 add 'get' command to CLI 2019-01-26 16:50:51 +01:00
SomberNight
53310690a5
version notifications: sig check would always fail on testnet 2019-01-26 15:30:30 +01:00
Vivek Teega
94b7eb3015 Fix exchanges and GUI quirks 2019-01-26 16:39:54 +05:30
Vivek Teega
1e1caf602d Send tab GUI change 2019-01-26 13:04:01 +05:30
SomberNight
b085d7cc59
document linux release process
closes #5030
2019-01-25 19:14:28 +01:00
ThomasV
0f0bdfe7f0 update release notes 2019-01-25 19:09:29 +01:00
ThomasV
c4774d7a8e update submodules 2019-01-25 18:46:29 +01:00
SomberNight
0bfda7c8c7 validate version update announcements using "bitcoin address" message signing 2019-01-25 18:16:56 +01:00
Johann Bauer
34c99c3b36 [Qt] Add optional update notifications 2019-01-25 18:16:32 +01:00
ThomasV
5613f9b903 hardware cmdline handler: print messages to stderr 2019-01-25 18:16:03 +01:00
SomberNight
4b560250a6
fix wine build: pyinstaller failed to install with new pip
see pypa/pip#6163
2019-01-25 16:46:17 +01:00
ThomasV
4be4444d6b prepare release 3.3.3 2019-01-25 12:06:19 +01:00
Vivek Teega
ed1421da8e Fix android startup 2019-01-25 14:30:10 +05:30
Vivek Teega
8f6a1880e9 Fix GUI 2019-01-25 11:50:32 +05:30
sajolida
889d133abd Give visual feedback while starting (#4997)
See https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
2019-01-25 02:33:54 +01:00
SomberNight
5f05469852
rerun freeze_packages
closes #4993
closes #5006
2019-01-25 02:06:30 +01:00
ThomasV
54eb89ccaf
Merge pull request #5026 from JeremyRand/utxolist-headers
Refactor UTXOList headers into class attribute
2019-01-24 13:47:58 +01:00
Vivek Teega
2cda8b266f Merge upstream 2019-01-24 14:00:37 +05:30
JeremyRand
b3d8a81e15
Refactor UTXOList headers into class attribute
This makes it more straightforward to subclass UTXOList.
2019-01-24 00:09:36 +00:00
ThomasV
920d4c2b27 simplify qr window 2019-01-23 17:17:13 +01:00
ThomasV
f994cd4a5d draw qrcode with fixed framesize 2019-01-23 16:56:29 +01:00
SomberNight
f2ad116b0b
wizard: better hww debug messages when unpaired_device_infos fails
[DeviceMgr] scanning devices...
[DeviceMgr] failed to create client for ledger at b'0002:0007:00': OSError('open failed',)
[DeviceMgr] error getting device infos for ledger: open failed

^ GUI did not contain any info about failure
2019-01-21 18:44:36 +01:00
SomberNight
5fc715cdee
commands: add convert_xkey for converting between {x,y,z}|{pub,prv} 2019-01-20 15:49:42 +01:00
SomberNight
9cff42328d
qt contact list: fix copying address, sort order
closes #5015
2019-01-19 23:11:21 +01:00
SomberNight
e39e2ed8f1
fix typo
follow-up #5011
closes #5014
2019-01-19 17:50:22 +01:00
ThomasV
c995d5364e
Merge pull request #5011 from SomberNight/tx_broadcast_sanitize_response
network: sanitize tx broadcast response
2019-01-19 11:27:48 +01:00
SomberNight
5403ae7687
network: sanitize tx broadcast response 2019-01-18 20:25:21 +01:00
SomberNight
7ffd928e80
wallet: add comment 2019-01-17 17:19:08 +01:00
SomberNight
d77e4d8f5d
exception formatting: use repr(e) instead of str(e) in messages
repr(e) is more useful
2019-01-17 17:16:19 +01:00
SomberNight
44a2ceab3c
qt history list: fix minor sorting issue
closes #4989
2019-01-17 17:09:22 +01:00
ThomasV
4e10f9e301
Merge pull request #5007 from cculianu/fix_codesign_electrum
[MacOS] Fixed code signing on macos to code sign *all* embedded binaries
2019-01-17 16:22:31 +01:00
SomberNight
c7f3adb67e
trezor: fix minor string formatting re translations
closes #4996
2019-01-16 19:11:04 +01:00
ghost43
dc19cf1fa1
wallet: randomise locktime of transactions a bit. also check if stale. (#4967) 2019-01-16 18:51:59 +01:00
chris-belcher
d5c8a0e0d0 Add flag --skipmerklecheck (#4957)
The --skipmerklecheck optional flag makes Electrum tolerate invalid
merkle proofs from the server. This is useful for building Electrum
servers that need a minimum amount of storage, though of course users
should only enable it if they completely trust the connected server.
2019-01-16 18:48:10 +01:00
Calin Culianu
5ec330680e [MacOS] Fixed code signing on macos to codesign all embdedded binaries
This was pulled from Electron Cash #1110
2019-01-16 10:58:44 +02:00
Vivek Teega
f66ab7ce57 Adding scrypt modules and their SHA256 hash in build requirements 2019-01-15 07:57:01 +05:30
Johann Bauer
0caf8e30cd Revealer: Fix typo 2019-01-12 22:06:47 +01:00
Johann Bauer
019566b383 Change string formatting to improve translation
For example, "Hide Console" would be "Konsole anzeigen" in German.
Currently, translators can only show "Anzeigen Konsole" which doesn't
make much sense.
2019-01-12 21:58:21 +01:00
Romano
c60583293a [Docker] fix gnupg2 and dirmngr 2019-01-12 21:32:52 +01:00
Vivek Teega
89cc1efc41 Changing default data directory and testing scrypt packages 2019-01-12 18:15:06 +05:30
Johann Bauer
424430723b
[Revealer] Fix spelling mistake 2019-01-08 17:22:53 +01:00
SomberNight
2f789468ea
requirements: lower bound for btchip-python 2019-01-07 11:00:50 +01:00
Tom Kneiphof
8fd84f77c7 Fix ledger transaction version (#4991) 2019-01-07 10:49:10 +01:00
SomberNight
4d0030363b
interface: catch more SOCKS exceptions 2019-01-04 11:00:48 +01:00
SomberNight
fd5ad9ac70
qt network dialog: detect Tor proxy dynamically
keep thread running to detect changes
2019-01-04 10:58:59 +01:00
SomberNight
192ec8596d
trezor: fix matrix recovery
closes #4983
2019-01-04 10:15:26 +01:00
Vivek Teega
8702ed096b Merge remote-tracking branch 'upstream/master' into upstreamMerge 2019-01-03 18:46:09 +05:30
Johann Bauer
bde655ae00 Qt: Show pointer cursor for status bar buttons 2019-01-01 20:39:27 +01:00
ThomasV
549d71fb0b complete release notes 2018-12-28 13:43:18 +01:00
Vivek Teega
091b8bc73f Fixed upstreamMerge problems 2018-12-26 12:16:14 +05:30
SomberNight
bd1f7b539e
qt: don't import PyQt5.Qt
related #4960
2018-12-26 03:58:50 +01:00
Vivek Teega
cd8be3f586 Pulling upstream changes 2018-12-25 13:47:12 +05:30
Vivek Teega
3c0f05ae93 Replacing scrypt module with hashlib standard library 2018-12-25 13:01:18 +05:30
SomberNight
ccec45a564
qt: fix address list context menu race
closes #4961
2018-12-24 19:03:10 +01:00
SomberNight
0bce96d2de
qt crash report: fix formatting
follow-up 5dc240d4ed
2018-12-24 18:52:03 +01:00
ThomasV
5469e3668e fix #4958 2018-12-23 10:02:19 +01:00
SomberNight
dac5af8eca
trustedcoin: friendlier error msg for invalid otp when signing 2018-12-22 09:06:30 +01:00
ThomasV
30845ee776 prepare release 3.2.2 2018-12-21 22:41:12 +01:00
ThomasV
649f8414a9 update icons submodule 2018-12-21 22:32:57 +01:00
Johann Bauer
d0a1bae68f [Qt] Fix wrong orientation of finger in clock icons
Closes: #4954
2018-12-21 21:57:23 +01:00
SomberNight
5dc240d4ed
qt: show_message and friends display plaintext by default 2018-12-21 20:46:47 +01:00
SomberNight
5248613e9d
gui: prepend broadcast_transaction errors with explanatory message 2018-12-21 20:44:38 +01:00
SomberNight
b491a30dd9
kivy network dialog: update server and proxy properly
Previously "proxy" would only get updated when closing and reopening
the network dialog. "server" would only get updated after successful
connection establishment to specified server.
2018-12-21 16:59:43 +01:00
Vivek Teega
300241428c Sync Upstream
Pulling upstream changes in Electrum BTC and integrating them for FLO
2018-12-21 00:52:07 +05:30
SomberNight
1d303fa9d2
win build: rm win_inet_pton
was needed by PySocks; and we no longer use PySocks
also, it seems the functionality it provided is now part of Python stdlib since 3.4
https://docs.python.org/3/library/socket.html#socket.inet_pton

related: #2358
2018-12-20 18:06:16 +01:00
SomberNight
744bfc1eeb
util.profiler: simplify
follow-up 6192bfce46
closes #4904
2018-12-20 17:09:58 +01:00
ghost43
7773443c17
network: put NetworkTimeout constants together (#4945)
* network: put NetworkTimeout constants together

* fix prev
2018-12-20 16:49:17 +01:00
SomberNight
43461a1866
qt history: fix exporting history
closes #4948
2018-12-20 16:46:58 +01:00
ThomasV
85b712967f prepare release 3.3.1 2018-12-20 13:24:35 +01:00
ThomasV
b1b6b250d1 kivy: do not request PIN for watching-only wallets 2018-12-20 13:23:46 +01:00
ThomasV
2e078493a7 kivy: improve context menu 2018-12-20 12:43:31 +01:00
ThomasV
96b66b7e4f kivy: use on_state instead of on_release 2018-12-20 12:19:54 +01:00
ThomasV
58a9fa0ad5 kivy: use default scroll_distance and scroll_timeout 2018-12-20 11:32:01 +01:00
ghost43
8e5331e5b2
Merge pull request #4932 from SomberNight/revealer_cleanup_20181215
revealer: clean-up, allow restoring v0
2018-12-20 04:24:21 +01:00
SomberNight
caae9f8a6a
revealer: warning re version 0 now includes URL 2018-12-20 04:21:40 +01:00
SomberNight
1b7672f70e
qt: fix invoices tab
closes #4941
2018-12-20 01:09:16 +01:00
SomberNight
fc18912ecd
release notes: mention 2fa, shorten qt 2018-12-19 21:24:38 +01:00
SomberNight
4225e79456
win build: wine upstream gpg key weirdness 2018-12-19 21:22:41 +01:00
ThomasV
df15571b82 osx build: revert to python 3.6.4 2018-12-19 18:37:44 +01:00
ThomasV
383b517405 update submodule (follow-up prev commit) 2018-12-19 18:35:15 +01:00
ThomasV
1ee8d6a380 Merge branch 'master' of github.com:spesmilo/electrum 2018-12-19 18:28:53 +01:00
ThomasV
b2d635060c update submodule 2018-12-19 18:28:34 +01:00
SomberNight
d5591da682
qt history: consider column is hidden in context menu 2018-12-19 17:01:20 +01:00
SomberNight
f0f73380a2
qt history: fix refresh bug ("verified"/fee histogram interplay) 2018-12-19 16:47:26 +01:00
ThomasV
0247802479 update submodules 2018-12-19 13:29:50 +01:00
ThomasV
4360d07918
Merge pull request #4938 from SomberNight/mac_build_qr_scanner_old_xcode
mac: build qr scanner using old xcode
2018-12-19 12:03:41 +01:00
SomberNight
ba33bc4ad8
plugins: fix hook/attr name collision in close()
Revealer plugin has method "password_dialog"
"password_dialog" is also a hook name, but revealer.password_dialog is not a hook
2018-12-19 02:10:47 +01:00
SomberNight
bec1860197
mac build: build qr scanner on separate machine 2018-12-18 22:05:44 +01:00
SomberNight
f54c387172
mac build: bump python version 2018-12-18 22:05:42 +01:00
SomberNight
f160f4bf67
mac build: use old xcode to build qr scanner on El Capitan 2018-12-18 22:05:02 +01:00
SomberNight
fa33d1880c
win build: bump python version 2018-12-18 21:41:29 +01:00
SomberNight
ba4af29bf8
rerun freeze_packages 2018-12-18 21:01:16 +01:00
SomberNight
8d1cb3c36a
bump pyqt version in binaries
closes #4777
2018-12-18 20:52:01 +01:00
SomberNight
8f5f0e46aa
keystore: fail sooner if unsupported version
follow-up #4937
2018-12-18 19:57:58 +01:00
ThomasV
fa389a4e06
Merge pull request #4937 from SomberNight/revert_password_v2
keystore: revert KDF change
2018-12-18 19:35:57 +01:00
SomberNight
0c9a03ac54
keystore: revert KDF change from #4838
making the KDF expensive is blocked on #4909
2018-12-18 15:37:29 +01:00
SomberNight
c59ac49fea
fix greenaddress plugin: follow-up 75f6ab9133 2018-12-17 13:41:00 +01:00
SomberNight
f0868f5a51
revealer: warning re version 0 vulnerability 2018-12-15 09:26:54 +01:00
SomberNight
e7e9f8e7f2
revealer: fix unlucky hex seed causing crash 2018-12-15 09:05:12 +01:00
SomberNight
f969edcf50
revealer: split some core parts out into separate file
for easier testing
2018-12-15 08:52:00 +01:00
SomberNight
94afd7a9ea
revealer: clean-up noise-generation. support regeneration of v0 again 2018-12-15 08:13:30 +01:00
SomberNight
91ef367176
revealer: fix path madness
don't use translated strings in file system paths!
2018-12-15 01:12:59 +01:00
ghost43
5fd83722d8
Merge pull request #4916 from tiagotrs/master
revealer: new interface
closes #4540 
closes #4676
2018-12-15 01:11:14 +01:00
SomberNight
e1ba962fe1
revealer: clean-up prev and fixes 2018-12-15 01:07:35 +01:00
tiagotrs
ff2cdf9f16
small fixes, simplification/improvement of texts 2018-12-15 00:48:44 +01:00
tiagotrs
b41a83ceda
new hook/interface ref #4540 2018-12-15 00:48:40 +01:00
SomberNight
0657bb1b36
test_wallet_vertical: add segwit 2fa test 2018-12-14 23:01:52 +01:00
SomberNight
664b0c234e
wizard: fix imported address wallets
assertion added in 9350709f13 was failing
2018-12-14 22:50:25 +01:00
ThomasV
1e8b34e63e rerun freeze_packages 2018-12-14 08:57:31 +01:00
ThomasV
27caa683fe kivy: show synchronization status in the balance field 2018-12-14 08:27:03 +01:00
ThomasV
75f6ab9133 rm requests from greenaddress plugin 2018-12-14 07:41:26 +01:00
ThomasV
95cb2fbebf remove requests from requirements 2018-12-14 07:19:26 +01:00
SomberNight
8b775fd24a
contrib: import 'requests' in try-except 2018-12-13 23:25:52 +01:00
SomberNight
78f5afff74
use certifi directly instead of requests 2018-12-13 23:11:59 +01:00
SomberNight
c09ac41b27
ssl: use certifi explicitly for aiohttp and electrum-server connections
fixes ssl issues on Android
2018-12-13 22:54:53 +01:00
ThomasV
7a4270f5a4 Qt: camera icon 2018-12-13 17:21:56 +01:00
SomberNight
67b2aebed6
android build: use rebased p4a fork
86eeec7c19
2018-12-13 16:23:58 +01:00
Calin Culianu
14363f8f2f
[Qt] Got rid of qt.util.Timer class and instead replaced the functionality with the more efficient QTimer. Also added disconnection from the timer on window close.
(cherry picked from 19a21eb08d)
2018-12-13 16:00:44 +01:00
ThomasV
3184d6f369 simplify previous commit 2018-12-13 12:10:36 +01:00
SomberNight
ef94af950c
wallet: try detecting internal address corruption 2018-12-12 20:50:53 +01:00
SomberNight
9bbfd610be
qt: don't flash QWidgets on startup before main window is visible
Consider wallet without password set. Using Qt GUI.
When starting the app, before the main window appears, small artefacts
("minimised" windows?) would appear very briefly and then disappear.
2018-12-12 19:58:13 +01:00
SomberNight
363dd12a2a
qt: try even harder not to crash whole app on first start 2018-12-11 21:29:23 +01:00
ThomasV
dd848304e6
Merge pull request #4880 from spesmilo/2fa_segwit
2fa segwit (from ghost43's PR)
2018-12-11 18:33:41 +01:00
ThomasV
4681ac8c23 CLI deserialize: always force full parse 2018-12-11 13:58:05 +01:00
ThomasV
502a4819b6 trustedcoin: do not set wallet.plugin in constructor 2018-12-11 13:08:10 +01:00
ThomasV
467e40b555 trustedcoin: serialize using PARTIAL_TXN_HEADER_MAGIC 2018-12-11 11:46:31 +01:00
ThomasV
040b5b3f88 trustedcoin: fix get_xkeys 2018-12-11 09:59:39 +01:00
SomberNight
84519752c3 trustedcoin: fix prev. remove temp xpubs. 2018-12-11 09:28:35 +01:00
ThomasV
852f2a0d65 trustedcoin: do not require wallet file upgrade 2018-12-11 09:28:35 +01:00
SomberNight
7b90d69443 trustedcoin: p2wpkh billing addresses 2018-12-11 09:28:35 +01:00
SomberNight
eeea4fcb31 rename 2fa non-segwit type to "legacy 2fa" and make segwit the default 2018-12-11 09:28:35 +01:00
ThomasV
df59a43300 fix test 2018-12-11 09:28:35 +01:00
ThomasV
5a93bf054e 2fa segwit (from ghost43's PR) 2018-12-11 09:28:35 +01:00
SomberNight
0ec7005f90
qt history: data() should return QVariant
the docs says so,
and also HistoryList.create_menu() was crashing sometimes re "Copy {}"
2018-12-10 19:42:31 +01:00
SomberNight
4e7b2f3ea3
qt history: use IntEnum for column indices 2018-12-10 19:25:38 +01:00
ghost43
53b64a6367
Merge pull request #4915 from spesmilo/qabstractitemmodel
use QAbstractItemModel in History tab
2018-12-10 18:10:00 +01:00
SomberNight
0ddccd56c7
interface: fix only-genesis regtest case 2018-12-10 17:46:37 +01:00
SomberNight
4791c7f424
qt history: fix toggling fiat capital gains 2018-12-10 16:53:46 +01:00
SomberNight
b0631f90f8
qt history: fix slowness
arghhhhh finalllllllllllly figured it out...
2018-12-10 16:48:55 +01:00
ThomasV
e35ed17200 remove call to undefined method refresh_headers 2018-12-10 13:07:03 +01:00
ThomasV
059fb51893 reintroduce profiler 2018-12-10 10:18:24 +01:00
SomberNight
ca1043ffda
qt history list: hide columns sooner
while wallet was starting up "hidden columns" were visible
2018-12-10 09:31:53 +01:00
SomberNight
5be6966462
qt history list: allow filtering by (partial) txid 2018-12-10 09:31:52 +01:00
SomberNight
0d755b86ab
qt address dialog: HistoryModel needs reference to correct HistoryList
refresh() was hiding/showing the headers of the main HistoryList
2018-12-10 09:31:52 +01:00
SomberNight
a99b92f613
qt history list: optimise fee histogram induced refresh 2018-12-10 09:31:51 +01:00
SomberNight
696db310a5
qt history list: optimise update_item (tx mined status) 2018-12-10 09:31:50 +01:00
SomberNight
b1e15751d6
qt history list: "status"-based sort should also tie-break on height 2018-12-10 09:31:50 +01:00
SomberNight
65e8eef87f
qt history list: use OrderedDictWithIndex for txns 2018-12-10 09:31:49 +01:00
SomberNight
8bb930dd04
fix OrderedDictWithIndex
setitem() would modify the dict of the class. oops.
2018-12-10 09:31:49 +01:00
SomberNight
3c3fac7ca4
qt history list: fix shortcut in refresh() 2018-12-10 09:31:48 +01:00
SomberNight
5e61ad09c1
qt addresses list: fix filtering 2018-12-10 09:31:48 +01:00
SomberNight
48e119b59e
qt history: minor clean-up and sanity checking 2018-12-10 09:31:47 +01:00
SomberNight
e023d8abdd
qt history list: sorting of first column now considers txpos
same block txns were in unnatural order, maybe sort is not stable?
2018-12-10 09:31:47 +01:00
SomberNight
1c0c21159b
qt history list: performance optimisations 2018-12-10 09:31:46 +01:00
Janus
d2ddb255ef
QAbstractItemModel: Release Notes and Address List fiat bug fix 2018-12-10 09:31:46 +01:00
Janus
3960070a50
QAbstractItemModel: fix sorting, QAbstractItemDelegate usage, QVariant usage 2018-12-10 09:31:45 +01:00
Janus
4eb4b341db
QAbstractItemModel: initial version, filter not done 2018-12-10 09:31:39 +01:00
SomberNight
5b9b6a931d
qt network dialog: fix NodesListWidget if there is fork
undo part of 5473320ce4
2018-12-10 08:04:54 +01:00
SomberNight
9607854b67
network: fix switching interface (restart old one)
follow-up b3ff173b45
connection_down was killing the already restarted old interface
2018-12-10 08:03:42 +01:00
SomberNight
62e352a2a8
network: don't let _maintain_sessions die from CancelledError
as then the network would get paralysed and no one can fix it
2018-12-09 20:04:42 +01:00
SomberNight
b3ff173b45
interface: change close() implementation
was getting on lightning branch in some circumstances
RecursionError: maximum recursion depth exceeded while calling a Python object
2018-12-09 20:02:00 +01:00
SomberNight
762082e13d
wine build: dedupe PYTHON_VERSION 2018-12-09 07:17:37 +01:00
ghost43
f59a4f85db
win/mac build: strip parts of pyqt5 from binaries to reduce size (#4901)
When bumping pyinstaller to 3.4, binary sizes had increased drastically.
The main reason seems to be that pyinstaller is pulling in "all" of qt.

based on Electron-Cash/Electron-Cash@4b09969594
2018-12-09 05:09:28 +01:00
benma
6c20340338 bitbox: fix seed command (#4906)
Entropy required to be 64 bytes.
2018-12-08 17:02:24 +01:00
SomberNight
0294844c11
labels plugin qt: only update corresponding window; disconnect signal 2018-12-08 06:56:18 +01:00
SomberNight
258b504000
qt main window: unregister network callbacks 2018-12-08 06:31:28 +01:00
SomberNight
c9482b5ea2
fix prev 2018-12-07 20:59:19 +01:00
SomberNight
c017f788ac
wallet: TxMinedInfo (merged TxMinedStatus and VerifiedTxInfo) 2018-12-07 20:47:28 +01:00
Janus
e1f4865844 digitalbitbox, trustedcoin: proxied http client
use common cross-thread HTTP method, which is put in network.py,
since that is where the proxy is. TrustedCoin tested successfully,
but DigitalBitbox can't be tested completely due to #4903

before this commit, digitalbitbox would not use any proxying
2018-12-07 19:19:40 +01:00
Janus
0169ec880c digitalbitbox: make constant strings 2018-12-07 19:18:33 +01:00
Janus
9a3f2e8fcc digitalbitbox: fix stretch_key bytes/str confusion 2018-12-07 18:41:40 +01:00
SomberNight
31a5d0c2f0
tweak release notes for 3.3 2018-12-07 04:32:17 +01:00
SomberNight
503bd357f4
requirements: bump python-trezor to 0.11 2018-12-07 04:06:51 +01:00
SomberNight
8c3920a0db
hw: check_libraries_available now gets version of incompatible libs
previously we would return early and the user would
just see "missing libraries"
2018-12-06 19:39:58 +01:00
ghost43
1546d65ebe
Merge pull request #4875 from matejcik/trezor-0.11
WIP: Trezor 0.11
2018-12-06 19:38:51 +01:00
SomberNight
20fa7fc2f7
trezor: fix sign_transaction prev_tx 2018-12-06 19:37:12 +01:00
SomberNight
9e86bc586c
trezor: only confirm passphrase when creating wallet
but not when decrypting
2018-12-06 19:37:11 +01:00
SomberNight
605982a2b7
android build: less verbose buildozer logs 2018-12-06 17:25:00 +01:00
SomberNight
2f7573850e
fix prev 2018-12-06 16:05:35 +01:00
SomberNight
8999e92f76
android build: fix warning re ndk_api
"NDK API target was not set manually, using the default of 21 = min(android-api=28, default ndk-api=21)"
2018-12-06 13:43:24 +01:00
SomberNight
a62e5d39ca
android build: add "how to deploy apk on phone" to readme 2018-12-06 05:10:24 +01:00
SomberNight
993374dce7
travis: build android apk 2018-12-06 05:09:08 +01:00
SomberNight
e8a8a17217
test_wallet_vertical: offline sign with old seed 2018-12-05 18:55:19 +01:00
matejcik
8e681c1723 trezor: update name (TREZOR -> Trezor) 2018-12-05 15:44:24 +01:00
matejcik
43acd09df8 trezor: support outdated firmware notifications
Outdated firmware error messages were originally raised from
create_client, which would mean that a client for an outdated device
would not be created.

This had a number of undesirable outcomes due to "client does not exist"
being conflated with "no device is connected".

Instead, we raise in setup_client (which prevents creating new wallets
with outdated devices, BUT shows them in device list), and python-trezor
also raises on most calls (which gives us an error message when opening
wallet and/or trying to do basically anything with it).

This is still suboptimal - i.e., there's currently no way for Electrum to
claim higher version requirement than the underlying python-trezor, and
so minimum_firmware property is pretty much useless ATM.
2018-12-05 14:26:19 +01:00
matejcik
8571cafcc8 trezor: call get_xpub with correct argument
`creating` indicates that this is a new wallet. Which is always the case
in `setup_device`
2018-12-05 14:24:32 +01:00
SomberNight
c3deb16a7d
exchange rate: fix coinbase
closes #4897
2018-12-05 12:26:03 +01:00
SomberNight
cc0db41879
qt history: speed up ensure_fields_available (faster startup) 2018-12-04 22:24:32 +01:00
SomberNight
e35f2c5bed
qt history list: fix #4896 2018-12-04 17:27:02 +01:00
SomberNight
923a9c36cb
util: Satoshis and Fiat should not be namedtuples
undo part of 37b009a342
due to json encoding problems
2018-12-04 16:44:50 +01:00
SomberNight
960855d0aa
wallet history fees: only calculate fees when exporting history
it's expensive, and it slows down startup of large wallets a lot
2018-12-04 16:17:22 +01:00
ThomasV
ebea5b0159 follow-up 5473320ce4: do not call get_full_history in constructor 2018-12-04 12:26:14 +01:00
ThomasV
bd5c82404d do not block load_wallet with watching_only warning 2018-12-04 11:52:31 +01:00
ThomasV
5ae9365f77
Merge pull request #4895 from benma/bitbox
plugins/digitalbitbox: compatibility with firmware v5.0.0
2018-12-04 11:19:00 +01:00
Marko Bencun
92a9cda4fc plugins/digitalbitbox: compatibility with firmware v5.0.0 2018-12-03 22:11:36 +01:00
SomberNight
059beab700
qt history list: small clean-up 2018-12-03 19:12:36 +01:00
SomberNight
ea235a1468
qt dark theme: use correct QR code icon (light/dark) 2018-12-03 17:51:05 +01:00
matejcik
8973bb6f71 Merge branch 'master' into trezor-0.11 2018-12-03 17:00:22 +01:00
SomberNight
d69ef890c0
downgrade qdarkstyle for now
see ColinDuquesnoy/QDarkStyleSheet#123
2018-12-03 16:04:17 +01:00
Janus
0677ce6d52 qt: avoid app.palette().text().color(), doesn't work on dark style 2018-12-03 15:54:21 +01:00
Janus
72957f4d51 qt_standardmodel: only use proxymodel when appropriate 2018-12-03 15:35:54 +01:00
Janus
5473320ce4 qt: use QStandardItemModel 2018-12-03 15:35:54 +01:00
SomberNight
9350709f13
wallet creation: take care not to write plaintext keys to disk
when creating imported privkey wallets the privkeys
were written to disk unencrypted first, then overwritten with ciphertext
2018-12-03 13:02:14 +01:00
SomberNight
ff454ab29d
cli restore: fix imported privkeys with password
closes #4894
2018-12-03 12:46:12 +01:00
ThomasV
e1fb75a81d
Merge pull request #4892 from preserveddarnell/patch-2
Update README.rst
2018-12-03 09:44:53 +01:00
ThomasV
dc46149f1c
Merge pull request #4893 from cculianu/fix_tx_desc_coins_tab
UI: Make Coins Tab -> Details TX Dialog show TX Description (label)
2018-12-03 09:08:30 +01:00
Calin Culianu
4386799fb0 follow-up 2018-12-02 15:20:32 +02:00
Calin Culianu
d2374d62aa UI Pet Peeve: Make Coins Tab -> Details pop up a tx dialog that actually includes the tx description as seen in UTXOList (if available) 2018-12-02 14:53:44 +02:00
Ken
2f4b9aa1f0
Update README.rst 2018-12-01 21:28:46 -05:00
SomberNight
74f6ac27af
wizard/hw: cap transport string
follow-up 32af83b7ae
2018-11-30 20:45:54 +01:00
Janus
ec5f406f49 plugins: labels: dump response if malformed sync server response 2018-11-30 19:16:07 +01:00
SomberNight
fe6367cbcd
network: validate donation address for server 2018-11-30 18:56:35 +01:00
SomberNight
ed22f968f9
text gui: fix network event handler 2018-11-30 17:18:06 +01:00
SomberNight
73e2b09ba8
blockchain: check best chain on disk is consistent with checkpoints
had a corrupted mainnet datadir that had testnet blockchain_headers file
(I had probably corrupted it myself but electrum could not recover from it)
2018-11-30 16:36:37 +01:00
ThomasV
2484c52611
Merge pull request #4838 from SomberNight/keystore_pw_hash2b
keystore: stronger pbkdf for encryption
2018-11-30 11:48:03 +01:00
ThomasV
1165d3f330 update version number 2018-11-30 11:23:01 +01:00
ThomasV
86e42a9081 release notes for 3.3 2018-11-30 11:22:40 +01:00
SomberNight
bddea809ec
storage/blockchain: use os.replace 2018-11-30 04:08:02 +01:00
ThomasV
863ee984fe wallet: cache NaN coin prices, clear cache on new history 2018-11-29 20:47:26 +01:00
SomberNight
ee287740a7
coldcard: fix p2pkh signing for new fw (1.1.0)
PSBT was serialised incorrectly but old fw did not complain
2018-11-29 20:28:27 +01:00
ThomasV
1253e3db1d
Merge pull request #4873 from SomberNight/android_docker
android docker build
2018-11-29 16:34:49 +01:00
SomberNight
124d2e23b7
fix travis macOS build 2018-11-29 13:24:44 +01:00
ThomasV
f4513c12eb follow-up 2018-11-29 11:47:02 +01:00
ThomasV
f0a59f06cd fix module path 2018-11-29 11:46:34 +01:00
ThomasV
d7bf8826fc rename contrib/build-osx as contrib/osx. Move QRReader submodule there. 2018-11-29 11:39:57 +01:00
Calin Culianu
db89286ec3 [macOS] Added QR scanner facility using platform-native helper app. 2018-11-29 10:15:51 +01:00
Vivek Teega
64733e93d9 FLO URI on the QT wallet 2018-11-29 12:50:15 +05:30
SomberNight
d0e6b8c89d
hw: fix passphrase dialog with confirmation
closes #4876
2018-11-28 20:54:57 +01:00
SomberNight
243a0e3cf1
android docker: make_apk optionally takes "release" as arg 2018-11-28 19:40:29 +01:00
SomberNight
505cb2f65d
build-wine: update git version 2018-11-28 16:44:15 +01:00
SomberNight
99325618a6
wallet: add FIXME re fiat coin_price calculation 2018-11-28 15:52:38 +01:00
ThomasV
fc2972e977
Merge pull request #4869 from cculianu/add_macos_codesign
[macOS] Added optional code signing capability to the OSX build scripts.
2018-11-28 14:02:29 +01:00
ThomasV
04571d3b20
Merge pull request #4724 from un1t/master
use system language by default
2018-11-28 13:05:42 +01:00
ThomasV
d062548e41
Merge pull request #4861 from SomberNight/blockchain_fork_ids
blockchain: generalise fork handling and follow most work chain
2018-11-28 12:54:57 +01:00
SomberNight
e12af33626
wallet: cache more in get_tx_fee
closes #4879
2018-11-28 12:35:53 +01:00
SomberNight
4a7ce238fd
qt history list: fix sort order of fiat columns 2018-11-27 21:32:55 +01:00
SomberNight
d4d5e32c91
qt history list: fix Qt.UserRole collision 2018-11-27 21:15:31 +01:00
ThomasV
c5b8706225 simplify test 2018-11-27 18:34:36 +01:00
ThomasV
6bf48d0506
Merge pull request #4872 from spesmilo/qt_fiat_fixes
qt history view custom fiat input fixes
2018-11-27 18:16:05 +01:00
Janus
37b009a342 qt history view custom fiat input fixes
previously, when you submitted a fiat value with thousands separator,
it would be discarded.
2018-11-27 17:00:26 +01:00
matejcik
b040db26a7 drop trezor/client.py from build specs 2018-11-27 16:51:49 +01:00
matejcik
c33c907330 trezor: update to trezor 0.11.0 2018-11-27 15:34:19 +01:00
matejcik
5411ad9633 plugins can also check maximum library version 2018-11-27 15:32:33 +01:00
SomberNight
a34d42492d
android docker build 2018-11-27 03:53:22 +01:00
ghost43
12c6a4043b
Merge pull request #4864 from SomberNight/android_build_2018nov
android: build apk using new python3 p4a toolchain
2018-11-26 22:02:52 +01:00
SomberNight
b21064f16f
android: don't use external storage
so that we don't need the extra permission.
also because phones these days have enough internal storage for
the headers; and maybe it's better even for security reasons to
store it there.
no upgrade path is provided for the headers stored on external storage,
we will litter the filesystem and leave them there. they will be
downloaded again into internal storage.
2018-11-26 17:54:07 +01:00
SomberNight
29b697df1a
android: runtime permission dialog for camera 2018-11-26 17:54:07 +01:00
SomberNight
f095b35663
android: build apk using new python3 p4a toolchain 2018-11-26 17:54:05 +01:00
Calin Culianu
d296a1be65 [macOS] Added optional code signing capability to the OSX build scripts. 2018-11-26 13:36:51 +02:00
SomberNight
a53dded50f
bitcoin: avoid floating point in int_to_hex 2018-11-26 01:34:23 +01:00
SomberNight
d7c5949365
prefer int.from_bytes over int('0x'+hex, 16) 2018-11-26 01:16:26 +01:00
SomberNight
67abea567f
rerun freeze packages 2018-11-22 19:41:06 +01:00
SomberNight
0dd3a58a63
requirements: also accept aiorpcx 0.10.x 2018-11-22 19:37:56 +01:00
SomberNight
f04e5fbed6
crypto: fix pkcs7 padding check
related: ricmoo/pyaes#22

in practice, the only strings we would incorrectly accept are
(certain length of) all zero bytes
2018-11-22 18:21:19 +01:00
SomberNight
65ce3deeaa
blockchain: chain hierarchy based on most work, not length 2018-11-22 17:13:43 +01:00
SomberNight
141ff99580
blockchain.py: generalise fork ids to get rid of conflicts 2018-11-22 16:57:22 +01:00
SomberNight
a8e6eaa247
blockchain: fix difficulty retarget
"target" is a 256 bit int, but the "bits" field in the block headers
that is used to represent target is only 32 bits.
We were checking PoW against the untruncated target value, which is a
slightly larger value than the one that can actually be represented,
and hence we would have accepted a slightly lower difficulty chain
than what the consensus requires.
2018-11-22 16:52:51 +01:00
Vivek Teega
dd6e5ae9ac Changing icons from BTC -> FLO 2018-11-22 17:15:58 +05:30
Vivek Teega
34e84858c8 Changed naming from BTC -> FLO 2018-11-22 11:03:15 +05:30
Vivek Teega
ecea61d9ca Fixed display of saved transactions 2018-11-21 21:15:28 +05:30
SomberNight
55963bd092
network: oneserver should be bool
fix #4858
2018-11-20 11:59:06 +01:00
SomberNight
36f64d1ad9
bitcoin/ecc: some more type annotations 2018-11-18 22:07:27 +01:00
SomberNight
5376d37c24
history export: include tx fee
closes #3504
2018-11-18 16:46:07 +01:00
SomberNight
32af83b7ae
wizard/hw: show transport type when listing HWDs 2018-11-16 19:03:25 +01:00
SomberNight
eba97f74b4
decorate some methods with @profiler to debug slow startup 2018-11-16 14:39:22 +01:00
ghost43
4d62963efe
qt: count wizards in progress (#4349)
fixes #4348
2018-11-14 22:39:49 +01:00
SomberNight
f767d41409
tests: spanish test case for mnemonic.py, and refactoring 2018-11-14 18:58:27 +01:00
Calin Culianu
75e30ddc9d Show description (label) in TxDialog screen when opened from History (#4775) 2018-11-14 16:43:58 +01:00
SomberNight
e1c66488b1
paymentrequest: don't show PaymentAck to user
mainly because the main "merchant" using bip70 is bitpay, and they
are failing all the PaymentAcks due to the tx is using RBF...
no need to confuse users.

follow-up 1686a97ece
2018-11-14 16:33:41 +01:00
ThomasV
f7f4fef156
Merge pull request #4827 from SomberNight/android_oneserver
implement oneserver option for kivy
2018-11-14 16:13:05 +01:00
SomberNight
e059867314
paymentrequest: be explicit about only allowing "addresses" 2018-11-14 16:04:43 +01:00
ThomasV
a266de6735 PrintError: display verbosity filter 2018-11-14 13:16:08 +01:00
SomberNight
e1b85327be
transaction: clean-up multisig_script 2018-11-14 00:37:03 +01:00
SomberNight
e04e8d2365
plugins: when loading plugins, use newer importlib mechanism
fixes #4842
2018-11-11 23:55:34 +01:00
SomberNight
48b0de7871
keystore: stronger pbkdf for encryption 2018-11-10 16:36:41 +01:00
SomberNight
aceb022f9d
crypto: more type annotations 2018-11-10 13:30:34 +01:00
SomberNight
a6a003a345
RBF batching: fix logic bug 2018-11-09 22:47:41 +01:00
SomberNight
2ab8234e9c
RBF batching: smarter fee handling 2018-11-09 20:04:06 +01:00
SomberNight
d905f0e55e
RBF batching: for now, let user deal with fee problems (honour slider) 2018-11-09 19:15:46 +01:00
SomberNight
436f6a4870
qt history export: include fiat value in csv 2018-11-09 18:48:12 +01:00
SomberNight
71ac3bb305
RBF batching: some fixes 2018-11-09 17:56:42 +01:00
ThomasV
f55db2f90b
add batch_rbf option to Qt GUI 2018-11-09 17:29:31 +01:00
ThomasV
2b8d801b36 if possible, batch new transaction with existing rbf transaction 2018-11-09 16:33:29 +01:00
SomberNight
bd32b88f62
introduce UserFacingException
we should not raise generic Exception when wanting to communicate with
the user. it makes distinguishing programming errors and messages hard,
as the caller will necessarily need to catch all Exceptions then
2018-11-08 19:46:15 +01:00
SomberNight
dace2e5495
trezor: don't let bridge transport failing block all other transports
[trezor] connecting to device at bridge:hid...
[trezor] connected to device at bridge:hid...
Traceback (most recent call last):
  File "...\electrum\electrum\base_wizard.py", line 255, in choose_hw_device
    u = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices)
  File "...\electrum\electrum\plugin.py", line 501, in unpaired_device_infos
    client = self.create_client(device, handler, plugin)
  File "...\electrum\electrum\plugin.py", line 374, in create_client
    client = plugin.create_client(device, handler)
  File "...\electrum\electrum\plugins\trezor\trezor.py", line 124, in create_client
    client = self.client_class(transport, handler, self)
  File "...\electrum\electrum\plugins\trezor\client.py", line 7, in __init__
    ProtocolMixin.__init__(self, transport=transport)
  File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 444, in __init__
    self.init_device()
  File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 454, in init_device
    self.features = expect(proto.Features)(self.call)(init_msg)
  File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 115, in wrapped_f
    ret = f(*args, **kwargs)
  File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 129, in wrapped_f
    client.transport.session_begin()
  File "...\Python36-32\lib\site-packages\trezorlib\transport\__init__.py", line 42, in session_begin
    self.open()
  File "...\Python36-32\lib\site-packages\trezorlib\transport\bridge.py", line 69, in open
    raise TransportException('trezord: Could not acquire session' + get_error(r))
trezorlib.transport.TransportException: trezord: Could not acquire session (error=400 str=wrong previous session)
[DeviceMgr] error getting device infos for trezor: trezord: Could not acquire session (error=400 str=wrong previous session)
2018-11-08 17:07:05 +01:00
Vivek Teega
b3a21f53bc Chunk verification without saving headers locally 2018-11-08 21:07:26 +05:30
SomberNight
47b6d3c52c
wizard: make native segwit (bech32) the default for bip39/hw 2018-11-08 13:01:40 +01:00
ThomasV
3d4773b161 wizard: make segwit/bech32 the default choice during wallet creation 2018-11-07 15:03:54 +01:00
SomberNight
7d114ff32d
cpfp: don't reuse address 2018-11-07 14:48:33 +01:00
ThomasV
39fb5b8f58 use blockstream.info as default block explorer 2018-11-07 12:38:29 +01:00
neoCogent
6d5b28a9c5 add blockstream.info as explorer option (#4829) 2018-11-07 02:18:00 +01:00
SomberNight
8b61d18a9f
transaction.serialize_output: use namedtuple fields 2018-11-06 17:04:12 +01:00
SomberNight
5d52dc204c
coldcard: fix spending when there is no change
follow-up 9037f25da1
2018-11-06 16:17:18 +01:00
SomberNight
1686a97ece
bip70 PRs: use aiohttp instead of requests. use proxy. small fixes. 2018-11-05 19:31:17 +01:00
SomberNight
1b46866e34
qt: re sweeping, minor clean-up 2018-11-05 01:53:35 +01:00
SomberNight
a89e67eeed
network: trivial clean-up 2018-11-04 19:25:23 +01:00
SomberNight
160bc93e26
implement oneserver option for kivy
closes #4826
2018-11-03 17:21:38 +01:00
SomberNight
7a46bd1089
network: minor clean-up 2018-11-03 17:11:08 +01:00
SomberNight
908c979338
network: update hardcoded mainnet servers 2018-11-02 20:38:26 +01:00
SomberNight
1a5c77aa82
follow-up prev 2018-11-02 20:37:37 +01:00
SomberNight
e37da62a1c
fix most "scripts"
related: #4754
2018-11-02 20:14:59 +01:00
SomberNight
5c4a6c0f2b
rm network.add_job
current implementation is prone to race, and is not used anyway
2018-11-02 16:06:47 +01:00
SomberNight
c2ecfaf239
move event loop construction to daemon 2018-11-01 16:30:03 +01:00
SomberNight
ca8eae919f
daemon: clarify error print 2018-10-31 19:59:07 +01:00
SomberNight
0862fdb9a9
plugins: somewhat clearer exception is loading plugin fails
see #4817 (issuecomment-434778055)
2018-10-31 18:33:28 +01:00
SomberNight
386e0d560e
wizard,hw: tell user about errors during plugin init
see #4817 (issuecomment-434765570)
2018-10-31 17:58:47 +01:00
SomberNight
4f7283a3b0
expose electrum version as __version__ 2018-10-31 16:21:04 +01:00
SomberNight
1c63bca2c7
follow-up prev 2018-10-30 19:19:46 +01:00
SomberNight
5b4fada2a0
fix some network.get_transaction calls
see #4814 (issuecomment-434392195)
2018-10-30 19:07:37 +01:00
SomberNight
f53b480f1c
wallet: more powerful add_input_info
tangentially related: #4814

also recognise that input is_mine if tx was not fully parsed
but we have the prevout UTXO
2018-10-29 21:34:44 +01:00
SomberNight
f819e9b6f4
openalias: minor clean-up 2018-10-29 17:09:23 +01:00
SomberNight
af232223ee
windows build script: add note to build from fresh clone 2018-10-29 15:48:04 +01:00
SomberNight
5e0179dac4
qt console: expose more refs, and fix auto-complete for >2 depth 2018-10-29 00:20:45 +01:00
SomberNight
9037f25da1
kill old-style namedtuples 2018-10-28 00:28:29 +02:00
SomberNight
34569d172f
wallet: make importing thousands of addr/privkeys fast
fixes #3101
closes #3106
closes #3113
2018-10-27 17:36:10 +02:00
SomberNight
917b7fa898
network shutdown safety belts 2018-10-26 22:43:33 +02:00
SomberNight
416b687054
storage: add a sanity check
see #4803
2018-10-26 19:31:20 +02:00
SomberNight
78258a3a95
fix #4802 2018-10-26 18:45:36 +02:00
SomberNight
bcdb0c46fc
update to aiorpcx 0.9 and require it 2018-10-26 17:06:42 +02:00
SomberNight
263c9265ae
rerun freeze packages 2018-10-26 16:56:23 +02:00
SomberNight
92d16e8b10
follow-up prev: unshallow no longer needed 2018-10-26 15:56:08 +02:00
SomberNight
2aefc8440a
travis: make sure to have latest tag
The Win/Mac build scripts name the binaries using "git describe --tags",
so reproducibility requires git to find the latest tag.
By default, Travis uses depth=50.
2018-10-26 15:34:46 +02:00
SomberNight
791e0e1a67
move relayfee and dust_threshold to bitcoin.py 2018-10-25 23:08:59 +02:00
SomberNight
99d18a48f2
types: make some import conditional 2018-10-25 23:01:53 +02:00
SomberNight
082a83dd85
rename crypto.Hash to sha256d 2018-10-25 22:28:24 +02:00
SomberNight
a88a2dea82
split bip32 from bitcoin.py 2018-10-25 22:20:33 +02:00
SomberNight
c61e13c1e9
add more block explorers, and change defaults 2018-10-25 18:27:41 +02:00
Andrew Zhuk
07a06b5d15 Update util.py (#4797)
Adding Bitupper Explorer to the list
2018-10-25 17:09:52 +02:00
SomberNight
361ffc0620
correctly handle bitcoin URIs if GUI is already running
see #4796
2018-10-25 00:18:14 +02:00
SomberNight
0e6160bf2d
follow-up prev: bad idea to eval translated string 2018-10-23 03:01:23 +02:00
SomberNight
b68729115a
qt wallet information: added keystore type 2018-10-23 02:54:54 +02:00
SomberNight
2a60a701bf
qt wallet information: show has_seed and watching_only 2018-10-22 23:47:34 +02:00
SomberNight
2d352bc3f0
transaction.BIP69_sort: use namedtuple fields 2018-10-22 20:43:31 +02:00
SomberNight
c4e09fa874
simplify Plugins constructor 2018-10-22 18:21:38 +02:00
SomberNight
81cc20039e
more type annotations in core lib 2018-10-22 16:41:25 +02:00
SomberNight
6958c0ccc3
config: reject non-json-serialisable writes
see #4788
2018-10-21 14:58:55 +02:00
Vivek Teega
085cd9919e Target calculation & chunk verification
This commit adds the code for FLO chunk verification. FLO chunk verification is different from Bitcoin chunk verification as the blockchain has 3 different target recalculation times. ie. 90 blocks at height 0->208440, 15 blocks at height 208440->426000 & 1 block further down

Changes have also been maded such that chunk doesnt need to be saved in parts
2018-10-21 07:42:07 +05:30
SomberNight
ef2a6359e4
fix SSL log spam on py3.7
based on kyuupichan/electrumx@83813ff1ac
see pooler/electrum-ltc#191
2018-10-21 03:09:47 +02:00
SomberNight
637e65efe3
network.stop: fix await 2018-10-20 23:17:10 +02:00
ghost43
c47533f6cf
Merge pull request #4784 from Coldcard/fix4729
Fix Coldcard plugin's p2wpkh-p2sh support (see issue #4729)
2018-10-19 22:27:46 +02:00
SomberNight
bf18e2bbc9
follow-up prev 2018-10-19 22:24:42 +02:00
SomberNight
10a4c7a6ed
wallet.mktx: add new args: rbf, nonlocal_only
used on lightning branch
2018-10-19 20:48:48 +02:00
SomberNight
e8bc025f5c
verifier: fix race in __init__ 2018-10-19 18:10:04 +02:00
Peter D. Gray
14b4955a6f
Fix p2wpkh-p2sh support per issue #4729 2018-10-18 12:38:47 -04:00
Vivek Teega
d418976f5e GUI changes on KIVY 2018-10-18 00:19:37 +05:30
Calin Culianu
1526fd3722 Removal of macOS Info.plist. It isn't being used by anything. (#4773) 2018-10-14 14:12:25 +02:00
SomberNight
2cc77c1c7d
rm system config sample
system-level configs are no longer supported since 04a1809969
2018-10-14 05:13:56 +02:00
SomberNight
60f8cf665e
dnssec: trivial clean-up 2018-10-14 04:23:42 +02:00
SomberNight
0e59bc1bc5
network: "switch unwanted fork" should check what fork we are on..
follow-up #4767
2018-10-14 04:23:10 +02:00
SomberNight
1af225015a
fix some type annotations involving tuples 2018-10-13 05:16:36 +02:00
SomberNight
7c4d6c6801
fix #4771 2018-10-13 04:22:53 +02:00
SomberNight
5afdc14913
util: small clean-up re format_satoshis
related #4771
2018-10-13 04:21:07 +02:00
SomberNight
8fa6bd2aac
network: add_job 2018-10-12 19:03:36 +02:00
ThomasV
e573c6d385
Merge pull request #4770 from SomberNight/kill_aiosafe
rm aiosafe decorator. instead: log_exceptions and ignore_exceptions
2018-10-12 18:53:29 +02:00
SomberNight
e3b372946a
rm aiosafe decorator. instead: log_exceptions and ignore_exceptions 2018-10-12 18:36:48 +02:00
SomberNight
ab441a507a
readme: use 'python3 -m pip install' to install 2018-10-12 17:02:38 +02:00
SomberNight
372921b423
mv NetworkJobOnDefaultServer to util
break ref cycles
2018-10-12 16:09:41 +02:00
脇山P
9ce3814d8b build-wine: update git version (#4769) 2018-10-12 11:44:34 +02:00
ThomasV
684e69763a
Merge pull request #4767 from SomberNight/auto_jump_forks
network: auto-switch servers to preferred fork (or longest chain)
2018-10-12 10:50:47 +02:00
ThomasV
cd5152a02d
Merge pull request #4765 from SomberNight/cli_restore_cmd
cli/rpc: 'restore' and 'create' commands are now available via RPC
2018-10-12 10:48:09 +02:00
SomberNight
1233309ebd
cli/rpc: 'restore' and 'create' commands are now available via RPC 2018-10-11 20:57:15 +02:00
SomberNight
37206ec08e
network: auto-switch servers to preferred fork (or longest chain)
If auto_connect is enabled, allow jumping between forks too.
(Previously auto_connect was only switching servers on a given fork,
not across forks)
If there is a preferred fork set, jump to that (and stay);
if there isn't, always jump to the longest fork.
2018-10-11 20:07:19 +02:00
SomberNight
1ef804c652
small import clean-up 2018-10-11 16:30:30 +02:00
ThomasV
cd5453e477
Merge pull request #4753 from SomberNight/synchronizer_rewrite
restructure synchronizer
2018-10-10 20:46:17 +02:00
SomberNight
150e27608b
wallet: rm electrum_version field 2018-10-10 20:26:12 +02:00
ThomasV
e975727075 follow-up prev commit 2018-10-10 19:26:02 +02:00
ThomasV
bb9871ded7 simplify prev commit 2018-10-10 19:24:24 +02:00
ThomasV
f037f06e74
Merge pull request #4758 from SomberNight/qt_fork_icon
qt network status: display 'fork' in icon when chain split is detected
2018-10-10 19:18:11 +02:00
SomberNight
87b05e1c9e
network: change broadcast_transaction api
raise exceptions instead of weird return values
closes #4433
2018-10-10 15:56:41 +02:00
ThomasV
c7833b8bc0
Merge pull request #4727 from SomberNight/refresh_gui_f5
qt: refresh gui with "F5"
2018-10-10 10:53:00 +02:00
Felix Yan
ad503daaca Fix some typos in RELEASE-NOTES (#4762) 2018-10-09 16:28:27 +02:00
SomberNight
cc18f66793
network: don't save negative ETA fee estimates
-1 means bitcoind could not give an estimate
2018-10-09 12:03:38 +02:00
Mark B Lundeberg
508793b010
qt transaction_dialog: normal close if user presses Esc
(Electron-Cash/Electron-Cash#890)
2018-10-09 01:14:33 +02:00
SomberNight
dc1a31d802
fix tests
follow-up 70cca3bad9
2018-10-07 18:43:35 +02:00
SomberNight
f3f2534877
qt status: display "loading wallet" temporarily
this will likely only be visible for large wallets;
it gets overwritten by update_status()
2018-10-07 17:59:32 +02:00
SomberNight
70cca3bad9
fix #4759 2018-10-07 17:50:52 +02:00
SomberNight
b37695f9c8
linux launcher madness
see #4300
2018-10-06 01:58:30 +02:00
Johann Bauer
1d7bf698f2
Windows: Update copyright notice in installed apps 2018-10-05 16:39:41 +02:00
SomberNight
decb8bfd52
qt network status: display 'fork' in icon when chain split is detected 2018-10-05 00:16:06 +02:00
SomberNight
d759546b32
qt console: fix word wrap 2018-10-03 18:26:09 +02:00
SomberNight
02f108d927
restructure synchronizer
fix CLI notify cmd. fix merchant websockets.
2018-10-03 17:13:46 +02:00
SomberNight
788b5b04fe
ledger: always use finalizeInput in sign_transaction
related #4749
2018-10-02 15:52:24 +02:00
SomberNight
a61953673a
fees: add 1-2 s/b static options 2018-10-02 15:44:09 +02:00
SomberNight
da9d1e6001
network: ensure there is a main interface
scenario with previous code:
auto_connect enabled, there is only one server in regtest environment.
client started before server; client would not switch to server after it is started.
2018-10-01 18:16:37 +02:00
SomberNight
7dd4032cce
daemon: call self.start in __init__, and allow not to listen on jsonrpc 2018-10-01 17:56:51 +02:00
SomberNight
4653a1007c
daemon: more convenient constructor for scripts 2018-10-01 15:49:26 +02:00
Johann Bauer
3f4e632cc4
Travis: Fix crowdin upload 2018-10-01 13:20:05 +02:00
SomberNight
626828e980
fix sweeping 2018-10-01 05:16:03 +02:00
SomberNight
4d43d12abf
transaction: don't convert p2pk to p2pkh address when displaying
also closes #4742
2018-10-01 04:58:26 +02:00
SomberNight
ab1ec57429
trezor and clones: rm dead code
see Electron-Cash/Electron-Cash#872
see Electron-Cash/Electron-Cash#874
2018-09-30 02:10:17 +02:00
SomberNight
8aebb8249a
keepkey: full segwit support
ported from trezor plugin
needs new fw to work (5.8??)

fixes #3462
2018-09-30 01:29:27 +02:00
SomberNight
70c32590a9
hw plugins: fix only_hook_if_libraries_available
follow-up f9a5f2e183
2018-09-30 00:25:36 +02:00
SomberNight
ce5cc135cd
transaction: make get_address_from_output_script safer
closes #4743
2018-09-29 19:47:55 +02:00
Vivek Teega
91273f7114 Adding FLO data support
1. All the GUI changes for the QT version of the wallet
2. Saving flodata locally in the wallet
2018-09-29 23:09:21 +05:30
Vivek Teega
4b1b6e3adf Renaming everything from FloData to txcomment 2018-09-29 18:20:06 +05:30
Vivek Teega
6a2d2fbc3d Creating Morph branch
This is an attempt to commit the changes made to Electrum-BTC to morph it into FLO, on top of the latest Electrum-BTC codebase at the time of writing. The code has become messy pulling upstream changes and its become difficult to debug issues
2018-09-29 17:52:30 +05:30
SomberNight
53fd6a2df5
transaction: always sort i/o deterministically
this was previously the caller's responsibility; now it's done implicitly when creating a txn
2018-09-28 19:17:45 +02:00
SomberNight
5e4a4ae16b
minor clean-up (prints/types/imports) 2018-09-28 17:58:46 +02:00
SomberNight
32d5305295
fix daemon.load_wallet 2018-09-28 16:43:25 +02:00
Vivek Teega
826a56311c
Merge pull request #7 from spesmilo/master
Pulling upstream changes
2018-09-28 18:02:50 +05:30
SomberNight
071bc27016
setup.py: rm deprecated 'imp'. dedupe min py version 2018-09-28 02:47:36 +02:00
SomberNight
12e79ecd60
qt tx dialog: make input/output fields expand
based on Electron-Cash/Electron-Cash@169c137211
2018-09-27 21:44:18 +02:00
SomberNight
3e2c5e8656
network.best_effort_reliable: force DC if req times out; retry on new iface 2018-09-27 21:15:07 +02:00
SomberNight
4984890265
follow-up prev: make best_effort_reliable react faster to disconnects 2018-09-27 20:04:36 +02:00
SomberNight
6b8ad2d126
fix some CLI/RPC commands 2018-09-27 18:01:25 +02:00
SomberNight
3b9a55fab4
rerun freeze packages 2018-09-26 19:33:12 +02:00
SomberNight
c4f3fbaca0
labels: fix potential threading issues
also handle --offline
2018-09-25 21:23:44 +02:00
SomberNight
deda6535e0
bump min aiorpcx to 0.8.2 2018-09-25 19:22:37 +02:00
SomberNight
33d14e4238
some import clean-up in qt 2018-09-25 18:15:28 +02:00
SomberNight
9d7cf12244
follow-up prev: fix tests 2018-09-25 17:00:43 +02:00
SomberNight
952e9b87e1
network: clean-up. make external API clear. rm interface_lock (mostly). 2018-09-25 16:44:39 +02:00
SomberNight
7cc628dc79
synchronizer: fix adding duplicate addresses race 2018-09-24 17:37:09 +02:00
Vivek Teega
2ce11fa83b
Merge pull request #6 from ranchimall/updateFork
Pulling upstream data
2018-09-23 22:04:46 +05:30
Vivek Teega
4253dd27eb Fixing upstream merge conflicts 2018-09-23 22:02:05 +05:30
Ilya Shalyapin
4c8103af3b move get_default_language to gui.qt.util 2018-09-23 14:11:50 +05:00
SomberNight
3be5b4b00f
network: fix some threading issues 2018-09-20 21:07:31 +02:00
SomberNight
1294608571
synchronizer: offload cpu-heavy address generation to other thread 2018-09-20 20:16:03 +02:00
SomberNight
172ddf4aaf
wallet: synchronize_sequence cleaned up a bit 2018-09-20 20:04:50 +02:00
SomberNight
55b582511e
fix deprecation warnings in regexes 2018-09-20 18:31:17 +02:00
SomberNight
e4fd5ec1ae
tox: add python 3.7 to envlist
previous CI build was complaining
2018-09-20 18:25:46 +02:00
SomberNight
002b8a99e2
synchronizer: make 'add' thread-safe, and some clean-up 2018-09-20 18:11:26 +02:00
SomberNight
eccb8ec2d6
normalize wallet file paths
fix #4020
fix #4126
2018-09-20 01:21:42 +02:00
SomberNight
61b5ce0451
fix import error 2018-09-20 01:20:13 +02:00
SomberNight
d50b36d314
daemon: suppress pop wallet failure
follow-up 3ec0ceba3e
related: #4126
2018-09-20 00:55:09 +02:00
SomberNight
9586157479
qt: refresh gui with "F5" 2018-09-19 22:12:02 +02:00
SomberNight
cedd518aea
mark 'blockchain_headers' file as sparse on windows
based on fyookball/electrum@647a6cc26d
2018-09-19 22:09:54 +02:00
SomberNight
855a70bc66
network: new trigger 'blockchain_updated'
follow-up af63913189
needed to update history tab when new blocks come,
to refresh the number of confirmations (icons/tooltips)
2018-09-19 21:56:09 +02:00
SomberNight
cbd91ba5b1
synchronizer: fix race
The synchronizer would sometimes not send 'wallet_updated' triggers
if it was fast enough to do all the work between two 0.1 sec ticks.
(is_up_to_date() would return True both before and after)
2018-09-19 21:41:10 +02:00
SomberNight
8ee1f140d8
interface: split run_fetch_blocks
The 'continue' in the middle was too easy to miss.
We want a 'network_updated' trigger from every interface,
not just the fastest.
2018-09-19 20:30:54 +02:00
SomberNight
f9a5f2e183
fix #4698 2018-09-19 20:02:03 +02:00
SomberNight
8caab35d90
trezor: re-enable bridge transport
It was disabled in 680df7d6b6 due to #4421,
but that has since been fixed.
Also related is #4060; and now that that is closed, the bridge transport
is not proxied anyway.
2018-09-19 18:14:55 +02:00
SomberNight
9161e8c8f4
interface: refuse to overwrite blockchain of main interface
in case of conflicting forks
2018-09-19 17:56:42 +02:00
SomberNight
7e1a784fca
follow-up prev: fix race between load_wallet and network events
[127.0.0.1] Exception in wrapper_func : AttributeError 'ElectrumWindow' object has no attribute 'wallet'
Traceback (most recent call last):
  File "/home/user/wspace/electrum/electrum/util.py", line 839, in f2
    return await f(*args, **kwargs)
  File "/home/user/wspace/electrum/electrum/interface.py", line 245, in wrapper_func
    return await func(self, *args, **kwargs)
  File "/home/user/wspace/electrum/electrum/interface.py", line 260, in run
    await self.open_session(ssl_context, exit_early=False)
  File "/home/user/wspace/electrum/electrum/interface.py", line 357, in open_session
    await group.spawn(self.monitor_connection())
  File "/usr/local/lib/python3.6/dist-packages/aiorpcx/curio.py", line 241, in __aexit__
    await self.join(wait=self._wait)
  File "/usr/local/lib/python3.6/dist-packages/aiorpcx/curio.py", line 214, in join
    raise task.exception()
  File "/home/user/wspace/electrum/electrum/address_synchronizer.py", line 173, in job
    await group.spawn(self.synchronizer.main())
  File "/usr/local/lib/python3.6/dist-packages/aiorpcx/curio.py", line 241, in __aexit__
    await self.join(wait=self._wait)
  File "/usr/local/lib/python3.6/dist-packages/aiorpcx/curio.py", line 214, in join
    raise task.exception()
  File "/home/user/wspace/electrum/electrum/synchronizer.py", line 181, in main
    self.wallet.network.trigger_callback('wallet_updated', self.wallet)
  File "/home/user/wspace/electrum/electrum/network.py", line 267, in trigger_callback
    callback(event, *args)
  File "/home/user/wspace/electrum/electrum/gui/qt/main_window.py", line 300, in on_network
    if wallet == self.wallet:
AttributeError: 'ElectrumWindow' object has no attribute 'wallet'
2018-09-19 17:44:52 +02:00
SomberNight
96b699e534
synchronizer: fix refresh bug 2018-09-19 16:35:30 +02:00
SomberNight
6f0dceb152
fix #4726
follow-up 88fc62e8f7
2018-09-19 15:26:03 +02:00
ghost43
924ee1a672
Merge pull request #4725 from joren485/unreachable_return
Remove unreachable return statement
2018-09-19 13:09:49 +02:00
Joren Vrancken
ae501ca8ed
Remove unreachable return statement 2018-09-19 11:35:29 +02:00
Ilya Shalyapin
d840804818 use system language by default 2018-09-19 13:07:19 +05:00
SomberNight
adc91eb75e
interface: hostname cannot be empty 2018-09-18 20:21:10 +02:00
SomberNight
916cdebacb
network: send out update trigger when stopping/starting network 2018-09-18 19:27:33 +02:00
SomberNight
a2ed08615c
minor.. move imports out of functions 2018-09-18 18:07:12 +02:00
SomberNight
39db32c3ce
follow-up prev 2018-09-18 17:59:02 +02:00
SomberNight
af63913189
network triggers: rm 'updated'; more fine-grained instead
rm 'interfaces'
add 'wallet_updated', add 'network_updated'
2018-09-18 16:49:48 +02:00
SomberNight
fef15f9c02
wallet: minor opt in get_history 2018-09-18 16:41:56 +02:00
SomberNight
825d7c2cbd
interface: subscribe to headers in run_fetch_blocks
so that 'monitor_connection' is already running while waiting for first header
2018-09-18 15:40:32 +02:00
ThomasV
3ec0ceba3e add option to leave daemon running after GUI is closed 2018-09-18 12:05:37 +02:00
SomberNight
67d3d6b5b5
qt: don't update tabs in ElectrumWindow.__init__ directly 2018-09-18 04:19:12 +02:00
SomberNight
01246b0d97
wallet/verifier: when adding into unverified_tx, don't remove from verifier
Not needed since aee2d8e120
And was never really working I guess (race..)
Also, during normal initial history sync, it caused the verifier to request
proofs multiple times.
2018-09-18 03:48:14 +02:00
SomberNight
533bd97a05
qt HistoryList.update_item: perf optimisation 2018-09-18 03:19:24 +02:00
SomberNight
c8f82c71c9
wallet: small perf optimisation in add_transaction 2018-09-18 02:14:23 +02:00
SomberNight
11bf084a1f
network triggers: 'verified' notification now includes wallet
this is a performance optimisation.

measurements using a large wallet with 11k txns:
syncing XPUB for the first time takes 10 seconds. leaving window open, and
syncing same XPUB again in new window takes 30 seconds. in third window,
it takes ~50 seconds. then ~70s. presumably scaling linearly.
this is due to the history_list.update_item call being CPU-heavy.
now all of them take 10 seconds.
2018-09-18 01:40:34 +02:00
SomberNight
24ec7ce6b8
qt network dialog: maybe fix refresh bug 2018-09-17 22:31:31 +02:00
SomberNight
7221fb3231
interface: further simplifications for fork resolution 2018-09-17 22:30:25 +02:00
SomberNight
b3a2bce213
interface: simplify fork resolution logic 2018-09-17 22:30:21 +02:00
SomberNight
435efb47d0
wallet: lock in get_addr_io, get_tx_delta, get_tx_value
probably fixes #4716
2018-09-17 18:50:47 +02:00
SomberNight
1b95cced5d
verifier: perf optimisations
blockchain.read_header is expensive. do cheap tests first
on a wallet with 11k txns, that is synced except for spv proofs,
finishing sync now takes 80 sec instead of 180 sec
2018-09-17 18:31:25 +02:00
SomberNight
e5e3ac0364
fix #4720 2018-09-17 14:44:01 +02:00
SomberNight
aee2d8e120
verifier: fix a race during reorgs
related: 41e088693d
If our guess of a txn getting confirmed at the same height in the new chain
as it was at in the old chain is incorrect, there is a race between the
verifier and the synchronizer. If the verifier wins, the exception will cause
us to disconnect.
2018-09-17 03:35:25 +02:00
SomberNight
dcab22dcc7
verifier: small clean-up 2018-09-16 22:21:49 +02:00
SomberNight
78488ebcbf
aiosafe safety belts
traceback.print_exc was raising, and self.exception did not got set,
and the whole trace was lost. arghhhh
2018-09-16 22:17:20 +02:00
SomberNight
4360a785ad
blockchain: blockchains_lock needed to write/iterate global dict 2018-09-16 18:26:40 +02:00
SomberNight
7dc5665ab1
interface: faster bootstrap of backwards search 2018-09-16 18:18:49 +02:00
SomberNight
4d502eb2bf
qt tx notifications: wait until sync finishes
Comment is no longer relevant. Also, actually it was incorrect.
Each txn is only downloaded once, though 'added' multiple times to the wallet.
The triggers are only sent out by the Synchronizer, once, when downloaded.
The actual reason for the inconsistency was that get_wallet_delta can only
give complete results once the wallet is synced.
2018-09-16 09:40:07 +02:00
SomberNight
9c919e6478
interface: fix off-by-one in request_chunk
was harmless; usually we just downloaded an extra individual header after the chunk
2018-09-16 09:01:53 +02:00
SomberNight
1d711eeadc
interface: split up 'step'; binary search of headers 2018-09-16 08:29:01 +02:00
SomberNight
58a5346d72
network: switch lagging interface 2018-09-16 07:59:36 +02:00
SomberNight
27e42b4826
interface: if header is on other chain already, just switch (regression) 2018-09-16 07:42:25 +02:00
SomberNight
3fc9326c43
interface: try hard not to infinite loop while getting headers 2018-09-16 07:35:11 +02:00
SomberNight
da23e71db1
interface: block header search simplifications 2018-09-16 07:34:05 +02:00
SomberNight
ab94a47b8e
network: mv request_chunk to interface
this is a bugfix: the old code always tried to connect the chunk to
network.blockchain(). the correct behaviour is to connect to the
blockchain of the interface.
2018-09-16 06:09:14 +02:00
SomberNight
1635bc8cb3
blockchain: use HEADER_SIZE named constant instead of magic numbers 2018-09-16 03:06:21 +02:00
SomberNight
a9197236a2
change 'new_transaction' notification to include wallet 2018-09-16 02:48:13 +02:00
SomberNight
2453872a09
synchronizer: rm redundant 'updated' notification 2018-09-16 02:31:56 +02:00
SomberNight
6f5a4a5502
fix prev: rm incorrect assert 2018-09-15 08:23:49 +02:00
SomberNight
482259df8b
interface: further clean-up in 'step' 2018-09-15 07:26:36 +02:00
SomberNight
beb37aafc5
interface: clean-up 'step'; backwards search 2018-09-15 06:44:18 +02:00
SomberNight
2a958499b6
fx: disable checking mime type in get_json
looking at you, CoinDesk..
2018-09-15 00:30:43 +02:00
SomberNight
f38ec93ae9
qt fx settings: restore selected exchange in combobox if list changes 2018-09-14 23:07:13 +02:00
SomberNight
6ccd83397c
fx: asyncio.Event is not thread-safe; also the 'timeout' field was removed 2018-09-14 23:01:28 +02:00
SomberNight
d1f11f5fe9
fix #4717 2018-09-14 16:12:47 +02:00
Filip Gospodinov
f05f3b430a build-wine: fix locale path
`$i` already contains `locale/`.
2018-09-14 14:37:14 +02:00
Filip Gospodinov
bdecef0eaf contrib: bump pyinstaller to 3.4
PyInstaller 3.4 highlights:

* patch for deterministic builds by electrum
* improved support for Qt5-based applications
* added support for Python 3.7
2018-09-14 14:29:19 +02:00
SomberNight
2bd5e0f25d
packaging: check in make_tgz if packages folder exists
related: #4714
2018-09-13 23:29:44 +02:00
SomberNight
2e61359d50
network: stop pending connections when stopping network 2018-09-13 21:20:55 +02:00
SomberNight
23f56ffa8a
network: avoid infinite reconnect loop to same server 2018-09-13 21:02:37 +02:00
SomberNight
e4bd445a38
network.new_interface: clarify how timed out interfaces are closed 2018-09-13 20:50:32 +02:00
SomberNight
64ab8222f7
interface: if request times out, no need to dump trace 2018-09-13 20:17:58 +02:00
SomberNight
819044221b
verifier: need to wait for reorg
fixes race between verifier and block header download.
scenario: client starts, connects to server. while client was offline,
there was a reorg. txn A was not mined in the old chain, but is mined
after reorg. client subscribes to addresses and starts downloading headers,
concurrently. server tells client txn A is mined at height H >= reorg height.
client sees it has block header at height H, asks for SPV proof for txn A.
but the header the client has is still the old one, the verifier was faster
than the block header download (race...). client receives proof. proof is
incorrect for old header. client disconnects.
2018-09-13 19:00:21 +02:00
SomberNight
78e9152723
network: get_servers to always include recent servers 2018-09-13 16:06:41 +02:00
SomberNight
43664d5f11
fixes for stdio/text gui 2018-09-13 15:11:28 +02:00
SomberNight
1f14894c43
network: add server to recent_servers only after checks 2018-09-13 03:45:21 +02:00
SomberNight
a9fcf2fabf
bump min aiorpcx to 0.8.1 2018-09-13 01:21:53 +02:00
SomberNight
c93d137c5e
interface: minor clean-up split out _set_proxy from init 2018-09-13 01:20:20 +02:00
SomberNight
c40468a8d3
interface: disable bw rate limiting done by aiorpcx 2018-09-12 22:58:36 +02:00
SomberNight
2e18e3c62b
adapt to aiorpcx 0.8.1: rm report_crash kwarg from group.spawn 2018-09-12 22:09:59 +02:00
SomberNight
a3fb865db0
follow-up prev
this is already running inside interface.group
2018-09-12 21:22:46 +02:00
SomberNight
6452582a17
network: batch requests in request_server_info 2018-09-12 21:18:08 +02:00
SomberNight
e7fa42ce3e
wallet: don't write to disk when switching servers 2018-09-12 20:25:13 +02:00
SomberNight
cad4fb80c1
interface: throttle messages 2018-09-12 20:17:12 +02:00
SomberNight
47a97279af
rename CustomTaskGroup to SilentTaskGroup 2018-09-12 19:24:58 +02:00
SomberNight
2039c07a2d
interface.mark_ready: handle cancellation 2018-09-12 18:45:15 +02:00
SomberNight
1419a5c60d
interface: change how GracefulDisconnect is handled 2018-09-12 18:43:50 +02:00
SomberNight
6f7a065081
bump aiorpcx version 2018-09-12 18:43:07 +02:00
SomberNight
3842205b8a
keystore: add note regarding xpubkeys 2018-09-12 18:22:34 +02:00
SomberNight
152c6abb86
network: fix another race in session.subscribe
key in session.subscriptions does not imply key in session.cache
2018-09-12 16:58:15 +02:00
SomberNight
9505a203d8
util: rm dead network code 2018-09-12 16:57:12 +02:00
ThomasV
15b21abc99 fix fee_histogram notifications 2018-09-12 12:56:51 +02:00
ThomasV
ce4608ae76 add help text to bump fee dialog 2018-09-12 12:18:27 +02:00
Vivek Teega
f2e93e323a Fixing target calculation after changes from the upstream 2018-09-12 06:39:04 +05:30
SomberNight
8cd08cc0fa
network: rm dead code; simplify 2018-09-12 01:40:54 +02:00
Vivek Teega
9e0777a7b4 Solved upstream merge conflicts 2018-09-12 03:38:43 +05:30
SomberNight
ab3c3c5ed7
interface: small clean-up 2018-09-11 22:16:30 +02:00
SomberNight
a5b3f809ce
blockchain.py: add type annotations 2018-09-11 22:14:57 +02:00
SomberNight
014c0d3a41
network: update UI when downloading chunks 2018-09-11 21:44:17 +02:00
SomberNight
518c6280e9
interface: minor clean-up re timeouts 2018-09-11 21:23:37 +02:00
SomberNight
6b9a83ae80
don't test with python 3.5
also, typing is no longer needed (part of stdlib from 3.5)
2018-09-11 21:10:47 +02:00
SomberNight
bed35a65c7
bump min python to 3.6 2018-09-11 21:04:36 +02:00
SomberNight
9ffd2de492
Merge branch 'aiorpcx' 2018-09-11 20:52:58 +02:00
SomberNight
ecc296cf67
fix race in session.subscribe 2018-09-11 20:39:16 +02:00
SomberNight
8b8ca14c6d
move get_index from network to session 2018-09-11 20:37:53 +02:00
SomberNight
e829d6bbcf
wallet: put Sync and Verifier in their own TaskGroup, and that into interface.group 2018-09-11 20:24:01 +02:00
SomberNight
19d4bd4837
simplify prev 2018-09-11 18:28:59 +02:00
SomberNight
4e0d179937
rate limit txn notifications in qt 2018-09-11 18:13:52 +02:00
Janus
09dfb0fd1d fix off-by-one error when syncing from genesis w/o checkpoints 2018-09-11 17:16:37 +02:00
ThomasV
3b6af914e1 add multiplexing capability to NotificationSession, simplify interface 2018-09-11 17:06:41 +02:00
SomberNight
1728dff576
fix prev: that's not how you use the context manager... 2018-09-11 12:25:57 +02:00
SomberNight
557334aa36
interface: introduce tip_lock 2018-09-11 11:44:49 +02:00
SomberNight
20957ac4d9
follow-up prev 2018-09-11 02:43:54 +02:00
Calin Culianu
a4396f4f13
Fixed potential bug when clicking in History List on slow wallet synch 2018-09-11 02:38:57 +02:00
Vivek Teega
278a6bcb4d
Merge pull request #3 from ranchimall/txcomment
Updating further changes to master branch
2018-09-11 00:14:45 +05:30
SomberNight
19e244a85e
interface: rm unnecessary writes to self.tip 2018-09-10 19:47:36 +02:00
SomberNight
54cc822227
network: send out 'interfaces' event on new_interface
network dialog was not always showing up-to-date data
2018-09-10 19:03:06 +02:00
SomberNight
e2338581eb
broadcast_transaction: introduce async variant 2018-09-10 18:39:10 +02:00
SomberNight
b279d351d8
interface.session: add default timeout to send_request 2018-09-10 17:12:05 +02:00
SomberNight
fffec71fb3
kivy fx: make sure displayed fiat values get updated 2018-09-10 16:43:04 +02:00
SomberNight
3e3d387161
fix kivy: follow-up 3d424077fd 2018-09-10 15:18:11 +02:00
ThomasV
061231494d revert rm requests 2018-09-10 13:26:50 +02:00
ThomasV
e8f87d2e62 remove requests and pysocks from requirements 2018-09-10 13:06:48 +02:00
Vivek Teega
66777d4566 FLO data box moved up 2018-09-10 11:27:27 +05:30
Vivek Teega
d8d2f3329b Save txcomments/floData locally 2018-09-10 11:12:05 +05:30
SomberNight
526319630e
network: minor fix in switch_to_interface 2018-09-10 02:30:27 +02:00
SomberNight
999ae1f713
test_mnemonic: add foreign lang tests 2018-09-10 02:03:42 +02:00
SomberNight
6b2509b106
interface.run: catch OSError instead of subtypes 2018-09-10 01:09:35 +02:00
SomberNight
b2547601a5
rm dead code 2018-09-10 01:08:51 +02:00
SomberNight
97ea4679a7
network: fix monkey-patching in set_proxy 2018-09-10 01:08:28 +02:00
SomberNight
3d424077fd
introduce NetworkParameters namedtuple 2018-09-10 00:59:53 +02:00
SomberNight
ecf4ea9ba7
move (de)serialize_server to interface; and use it 2018-09-09 23:08:44 +02:00
SomberNight
b381a7fdbf
follow-up prev 2018-09-09 22:02:42 +02:00
SomberNight
48a5b8527a
split up interface.run 2018-09-09 21:16:48 +02:00
SomberNight
096b3e6026
network.maintain_sessions: rm redundant 'update' notifications 2018-09-09 05:32:07 +02:00
SomberNight
e3fb991b1b
clean-up network start/stop a bit 2018-09-09 05:05:08 +02:00
SomberNight
cdca74aa39
move max_checkpoint from network to constants 2018-09-09 05:00:09 +02:00
SomberNight
2f224819ac
interface: small clean-up 2018-09-09 01:15:06 +02:00
SomberNight
57cac47944
fix synchronizer: ask for missing txns on start
Previously it could happen that a wallet was fully synced,
except it had missing transactions, and it would not recover from this state.
2018-09-08 22:44:14 +02:00
SomberNight
86733279f6
docker-wine: update package versions
the previous version is no longer available. hopefully these versions are "lts"
ref: 6899ca2527
2018-09-08 19:57:20 +02:00
SomberNight
c5bedbd3ef
wallet: only do fiat history computations if specifically enabled 2018-09-08 19:38:38 +02:00
SomberNight
77d86f074f
verifier: don't try to request same chunk multiple times 2018-09-08 19:11:02 +02:00
SomberNight
b33b2c0945
synchronizer: more batching 2018-09-08 18:38:58 +02:00
SomberNight
c49e563881
verifier: if we fail to verify SPV proof, disconnect from server 2018-09-08 18:10:21 +02:00
SomberNight
4a88ca1a3a
fix --offline option for fx and trustedcoin 2018-09-08 17:56:29 +02:00
SomberNight
86bc59cd60
update mainnet block header checkpoints 2018-09-08 17:32:28 +02:00
Dzhelil Rufat
c9ffffc526 Remove unneccessary imports from the unit testing directory. (#4699) 2018-09-08 17:24:23 +02:00
SomberNight
57e66324cb
batch fee estimates 2018-09-08 15:36:16 +02:00
SomberNight
ddee03d324
interface.run: catch more exceptions 2018-09-08 02:15:51 +02:00
SomberNight
136df7e5ee
wallet: recreate Synchronizer and Verifier when switching servers
not that nice but solves races
2018-09-08 01:34:33 +02:00
SomberNight
32528d6aa6
rm dupe code 2018-09-08 01:10:41 +02:00
SomberNight
64a03c245c
small timeout change
(re KeyError: can happen after proxy settings change)
2018-09-08 00:25:38 +02:00
SomberNight
7500b1fbee
detect lost connection
supersedes #4697
2018-09-07 20:26:45 +02:00
SomberNight
56c3c76d8b
follow-up 26172686b8 2018-09-07 19:54:26 +02:00
SomberNight
fd40dee337
make sure to retry nodes for network 2018-09-07 19:35:35 +02:00
SomberNight
26172686b8
restructure synchronizer/verifier <--> interface coupling 2018-09-07 19:34:28 +02:00
SomberNight
1fa07c920c
network: restore previous API for broadcast_transaction 2018-09-07 17:07:15 +02:00
Janus
52b877ac3d network: add singleton accessor classmethod, port trustedcoin to aiohttp 2018-09-07 11:35:16 +02:00
ThomasV
8f4b57f718 run freeze_packages 2018-09-06 18:49:37 +02:00
Janus
617103bb2a labels: fix saving single label 2018-09-06 18:30:24 +02:00
SomberNight
dc51e82f54
fx: don't dump trace if getting rates fails 2018-09-06 18:25:23 +02:00
Johann Bauer
e5cd2ed52f
Goldcard: Change spelling mistake 2018-09-06 18:15:44 +02:00
ThomasV
96fb75ffc6
Merge pull request #4685 from toxeus/locale
build-wine: avoid untracked changes in submodule
2018-09-06 17:59:25 +02:00
SomberNight
8467f95a28
rm @profiler from Transaction.estimated_size
in certain situations, estimated_size is called hundreds of times, flooding the log
2018-09-06 17:51:13 +02:00
ThomasV
8fe066707a rm import 2018-09-06 17:47:10 +02:00
SomberNight
77aefdfe71
gitignore: add kivy atlas 2018-09-06 17:46:50 +02:00
Janus
573760daf0 remove generated kivy theming 2018-09-06 17:25:11 +02:00
ThomasV
73bf7a92a2
Merge pull request #4690 from spesmilo/aiorpcx-fx
asyncio: port exchange_rate and labels to aiohttp
2018-09-06 17:18:55 +02:00
Janus
be50394f11 aiorpcx: increase crash reporter timeout, avoid is_running in kivy 2018-09-06 17:18:26 +02:00
SomberNight
0ad504bdf0
interface: catch many common exceptions explicitly 2018-09-06 16:45:43 +02:00
Janus
6e80ba7b4f asyncio: labels, crash_reporter, fx: migrate requests use to aiohttp 2018-09-06 16:18:45 +02:00
ThomasV
5ef04a039b move NotificationSession 2018-09-06 15:53:41 +02:00
ThomasV
234273809a set interface.session before marking as ready 2018-09-06 15:44:11 +02:00
SomberNight
0142e0fa22
fix 'daemon load_wallet' over RPC for python > 3.5.3
related: #3764
2018-09-06 15:14:35 +02:00
Janus
d367199553
async block headers: remove BlockHeaderInterface and Conn classes, make self.height a local 2018-09-06 14:17:45 +02:00
Janus
9c363db440
async block headers: avoid duplicate tip fields, handle electrumx server skipping blocks 2018-09-06 14:17:44 +02:00
SomberNight
4d95452ae7
wallet: partial fix for race in on_default_server_changed 2018-09-06 14:17:44 +02:00
SomberNight
2187615c08
verifier: request proofs in batches 2018-09-06 14:17:43 +02:00
Janus
c89020725b
address synchronizer: fetch initial addresses from wallet 2018-09-06 14:17:43 +02:00
SomberNight
14a032a0b1
disconnect from servers on exception 2018-09-06 14:17:42 +02:00
SomberNight
3f0d79f07d
blockchain.py: better handling of missing headers. more restrictive verify_chunk. 2018-09-06 14:17:42 +02:00
SomberNight
2157eae499
fix request_chunk 2018-09-06 14:17:41 +02:00
Janus
e9ceeb85af
async block headers 2018-09-06 14:17:41 +02:00
Janus
19387ff911
aiorpcx: simplify open_session 2018-09-06 14:17:39 +02:00
Janus
f12074397f
aiorpcx: reintroduce periodic fee updates 2018-09-06 14:17:39 +02:00
SomberNight
a4ffa0b22a
interface: clean-up proxy username/pw handling 2018-09-06 14:17:38 +02:00
SomberNight
6700364ac8
interface: fix cert handling
notably os.unlink cannot be inside the "with open"
2018-09-06 14:17:38 +02:00
Janus
9543a108be
aiorpcx: revive some maintain_sockets code, reintroduce NODES_RETRY_INTERVAL and SERVER_RETRY_INTERVAL usage, and fix --oneserver 2018-09-06 14:17:37 +02:00
ThomasV
5117a520ae
fix start_network 2018-09-06 14:17:37 +02:00
Janus
9bfb5fe71f
address synchronizer: use aiorpcx session object in network's interface,
request, fees
2018-09-06 14:17:36 +02:00
Janus
8f36c9167d
aiorpcx: remove callback based code, add session to Interface 2018-09-06 14:17:29 +02:00
Janus
b120584f97
aiorpcx address synchronizer 2018-09-06 14:11:36 +02:00
Janus
f733cb8947
aiorpcx: socks support 2018-09-06 14:11:36 +02:00
ThomasV
c53caecd1e
fix else statement 2018-09-06 14:11:35 +02:00
Janus
89a01a6463
aiorpcx: pin certificates 2018-09-06 14:11:35 +02:00
Janus
8080a713b2
aiorpcx: pass ssl context, sleep after connecting 2018-09-06 14:11:34 +02:00
Janus
97ea0fc439
aiorpcx: replace network loop with asyncio and try to maintain ten sessions 2018-09-06 14:11:20 +02:00
SomberNight
40ceabff79
rm redundant function from util 2018-09-05 18:36:13 +02:00
SomberNight
69a204d726
fix #4657 2018-09-05 18:30:53 +02:00
SomberNight
44fbd8330b
update gitignore 2018-09-05 18:07:24 +02:00
SomberNight
ecbbfdd10c
rerun freeze packages 2018-09-05 15:58:39 +02:00
ThomasV
951fd8a47f bump apk version number 2018-09-05 15:33:31 +02:00
SomberNight
1e3c3a528c
attempt at fixing wallet syncing crash
fix #3998
fix #4689
2018-09-05 15:22:57 +02:00
Janus
73e367dc3b wallet: don't cache NaN coin price
if NaN coin price is cached, historial acquisition prices are not shown
correctly since the historial prices are requested after the full
history is initially shown. As such, "No data" will be shown, even
though the user required using historical pricing.
2018-09-05 14:38:43 +02:00
Calin Culianu
0da1e904fe macOS: Add missing URI handler(#4557)
Backport from Electron Cash -- fix missing bitcoin: URI types form macOS Info.plist
2018-09-04 22:38:38 +02:00
Filip Gospodinov
6c7bfe613f contrib: remove git describe workaround (#4683)
`git describe` fails if no tag can be found, leading
to the whole build script to fail. This is not always
desired. To prevent `git describe` from failing in
this case the `--always` flag can be passed which
causes a short commit hash to be output when a tag
is not present.
2018-09-04 22:32:46 +02:00
SomberNight
e9f1888321
include plugins in win/mac binaries 2018-09-04 17:06:42 +02:00
Filip Gospodinov
9220545e60 build-wine: avoid untracked changes in submodule
The locale output file can simply be output in the folder
where it's actually needed. This also saves a recursive copy.

This makes `.gitignore` in the electrum-locale submodule
obsolete.
2018-09-04 16:53:53 +02:00
SomberNight
1ec71cbaca
follow-up prev
fix #4078
2018-09-04 16:42:08 +02:00
SomberNight
7d84409628
fix #4078 2018-09-04 16:31:35 +02:00
SomberNight
bc35c82619
update submodules 2018-09-04 13:38:49 +02:00
ThomasV
28c49d2c48 update submodules 2018-09-03 15:39:45 +02:00
ThomasV
56e7ba41c4 prepare release 3.2.3 and release notes 2018-09-03 14:57:29 +02:00
SomberNight
1bb1fc37f4
network: don't ask for block -1 if server is on wrong chain 2018-08-30 19:25:42 +02:00
Filip Gospodinov
c42f0dac53 test_bitcoin: fix decorators for running tests twice (#4669)
This bug is triggered if ecc_fast._libsecp256k1 and/or
crypto.AES are not present.

Before, if the first test would have raised an exception
it would have been implicitly caught by returning from
the finally block and hence this effectively masks the
test's outcome. Now, the exception is properly propagated
causing the test to fail if an exception is raised.
2018-08-30 18:53:14 +02:00
SomberNight
6ee689345f
fix -v syntax
After the introduction of arguments for -v, it would sometimes incorrectly consume the CLI cmd as its argument.
This change keeps the old "-v" syntax working, at the cost of having to provide the arguments without a whitespace directly after -v (and the args need to be single letters).
2018-08-30 18:37:03 +02:00
RGauthamRam
aac7a34405 Update __init__.py (#4668)
Resolving the issue #4363
2018-08-30 16:35:01 +02:00
ghost43
c99007bda7
setup.py: add 'gui' extra. potentially build Qt icons files (#4647) 2018-08-30 16:16:14 +02:00
SomberNight
1b19cdd0f4
transaction.py: fix script_GetOp for malformed scripts
related fyookball/electrum#829
2018-08-28 20:17:21 +02:00
SomberNight
0137626a63
wallet restore: remove dead code. add log lines. 2018-08-28 18:20:35 +02:00
ThomasV
c25baf3dd7
Merge pull request #4659 from spesmilo/22nd_century_certs
x509: handle dates in the 22nd century
2018-08-27 16:13:38 +02:00
Janus
262d431ff5 x509: handle dates in the 22nd century 2018-08-24 14:56:58 +02:00
SomberNight
91c369e392
hw wallets: generalise 'minimum_library' for those that provide a version number 2018-08-23 18:31:14 +02:00
ghost43
9279f30363
Merge pull request #4470 from Coldcard/ckcc
Support for new hardware wallet: Coldcard
2018-08-23 16:51:52 +02:00
tiagotrs
2a5f108d4a change prng, add warning against encrypting multiple secrets (#4649)
* substitute python prng generator with hmac_drbg

* add warning, change version

* brick cards version 0

* separate python-drbg module, include tests and license

* import to match PEP 8

* fix line break, minor changes in wording

* fixes noise_seed formatting errors

* fix import, include license exclude tests drbg module
2018-08-22 22:25:12 +02:00
SomberNight
04c1b522d6
minor fixes for prev
use TxOutputHwInfo namedtuple

warn user if device is set to wrong chain

undo parts of prev re testnet. fix p2wpkh.

testnet support. and minor stuff
2018-08-22 21:52:28 +02:00
Peter D. Gray
0bcea80bdf
Support for new hardware wallet: Coldcard
build-wine/deterministic.spec: add Coldcard plugin and ckcc-protocol dependancy

Require version 0.7.2 of ckcc-protocol (window fixes)

Rework import paths to new standards

Updated icons

New minimum version, for latest PSBT constants

Upgrade to final PSBT (BIP 174) standard encoding

Remove log noise

Show bootloader version number as well

Handle case where libraries are missing better

Remove noise about missing packages, for rest of world

Add reference to ckcc-protocol module/data

Remove dead code

Beef up the README more

Slightly better looking

Add version numbers and upgrade firmware feature

Split out DFU support into own file

First pass at adding Coinkite Coldcard hardware wallet to Electrum
2018-08-22 21:43:03 +02:00
SomberNight
bc6010303a
fix #4651 2018-08-22 17:05:48 +02:00
Vivek Teega
1e6edd0abc Fixing exchanges and block explorer for FLO 2018-08-18 11:39:26 +05:30
SomberNight
7044f1145f
ecc_fast: clarify fallback message 2018-08-17 16:01:03 +02:00
ghost43
941df4153b
wallet: try to plug gap limit for change addresses (#4530) 2018-08-15 14:33:12 +02:00
SomberNight
f3f5b8a5d6
fix #4312 2018-08-15 13:43:19 +02:00
SomberNight
db834800c0
wine-build: clarify to use docker for reproducible builds. move parts of readme. 2018-08-15 13:22:24 +02:00
SomberNight
3089edd3a2
wallet: remove method get_num_tx 2018-08-14 21:54:11 +02:00
SomberNight
f7166e95c4
wallet: move get_depending_transactions to AddressSynchronizer
and resolve TODO
2018-08-14 21:53:05 +02:00
SomberNight
b7178f2d21
coinchooser: small clean-up (use TxOutput.value) 2018-08-14 21:50:59 +02:00
SomberNight
88fc62e8f7
fix #4626 2018-08-14 19:38:19 +02:00
SomberNight
5f3408dd70
transaction.py: introduce TxOutputHwInfo namedtuple 2018-08-14 19:15:15 +02:00
SomberNight
b4b1de088a
move TrezorClient.expand_path to bitcoin.py
and allow its input to end with a '/' slash
2018-08-14 18:19:16 +02:00
SomberNight
52a4810752
trezor: add "show address" option in addresses tab context menu for all trezors
in case of a multisig wallet, with multiple trezors, previously only one of the trezors could be used
2018-08-14 15:02:31 +02:00
SomberNight
93578d9be2
wallet: is_mine should not depend on history keys
this makes clear_history work
2018-08-14 14:59:55 +02:00
SomberNight
820316e745
safe_t: disable udp transport as it was interfering with trezor emulator 2018-08-14 13:33:32 +02:00
Vivek Teega
c0daef2657 More changes to for save_chunk()
save_chunk() required more changes to save the block header chunks properly locally.
2018-08-14 15:10:19 +05:30
ThomasV
e7ef4aa4f6
Merge pull request #4611 from tiagotrs/master
fix Revealer hidpi related bug #4576, and make PNGs the same size
2018-08-13 11:21:08 +02:00
Johann Bauer
deee29228e Travis: Use 3.7 to run tests too 2018-08-09 20:42:48 +02:00
tiagotrs
576c2718c8 Merge branch 'master' of https://github.com/spesmilo/electrum 2018-08-07 16:38:06 +02:00
tiagotrs
91bcc5f560 fix problems with hidpi monitors issue #4576, ensure PNGS are precise the same size. 2018-08-07 16:33:58 +02:00
zebra-lucky
059a4fff5c fix ecc_fast.py for win64 (#4606) 2018-08-05 22:30:58 +02:00
Vivek Teegalapally
a64228ba2c Small change to fix save_chunk_part() 2018-08-05 23:21:46 +05:30
Vivek Teegalapally
85bae50e62 Changes for target calculation
All the changes required for target calculation, verification and saving parts of chunks for FLO
@akhil2015

Co-authored-by: Akhil Bharti <akhil2015@users.noreply.github.com>
2018-08-05 23:18:30 +05:30
Vivek Teegalapally
ca30705c69 Commiting comment/floData changes
The changes made in this commit make sure the transaction comments or floData are being sent out of the wallet.
It still has some errors left though, related to verifying SegWit address.
2018-08-05 21:12:18 +05:30
Vivek Teegalapally
95b7aef579 Changing data directories to reflect FLO 2018-08-05 16:18:53 +05:30
Bitspill
e0d33279de Commiting changes made by @bitspill
The following are the changes made by @bitspill to the original repo.
1. Changes to transaction.py to accomodate the txcomment/floData feature
2. Changes in server list
3. Renaming of BTC to FLO
The GUI and code changes required for exchange_rate.py file haven't been implemented. Its an independant module of the wallet, so will be changed later on
2018-08-05 16:10:36 +05:30
Vivek Teega
f008f2e4dc
Merge pull request #2 from spesmilo/master
Merging upstream commits
2018-08-05 14:42:45 +05:30
SomberNight
a7cfa56621
cosigner pool: don't block gui 2018-08-03 20:53:56 +02:00
SomberNight
9228cb5b8e
wallet: override get_addresses in Imported_Wallet so that clear_history works 2018-08-03 19:56:35 +02:00
SomberNight
2a9f5db576
blockchain.py: fix: chunks in checkpoint region were not getting saved if we were on a fork 2018-08-03 19:06:23 +02:00
SomberNight
531cdeffa9
blockchain.py: rename 'checkpoint' to 'forkpoint' 2018-08-03 18:25:53 +02:00
SomberNight
7307c800d7
small optimisations for history tab refresh (and related) 2018-08-03 16:12:41 +02:00
SomberNight
6b42e8448c
address_synchronizer: cache local_height in some cases 2018-08-03 16:10:36 +02:00
SomberNight
cf14d7b346
wallet: change meaning of is_used 2018-08-03 16:02:37 +02:00
SomberNight
6192bfce46
util.profiler: prepend class name to prints 2018-08-02 15:38:01 +02:00
ThomasV
194ee395e7
Merge pull request #4596 from SomberNight/txoutput_namedtuple
transaction: introduce TxOutput namedtuple
2018-08-02 12:49:51 +02:00
SomberNight
2eb72d496f
transaction: introduce TxOutput namedtuple 2018-08-01 19:10:08 +02:00
SomberNight
f64062b6f1
add --noonion option to filter out onion servers
closes #4531
2018-07-31 20:25:53 +02:00
ghost43
32f5feff8f
Merge pull request #4538 from SomberNight/verifier_reorgs_st18
verifier: better handle reorgs (and storage upgrade)
2018-07-31 19:37:46 +02:00
SomberNight
a29e2218c8
wallet: introduce namedtuples TxMinedStatus and VerifiedTxInfo 2018-07-31 17:10:15 +02:00
SomberNight
41e088693d
verifier: better handle reorgs (and storage upgrade) 2018-07-31 15:51:05 +02:00
SomberNight
861640949e
kivy: on tx broadcast, truncate error message
related #4593
2018-07-31 14:03:08 +02:00
SomberNight
c9c8b7656d
follow-up prev. sanity check OP_RETURN outputs
based on 86c63a3a08
2018-07-31 13:03:34 +02:00
SomberNight
e1b2195cf7
fix #4591: pay to OP_RETURN on trezor 2018-07-31 12:30:43 +02:00
SomberNight
629b9cb3b5
fee estimation: split eta_to_fee into two methods 2018-07-30 19:15:05 +02:00
SomberNight
8e69174374
logging: self.print_error should not print without -v flag 2018-07-29 04:29:19 +02:00
SomberNight
eaf72aa951
network: handle one-block-long fork
also add fixme about incorrect behaviour in case of a fork height higher than our local chain tip
2018-07-29 04:00:02 +02:00
ghost43
0d05b84dc3
network: handle reorg (sooner) in case of multiple forks at given height (#4537) 2018-07-29 03:56:10 +02:00
Jean-Christophe Rona
02c30e3d52
Add support for Archos Safe-T mini hardware wallet (#4445)
commit 10c46477f3a6f2fbc0596345511e0994253081eb
Author: SomberNight <somber.night@protonmail.com>
Date:   Wed Jul 25 19:40:05 2018 +0200

    backport changes of trezor plugin

commit 213619e880f709188c1ea6272758896748e681a8
Merge: a855b75b6 6899ca252
Author: Jean-Christophe Rona <jc@rona.fr>
Date:   Wed Jul 25 18:45:19 2018 +0200

    Merge branch 'master' into safe-t-mini

commit a855b75b6f5af5f707c4680d0bac79eb66a85ace
Author: Jean-Christophe Rona <rona@archos.com>
Date:   Wed Jul 25 18:37:12 2018 +0200

    Safe-T: Switch to safet 0.1.3 to remove the rlp dependency

commit 9bee44ca33289158c91c03d47dec45de6577f17b
Author: SomberNight <somber.night@protonmail.com>
Date:   Wed Jul 18 14:01:10 2018 +0200

    safe-t: bump min fw to 1.0.5

    older fw has a bug when restoring from seed

commit 01816607e8ba308cb5cff96b5fb844e4f6b8fcc1
Author: SomberNight <somber.night@protonmail.com>
Date:   Wed Jul 18 13:57:17 2018 +0200

    safe-t: fix rlp version to avoid eth stuff

commit 430206bea1fa10b762ff953fbc7652ce0d0e939d
Merge: a999ae266 b4b862b0c
Author: SomberNight <somber.night@protonmail.com>
Date:   Wed Jul 18 13:29:41 2018 +0200

    Merge branch 'master' into pr/4445

commit a999ae266f499f180946d53d4e860cc871d562ab
Author: Jean-Christophe Rona <rona@archos.com>
Date:   Tue Jun 19 14:18:03 2018 +0200

    Safe-T mini: Remove supported coins

    This is not really useful there.

commit 7922df1031b2c4b132f7f9c90232480b5bf9585c
Author: Jean-Christophe Rona <rona@archos.com>
Date:   Tue May 29 16:43:37 2018 +0200

    Safe-T mini: Add support for the Safe-T mini
2018-07-25 20:11:04 +02:00
SomberNight
6899ca2527
docker-wine: update a package version
the previous version is no longer available. this suggests that it's difficult to reproduce old builds.
not sure about long term solution.
2018-07-24 20:23:33 +02:00
SomberNight
a799a00dc5
fix #4577 2018-07-24 18:57:49 +02:00
SomberNight
579d48cf0c
follow-up a830747f83
on_history expects fewer arguments than what the fee_histogram callback gives
2018-07-24 18:25:22 +02:00
SomberNight
61aa19539c
some packaging clean-up 2018-07-24 17:32:18 +02:00
SomberNight
53130da682
storage: factor out 'JsonDB' 2018-07-23 19:59:10 +02:00
SomberNight
d2abaf54e8
verifier: small refactor 2018-07-23 19:59:05 +02:00
SomberNight
89aa9eb0a7
revealer: minor fix and clean-up 2018-07-22 19:40:10 +02:00
SomberNight
a830747f83
kivy: update history screen on fee histogram
related: #4573
2018-07-21 23:23:25 +02:00
SomberNight
4284f4feb3
fix #4575 2018-07-21 23:09:46 +02:00
SomberNight
f8e13c5c33
kivy: use correct i18n 2018-07-21 16:15:45 +02:00
SomberNight
281805a0a4
linux sdist: 'typing' was not included, which is needed on py3.4
not making typing conditioned on py version as then freeze_packages would not pick it up.
2018-07-20 16:38:18 +02:00
SomberNight
f7dce426cb
fix #4574 2018-07-19 19:52:06 +02:00
SomberNight
597295e359
address_synchronizer fixes
is_mine: wallet expects get_address_index to work
imported wallets: history did not include addr keys after creation
deterministic wallets: get_addresses() should be sorted in derivation order
2018-07-19 18:16:23 +02:00
SomberNight
801d3113ab
wine build: remove pgp.mit.edu from keyservers
sometimes slow, and does not return all the pubkeys asked for (so build fails)
2018-07-19 14:53:04 +02:00
SomberNight
cb6bde49b4
fix some wine build failures on branches/forks 2018-07-19 14:36:30 +02:00
SomberNight
0100af9389
fix #4572 2018-07-19 13:59:38 +02:00
SomberNight
01193be241
logging: when not giving args to -v, log everything, as before 2018-07-19 13:55:05 +02:00
SomberNight
cc77ba523f
fix minor undefined stuff in address_synchronizer 2018-07-19 13:47:49 +02:00
Janus
1fb0b6d7bd plugins/ledger: just hardcode BTCHIP_DEBUG to False 2018-07-19 13:33:57 +02:00
Janus
f9f6ea4365 commands: tolerate lack of argument to 'verbosity' 2018-07-19 12:43:53 +02:00
ThomasV
b96b5af101 fix imports 2018-07-19 10:25:46 +02:00
ThomasV
0025073b24 move more methods from wallet to address_synchronizer 2018-07-19 10:15:22 +02:00
Janus
780b2d067c Whitelist classes in verbose (-v) option 2018-07-19 01:21:33 +02:00
SomberNight
8f17f38b02
trezor/kk: when using old fw, wizard did not display instructions properly 2018-07-18 20:17:03 +02:00
ThomasV
0186f09c27
Merge pull request #4567 from SomberNight/issue_4566
bip39 passphrases with multiple spaces
2018-07-18 18:54:59 +02:00
SomberNight
aa86440866
fix #4566: bip39 passphrases with multiple spaces 2018-07-18 18:42:04 +02:00
Yura Pakhuchiy
27b36486df Trezor: fix spending coinbase outputs (#4565)
Attempt to spend coinbase output results in error:
a bytes-like object is required, not 'str'
2018-07-18 17:39:32 +02:00
SomberNight
e5661156f0
follow-up e3888752d6 2018-07-18 15:32:26 +02:00
ThomasV
bdb8220a1a
Merge pull request #4563 from spesmilo/remove_pbkdf2
remove pbkdf2 dependency, use stdlib instead
2018-07-18 14:49:36 +02:00
Janus
1e715113ab remove pbkdf2 dependency, use stdlib instead 2018-07-18 14:34:59 +02:00
tiagotrs
b4b862b0cc add warning that seed extension will not be included in the backup (#4555) 2018-07-18 13:15:31 +02:00
ThomasV
e3888752d6 separate address synchronizer from wallet 2018-07-18 11:18:57 +02:00
ghost43
69dc762a5a
Merge pull request #4560 from marceloneil/fix/electrum/localization
localization: fix download link + badge
2018-07-18 10:52:58 +02:00
Marcel O'Neil
21204fc552
localization: fix download link + badge 2018-07-17 22:09:04 -04:00
ThomasV
4b74f9c7fb
Merge pull request #4553 from marceloneil/fix/electrum/tests-in-coverage
Remove test files from coverage
2018-07-16 07:22:31 +02:00
Marcel O'Neil
c856633b9c
remove test files from coverage 2018-07-16 00:58:41 -04:00
Johann Bauer
cf7caa7ef9
Don't measure coverage for files in gui or plugins 2018-07-15 19:25:28 +02:00
ThomasV
8ba70ee0e4
Merge pull request #4524 from toxeus/docker
build-wine: allow local testing
2018-07-15 11:21:32 +02:00
ThomasV
88eb2390e6
Merge branch 'master' into docker 2018-07-15 11:13:51 +02:00
SomberNight
8bb59fcc3c
follow-up prev: fix bug in fee_to_depth, and typo and tests 2018-07-14 19:02:41 +02:00
SomberNight
b8ab36546d
mempool fees: increase estimate by max precision of histogram
related: #4551
2018-07-14 18:45:02 +02:00
ThomasV
f8ee203225 rm deprecated info about apk version 2018-07-13 18:57:04 +02:00
SomberNight
b44aca1654
network: disconnect from server on incorrect header length
fix #4522
2018-07-13 18:11:48 +02:00
Janus
87f6aa09df log failure to import plugins or plot module 2018-07-13 15:24:16 +02:00
Janus Troelsen
b29c2a0abd
Correct path to kivy Readme.md 2018-07-13 15:08:00 +02:00
ThomasV
77940148fa fix paths in buildozer/makefile 2018-07-13 14:45:08 +02:00
Janus
1cc1c8a051 restructuring: add missing import 2018-07-13 14:19:02 +02:00
Janus
097ac144d9 file reorganization with top-level module 2018-07-13 14:01:37 +02:00
Randy Brito
30a7952cbb Show fiat options from api.bitcoinvenezuela.com
Adding the options to be able to select BitcoinVenezuela.com fiat exchange rates in the Preferences. Certificate validation has been fixed.
2018-07-12 23:47:13 +02:00
SomberNight
5d462f9555
win binaries: bump python version to 3.6.6 2018-07-12 18:33:43 +02:00
SomberNight
e3d8edd0a0
rerun freeze packages 2018-07-12 18:33:43 +02:00
SomberNight
0ddbd2e575
restrict PyQt version to <5.11 for binaries, as 5.11 raises min MacOS version to 10.11
see spyder-ide/qtpy#155
2018-07-12 18:33:42 +02:00
ThomasV
94dc214982
Merge pull request #4541 from haarts/preliminaries
Preliminaries
2018-07-12 10:12:45 +02:00
Harm Aarts
3f4687d3e4 Improve logging 2018-07-12 09:51:31 +02:00
Harm Aarts
73896bad72 Remove unused is_up_to_date network method 2018-07-12 09:51:31 +02:00
Harm Aarts
48356a03e6 Rename 'r' to 'response' 2018-07-12 09:51:31 +02:00
Harm Aarts
b1cd260aa9 Minor linter nits 2018-07-12 09:51:31 +02:00
Harm Aarts
eb44ef327d Reduce indentation level
This makes the method easier to read.
Skip negative tx heights too. A transaction height can be negative too, see the
wallet modules TX_HEIGHT_LOCAL and TX_HEIGHT_UNCONF_PARENT constants.
2018-07-12 09:50:50 +02:00
SomberNight
358722b9cc
fix #4533 2018-07-11 15:25:05 +02:00
Johann Bauer
40a43afa12 Add OXT.me block explorer
Closes: #4441
2018-07-10 21:52:06 +02:00
SomberNight
a51940fac0
fix #4529 2018-07-10 18:07:52 +02:00
Filip Gospodinov
73fee2fefa build-wine: allow local testing
Before, it was only possible to test commits that are
on Github (pull request or merged). Now, changes can be
tested locally too.

This introduces the risk that a release could be built
containing uncommitted changes which by definition breaks
deterministic builds. Fortunately, this will always be
detected because the version string is created using
`git describe --tags --dirty`.

Also, retire $TARGET variable because it decouples the
build scripts from the commit revision to be built. This
is a problem for deterministic  builds.
2018-07-10 13:33:46 +02:00
512 changed files with 89446 additions and 14442 deletions

14
.gitignore vendored
View File

@ -4,20 +4,24 @@
build/
dist/
*.egg/
/electrum.py
contrib/pyinstaller/
Electrum.egg-info/
gui/qt/icons_rc.py
locale/
electrum/locale/
.devlocaltmp/
*_trial_temp
packages
env/
.tox/
.buildozer/
bin/
/app.fil
.idea
# tox files
# icons
electrum/gui/kivy/theming/light-0.png
electrum/gui/kivy/theming/light.atlas
# tests/tox
.tox/
.cache/
.coverage
.pytest_cache

6
.gitmodules vendored
View File

@ -1,6 +1,6 @@
[submodule "contrib/deterministic-build/electrum-icons"]
path = contrib/deterministic-build/electrum-icons
url = https://github.com/spesmilo/electrum-icons
[submodule "contrib/deterministic-build/electrum-locale"]
path = contrib/deterministic-build/electrum-locale
url = https://github.com/spesmilo/electrum-locale
[submodule "contrib/CalinsQRReader"]
path = contrib/osx/CalinsQRReader
url = https://github.com/spesmilo/CalinsQRReader

View File

@ -1,14 +1,19 @@
sudo: false
sudo: true
dist: xenial
language: python
python:
- 3.5
- 3.6
- 3.7
git:
depth: false
addons:
apt:
sources:
- sourceline: 'ppa:tah83/secp256k1'
packages:
- libsecp256k1-0
before_install:
- git tag
install:
- pip install -r contrib/requirements/requirements-travis.txt
cache:
@ -18,11 +23,12 @@ cache:
script:
- tox
after_success:
- if [ "$TRAVIS_BRANCH" = "master" ]; then pip install pycurl requests && contrib/make_locale; fi
- if [ "$TRAVIS_BRANCH" = "master" ]; then pip install requests && contrib/make_locale; fi
- coveralls
jobs:
include:
- stage: binary builds
name: "Windows build"
sudo: true
language: c
python: false
@ -31,25 +37,57 @@ jobs:
services:
- docker
install:
- sudo docker build --no-cache -t electrum-wine-builder-img ./contrib/build-wine/docker/
- sudo docker build --no-cache -t electrum-wine-builder-img ./contrib/build-wine/docker/
script:
- sudo docker run --name electrum-wine-builder-cont -v $PWD:/opt/electrum --rm --workdir /opt/electrum/contrib/build-wine electrum-wine-builder-img ./build.sh $TRAVIS_COMMIT
- sudo docker run --name electrum-wine-builder-cont -v $PWD:/opt/wine64/drive_c/electrum --rm --workdir /opt/wine64/drive_c/electrum/contrib/build-wine electrum-wine-builder-img ./build.sh
after_success: true
- os: osx
- name: "Android build"
language: python
python: 3.7
env:
# reset API key to not have make_locale upload stuff here
- crowdin_api_key=
services:
- docker
install:
- pip install requests && ./contrib/make_locale
- ./contrib/make_packages
- sudo docker build --no-cache -t electrum-android-builder-img electrum/gui/kivy/tools
script:
- sudo chown -R 1000:1000 .
# Output something every minute or Travis kills the job
- while sleep 60; do echo "=====[ $SECONDS seconds still running ]====="; done &
- sudo docker run -it -u 1000:1000 --rm --name electrum-android-builder-cont -v $PWD:/home/user/wspace/electrum --workdir /home/user/wspace/electrum electrum-android-builder-img ./contrib/make_apk
# kill background sleep loop
- kill %1
- ls -la bin
- if [ $(ls bin | grep -c Electrum-*) -eq 0 ]; then exit 1; fi
after_success: true
- name: "MacOS build"
os: osx
language: c
env:
- TARGET_OS=macOS
python: false
install:
- git fetch --all --tags
- git fetch origin --unshallow
script: ./contrib/build-osx/make_osx
script: ./contrib/osx/make_osx
after_script: ls -lah dist && md5 dist/*
after_success: true
- name: "AppImage build"
sudo: true
language: c
python: false
services:
- docker
install:
- sudo docker build --no-cache -t electrum-appimage-builder-img ./contrib/build-linux/appimage/
script:
- sudo docker run --name electrum-appimage-builder-cont -v $PWD:/opt/electrum --rm --workdir /opt/electrum/contrib/build-linux/appimage electrum-appimage-builder-img ./build.sh
after_success: true
- stage: release check
install:
- git fetch --all --tags
- git fetch origin --unshallow
script:
- ./contrib/deterministic-build/check_submodules.sh
after_success: true

11
AUTHORS
View File

@ -1,3 +1,5 @@
Electrum-BTC
------------
ThomasV - Creator and maintainer.
Animazing / Tachikoma - Styled the new GUI. Mac version.
Azelphur - GUI stuff.
@ -10,3 +12,12 @@ Slush - Work on the server. Designed the original Stratum spec.
Julian Toash (Tuxavant) - Various fixes to the client.
rdymac - Website and translations.
kyuupichan - Miscellaneous.
FLO-Electrum
------------
vivekteega - Maintainer and remaining stuff
Bitspill - Bootstraped the project with core FLO changes
Rohit Tripathy - Ideation and problem solving
akhil2015 - Flodata and scrypt hashing

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>bitcoin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bitcoin</string>
</array>
</dict>
</array>
<key>LSArchitecturePriority</key>
<array>
<string>x86_64</string>
<string>i386</string>
</array>
</dict>
</plist>

View File

@ -1,17 +1,17 @@
include LICENCE RELEASE-NOTES AUTHORS
include README.rst
include electrum.conf.sample
include electrum.desktop
include *.py
include electrum
include run_electrum
include contrib/requirements/requirements.txt
include contrib/requirements/requirements-hw.txt
recursive-include lib *.py
recursive-include gui *.py
recursive-include plugins *.py
recursive-include packages *.py
recursive-include packages cacert.pem
include icons.qrc
recursive-include icons *
recursive-include scripts *
graft electrum
prune electrum/tests
global-exclude __pycache__
global-exclude *.py[co~]
global-exclude *.py.orig
global-exclude *.py.rej

View File

@ -5,7 +5,7 @@ Electrum - Lightweight Bitcoin client
Licence: MIT Licence
Author: Thomas Voegtlin
Language: Python
Language: Python (>= 3.6)
Homepage: https://electrum.org/
@ -15,9 +15,9 @@ Electrum - Lightweight Bitcoin client
.. image:: https://coveralls.io/repos/github/spesmilo/electrum/badge.svg?branch=master
:target: https://coveralls.io/github/spesmilo/electrum?branch=master
:alt: Test coverage statistics
.. image:: https://img.shields.io/badge/help-translating-blue.svg
.. image:: https://d322cqt584bo4o.cloudfront.net/electrum/localized.svg
:target: https://crowdin.com/project/electrum
:alt: Help translating Electrum online
:alt: Help translate Electrum online
@ -32,19 +32,19 @@ Qt interface, install the Qt dependencies::
sudo apt-get install python3-pyqt5
If you downloaded the official package (tar.gz), you can run
Electrum from its root directory, without installing it on your
Electrum from its root directory without installing it on your
system; all the python dependencies are included in the 'packages'
directory. To run Electrum from its root directory, just do::
./electrum
./run_electrum
You can also install Electrum on your system, by running this command::
sudo apt-get install python3-setuptools
pip3 install .[fast]
python3 -m pip install .[fast]
This will download and install the Python dependencies used by
Electrum, instead of using the 'packages' directory.
Electrum instead of using the 'packages' directory.
The 'fast' extra contains some optional dependencies that we think
are often useful but they are not strictly needed.
@ -64,21 +64,13 @@ Check out the code from GitHub::
Run install (this should install dependencies)::
pip3 install .[fast]
python3 -m pip install .[fast]
Render the SVG icons to PNGs (optional)::
for i in lock unlock confirmed status_lagging status_disconnected status_connected_proxy status_connected status_waiting preferences; do convert -background none icons/$i.svg icons/$i.png; done
Compile the icons file for Qt::
sudo apt-get install pyqt5-dev-tools
pyrcc5 icons.qrc -o gui/qt/icons_rc.py
Compile the protobuf description file::
sudo apt-get install protobuf-compiler
protoc --proto_path=lib/ --python_out=lib/ lib/paymentrequest.proto
protoc --proto_path=electrum --python_out=electrum electrum/paymentrequest.proto
Create translations (optional)::
@ -91,25 +83,25 @@ Create translations (optional)::
Creating Binaries
=================
Linux
-----
To create binaries, create the 'packages' directory::
See :code:`contrib/build-linux/README.md`.
./contrib/make_packages
This directory contains the python dependencies used by Electrum.
Mac OS X / macOS
--------
----------------
See :code:`contrib/osx/README.md`.
See `contrib/build-osx/`.
Windows
-------
See `contrib/build-wine/`.
See :code:`contrib/build-wine/docker/README.md`.
Android
-------
See `gui/kivy/Readme.txt` file.
See :code:`electrum/gui/kivy/Readme.md`.

View File

@ -1,3 +1,95 @@
# Release 3.3.4 - (February 13, 2019)
* AppImage: we now also distribute self-contained binaries for x86_64
Linux in the form of an AppImage (#5042). The Python interpreter,
PyQt5, libsecp256k1, PyCryptodomex, zbar, hidapi/libusb (including
hardware wallet libraries) are all bundled. Note that users of
hw wallets still need to set udev rules themselves.
* hw wallets: fix a regression during transaction signing that prompts
the user too many times for confirmations (commit 2729909)
* transactions now set nVersion to 2, to mimic Bitcoin Core
* fix Qt bug that made all hw wallets unusable on Windows 8.1 (#4960)
* fix bugs in wallet creation wizard that resulted in corrupted
wallets being created in rare cases (#5082, #5057)
* fix compatibility with Qt 5.12 (#5109)
# Release 3.3.3 - (January 25, 2019)
* Do not expose users to server error messages (#4968)
* Notify users of new releases. Release announcements must be signed,
and they are verified byElectrum using a hardcoded Bitcoin address.
* Hardware wallet fixes (#4991, #4993, #5006)
* Display only QR code in QRcode Window
* Fixed code signing on MacOS
* Randomise locktime of transactions
# Release 3.3.2 - (December 21, 2018)
* Fix Qt history export bug
* Improve network timeouts
* Prepend server transaction_broadcast error messages with
explanatory message. Render error messages as plain text.
# Release 3.3.1 - (December 20, 2018)
* Qt: Fix invoices tab crash (#4941)
* Android: Minor GUI improvements
# Release 3.3.0 - Hodler's Edition (December 19, 2018)
* The network layer has been rewritten using asyncio and aiorpcx.
In addition to easier maintenance, this makes the client
more robust against misbehaving servers.
* The minimum python version was increased to 3.6
* The blockchain headers and fork handling logic has been generalized.
Clients by default now follow chain based on most work, not length.
* New wallet creation defaults to native segwit (bech32).
* Segwit 2FA: TrustedCoin now supports native segwit p2wsh
two-factor wallets.
* RBF batching (opt-in): If the wallet has an unconfirmed RBF
transaction, new payments will be added to that transaction,
instead of creating new transactions.
* MacOS: support QR code scanner in binaries.
* Android APK:
- build using Google NDK instead of Crystax NDK
- target API 28
- do not use external storage (previously for block headers)
* hardware wallets:
- Coldcard now supports spending from p2wpkh-p2sh,
fixed p2pkh signing for fw 1.1.0
- Archos Safe-T mini: fix #4726 signing issue
- KeepKey: full segwit support
- Trezor: refactoring and compat with python-trezor 0.11
- Digital BitBox: support firmware v5.0.0
* fix bitcoin URI handling when app already running (#4796)
* Qt listings rewritten:
the History tab now uses QAbstractItemModel, the other tabs use
QStandardItemModel. Performance should be better for large wallets.
* Several other minor bugfixes and usability improvements.
# Release 3.2.3 - (September 3, 2018)
* hardware wallet: the Safe-T mini from Archos is now supported.
* hardware wallet: the Coldcard from Coinkite is now supported.
* BIP39 seeds: if a seed extension (aka passphrase) contained
multiple consecutive whitespaces or leading/trailing whitespaces
then the derived addresses were not following spec. This has been
fixed, and affected should move their coins. The wizard will show a
warning in this case. (#4566)
* Revealer: the PRNG used has been changed (#4649)
* fix Linux distributables: 'typing' was not bundled, needed for python 3.4
* fix #4626: fix spending from segwit multisig wallets involving a Trezor
cosigner when using a custom derivation path
* fix #4491: on Android, if user had set "uBTC" as base unit, app crashed
* fix #4497: on Android, paying bip70 invoices from cold start did not work
* Several other minor bugfixes and usability improvements.
# Release 3.2.2 - (July 2nd, 2018)
* Fix DNS resolution on Windows
@ -205,7 +297,7 @@ issue #3374. Users should upgrade to 3.0.5.
* Qt GUI: sweeping now uses the Send tab, allowing fees to be set
* Windows: if using the installer binary, there is now a separate shortcut
for "Electrum Testnet"
* Digital Bitbox: added suport for p2sh-segwit
* Digital Bitbox: added support for p2sh-segwit
* OS notifications for incoming transactions
* better transaction size estimation:
- fees for segwit txns were somewhat underestimated (#3347)
@ -433,7 +525,7 @@ issue #3374. Users should upgrade to 3.0.5.
# Release 2.7.7
* Fix utf8 encoding bug with old wallet seeds (issue #1967)
* Fix delete request from menu (isue #1968)
* Fix delete request from menu (issue #1968)
# Release 2.7.6
* Fixes a critical bug with imported private keys (issue #1966). Keys
@ -796,7 +888,7 @@ issue #3374. Users should upgrade to 3.0.5.
* New 'Receive' tab in the GUI:
- create and manage payment requests, with QR Codes
- the former 'Receive' tab was renamed to 'Addresses'
- the former Point of Sale plugin is replaced by a resizeable
- the former Point of Sale plugin is replaced by a resizable
window that pops up if you click on the QR code
* The 'Send' tab in the Qt GUI supports transactions with multiple
@ -819,7 +911,7 @@ issue #3374. Users should upgrade to 3.0.5.
* The client accepts servers with a CA-signed SSL certificate.
* ECIES encrypt/decrypt methods, availabe in the GUI and using
* ECIES encrypt/decrypt methods, available in the GUI and using
the command line:
encrypt <pubkey> <message>
decrypt <pubkey> <message>
@ -892,7 +984,7 @@ bugfixes: connection problems, transactions staying unverified
# Release 1.8.1
* Notification option when receiving new tranactions
* Notification option when receiving new transactions
* Confirm dialogue before sending large amounts
* Alternative datafile location for non-windows systems
* Fix offline wallet creation

View File

@ -0,0 +1,20 @@
Source tarballs
===============
1. Build locale files
```
contrib/make_locale
```
2. Prepare python dependencies used by Electrum.
```
contrib/make_packages
```
3. Create source tarball.
```
contrib/make_tgz
```

View File

@ -0,0 +1,25 @@
FROM ubuntu:14.04@sha256:cac55e5d97fad634d954d00a5c2a56d80576a08dcc01036011f26b88263f1578
ENV LC_ALL=C.UTF-8 LANG=C.UTF-8
RUN apt-get update -q && \
apt-get install -qy \
git \
wget \
make \
autotools-dev \
autoconf \
libtool \
xz-utils \
libssl-dev \
zlib1g-dev \
libffi6 \
libffi-dev \
libusb-1.0-0-dev \
libudev-dev \
gettext \
libzbar0 \
&& \
rm -rf /var/lib/apt/lists/* && \
apt-get autoremove -y && \
apt-get clean

View File

@ -0,0 +1,41 @@
AppImage binary for Electrum
============================
This assumes an Ubuntu host, but it should not be too hard to adapt to another
similar system. The docker commands should be executed in the project's root
folder.
1. Install Docker
```
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
$ sudo apt-get update
$ sudo apt-get install -y docker-ce
```
2. Build image
```
$ sudo docker build --no-cache -t electrum-appimage-builder-img contrib/build-linux/appimage
```
3. Build binary
```
$ sudo docker run -it \
--name electrum-appimage-builder-cont \
-v $PWD:/opt/electrum \
--rm \
--workdir /opt/electrum/contrib/build-linux/appimage \
electrum-appimage-builder-img \
./build.sh
```
4. The generated binary is in `./dist`.
## FAQ
### How can I see what is included in the AppImage?
Execute the binary as follows: `./electrum*.AppImage --appimage-extract`

View File

@ -0,0 +1,11 @@
#!/bin/bash
set -e
APPDIR="$(dirname "$(readlink -e "$0")")"
export LD_LIBRARY_PATH="${APPDIR}/usr/lib/:${APPDIR}/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH+:$LD_LIBRARY_PATH}"
export PATH="${APPDIR}/usr/bin:${PATH}"
export LDFLAGS="-L${APPDIR}/usr/lib/x86_64-linux-gnu -L${APPDIR}/usr/lib"
exec "${APPDIR}/usr/bin/python3.6" -s "${APPDIR}/usr/bin/electrum" "$@"

View File

@ -0,0 +1,197 @@
#!/bin/bash
set -e
PROJECT_ROOT="$(dirname "$(readlink -e "$0")")/../../.."
CONTRIB="$PROJECT_ROOT/contrib"
DISTDIR="$PROJECT_ROOT/dist"
BUILDDIR="$CONTRIB/build-linux/appimage/build/appimage"
APPDIR="$BUILDDIR/electrum.AppDir"
CACHEDIR="$CONTRIB/build-linux/appimage/.cache/appimage"
# pinned versions
PYTHON_VERSION=3.6.8
PKG2APPIMAGE_COMMIT="83483c2971fcaa1cb0c1253acd6c731ef8404381"
LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7"
VERSION=`git describe --tags --dirty --always`
APPIMAGE="$DISTDIR/electrum-$VERSION-x86_64.AppImage"
rm -rf "$BUILDDIR"
mkdir -p "$APPDIR" "$CACHEDIR" "$DISTDIR"
. "$CONTRIB"/build_tools_util.sh
info "downloading some dependencies."
download_if_not_exist "$CACHEDIR/functions.sh" "https://raw.githubusercontent.com/AppImage/pkg2appimage/$PKG2APPIMAGE_COMMIT/functions.sh"
verify_hash "$CACHEDIR/functions.sh" "a73a21a6c1d1e15c0a9f47f017ae833873d1dc6aa74a4c840c0b901bf1dcf09c"
download_if_not_exist "$CACHEDIR/appimagetool" "https://github.com/probonopd/AppImageKit/releases/download/11/appimagetool-x86_64.AppImage"
verify_hash "$CACHEDIR/appimagetool" "c13026b9ebaa20a17e7e0a4c818a901f0faba759801d8ceab3bb6007dde00372"
download_if_not_exist "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" "https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tar.xz"
verify_hash "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" "35446241e995773b1bed7d196f4b624dadcadc8429f26282e756b2fb8a351193"
info "building python."
tar xf "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" -C "$BUILDDIR"
(
cd "$BUILDDIR/Python-$PYTHON_VERSION"
export SOURCE_DATE_EPOCH=1530212462
./configure \
--cache-file="$CACHEDIR/python.config.cache" \
--prefix="$APPDIR/usr" \
--enable-ipv6 \
--enable-shared \
--with-threads \
-q
make -s
make -s install > /dev/null
)
info "building libsecp256k1."
(
git clone https://github.com/bitcoin-core/secp256k1 "$CACHEDIR"/secp256k1 || (cd "$CACHEDIR"/secp256k1 && git pull)
cd "$CACHEDIR"/secp256k1
git reset --hard "$LIBSECP_VERSION"
git clean -f -x -q
export SOURCE_DATE_EPOCH=1530212462
./autogen.sh
echo "LDFLAGS = -no-undefined" >> Makefile.am
./configure \
--prefix="$APPDIR/usr" \
--enable-module-recovery \
--enable-experimental \
--enable-module-ecdh \
--disable-jni \
-q
make -s
make -s install > /dev/null
)
appdir_python() {
env \
PYTHONNOUSERSITE=1 \
LD_LIBRARY_PATH="$APPDIR/usr/lib:$APPDIR/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH+:$LD_LIBRARY_PATH}" \
"$APPDIR/usr/bin/python3.6" "$@"
}
python='appdir_python'
info "installing pip."
"$python" -m ensurepip
info "preparing electrum-locale."
(
cd "$PROJECT_ROOT"
git submodule update --init
pushd "$CONTRIB"/deterministic-build/electrum-locale
if ! which msgfmt > /dev/null 2>&1; then
echo "Please install gettext"
exit 1
fi
for i in ./locale/*; do
dir="$PROJECT_ROOT/electrum/$i/LC_MESSAGES"
mkdir -p $dir
msgfmt --output-file="$dir/electrum.mo" "$i/electrum.po" || true
done
popd
)
info "installing electrum and its dependencies."
mkdir -p "$CACHEDIR/pip_cache"
"$python" -m pip install --cache-dir "$CACHEDIR/pip_cache" -r "$CONTRIB/deterministic-build/requirements.txt"
"$python" -m pip install --cache-dir "$CACHEDIR/pip_cache" -r "$CONTRIB/deterministic-build/requirements-binaries.txt"
"$python" -m pip install --cache-dir "$CACHEDIR/pip_cache" -r "$CONTRIB/deterministic-build/requirements-hw.txt"
"$python" -m pip install --cache-dir "$CACHEDIR/pip_cache" "$PROJECT_ROOT"
info "copying zbar"
cp "/usr/lib/libzbar.so.0" "$APPDIR/usr/lib/libzbar.so.0"
info "desktop integration."
cp "$PROJECT_ROOT/electrum.desktop" "$APPDIR/electrum.desktop"
cp "$PROJECT_ROOT/electrum/gui/icons/electrum.png" "$APPDIR/electrum.png"
# add launcher
cp "$CONTRIB/build-linux/appimage/apprun.sh" "$APPDIR/AppRun"
info "finalizing AppDir."
(
export PKG2AICOMMIT="$PKG2APPIMAGE_COMMIT"
. "$CACHEDIR/functions.sh"
cd "$APPDIR"
# copy system dependencies
# note: temporarily move PyQt5 out of the way so
# we don't try to bundle its system dependencies.
mv "$APPDIR/usr/lib/python3.6/site-packages/PyQt5" "$BUILDDIR"
copy_deps; copy_deps; copy_deps
move_lib
mv "$BUILDDIR/PyQt5" "$APPDIR/usr/lib/python3.6/site-packages"
# apply global appimage blacklist to exclude stuff
# move usr/include out of the way to preserve usr/include/python3.6m.
mv usr/include usr/include.tmp
delete_blacklisted
mv usr/include.tmp usr/include
)
info "stripping binaries from debug symbols."
strip_binaries()
{
chmod u+w -R "$APPDIR"
{
printf '%s\0' "$APPDIR/usr/bin/python3.6"
find "$APPDIR" -type f -regex '.*\.so\(\.[0-9.]+\)?$' -print0
} | xargs -0 --no-run-if-empty --verbose -n1 strip
}
strip_binaries
remove_emptydirs()
{
find "$APPDIR" -type d -empty -print0 | xargs -0 --no-run-if-empty rmdir -vp --ignore-fail-on-non-empty
}
remove_emptydirs
info "removing some unneeded stuff to decrease binary size."
rm -rf "$APPDIR"/usr/lib/python3.6/test
rm -rf "$APPDIR"/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/translations/qtwebengine_locales
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/resources/qtwebengine_*
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/qml
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Web*
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Designer*
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Qml*
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Quick*
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Location*
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Test*
rm -rf "$APPDIR"/usr/lib/python3.6/site-packages/PyQt5/Qt/lib/libQt5Xml*
info "creating the AppImage."
(
cd "$BUILDDIR"
chmod +x "$CACHEDIR/appimagetool"
"$CACHEDIR/appimagetool" --appimage-extract
env VERSION="$VERSION" ./squashfs-root/AppRun --no-appstream --verbose "$APPDIR" "$APPIMAGE"
)
info "done."
ls -la "$DISTDIR"
sha256sum "$DISTDIR"/*

View File

@ -1,36 +0,0 @@
Building Mac OS binaries
========================
This guide explains how to build Electrum binaries for macOS systems.
The build process consists of two steps:
## 1. Building the binary
This needs to be done on a system running macOS or OS X. We use El Capitan (10.11.6) as building it on High Sierra
makes the binaries incompatible with older versions.
Before starting, make sure that the Xcode command line tools are installed (e.g. you have `git`).
cd electrum
./contrib/build-osx/make_osx
This creates a folder named Electrum.app.
## 2. Building the image
The usual way to distribute macOS applications is to use image files containing the
application. Although these images can be created on a Mac with the built-in `hdiutil`,
they are not deterministic.
Instead, we use the toolchain that Bitcoin uses: genisoimage and libdmg-hfsplus.
These tools do not work on macOS, so you need a separate Linux machine (or VM).
Copy the Electrum.app directory over and install the dependencies, e.g.:
apt install libcap-dev cmake make gcc faketime
Then you can just invoke `package.sh` with the path to the app:
cd electrum
./contrib/build-osx/package.sh ~/Electrum.app/

View File

@ -1,12 +0,0 @@
#!/usr/bin/env bash
RED='\033[0;31m'
BLUE='\033[0,34m'
NC='\033[0m' # No Color
function info {
printf "\r💬 ${BLUE}INFO:${NC} ${1}\n"
}
function fail {
printf "\r🗯 ${RED}ERROR:${NC} ${1}\n"
exit 1
}

View File

@ -1,94 +0,0 @@
#!/usr/bin/env bash
# Parameterize
PYTHON_VERSION=3.6.4
BUILDDIR=/tmp/electrum-build
PACKAGE=Electrum
GIT_REPO=https://github.com/spesmilo/electrum
LIBSECP_VERSION=452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4
. $(dirname "$0")/base.sh
src_dir=$(dirname "$0")
cd $src_dir/../..
export PYTHONHASHSEED=22
VERSION=`git describe --tags --dirty`
which brew > /dev/null 2>&1 || fail "Please install brew from https://brew.sh/ to continue"
info "Installing Python $PYTHON_VERSION"
export PATH="~/.pyenv/bin:~/.pyenv/shims:~/Library/Python/3.6/bin:$PATH"
if [ -d "~/.pyenv" ]; then
pyenv update
else
curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash > /dev/null 2>&1
fi
PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install -s $PYTHON_VERSION && \
pyenv global $PYTHON_VERSION || \
fail "Unable to use Python $PYTHON_VERSION"
info "Installing pyinstaller"
python3 -m pip install git+https://github.com/ecdsa/pyinstaller@fix_2952 -I --user || fail "Could not install pyinstaller"
info "Using these versions for building $PACKAGE:"
sw_vers
python3 --version
echo -n "Pyinstaller "
pyinstaller --version
rm -rf ./dist
git submodule init
git submodule update
rm -rf $BUILDDIR > /dev/null 2>&1
mkdir $BUILDDIR
cp -R ./contrib/deterministic-build/electrum-locale/locale/ ./lib/locale/
cp ./contrib/deterministic-build/electrum-icons/icons_rc.py ./gui/qt/
info "Downloading libusb..."
curl https://homebrew.bintray.com/bottles/libusb-1.0.22.el_capitan.bottle.tar.gz | \
tar xz --directory $BUILDDIR
cp $BUILDDIR/libusb/1.0.22/lib/libusb-1.0.dylib contrib/build-osx
info "Building libsecp256k1"
brew install autoconf automake libtool
git clone https://github.com/bitcoin-core/secp256k1 $BUILDDIR/secp256k1
pushd $BUILDDIR/secp256k1
git reset --hard $LIBSECP_VERSION
git clean -f -x -q
./autogen.sh
./configure --enable-module-recovery --enable-experimental --enable-module-ecdh --disable-jni
make
popd
cp $BUILDDIR/secp256k1/.libs/libsecp256k1.0.dylib contrib/build-osx
info "Installing requirements..."
python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user && \
python3 -m pip install -Ir ./contrib/deterministic-build/requirements-binaries.txt --user || \
fail "Could not install requirements"
info "Installing hardware wallet requirements..."
python3 -m pip install -Ir ./contrib/deterministic-build/requirements-hw.txt --user || \
fail "Could not install hardware wallet requirements"
info "Building $PACKAGE..."
python3 setup.py install --user > /dev/null || fail "Could not build $PACKAGE"
info "Faking timestamps..."
for d in ~/Library/Python/ ~/.pyenv .; do
pushd $d
find . -exec touch -t '200101220000' {} +
popd
done
info "Building binary"
pyinstaller --noconfirm --ascii --clean --name $VERSION contrib/build-osx/osx.spec || fail "Could not build binary"
info "Creating .DMG"
hdiutil create -fs HFS+ -volname $PACKAGE -srcfolder dist/$PACKAGE.app dist/electrum-$VERSION.dmg || fail "Could not create .DMG"

View File

@ -1,97 +0,0 @@
# -*- mode: python -*-
from PyInstaller.utils.hooks import collect_data_files, collect_submodules, collect_dynamic_libs
import sys
import os
PACKAGE='Electrum'
PYPKG='electrum'
MAIN_SCRIPT='electrum'
ICONS_FILE='electrum.icns'
for i, x in enumerate(sys.argv):
if x == '--name':
VERSION = sys.argv[i+1]
break
else:
raise Exception('no version')
electrum = os.path.abspath(".") + "/"
block_cipher = None
# see https://github.com/pyinstaller/pyinstaller/issues/2005
hiddenimports = []
hiddenimports += collect_submodules('trezorlib')
hiddenimports += collect_submodules('btchip')
hiddenimports += collect_submodules('keepkeylib')
hiddenimports += collect_submodules('websocket')
datas = [
(electrum+'lib/*.json', PYPKG),
(electrum+'lib/wordlist/english.txt', PYPKG + '/wordlist'),
(electrum+'lib/locale', PYPKG + '/locale'),
(electrum+'plugins', PYPKG + '_plugins'),
]
datas += collect_data_files('trezorlib')
datas += collect_data_files('btchip')
datas += collect_data_files('keepkeylib')
# Add libusb so Trezor will work
binaries = [(electrum + "contrib/build-osx/libusb-1.0.dylib", ".")]
binaries += [(electrum + "contrib/build-osx/libsecp256k1.0.dylib", ".")]
# Workaround for "Retro Look":
binaries += [b for b in collect_dynamic_libs('PyQt5') if 'macstyle' in b[0]]
# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
a = Analysis([electrum+MAIN_SCRIPT,
electrum+'gui/qt/main_window.py',
electrum+'gui/text.py',
electrum+'lib/util.py',
electrum+'lib/wallet.py',
electrum+'lib/simple_config.py',
electrum+'lib/bitcoin.py',
electrum+'lib/dnssec.py',
electrum+'lib/commands.py',
electrum+'plugins/cosigner_pool/qt.py',
electrum+'plugins/email_requests/qt.py',
electrum+'plugins/trezor/client.py',
electrum+'plugins/trezor/qt.py',
electrum+'plugins/keepkey/qt.py',
electrum+'plugins/ledger/qt.py',
],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[])
# http://stackoverflow.com/questions/19055089/pyinstaller-onefile-warning-pyconfig-h-when-importing-scipy-or-scipy-signal
for d in a.datas:
if 'pyconfig' in d[0]:
a.datas.remove(d)
break
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.datas,
name=PACKAGE,
debug=False,
strip=False,
upx=True,
icon=electrum+ICONS_FILE,
console=False)
app = BUNDLE(exe,
version = VERSION,
name=PACKAGE + '.app',
icon=electrum+ICONS_FILE,
bundle_identifier=None,
info_plist={
'NSHighResolutionCapable': 'True',
'NSSupportsAutomaticGraphicsSwitching': 'True'
}
)

View File

@ -2,7 +2,8 @@ Windows Binary Builds
=====================
These scripts can be used for cross-compilation of Windows Electrum executables from Linux/Wine.
Produced binaries are deterministic, so you should be able to generate binaries that match the official releases.
For reproducible builds, see the `docker` folder.
Usage:
@ -34,49 +35,3 @@ The binaries are also built by Travis CI, so if you are having problems,
2. Make sure `/opt` is writable by the current user.
3. Run `build.sh`.
4. The generated binaries are in `./dist`.
Code Signing
============
Electrum Windows builds are signed with a Microsoft Authenticode™ code signing
certificate in addition to the GPG-based signatures.
The advantage of using Authenticode is that Electrum users won't receive a
Windows SmartScreen warning when starting it.
The release signing procedure involves a signer (the holder of the
certificate/key) and one or multiple trusted verifiers:
| Signer | Verifier |
|-----------------------------------------------------------|-----------------------------------|
| Build .exe files using `build.sh` | |
| Sign .exe with `./sign.sh` | |
| Upload signed files to download server | |
| | Build .exe files using `build.sh` |
| | Compare files using `unsign.sh` |
| | Sign .exe file using `gpg -b` |
| Signer and verifiers:
| Upload signatures to 'electrum-signatures' repo, as `$version/$filename.$builder.asc` |
Verify Integrity of signed binary
=================================
Every user can verify that the official binary was created from the source code in this
repository. To do so, the Authenticode signature needs to be stripped since the signature
is not reproducible.
This procedure removes the differences between the signed and unsigned binary:
1. Remove the signature from the signed binary using osslsigncode or signtool.
2. Set the COFF image checksum for the signed binary to 0x0. This is necessary
because pyinstaller doesn't generate a checksum.
3. Append null bytes to the _unsigned_ binary until the byte count is a multiple
of 8.
The script `unsign.sh` performs these steps.

View File

@ -1,14 +1,13 @@
#!/bin/bash
NAME_ROOT=electrum
PYTHON_VERSION=3.5.4
# These settings probably don't need any change
export WINEPREFIX=/opt/wine64
export PYTHONDONTWRITEBYTECODE=1
export PYTHONHASHSEED=22
PYHOME=c:/python$PYTHON_VERSION
PYHOME=c:/python3
PYTHON="wine $PYHOME/python.exe -OO -B"
@ -19,29 +18,13 @@ set -e
mkdir -p tmp
cd tmp
if [ -d ./electrum ]; then
rm ./electrum -rf
fi
pushd $WINEPREFIX/drive_c/electrum
git clone https://github.com/spesmilo/electrum -b master
pushd electrum
if [ ! -z "$1" ]; then
# a commit/tag/branch was specified
if ! git cat-file -e "$1" 2> /dev/null
then # can't find target
# try pull requests
git config --local --add remote.origin.fetch '+refs/pull/*/merge:refs/remotes/origin/pr/*'
git fetch --all
fi
git checkout $1
fi
# Load electrum-icons and electrum-locale for this release
# Load electrum-locale for this release
git submodule init
git submodule update
VERSION=`git describe --tags --dirty`
VERSION=`git describe --tags --dirty --always`
echo "Last commit: $VERSION"
pushd ./contrib/deterministic-build/electrum-locale
@ -50,7 +33,7 @@ if ! which msgfmt > /dev/null 2>&1; then
exit 1
fi
for i in ./locale/*; do
dir=$i/LC_MESSAGES
dir=$WINEPREFIX/drive_c/electrum/electrum/$i/LC_MESSAGES
mkdir -p $dir
msgfmt --output-file=$dir/electrum.mo $i/electrum.po || true
done
@ -59,11 +42,7 @@ popd
find -exec touch -d '2000-11-11T11:11:11+00:00' {} +
popd
rm -rf $WINEPREFIX/drive_c/electrum
cp -r electrum $WINEPREFIX/drive_c/electrum
cp electrum/LICENCE .
cp -r ./electrum/contrib/deterministic-build/electrum-locale/locale $WINEPREFIX/drive_c/electrum/lib/
cp ./electrum/contrib/deterministic-build/electrum-icons/icons_rc.py $WINEPREFIX/drive_c/electrum/gui/qt/
cp $WINEPREFIX/drive_c/electrum/LICENCE .
# Install frozen dependencies
$PYTHON -m pip install -r ../../deterministic-build/requirements.txt
@ -71,7 +50,7 @@ $PYTHON -m pip install -r ../../deterministic-build/requirements.txt
$PYTHON -m pip install -r ../../deterministic-build/requirements-hw.txt
pushd $WINEPREFIX/drive_c/electrum
$PYTHON setup.py install
$PYTHON -m pip install .
popd
cd ..
@ -79,7 +58,7 @@ cd ..
rm -rf dist/
# build standalone and portable versions
wine "C:/python$PYTHON_VERSION/scripts/pyinstaller.exe" --noconfirm --ascii --clean --name $NAME_ROOT-$VERSION -w deterministic.spec
wine "$PYHOME/scripts/pyinstaller.exe" --noconfirm --ascii --clean --name $NAME_ROOT-$VERSION -w deterministic.spec
# set timestamps in dist, in order to make the installer reproducible
pushd dist
@ -95,4 +74,4 @@ mv electrum-setup.exe $NAME_ROOT-$VERSION-setup.exe
cd ..
echo "Done."
md5sum dist/electrum*exe
sha256sum dist/electrum*exe

View File

@ -29,7 +29,8 @@ else
git pull
fi
git reset --hard 452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4
LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7"
git reset --hard "$LIBSECP_VERSION"
git clean -f -x -q
build_dll i686-w64-mingw32 # 64-bit would be: x86_64-w64-mingw32

View File

@ -2,10 +2,6 @@
# Lucky number
export PYTHONHASHSEED=22
if [ ! -z "$1" ]; then
to_build="$1"
fi
here=$(dirname "$0")
test -n "$here" -a -d "$here" || exit
@ -28,5 +24,5 @@ find -exec touch -d '2000-11-11T11:11:11+00:00' {} +
popd
ls -l /opt/wine64/drive_c/python*
$here/build-electrum-git.sh $to_build && \
$here/build-electrum-git.sh && \
echo "Done."

View File

@ -10,17 +10,23 @@ for i, x in enumerate(sys.argv):
else:
raise Exception('no name')
PYTHON_VERSION = '3.5.4'
PYHOME = 'c:/python' + PYTHON_VERSION
PYHOME = 'c:/python3'
home = 'C:\\electrum\\'
# see https://github.com/pyinstaller/pyinstaller/issues/2005
hiddenimports = []
hiddenimports += collect_submodules('trezorlib')
hiddenimports += collect_submodules('safetlib')
hiddenimports += collect_submodules('btchip')
hiddenimports += collect_submodules('keepkeylib')
hiddenimports += collect_submodules('websocket')
hiddenimports += collect_submodules('ckcc')
# safetlib imports PyQt5.Qt. We use a local updated copy of pinmatrix.py until they
# release a new version that includes https://github.com/archos-safe-t/python-safet/commit/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e
hiddenimports.remove('safetlib.qt.pinmatrix')
# Add libusb binary
binaries = [(PYHOME+"/libusb-1.0.dll", ".")]
@ -31,32 +37,37 @@ binaries += [b for b in collect_dynamic_libs('PyQt5') if 'qwindowsvista' in b[0]
binaries += [('C:/tmp/libsecp256k1.dll', '.')]
datas = [
(home+'lib/*.json', 'electrum'),
(home+'lib/wordlist/english.txt', 'electrum/wordlist'),
(home+'lib/locale', 'electrum/locale'),
(home+'plugins', 'electrum_plugins'),
('C:\\Program Files (x86)\\ZBar\\bin\\', '.')
(home+'electrum/*.json', 'electrum'),
(home+'electrum/wordlist/english.txt', 'electrum/wordlist'),
(home+'electrum/locale', 'electrum/locale'),
(home+'electrum/plugins', 'electrum/plugins'),
('C:\\Program Files (x86)\\ZBar\\bin\\', '.'),
(home+'electrum/gui/icons', 'electrum/gui/icons'),
]
datas += collect_data_files('trezorlib')
datas += collect_data_files('safetlib')
datas += collect_data_files('btchip')
datas += collect_data_files('keepkeylib')
datas += collect_data_files('ckcc')
# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
a = Analysis([home+'electrum',
home+'gui/qt/main_window.py',
home+'gui/text.py',
home+'lib/util.py',
home+'lib/wallet.py',
home+'lib/simple_config.py',
home+'lib/bitcoin.py',
home+'lib/dnssec.py',
home+'lib/commands.py',
home+'plugins/cosigner_pool/qt.py',
home+'plugins/email_requests/qt.py',
home+'plugins/trezor/client.py',
home+'plugins/trezor/qt.py',
home+'plugins/keepkey/qt.py',
home+'plugins/ledger/qt.py',
a = Analysis([home+'run_electrum',
home+'electrum/gui/qt/main_window.py',
home+'electrum/gui/text.py',
home+'electrum/util.py',
home+'electrum/wallet.py',
home+'electrum/simple_config.py',
home+'electrum/bitcoin.py',
home+'electrum/dnssec.py',
home+'electrum/commands.py',
home+'electrum/plugins/cosigner_pool/qt.py',
home+'electrum/plugins/email_requests/qt.py',
home+'electrum/plugins/trezor/qt.py',
home+'electrum/plugins/safe_t/client.py',
home+'electrum/plugins/safe_t/qt.py',
home+'electrum/plugins/keepkey/qt.py',
home+'electrum/plugins/ledger/qt.py',
home+'electrum/plugins/coldcard/qt.py',
#home+'packages/requests/utils.py'
],
binaries=binaries,
@ -72,6 +83,24 @@ for d in a.datas:
a.datas.remove(d)
break
# Strip out parts of Qt that we never use. Reduces binary size by tens of MBs. see #4815
qt_bins2remove=('qt5web', 'qt53d', 'qt5game', 'qt5designer', 'qt5quick',
'qt5location', 'qt5test', 'qt5xml', r'pyqt5\qt\qml\qtquick')
print("Removing Qt binaries:", *qt_bins2remove)
for x in a.binaries.copy():
for r in qt_bins2remove:
if x[0].lower().startswith(r):
a.binaries.remove(x)
print('----> Removed x =', x)
qt_data2remove=(r'pyqt5\qt\translations\qtwebengine_locales', )
print("Removing Qt datas:", *qt_data2remove)
for x in a.datas.copy():
for r in qt_data2remove:
if x[0].lower().startswith(r):
a.datas.remove(x)
print('----> Removed x =', x)
# hotfix for #3171 (pre-Win10 binaries)
a.binaries = [x for x in a.binaries if not x[1].lower().startswith(r'c:\windows')]
@ -90,7 +119,7 @@ exe_standalone = EXE(
debug=False,
strip=None,
upx=False,
icon=home+'icons/electrum.ico',
icon=home+'electrum/gui/icons/electrum.ico',
console=False)
# console=True makes an annoying black box pop up, but it does make Electrum output command line commands, with this turned off no output will be given but commands can still be used
@ -103,7 +132,7 @@ exe_portable = EXE(
debug=False,
strip=None,
upx=False,
icon=home+'icons/electrum.ico',
icon=home+'electrum/gui/icons/electrum.ico',
console=False)
#####
@ -117,7 +146,7 @@ exe_dependent = EXE(
debug=False,
strip=None,
upx=False,
icon=home+'icons/electrum.ico',
icon=home+'electrum/gui/icons/electrum.ico',
console=False)
coll = COLLECT(
@ -128,6 +157,6 @@ coll = COLLECT(
strip=None,
upx=True,
debug=False,
icon=home+'icons/electrum.ico',
icon=home+'electrum/gui/icons/electrum.ico',
console=False,
name=os.path.join('dist', 'electrum'))

View File

@ -6,28 +6,36 @@ RUN dpkg --add-architecture i386 && \
apt-get update -q && \
apt-get install -qy \
wget=1.19.4-1ubuntu2.1 \
gnupg2=2.2.4-1ubuntu1.1 \
dirmngr=2.2.4-1ubuntu1.1 \
software-properties-common=0.96.24.32.3 \
&& \
wget -nc https://dl.winehq.org/wine-builds/Release.key && \
apt-key add Release.key && \
apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/ && \
apt-get update -q && \
apt-get install -qy \
wine-stable-amd64:amd64=3.0.1~bionic \
wine-stable-i386:i386=3.0.1~bionic \
wine-stable:amd64=3.0.1~bionic \
winehq-stable:amd64=3.0.1~bionic \
git=1:2.17.1-1ubuntu0.1 \
gnupg2=2.2.4-1ubuntu1.2 \
dirmngr=2.2.4-1ubuntu1.2 \
python3-software-properties=0.96.24.32.1 \
software-properties-common=0.96.24.32.1
RUN apt-get update -q && \
apt-get install -qy \
git=1:2.17.1-1ubuntu0.4 \
p7zip-full=16.02+dfsg-6 \
make=4.1-9.1ubuntu1 \
mingw-w64=5.0.3-1 \
autotools-dev=20180224.1 \
autoconf=2.69-11 \
libtool=2.4.6-2 \
gettext=0.19.8.1-6 \
&& \
rm -rf /var/lib/apt/lists/* && \
gettext=0.19.8.1-6
RUN wget -nc https://dl.winehq.org/wine-builds/Release.key && \
echo "c51bcb8cc4a12abfbd7c7660eaf90f49674d15e222c262f27e6c96429111b822 Release.key" | sha256sum -c - && \
apt-key add Release.key && \
wget -nc https://dl.winehq.org/wine-builds/winehq.key && \
echo "78b185fabdb323971d13bd329fefc8038e08559aa51c4996de18db0639a51df6 winehq.key" | sha256sum -c - && \
apt-key add winehq.key && \
apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/ && \
apt-get update -q && \
apt-get install -qy \
wine-stable-amd64:amd64=4.0~bionic \
wine-stable-i386:i386=4.0~bionic \
wine-stable:amd64=4.0~bionic \
winehq-stable:amd64=4.0~bionic
RUN rm -rf /var/lib/apt/lists/* && \
apt-get autoremove -y && \
apt-get clean

View File

@ -1,6 +1,9 @@
Deterministic Windows binaries with Docker
==========================================
Produced binaries are deterministic, so you should be able to generate
binaries that match the official releases.
This assumes an Ubuntu host, but it should not be too hard to adapt to another
similar system. The docker commands should be executed in the project's root
folder.
@ -17,25 +20,84 @@ folder.
2. Build image
```
$ sudo docker build --no-cache -t electrum-wine-builder-img contrib/build-wine/docker
$ sudo docker build -t electrum-wine-builder-img contrib/build-wine/docker
```
Note: see [this](https://stackoverflow.com/a/40516974/7499128) if having dns problems
3. Build Windows binaries
It's recommended to build from a fresh clone
(but you can skip this if reproducibility is not necessary).
```
$ TARGET=master
$ sudo docker run \
$ FRESH_CLONE=contrib/build-wine/fresh_clone && \
rm -rf $FRESH_CLONE && \
mkdir -p $FRESH_CLONE && \
cd $FRESH_CLONE && \
git clone https://github.com/spesmilo/electrum.git && \
cd electrum
```
And then build from this directory:
```
$ git checkout $REV
$ sudo docker run -it \
--name electrum-wine-builder-cont \
-v .:/opt/electrum \
-v $PWD:/opt/wine64/drive_c/electrum \
--rm \
--workdir /opt/electrum/contrib/build-wine \
--workdir /opt/wine64/drive_c/electrum/contrib/build-wine \
electrum-wine-builder-img \
./build.sh $TARGET
./build.sh
```
4. The generated binaries are in `./contrib/build-wine/dist`.
Note: the `setup` binary (NSIS installer) is not deterministic yet.
Code Signing
============
Electrum Windows builds are signed with a Microsoft Authenticode™ code signing
certificate in addition to the GPG-based signatures.
The advantage of using Authenticode is that Electrum users won't receive a
Windows SmartScreen warning when starting it.
The release signing procedure involves a signer (the holder of the
certificate/key) and one or multiple trusted verifiers:
| Signer | Verifier |
|-----------------------------------------------------------|-----------------------------------|
| Build .exe files using `build.sh` | |
| Sign .exe with `./sign.sh` | |
| Upload signed files to download server | |
| | Build .exe files using `build.sh` |
| | Compare files using `unsign.sh` |
| | Sign .exe file using `gpg -b` |
| Signer and verifiers: |
|-----------------------------------------------------------------------------------------------|
| Upload signatures to 'electrum-signatures' repo, as `$version/$filename.$builder.asc` |
Verify Integrity of signed binary
=================================
Every user can verify that the official binary was created from the source code in this
repository. To do so, the Authenticode signature needs to be stripped since the signature
is not reproducible.
This procedure removes the differences between the signed and unsigned binary:
1. Remove the signature from the signed binary using osslsigncode or signtool.
2. Set the COFF image checksum for the signed binary to 0x0. This is necessary
because pyinstaller doesn't generate a checksum.
3. Append null bytes to the _unsigned_ binary until the byte count is a multiple
of 8.
The script `unsign.sh` performs these steps.

View File

@ -58,7 +58,7 @@
VIAddVersionKey ProductName "${PRODUCT_NAME} Installer"
VIAddVersionKey Comments "The installer for ${PRODUCT_NAME}"
VIAddVersionKey CompanyName "${PRODUCT_NAME}"
VIAddVersionKey LegalCopyright "2013-2016 ${PRODUCT_PUBLISHER}"
VIAddVersionKey LegalCopyright "2013-2018 ${PRODUCT_PUBLISHER}"
VIAddVersionKey FileDescription "${PRODUCT_NAME} Installer"
VIAddVersionKey FileVersion ${PRODUCT_VERSION}
VIAddVersionKey ProductVersion ${PRODUCT_VERSION}
@ -72,7 +72,7 @@
!define MUI_ABORTWARNING
!define MUI_ABORTWARNING_TEXT "Are you sure you wish to abort the installation of ${PRODUCT_NAME}?"
!define MUI_ICON "tmp\electrum\icons\electrum.ico"
!define MUI_ICON "c:\electrum\electrum\gui\icons\electrum.ico"
;--------------------------------
;Pages
@ -111,7 +111,7 @@ Section
;Files to pack into the installer
File /r "dist\electrum\*.*"
File "..\..\icons\electrum.ico"
File "c:\electrum\electrum\gui\icons\electrum.ico"
;Store installation folder
WriteRegStr HKCU "Software\${PRODUCT_NAME}" "" $INSTDIR

View File

@ -1,9 +1,9 @@
#!/bin/bash
# Please update these carefully, some versions won't work under Wine
NSIS_FILENAME=nsis-3.03-setup.exe
NSIS_FILENAME=nsis-3.04-setup.exe
NSIS_URL=https://prdownloads.sourceforge.net/nsis/$NSIS_FILENAME?download
NSIS_SHA256=bd3b15ab62ec6b0c7a00f46022d441af03277be893326f6fea8e212dc2d77743
NSIS_SHA256=4e1db5a7400e348b1b46a4a11b6d9557fd84368e4ad3d4bc4c1be636c89638aa
ZBAR_FILENAME=zbarw-20121031-setup.exe
ZBAR_URL=https://sourceforge.net/projects/zbarw/files/$ZBAR_FILENAME/download
@ -13,79 +13,26 @@ LIBUSB_FILENAME=libusb-1.0.22.7z
LIBUSB_URL=https://prdownloads.sourceforge.net/project/libusb/libusb-1.0/libusb-1.0.22/$LIBUSB_FILENAME?download
LIBUSB_SHA256=671f1a420757b4480e7fadc8313d6fb3cbb75ca00934c417c1efa6e77fb8779b
PYTHON_VERSION=3.5.4
PYTHON_VERSION=3.6.8
## These settings probably don't need change
export WINEPREFIX=/opt/wine64
#export WINEARCH='win32'
PYHOME=c:/python$PYTHON_VERSION
PYTHON_FOLDER="python3"
PYHOME="c:/$PYTHON_FOLDER"
PYTHON="wine $PYHOME/python.exe -OO -B"
# based on https://superuser.com/questions/497940/script-to-verify-a-signature-with-gpg
verify_signature() {
local file=$1 keyring=$2 out=
if out=$(gpg --no-default-keyring --keyring "$keyring" --status-fd 1 --verify "$file" 2>/dev/null) &&
echo "$out" | grep -qs "^\[GNUPG:\] VALIDSIG "; then
return 0
else
echo "$out" >&2
exit 1
fi
}
verify_hash() {
local file=$1 expected_hash=$2
actual_hash=$(sha256sum $file | awk '{print $1}')
if [ "$actual_hash" == "$expected_hash" ]; then
return 0
else
echo "$file $actual_hash (unexpected hash)" >&2
rm "$file"
exit 1
fi
}
download_if_not_exist() {
local file_name=$1 url=$2
if [ ! -e $file_name ] ; then
wget -O $PWD/$file_name "$url"
fi
}
# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/templates/header.sh
retry() {
local result=0
local count=1
while [ $count -le 3 ]; do
[ $result -ne 0 ] && {
echo -e "\nThe command \"$@\" failed. Retrying, $count of 3.\n" >&2
}
! { "$@"; result=$?; }
[ $result -eq 0 ] && break
count=$(($count + 1))
sleep 1
done
[ $count -gt 3 ] && {
echo -e "\nThe command \"$@\" failed 3 times.\n" >&2
}
return $result
}
# Let's begin!
here=$(dirname $(readlink -e $0))
here="$(dirname "$(readlink -e "$0")")"
set -e
# Clean up Wine environment
echo "Cleaning $WINEPREFIX"
rm -rf $WINEPREFIX
echo "done"
. $here/../build_tools_util.sh
wine 'wineboot'
cd /tmp/electrum-build
# Install Python
@ -96,8 +43,7 @@ KEYRING_PYTHON_DEV="keyring-electrum-build-python-dev.gpg"
for server in $(shuf -e ha.pool.sks-keyservers.net \
hkp://p80.pool.sks-keyservers.net:80 \
keyserver.ubuntu.com \
hkp://keyserver.ubuntu.com:80 \
pgp.mit.edu) ; do
hkp://keyserver.ubuntu.com:80) ; do
retry gpg --no-default-keyring --keyring $KEYRING_PYTHON_DEV --keyserver "$server" --recv-keys $KEYLIST_PYTHON_DEV \
&& break || : ;
done
@ -106,31 +52,21 @@ for msifile in core dev exe lib pip tools; do
wget -N -c "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi"
wget -N -c "https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi.asc"
verify_signature "${msifile}.msi.asc" $KEYRING_PYTHON_DEV
wine msiexec /i "${msifile}.msi" /qb TARGETDIR=C:/python$PYTHON_VERSION
wine msiexec /i "${msifile}.msi" /qb TARGETDIR=$PYHOME
done
# upgrade pip
$PYTHON -m pip install pip --upgrade
# Install pywin32-ctypes (needed by pyinstaller)
$PYTHON -m pip install pywin32-ctypes==0.1.2
# install PySocks
$PYTHON -m pip install win_inet_pton==1.0.1
$PYTHON -m pip install -r $here/../deterministic-build/requirements-binaries.txt
# Install dependencies specific to binaries
# note that this also installs pinned versions of both pip and setuptools
$PYTHON -m pip install -r "$here"/../deterministic-build/requirements-binaries.txt
# Install PyInstaller
$PYTHON -m pip install https://github.com/ecdsa/pyinstaller/archive/fix_2952.zip
$PYTHON -m pip install pyinstaller==3.4 --no-use-pep517
# Install ZBar
download_if_not_exist $ZBAR_FILENAME "$ZBAR_URL"
verify_hash $ZBAR_FILENAME "$ZBAR_SHA256"
wine "$PWD/$ZBAR_FILENAME" /S
# Upgrade setuptools (so Electrum can be installed later)
$PYTHON -m pip install setuptools --upgrade
# Install NSIS installer
download_if_not_exist $NSIS_FILENAME "$NSIS_URL"
verify_hash $NSIS_FILENAME "$NSIS_SHA256"
@ -140,10 +76,7 @@ download_if_not_exist $LIBUSB_FILENAME "$LIBUSB_URL"
verify_hash $LIBUSB_FILENAME "$LIBUSB_SHA256"
7z x -olibusb $LIBUSB_FILENAME -aoa
cp libusb/MS32/dll/libusb-1.0.dll $WINEPREFIX/drive_c/python$PYTHON_VERSION/
# add dlls needed for pyinstaller:
cp $WINEPREFIX/drive_c/python$PYTHON_VERSION/Lib/site-packages/PyQt5/Qt/bin/* $WINEPREFIX/drive_c/python$PYTHON_VERSION/
cp libusb/MS32/dll/libusb-1.0.dll $WINEPREFIX/drive_c/$PYTHON_FOLDER/
mkdir -p $WINEPREFIX/drive_c/tmp
cp secp256k1/libsecp256k1.dll $WINEPREFIX/drive_c/tmp/

69
contrib/build_tools_util.sh Executable file
View File

@ -0,0 +1,69 @@
#!/usr/bin/env bash
RED='\033[0;31m'
BLUE='\033[0;34m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
function info {
printf "\r💬 ${BLUE}INFO:${NC} ${1}\n"
}
function fail {
printf "\r🗯 ${RED}ERROR:${NC} ${1}\n"
exit 1
}
function warn {
printf "\r⚠ ${YELLOW}WARNING:${NC} ${1}\n"
}
# based on https://superuser.com/questions/497940/script-to-verify-a-signature-with-gpg
function verify_signature() {
local file=$1 keyring=$2 out=
if out=$(gpg --no-default-keyring --keyring "$keyring" --status-fd 1 --verify "$file" 2>/dev/null) &&
echo "$out" | grep -qs "^\[GNUPG:\] VALIDSIG "; then
return 0
else
echo "$out" >&2
exit 1
fi
}
function verify_hash() {
local file=$1 expected_hash=$2
actual_hash=$(sha256sum $file | awk '{print $1}')
if [ "$actual_hash" == "$expected_hash" ]; then
return 0
else
echo "$file $actual_hash (unexpected hash)" >&2
rm "$file"
exit 1
fi
}
function download_if_not_exist() {
local file_name=$1 url=$2
if [ ! -e $file_name ] ; then
wget -O $file_name "$url"
fi
}
# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/templates/header.sh
function retry() {
local result=0
local count=1
while [ $count -le 3 ]; do
[ $result -ne 0 ] && {
echo -e "\nThe command \"$@\" failed. Retrying, $count of 3.\n" >&2
}
! { "$@"; result=$?; }
[ $result -eq 0 ] && break
count=$(($count + 1))
sleep 1
done
[ $count -gt 3 ] && {
echo -e "\nThe command \"$@\" failed 3 times.\n" >&2
}
return $result
}

View File

@ -18,13 +18,6 @@ function get_git_mtime {
fail=0
for f in icons/* "icons.qrc"; do
if (( $(get_git_mtime "$f") > $(get_git_mtime "contrib/deterministic-build/electrum-icons/") )); then
echo "Modification time of $f (" $(get_git_mtime --readable "$f") ") is newer than"\
"last update of electrum-icons"
fail=1
fi
done
if [ $(date +%s -d "2 weeks ago") -gt $(get_git_mtime "contrib/deterministic-build/electrum-locale/") ]; then
echo "Last update from electrum-locale is older than 2 weeks."\

@ -1 +0,0 @@
Subproject commit 5af76dc1b04c782e622ac409cd802c483c529c1f

@ -1 +1 @@
Subproject commit de999ceffd2a864df54451d23f290ef5f333e8ea
Subproject commit ff5ad3a4436dddcc82799f8a91793013240c3b7b

View File

@ -1,7 +1,10 @@
#!/usr/bin/env python3
import sys
import requests
try:
import requests
except ImportError as e:
sys.exit(f"Error: {str(e)}. Try 'sudo python3 -m pip install <module-name>'")
def check_restriction(p, r):

View File

@ -1,55 +1,56 @@
pip==10.0.1 \
--hash=sha256:717cdffb2833be8409433a93746744b59505f42146e8d37de6c62b430e25d6d7 \
--hash=sha256:f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68
pycryptodomex==3.6.1 \
--hash=sha256:1869d7735f445bbf1681afa2acce10ad829857cfb7a4a7b702e484f222021892 \
--hash=sha256:24e054190d2b11ad3b8517d186c0b3df6f902a5f5a91be8e4bb6a3fcdc65b2cf \
--hash=sha256:26967d31fabb0d80cb2b254a7c0f55f8dec9931e8676891edd24aa5aaeb0d021 \
--hash=sha256:2a341b57bb5844d53b8f632f79277cd534762f502fb73bff5dc1a2f615ff91ed \
--hash=sha256:43d6eb014aba7be354f3e8fe2693fe96446f6791da2b9570e8d54d481e3ab224 \
--hash=sha256:4c271577f4f8c5cced55a60f4504b34545121c14facb8fc357f89c24089c81fc \
--hash=sha256:59721f2853df9cf2265304d3b6d6d8cebe3a86b1fddc00f2bfbf18eb2a48fb78 \
--hash=sha256:63a77a1b27d12ed1c42f4e539d9dbe588a88b70ec64b55271cdf1f56c1223bd6 \
--hash=sha256:6d04640386c55b9f44015747496c3b6582360b5b3b4e42f9ce3fc7c6840f80d0 \
--hash=sha256:730bd75d90e16975a112ea79863ce1faa7703d3b54f10d77656e7dadf6be0ef6 \
--hash=sha256:75a300aa86c56e9c19a7b476c397cb22fda3be7af4cf2f105990fdd94c52f486 \
--hash=sha256:7c6f67005c6e421f02fd7fe9d95876094307b31628d728adc6c2e038e2ed9c09 \
--hash=sha256:82b758f870c8dd859f9b58bc9cff007403b68742f9e0376e2cbd8aa2ad3baa83 \
--hash=sha256:8528a958b746c4da767bfba5ac370250dcb741f4c69e55873bd6efe89ac07291 \
--hash=sha256:93582ea5bc3e8f95cb36d9dd752c01452085b54b396e3ed775ac1793b8dc486a \
--hash=sha256:94e0105ad8d82d3bf5a032c92fc03b01e3bc9ea40b58308c2da42f8cf8c16c47 \
--hash=sha256:a65889424bf10a884ff031e7f3fd12273dd5b420ee08ca8fcfd431a2f6cbabc1 \
--hash=sha256:a8467982d26bfb90089f50c3c5d9ed541b7fe9f9df20803fede70d5046cd4ff1 \
--hash=sha256:ab497d4e7361511ede562ed3cd4528f46c005781bc23b1b943612d27bfb078c3 \
--hash=sha256:bb05caf3f6cf41d964c01e08dfaddfe48086c7b3e96708d50647f0a29ff33f56 \
--hash=sha256:c4643647f5656855975b2aaf70fe3aa1e0c1558f8d1b5de0c9a8ccac65114c57 \
--hash=sha256:c550e20834b679ed0b7608c345a816f97047d2297aab4f4599f95edee5d16e99 \
--hash=sha256:cc797712add76cd658110585481c380833637b68df1404190777ba715a81c9b9 \
--hash=sha256:dff0c883d495bf45d18acc74938d1de4d6a08b3345acb9177a46c6997a578c44 \
--hash=sha256:e4f69af1f5b46255ec7b8116a853879a55e8e6b595a73c39f14ca430c410c469 \
--hash=sha256:f61d0d83e9dd974849f9b0826ec20f49dbd9ed233fd90bf2592be1337231418e \
--hash=sha256:f65f21d2b616c30ad4ba801504343eb768fd0a2894c5f587e784201320556543
PyQt5==5.10.1 \
--hash=sha256:1e652910bd1ffd23a3a48c510ecad23a57a853ed26b782cd54b16658e6f271ac \
--hash=sha256:4db7113f464c733a99fcb66c4c093a47cf7204ad3f8b3bda502efcc0839ac14b \
--hash=sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7 \
--hash=sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61
setuptools==39.2.0 \
--hash=sha256:8fca9275c89964f13da985c3656cb00ba029d7f3916b37990927ffdf264e7926 \
--hash=sha256:f7cddbb5f5c640311eb00eab6e849f7701fa70bf6a183fc8a2c33dd1d1672fb2
SIP==4.19.8 \
--hash=sha256:09f9a4e6c28afd0bafedb26ffba43375b97fe7207bd1a0d3513f79b7d168b331 \
--hash=sha256:105edaaa1c8aa486662226360bd3999b4b89dd56de3e314d82b83ed0587d8783 \
--hash=sha256:1bb10aac55bd5ab0e2ee74b3047aa2016cfa7932077c73f602a6f6541af8cd51 \
--hash=sha256:265ddf69235dd70571b7d4da20849303b436192e875ce7226be7144ca702a45c \
--hash=sha256:52074f7cb5488e8b75b52f34ec2230bc75d22986c7fe5cd3f2d266c23f3349a7 \
--hash=sha256:5ff887a33839de8fc77d7f69aed0259b67a384dc91a1dc7588e328b0b980bde2 \
--hash=sha256:74da4ddd20c5b35c19cda753ce1e8e1f71616931391caeac2de7a1715945c679 \
--hash=sha256:7d69e9cf4f8253a3c0dfc5ba6bb9ac8087b8239851f22998e98cb35cfe497b68 \
--hash=sha256:97bb93ee0ef01ba90f57be2b606e08002660affd5bc380776dd8b0fcaa9e093a \
--hash=sha256:cf98150a99e43fda7ae22abe655b6f202e491d6291486548daa56cb15a2fcf85 \
--hash=sha256:d9023422127b94d11c1a84bfa94933e959c484f2c79553c1ef23c69fe00d25f8 \
--hash=sha256:e72955e12f4fccf27aa421be383453d697b8a44bde2cc26b08d876fd492d0174
wheel==0.31.1 \
--hash=sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c \
--hash=sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f
pip==19.0.1 \
--hash=sha256:aae79c7afe895fb986ec751564f24d97df1331bb99cdfec6f70dada2f40c0044 \
--hash=sha256:e81ddd35e361b630e94abeda4a1eddd36d47a90e71eb00f38f46b57f787cd1a5
pycryptodomex==3.7.3 \
--hash=sha256:0bda549e20db1eb8e29fb365d10acf84b224d813b1131c828fc830b2ce313dcd \
--hash=sha256:1210c0818e5334237b16d99b5785aa0cee815d9997ee258bd5e2936af8e8aa50 \
--hash=sha256:2090dc8cd7843eae75bd504b9be86792baa171fc5a758ea3f60188ab67ca95cf \
--hash=sha256:22e6784b65dfdd357bf9a8a842db445192b227103e2c3137a28c489c46742135 \
--hash=sha256:2edb8c3965a77e3092b5c5c1233ffd32de083f335202013f52d662404191ac79 \
--hash=sha256:310fe269ac870135ff610d272e88dcb594ee58f40ac237a688d7c972cbca43e8 \
--hash=sha256:456136b7d459f000794a67b23558351c72e21f0c2d4fcaa09fc99dae7844b0ef \
--hash=sha256:463e49a9c5f1fa7bd36aff8debae0b5c487868c1fb66704529f2ad7e92f0cc9f \
--hash=sha256:4a33b2828799ef8be789a462e6645ea6fe2c42b0df03e6763ccbfd1789c453e6 \
--hash=sha256:5ff02dff1b03929e6339226b318aa59bd0b5c362f96e3e0eb7f3401d30594ed3 \
--hash=sha256:6b1db8234b8ee2b30435d9e991389c2eeae4d45e09e471ffe757ba1dfae682bb \
--hash=sha256:6eb67ee02de143cd19e36a52bd3869a9dc53e9184cd6bed5c39ff71dee2f6a45 \
--hash=sha256:6f42eea5afc7eee29494fdfddc6bb7173953d4197d9200e4f67096c2a24bc21b \
--hash=sha256:87bc8082e2de2247df7d0b161234f8edb1384294362cc0c8db9324463097578b \
--hash=sha256:8df93d34bc0e3a28a27652070164683a07d8a50c628119d6e0f7710f4d01b42f \
--hash=sha256:989952c39e8fef1c959f0a0f85656e29c41c01162e33a3f5fd8ce71e47262ae9 \
--hash=sha256:a4a203077e2f312ec8677dde80a5c4e6fe5a82a46173a8edc8da668602a3e073 \
--hash=sha256:a793c1242dffd39f585ae356344e8935d30f01f6be7d4c62ffc87af376a2f5f9 \
--hash=sha256:b70fe991564e178af02ccf89435a8f9e8d052707a7c4b95bf6027cb785da3175 \
--hash=sha256:b83594196e3661cb78c97b80a62fbfbba2add459dfd532b58e7a7c62dd06aab4 \
--hash=sha256:ba27725237d0a3ea66ec2b6b387259471840908836711a3b215160808dffed0f \
--hash=sha256:d1ab8ad1113cdc553ca50c4d5f0142198c317497364c0c70443d69f7ad1c9288 \
--hash=sha256:dce039a8a8a318d7af83cae3fd08d58cefd2120075dfac0ae14d706974040f63 \
--hash=sha256:e3213037ea33c85ab705579268cbc8a4433357e9fb99ec7ce9fdcc4d4eec1d50 \
--hash=sha256:ec8d8023d31ef72026d46e9fb301ff8759eff5336bcf3d1510836375f53f96a9 \
--hash=sha256:ece65730d50aa57a1330d86d81582a2d1587b2ca51cb34f586da8551ddc68fee \
--hash=sha256:ed21fc515e224727793e4cc3fb3d00f33f59e3a167d3ad6ac1475ab3b05c2f9e \
--hash=sha256:eec1132d878153d61a05424f35f089f951bd6095a4f6c60bdd2ef8919d44425e
PyQt5==5.11.3 \
--hash=sha256:517e4339135c4874b799af0d484bc2e8c27b54850113a68eec40a0b56534f450 \
--hash=sha256:ac1eb5a114b6e7788e8be378be41c5e54b17d5158994504e85e43b5fca006a39 \
--hash=sha256:d2309296a5a79d0a1c0e6c387c30f0398b65523a6dcc8a19cc172e46b949e00d \
--hash=sha256:e85936bae1581bcb908847d2038e5b34237a5e6acc03130099a78930770e7ead
PyQt5-sip==4.19.13 \
--hash=sha256:125f77c087572c9272219cda030a63c2f996b8507592b2a54d7ef9b75f9f054d \
--hash=sha256:14c37b06e3fb7c2234cb208fa461ec4e62b4ba6d8b32ca3753c0b2cfd61b00e3 \
--hash=sha256:1cb2cf52979f9085fc0eab7e0b2438eb4430d4aea8edec89762527e17317175b \
--hash=sha256:4babef08bccbf223ec34464e1ed0a23caeaeea390ca9a3529227d9a57f0d6ee4 \
--hash=sha256:53cb9c1208511cda0b9ed11cffee992a5a2f5d96eb88722569b2ce65ecf6b960 \
--hash=sha256:549449d9461d6c665cbe8af4a3808805c5e6e037cd2ce4fd93308d44a049bfac \
--hash=sha256:5f5b3089b200ff33de3f636b398e7199b57a6b5c1bb724bdb884580a072a14b5 \
--hash=sha256:a4d9bf6e1fa2dd6e73f1873f1a47cee11a6ba0cf9ba8cf7002b28c76823600d0 \
--hash=sha256:a4ee6026216f1fbe25c8847f9e0fbce907df5b908f84816e21af16ec7666e6fe \
--hash=sha256:a91a308a5e0cc99de1e97afd8f09f46dd7ca20cfaa5890ef254113eebaa1adff \
--hash=sha256:b0342540da479d2713edc68fb21f307473f68da896ad5c04215dae97630e0069 \
--hash=sha256:f997e21b4e26a3397cb7b255b8d1db5b9772c8e0c94b6d870a5a0ab5c27eacaa
setuptools==40.8.0 \
--hash=sha256:6e4eec90337e849ade7103723b9a99631c1f0d19990d6e8412dc42f5ae8b304d \
--hash=sha256:e8496c0079f3ac30052ffe69b679bd876c5265686127a3159cfa415669b7f9ab
wheel==0.32.3 \
--hash=sha256:029703bf514e16c8271c3821806a1c171220cc5bdd325cbf4e7da1e056a01db6 \
--hash=sha256:1e53cdb3f808d5ccd0df57f964263752aa74ea7359526d3da6c02114ec1e1d44

View File

@ -1,50 +1,56 @@
btchip-python==0.1.27 \
--hash=sha256:e58a941abbb2d8901bf4858baa18012537c60812c7f895f9a039113ecce3032b
certifi==2018.4.16 \
--hash=sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7 \
--hash=sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0
btchip-python==0.1.28 \
--hash=sha256:da09d0d7a6180d428833795ea9a233c3b317ddfcccea8cc6f0eba59435e5dd83
certifi==2018.11.29 \
--hash=sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7 \
--hash=sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033
chardet==3.0.4 \
--hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \
--hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691
click==6.7 \
--hash=sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d \
--hash=sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b
Cython==0.28.3 \
--hash=sha256:0344e9352b0915910e212c38403b63f902ce1cba75dde7a43a9112ff960eb2a5 \
--hash=sha256:0a390c39e912fc5f82d5feae2d16ea061971407099e1efb0fecb255cb96fbeff \
--hash=sha256:0f2b2e09f94c498f555935e732b7321b5f62f00e7a789238f6c5ddd66987a54d \
--hash=sha256:15614592616b6dd5e919e158796350ebeba6cb6b5d2998cfff41b53f568c8355 \
--hash=sha256:1aae6d6e9858888144cea147eb5e677830f45faaff3d305d77378c3cba55f526 \
--hash=sha256:200583297f23e558744bc4688d8a2b2605ab6ad7d1494a9fd8c8094ad65ebf3c \
--hash=sha256:295facc211a6b55db9979455b856180f2839be22ab767ffdea55986bee83ca9f \
--hash=sha256:36c16bf39280fe857213d8da31c07a6179d3878c3dc2e435dce0974b9f8f0729 \
--hash=sha256:3fef8dfa9cf86ab7814ca31369374ddd5b9524f54406aa83b53b5937965b8e88 \
--hash=sha256:439d233d3214e3d69c033a9a93516758f2c8a03e83ea51ae14b6eed13687d224 \
--hash=sha256:455ab39c6c0849a6c008fcdf2fae42475f18d0801a3be229e8f75367bbe3b325 \
--hash=sha256:56821e3791209e6a11992e294afbf7e3dcda7d4fd54d06396dd521928d3d14fe \
--hash=sha256:62b594584889b33bbea7e71f9d7c5c6539091b341334ef7ca1ae7e30a9dd3e15 \
--hash=sha256:70f81a75fb25c1c3c61843e3a6fe771a76c4ebf4d154455a7eff0740ad47dff4 \
--hash=sha256:8011090beb09251cb4ece1e14263e574b38eda696b788552b369ad343373d0e9 \
--hash=sha256:80d6a0369333a162fc32a22637f5870f3e87fb038c7b58860bbe00b05b58aa62 \
--hash=sha256:85b04e32af58a3c008c0ba8169017770aaa342a5972b748f81d043d66363e437 \
--hash=sha256:9ed273d82116fa148c92901b9639030e087979d455982bd7bf727fb486c0bd17 \
--hash=sha256:a1af59e6c9b4acc07c429d8495fc016a35e0a1270f28c57317352f512df7e214 \
--hash=sha256:b894ff4daf8dfaf657bf2d5e7190a4de11b2400b1e0fb0902974d35c23a26dea \
--hash=sha256:c2659981150b4de04397dcfd4bff64e384d3ba25af60d1b22820fdf108298cb2 \
--hash=sha256:c981a750858f1727995acf861ab030b267d264ca6efda2f01104941187a3675f \
--hash=sha256:cc4152b19ec168391f7815d24b70c8911829ba281bd5fcd98cab9dc21abe62ff \
--hash=sha256:d0f5b1668e7f7f6fc9849f49a20c5db10562a0ab29cd66818894dfebbca7b304 \
--hash=sha256:d7152006ed1a3adb8f978077b57d237ddafa188240af53cd72b5c79e4ed000e3 \
--hash=sha256:e5f877472993474296125c22b84c334b550010815e513cccce73da854a132d64 \
--hash=sha256:e7c2c87ff2f99ed4be1bb046d6eddfb388af627928037f9e0a420c05daaf14ed \
--hash=sha256:edd7d499685655031be5b4d33005096b6345f81eeb7ab9d2dd415db0c7bcf64e \
--hash=sha256:f99a777fda569a88deea863eac2722b5e88957c4d5f4413949740da791857ac9
ckcc-protocol==0.7.2 \
--hash=sha256:31ee5178cfba8895eb2a6b8d06dc7830b51461a0ff767a670a64707c63e6b264 \
--hash=sha256:498db4ccdda018cd9f40210f5bd02ddcc98e7df583170b2eab4035c86c3cc03b
click==7.0 \
--hash=sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13 \
--hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7
construct==2.9.45 \
--hash=sha256:2271a0efd0798679dea825ff47e22a4c550456a5db0ba8baa82f7eae0af0118c
Cython==0.29.4 \
--hash=sha256:004eeb2fc64e9db4a3bc0d65583d69769c7242d29d9335121cbab776688dc122 \
--hash=sha256:028ee8571884a129e0d5c4d48296f6b3ea679668c096bb65fe8b2ff7ac29d707 \
--hash=sha256:162b8b794ca9210c7039d54b6d96cd342e0404e41e7e467baae69f0252d7e52a \
--hash=sha256:1aba4cf581d203e8fa3b6a7b432b09416e4f93c0d1f7744834acacfe3e9db424 \
--hash=sha256:1be8f08c87b92a880f2fd19f93293e738ca8647834ad05625635320cec9ecad4 \
--hash=sha256:21c707a811912aeb65abe8a66e5adebc759889661c8f4cf677523cd33c609084 \
--hash=sha256:234de250ef09ba667fc6a8f6ba07712d3fe5bb8d92d70d2b958d4c56e3172c4a \
--hash=sha256:33dad82003df518e1242ac3b0592fc63c49d65d0d37b696cb43b7d35085e6bd5 \
--hash=sha256:54ee6cbc1397b27670e598ae15cab36e826a01605f63bf267a5fd2642bd8a147 \
--hash=sha256:6058c57657d2704c9fad8a56458173d2f525dce4083ca46e9b99b1b35da2b27f \
--hash=sha256:6d3065f39ea1354eba4807e2752e97d57f26d6f68bc4a4c561264ca4300c46cb \
--hash=sha256:7059e5acac1d7a82e75e553924d9ea59b0e79203adf903cb999287fbcc8f50f1 \
--hash=sha256:71c31e01f20a3a7273f6f38760d29170ee89e895be540481130cb173ef6b7246 \
--hash=sha256:89225447801e8bd0f6d8e2c0807ded83af8ad7bf4086b5ecf1f22c5a68d1b3e3 \
--hash=sha256:9783f11fe4a4af66b0aa0da68fda833c10b95edd9099a6dbe710d03bcb96adf2 \
--hash=sha256:9a0be0aac30d71fe490a2b0377fca6e13a5242ecc01d09c7a358f1f2fcb07a80 \
--hash=sha256:9a2cccc26dcf2df1e0048cdf63bd714f1d5dfad457f03b9938c5cc3eef74c9ab \
--hash=sha256:b0889310f8558eb406a4a853d63553b90c621476f1b5b80b46b1ff57eef198cf \
--hash=sha256:c46ef7b771c88512435399e5ffbc3a70079d4945123d6fbfc6211b4cfdc4e546 \
--hash=sha256:c71a77c1047d65e5b4e614053cbb7b567c36359b2bc1d27fba23b984ab6dddd0 \
--hash=sha256:c9361811a1a49db11efce54fedd01a5544af8db074fce471c720bdb85ec9c7a8 \
--hash=sha256:d021a8326a1d2cdb182b0dd7f49bb42d8a4e6ddfb3c8d388ee5be26d57d49f3b \
--hash=sha256:d1ee3d39c73a094ae5b6e2f9263ae0dc61af1b549a0869ade8c3c30325ed9f26 \
--hash=sha256:d49d7cf82192edc6e386262a07ceb3515028afbd9009dd8ec669d2c0a9f20128 \
--hash=sha256:dc5fc1fa072a98f152e46465aaf3e02b3ea36a9d3b8c79bfabd47b0e3ad9226c \
--hash=sha256:e290fed7fe73860657af564e596fff87e75cfda861c067e89212970a47826cc6 \
--hash=sha256:fcf9a9a566ab98495db641eefee471eb03df71e394ee51fdfa9b4c0b9f6928eb \
--hash=sha256:fe8c1d2538867bf2753988a4a2d548bcb211fcbba125aa3e9092391b16f47b56
ecdsa==0.13 \
--hash=sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c \
--hash=sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa
hidapi==0.7.99.post21 \
--hash=sha256:1ac170f4d601c340f2cd52fd06e85c5e77bad7ceac811a7bb54b529f7dc28c24 \
--hash=sha256:6424ad75da0021ce8c1bcd78056a04adada303eff3c561f8d132b85d0a914cb3 \
--hash=sha256:8d3be666f464347022e2b47caf9132287885d9eacc7895314fc8fefcb4e42946 \
--hash=sha256:92878bad7324dee619b7832fbfc60b5360d378aa7c5addbfef0a410d8fd342c7 \
--hash=sha256:b4b1f6aff0192e9be153fe07c1b7576cb7a1ff52e78e3f76d867be95301a8e87 \
--hash=sha256:bf03f06f586ce7d8aeb697a94b7dba12dc9271aae92d7a8d4486360ff711a660 \
--hash=sha256:c76de162937326fcd57aa399f94939ce726242323e65c15c67e183da1f6c26f7 \
@ -52,36 +58,42 @@ hidapi==0.7.99.post21 \
--hash=sha256:d4b5787a04613503357606bb10e59c3e2c1114fa00ee328b838dd257f41cbd7b \
--hash=sha256:e0be1aa6566979266a8fc845ab0e18613f4918cf2c977fe67050f5dc7e2a9a97 \
--hash=sha256:edfb16b16a298717cf05b8c8a9ad1828b6ff3de5e93048ceccd74e6ae4ff0922
idna==2.7 \
--hash=sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e \
--hash=sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16
keepkey==4.0.2 \
--hash=sha256:cddee60ae405841cdff789cbc54168ceaeb2282633420f2be155554c25c69138
libusb1==1.6.4 \
--hash=sha256:8c930d9c1d037d9c83924c82608aa6a1adcaa01ca0e4a23ee0e8e18d7eee670d
idna==2.8 \
--hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \
--hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c
keepkey==6.0.2 \
--hash=sha256:3236dd701bde74768c41a92e724e322ea5e01b90985e2e6215eb85b77f9a0ae1 \
--hash=sha256:677e07deacc2ff97bee313b8dd7ae55faebab02e7d17b9a8e49b889996a36010 \
--hash=sha256:af107f610fb0e2417fc7a9d87a2fa22aac9b80b79559370d178be424bb85489a
libusb1==1.7 \
--hash=sha256:9d4f66d2ed699986b06bc3082cd262101cb26af7a76a34bd15b7eb56cba37e0f
mnemonic==0.18 \
--hash=sha256:02a7306a792370f4a0c106c2cf1ce5a0c84b9dbd7e71c6792fdb9ad88a727f1d
pbkdf2==1.3 \
--hash=sha256:ac6397369f128212c43064a2b4878038dab78dab41875364554aaf2a684e6979
pip==10.0.1 \
--hash=sha256:717cdffb2833be8409433a93746744b59505f42146e8d37de6c62b430e25d6d7 \
--hash=sha256:f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68
protobuf==3.6.0 \
--hash=sha256:12985d9f40c104da2f44ec089449214876809b40fdc5d9e43b93b512b9e74056 \
--hash=sha256:12c97fe27af12fc5d66b23f905ab09dd4fb0c68d5a74a419d914580e6d2e71e3 \
--hash=sha256:327fb9d8a8247bc780b9ea7ed03c0643bc0d22c139b761c9ec1efc7cc3f0923e \
--hash=sha256:3895319db04c0b3baed74fb66be7ba9f4cd8e88a432b8e71032cdf08b2dfee23 \
--hash=sha256:695072063e256d32335d48b9484451f7c7948edc3dbd419469d6a778602682fc \
--hash=sha256:7d786f3ef5b33a04e6538089674f244a3b0f588155016559d950989010af97d0 \
--hash=sha256:8bf82bb7a466a54be7272dcb492f71d55a2453a58d862fb74c3f2083f2768543 \
--hash=sha256:9bbc1ae1c33c1bd3a2fc05a3aec328544d2b039ff0ce6f000063628a32fad777 \
--hash=sha256:9e992c68103ab5635728d29fcf132c669cb4e2db24d012685210276185009d17 \
--hash=sha256:9f1087abb67b34e55108bc610936b34363a7aac692023bcbb17e065c253a1f80 \
--hash=sha256:9fefcb92a3784b446abf3641d9a14dad815bee88e0edd10b9a9e0e144d01a991 \
--hash=sha256:a37836aa47d1b81c2db1a6b7a5e79926062b5d76bd962115a0e615551be2b48d \
--hash=sha256:cca22955443c55cf86f963a4ad7057bca95e4dcde84d6a493066d380cfab3bb0 \
--hash=sha256:d7ac50bc06d31deb07ace6de85556c1d7330e5c0958f3b2af85037d6d1182abf \
--hash=sha256:dfe6899304b898538f4dc94fa0b281b56b70e40f58afa4c6f807805261cbe2e8
pip==19.0.1 \
--hash=sha256:aae79c7afe895fb986ec751564f24d97df1331bb99cdfec6f70dada2f40c0044 \
--hash=sha256:e81ddd35e361b630e94abeda4a1eddd36d47a90e71eb00f38f46b57f787cd1a5
protobuf==3.6.1 \
--hash=sha256:10394a4d03af7060fa8a6e1cbf38cea44be1467053b0aea5bbfcb4b13c4b88c4 \
--hash=sha256:1489b376b0f364bcc6f89519718c057eb191d7ad6f1b395ffd93d1aa45587811 \
--hash=sha256:1931d8efce896981fe410c802fd66df14f9f429c32a72dd9cfeeac9815ec6444 \
--hash=sha256:196d3a80f93c537f27d2a19a4fafb826fb4c331b0b99110f985119391d170f96 \
--hash=sha256:46e34fdcc2b1f2620172d3a4885128705a4e658b9b62355ae5e98f9ea19f42c2 \
--hash=sha256:4b92e235a3afd42e7493b281c8b80c0c65cbef45de30f43d571d1ee40a1f77ef \
--hash=sha256:574085a33ca0d2c67433e5f3e9a0965c487410d6cb3406c83bdaf549bfc2992e \
--hash=sha256:59cd75ded98094d3cf2d79e84cdb38a46e33e7441b2826f3838dcc7c07f82995 \
--hash=sha256:5ee0522eed6680bb5bac5b6d738f7b0923b3cafce8c4b1a039a6107f0841d7ed \
--hash=sha256:65917cfd5da9dfc993d5684643063318a2e875f798047911a9dd71ca066641c9 \
--hash=sha256:685bc4ec61a50f7360c9fd18e277b65db90105adbf9c79938bd315435e526b90 \
--hash=sha256:92e8418976e52201364a3174e40dc31f5fd8c147186d72380cbda54e0464ee19 \
--hash=sha256:9335f79d1940dfb9bcaf8ec881fb8ab47d7a2c721fb8b02949aab8bbf8b68625 \
--hash=sha256:a7ee3bb6de78185e5411487bef8bc1c59ebd97e47713cba3c460ef44e99b3db9 \
--hash=sha256:ceec283da2323e2431c49de58f80e1718986b79be59c266bb0509cbf90ca5b9e \
--hash=sha256:e7a5ccf56444211d79e3204b05087c1460c212a2c7d62f948b996660d0165d68 \
--hash=sha256:fcfc907746ec22716f05ea96b7f41597dfe1a1c088f861efb8a0d4f4196a6f10
pyaes==1.6.1 \
--hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f
pyblake2==1.1.2 \
--hash=sha256:3757f7ad709b0e1b2a6b3919fa79fe3261f166fc375cd521f2be480f8319dde9 \
--hash=sha256:407e02c7f8f36fcec1b7aa114ddca0c1060c598142ea6f6759d03710b946a7e3 \
@ -92,24 +104,31 @@ pyblake2==1.1.2 \
--hash=sha256:baa2190bfe549e36163aa44664d4ee3a9080b236fc5d42f50dc6fd36bbdc749e \
--hash=sha256:c53417ee0bbe77db852d5fd1036749f03696ebc2265de359fe17418d800196c4 \
--hash=sha256:fbc9fcde75713930bc2a91b149e97be2401f7c9c56d735b46a109210f58d7358
requests==2.19.1 \
--hash=sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1 \
--hash=sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a
setuptools==39.2.0 \
--hash=sha256:8fca9275c89964f13da985c3656cb00ba029d7f3916b37990927ffdf264e7926 \
--hash=sha256:f7cddbb5f5c640311eb00eab6e849f7701fa70bf6a183fc8a2c33dd1d1672fb2
six==1.11.0 \
--hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \
--hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb
trezor==0.10.1 \
--hash=sha256:09b4edfa83b787975c6f30728c13bb413621d5bdf722231748758ba0181b8a60 \
--hash=sha256:5bcad3e97129fccd6f8b4cf08f81862e423373617c857feb492cfa1b1807844e
urllib3==1.23 \
--hash=sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf \
--hash=sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5
websocket-client==0.48.0 \
--hash=sha256:18f1170e6a1b5463986739d9fd45c4308b0d025c1b2f9b88788d8f69e8a5eb4a \
--hash=sha256:db70953ae4a064698b27ae56dcad84d0ee68b7b43cb40940f537738f38f510c1
wheel==0.31.1 \
--hash=sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c \
--hash=sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f
requests==2.21.0 \
--hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \
--hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b
safet==0.1.4 \
--hash=sha256:522c257910f9472e9c77c487425ed286f6721c314653e232bc41c6cedece1bb1 \
--hash=sha256:b152874acdc89ff0c8b2d680bfbf020b3e53527c2ad3404489dd61a548aa56a1
setuptools==40.8.0 \
--hash=sha256:6e4eec90337e849ade7103723b9a99631c1f0d19990d6e8412dc42f5ae8b304d \
--hash=sha256:e8496c0079f3ac30052ffe69b679bd876c5265686127a3159cfa415669b7f9ab
six==1.12.0 \
--hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \
--hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73
trezor==0.11.1 \
--hash=sha256:6043f321d856e1b45b9df0c37810264f08d065bb56cd999f61a05fe2906e9e18 \
--hash=sha256:6119b30cf9a136667753935bd06c5f341e78950b35e8ccbadaecc65c12f1946d
typing-extensions==3.7.2 \
--hash=sha256:07b2c978670896022a43c4b915df8958bec4a6b84add7f2c87b2b728bda3ba64 \
--hash=sha256:f3f0e67e1d42de47b5c67c32c9b26641642e9170fe7e292991793705cd5fef7c \
--hash=sha256:fb2cd053238d33a8ec939190f30cfd736c00653a85a2919415cecf7dc3d9da71
urllib3==1.24.1 \
--hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \
--hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22
websocket-client==0.54.0 \
--hash=sha256:8c8bf2d4f800c3ed952df206b18c28f7070d9e3dcbd6ca6291127574f57ee786 \
--hash=sha256:e51562c91ddb8148e791f0155fdb01325d99bb52c4cdbb291aee7a3563fd0849
wheel==0.32.3 \
--hash=sha256:029703bf514e16c8271c3821806a1c171220cc5bdd325cbf4e7da1e056a01db6 \
--hash=sha256:1e53cdb3f808d5ccd0df57f964263752aa74ea7359526d3da6c02114ec1e1d44

View File

@ -1,64 +1,155 @@
certifi==2018.4.16 \
--hash=sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7 \
--hash=sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0
aiohttp==3.5.4 \
--hash=sha256:00d198585474299c9c3b4f1d5de1a576cc230d562abc5e4a0e81d71a20a6ca55 \
--hash=sha256:0155af66de8c21b8dba4992aaeeabf55503caefae00067a3b1139f86d0ec50ed \
--hash=sha256:09654a9eca62d1bd6d64aa44db2498f60a5c1e0ac4750953fdd79d5c88955e10 \
--hash=sha256:199f1d106e2b44b6dacdf6f9245493c7d716b01d0b7fbe1959318ba4dc64d1f5 \
--hash=sha256:296f30dedc9f4b9e7a301e5cc963012264112d78a1d3094cd83ef148fdf33ca1 \
--hash=sha256:368ed312550bd663ce84dc4b032a962fcb3c7cae099dbbd48663afc305e3b939 \
--hash=sha256:40d7ea570b88db017c51392349cf99b7aefaaddd19d2c78368aeb0bddde9d390 \
--hash=sha256:629102a193162e37102c50713e2e31dc9a2fe7ac5e481da83e5bb3c0cee700aa \
--hash=sha256:6d5ec9b8948c3d957e75ea14d41e9330e1ac3fed24ec53766c780f82805140dc \
--hash=sha256:87331d1d6810214085a50749160196391a712a13336cd02ce1c3ea3d05bcf8d5 \
--hash=sha256:9a02a04bbe581c8605ac423ba3a74999ec9d8bce7ae37977a3d38680f5780b6d \
--hash=sha256:9c4c83f4fa1938377da32bc2d59379025ceeee8e24b89f72fcbccd8ca22dc9bf \
--hash=sha256:9cddaff94c0135ee627213ac6ca6d05724bfe6e7a356e5e09ec57bd3249510f6 \
--hash=sha256:a25237abf327530d9561ef751eef9511ab56fd9431023ca6f4803f1994104d72 \
--hash=sha256:a5cbd7157b0e383738b8e29d6e556fde8726823dae0e348952a61742b21aeb12 \
--hash=sha256:a97a516e02b726e089cffcde2eea0d3258450389bbac48cbe89e0f0b6e7b0366 \
--hash=sha256:acc89b29b5f4e2332d65cd1b7d10c609a75b88ef8925d487a611ca788432dfa4 \
--hash=sha256:b05bd85cc99b06740aad3629c2585bda7b83bd86e080b44ba47faf905fdf1300 \
--hash=sha256:c2bec436a2b5dafe5eaeb297c03711074d46b6eb236d002c13c42f25c4a8ce9d \
--hash=sha256:cc619d974c8c11fe84527e4b5e1c07238799a8c29ea1c1285149170524ba9303 \
--hash=sha256:d4392defd4648badaa42b3e101080ae3313e8f4787cb517efd3f5b8157eaefd6 \
--hash=sha256:e1c3c582ee11af7f63a34a46f0448fca58e59889396ffdae1f482085061a2889
aiohttp-socks==0.2.2 \
--hash=sha256:e473ee222b001fe33798957b9ce3352b32c187cf41684f8e2259427925914993 \
--hash=sha256:eebd8939a7c3c1e3e7e1b2552c60039b4c65ef6b8b2351efcbdd98290538e310
aiorpcX==0.10.4 \
--hash=sha256:7130105d31230f069b0eea4e1893c7199cfe2d89a52a31aec718d37f4449935d \
--hash=sha256:e6dfd584f597ee3aa6a8d4cb5755c8ffbbe42754f32728561d9e5940379d5096
async_timeout==3.0.1 \
--hash=sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f \
--hash=sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3
attrs==18.2.0 \
--hash=sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69 \
--hash=sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb
certifi==2018.11.29 \
--hash=sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7 \
--hash=sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033
chardet==3.0.4 \
--hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \
--hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691
dnspython==1.15.0 \
--hash=sha256:40f563e1f7a7b80dc5a4e76ad75c23da53d62f1e15e6e517293b04e1f84ead7c \
--hash=sha256:861e6e58faa730f9845aaaa9c6c832851fbf89382ac52915a51f89c71accdd31
dnspython==1.16.0 \
--hash=sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01 \
--hash=sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d
ecdsa==0.13 \
--hash=sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c \
--hash=sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa
idna==2.7 \
--hash=sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e \
--hash=sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16
jsonrpclib-pelix==0.3.1 \
--hash=sha256:5417b1508d5a50ec64f6e5b88907f111155d52607b218ff3ba9a777afb2e49e3 \
--hash=sha256:bd89a6093bc4d47dc8a096197aacb827359944a4533be5193f3845f57b9f91b4
pbkdf2==1.3 \
--hash=sha256:ac6397369f128212c43064a2b4878038dab78dab41875364554aaf2a684e6979
pip==10.0.1 \
--hash=sha256:717cdffb2833be8409433a93746744b59505f42146e8d37de6c62b430e25d6d7 \
--hash=sha256:f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68
protobuf==3.6.0 \
--hash=sha256:12985d9f40c104da2f44ec089449214876809b40fdc5d9e43b93b512b9e74056 \
--hash=sha256:12c97fe27af12fc5d66b23f905ab09dd4fb0c68d5a74a419d914580e6d2e71e3 \
--hash=sha256:327fb9d8a8247bc780b9ea7ed03c0643bc0d22c139b761c9ec1efc7cc3f0923e \
--hash=sha256:3895319db04c0b3baed74fb66be7ba9f4cd8e88a432b8e71032cdf08b2dfee23 \
--hash=sha256:695072063e256d32335d48b9484451f7c7948edc3dbd419469d6a778602682fc \
--hash=sha256:7d786f3ef5b33a04e6538089674f244a3b0f588155016559d950989010af97d0 \
--hash=sha256:8bf82bb7a466a54be7272dcb492f71d55a2453a58d862fb74c3f2083f2768543 \
--hash=sha256:9bbc1ae1c33c1bd3a2fc05a3aec328544d2b039ff0ce6f000063628a32fad777 \
--hash=sha256:9e992c68103ab5635728d29fcf132c669cb4e2db24d012685210276185009d17 \
--hash=sha256:9f1087abb67b34e55108bc610936b34363a7aac692023bcbb17e065c253a1f80 \
--hash=sha256:9fefcb92a3784b446abf3641d9a14dad815bee88e0edd10b9a9e0e144d01a991 \
--hash=sha256:a37836aa47d1b81c2db1a6b7a5e79926062b5d76bd962115a0e615551be2b48d \
--hash=sha256:cca22955443c55cf86f963a4ad7057bca95e4dcde84d6a493066d380cfab3bb0 \
--hash=sha256:d7ac50bc06d31deb07ace6de85556c1d7330e5c0958f3b2af85037d6d1182abf \
--hash=sha256:dfe6899304b898538f4dc94fa0b281b56b70e40f58afa4c6f807805261cbe2e8
idna==2.8 \
--hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \
--hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c
idna_ssl==1.1.0 \
--hash=sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c
jsonrpclib-pelix==0.4.0 \
--hash=sha256:19c558e169a51480b39548783067ca55046b62b2409ab4559931255e12f635de \
--hash=sha256:a966d17f2f739ee89031cf5c807d85d92db6b2715fb2b2f8a88bbfc87f468b12
multidict==4.5.2 \
--hash=sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f \
--hash=sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3 \
--hash=sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef \
--hash=sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b \
--hash=sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73 \
--hash=sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc \
--hash=sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3 \
--hash=sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd \
--hash=sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351 \
--hash=sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941 \
--hash=sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d \
--hash=sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1 \
--hash=sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b \
--hash=sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a \
--hash=sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3 \
--hash=sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7 \
--hash=sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0 \
--hash=sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0 \
--hash=sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014 \
--hash=sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5 \
--hash=sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036 \
--hash=sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d \
--hash=sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a \
--hash=sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce \
--hash=sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1 \
--hash=sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a \
--hash=sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9 \
--hash=sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7 \
--hash=sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b
pip==19.0.1 \
--hash=sha256:aae79c7afe895fb986ec751564f24d97df1331bb99cdfec6f70dada2f40c0044 \
--hash=sha256:e81ddd35e361b630e94abeda4a1eddd36d47a90e71eb00f38f46b57f787cd1a5
protobuf==3.6.1 \
--hash=sha256:10394a4d03af7060fa8a6e1cbf38cea44be1467053b0aea5bbfcb4b13c4b88c4 \
--hash=sha256:1489b376b0f364bcc6f89519718c057eb191d7ad6f1b395ffd93d1aa45587811 \
--hash=sha256:1931d8efce896981fe410c802fd66df14f9f429c32a72dd9cfeeac9815ec6444 \
--hash=sha256:196d3a80f93c537f27d2a19a4fafb826fb4c331b0b99110f985119391d170f96 \
--hash=sha256:46e34fdcc2b1f2620172d3a4885128705a4e658b9b62355ae5e98f9ea19f42c2 \
--hash=sha256:4b92e235a3afd42e7493b281c8b80c0c65cbef45de30f43d571d1ee40a1f77ef \
--hash=sha256:574085a33ca0d2c67433e5f3e9a0965c487410d6cb3406c83bdaf549bfc2992e \
--hash=sha256:59cd75ded98094d3cf2d79e84cdb38a46e33e7441b2826f3838dcc7c07f82995 \
--hash=sha256:5ee0522eed6680bb5bac5b6d738f7b0923b3cafce8c4b1a039a6107f0841d7ed \
--hash=sha256:65917cfd5da9dfc993d5684643063318a2e875f798047911a9dd71ca066641c9 \
--hash=sha256:685bc4ec61a50f7360c9fd18e277b65db90105adbf9c79938bd315435e526b90 \
--hash=sha256:92e8418976e52201364a3174e40dc31f5fd8c147186d72380cbda54e0464ee19 \
--hash=sha256:9335f79d1940dfb9bcaf8ec881fb8ab47d7a2c721fb8b02949aab8bbf8b68625 \
--hash=sha256:a7ee3bb6de78185e5411487bef8bc1c59ebd97e47713cba3c460ef44e99b3db9 \
--hash=sha256:ceec283da2323e2431c49de58f80e1718986b79be59c266bb0509cbf90ca5b9e \
--hash=sha256:e7a5ccf56444211d79e3204b05087c1460c212a2c7d62f948b996660d0165d68 \
--hash=sha256:fcfc907746ec22716f05ea96b7f41597dfe1a1c088f861efb8a0d4f4196a6f10
pyaes==1.6.1 \
--hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f
PySocks==1.6.8 \
--hash=sha256:3fe52c55890a248676fd69dc9e3c4e811718b777834bcaab7a8125cf9deac672
qrcode==6.0 \
--hash=sha256:037b0db4c93f44586e37f84c3da3f763874fcac85b2974a69a98e399ac78e1bf \
--hash=sha256:de4ffc15065e6ff20a551ad32b6b41264f3c75275675406ddfa8e3530d154be3
requests==2.19.1 \
--hash=sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1 \
--hash=sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a
setuptools==39.2.0 \
--hash=sha256:8fca9275c89964f13da985c3656cb00ba029d7f3916b37990927ffdf264e7926 \
--hash=sha256:f7cddbb5f5c640311eb00eab6e849f7701fa70bf6a183fc8a2c33dd1d1672fb2
six==1.11.0 \
--hash=sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9 \
--hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb
urllib3==1.23 \
--hash=sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf \
--hash=sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5
wheel==0.31.1 \
--hash=sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c \
--hash=sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f
colorama==0.3.9 \
--hash=sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda \
--hash=sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1
QDarkStyle==2.5.4 \
--hash=sha256:3eb60922b8c4d9cedecb6897ca4c9f8a259d81bdefe5791976ccdf12432de1f0 \
--hash=sha256:51331fc6490b38c376e6ba8d8c814320c8d2d1c2663055bc396321a7c28fa8be
qrcode==6.1 \
--hash=sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5 \
--hash=sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369
setuptools==40.8.0 \
--hash=sha256:6e4eec90337e849ade7103723b9a99631c1f0d19990d6e8412dc42f5ae8b304d \
--hash=sha256:e8496c0079f3ac30052ffe69b679bd876c5265686127a3159cfa415669b7f9ab
six==1.12.0 \
--hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \
--hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73
typing-extensions==3.7.2 \
--hash=sha256:07b2c978670896022a43c4b915df8958bec4a6b84add7f2c87b2b728bda3ba64 \
--hash=sha256:f3f0e67e1d42de47b5c67c32c9b26641642e9170fe7e292991793705cd5fef7c \
--hash=sha256:fb2cd053238d33a8ec939190f30cfd736c00653a85a2919415cecf7dc3d9da71
wheel==0.32.3 \
--hash=sha256:029703bf514e16c8271c3821806a1c171220cc5bdd325cbf4e7da1e056a01db6 \
--hash=sha256:1e53cdb3f808d5ccd0df57f964263752aa74ea7359526d3da6c02114ec1e1d44
yarl==1.3.0 \
--hash=sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9 \
--hash=sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f \
--hash=sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb \
--hash=sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320 \
--hash=sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842 \
--hash=sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0 \
--hash=sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829 \
--hash=sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310 \
--hash=sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4 \
--hash=sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8 \
--hash=sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1
colorama==0.4.1 \
--hash=sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d \
--hash=sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48
pylibscrypt==1.7.1 \
--hash=sha256:7aa9424e211a12106c67ea884ccfe609856289372b900d3702faaf66e87f79ac
scrypt==0.8.6 \
--hash=sha256:85919f023148cd9fb01d75ad4e3e061928c298fa6249a0cd6cd469c4b947595e \
--hash=sha256:4ad7188f2e42dbee2ff1cd72e3da40b170ba41847effbf0d726444f62ae60f3a \
--hash=sha256:bc131f74a688fa09993c518ca666a2ebd4268b207e039cbab03a034228140d3e \
--hash=sha256:232acdbc3434d2de55def8d5dbf1bc4b9bfc50da7c5741df2a6eebc4e18d3720 \
--hash=sha256:971db040d3963ebe4b919a203fe10d7d6659951d3644066314330983dc175ed4 \
--hash=sha256:475ac80239b3d788ae71a09c3019ca915e149aaa339adcdd1c9eef121293dc88 \
--hash=sha256:18ccbc63d87c6f89b753194194bb37aeaf1abc517e4b989461d115c1d93ce128 \
--hash=sha256:c23daecee405cb036845917295c76f8d747fc890158df40cb304b4b3c3640079 \
--hash=sha256:f8239b2d47fa1d40bc27efd231dc7083695d10c1c2ac51a99380360741e0362d

View File

@ -1,6 +1,8 @@
#!/bin/bash
# Run this after a new release to update dependencies
set -e
venv_dir=~/.electrum-venv
contrib=$(dirname "$0")

View File

@ -1,6 +1,25 @@
#!/bin/bash
pushd ./gui/kivy/
set -e
CONTRIB="$(dirname "$(readlink -e "$0")")"
ROOT_FOLDER="$CONTRIB"/..
PACKAGES="$ROOT_FOLDER"/packages/
LOCALE="$ROOT_FOLDER"/electrum/locale/
if [ ! -d "$LOCALE" ]; then
echo "Run make_locale first!"
exit 1
fi
if [ ! -d "$PACKAGES" ]; then
echo "Run make_packages first!"
exit 1
fi
pushd ./electrum/gui/kivy/
make theming
if [[ -n "$1" && "$1" == "release" ]] ; then
echo -n Keystore Password:

View File

@ -24,6 +24,7 @@ string = string.replace("##VERSION_APK##", APK_VERSION)
files = {
'tgz': "Electrum-%s.tar.gz" % version,
'appimage': "electrum-%s-x86_64.AppImage" % version,
'zip': "Electrum-%s.zip" % version,
'mac': "electrum-%s.dmg" % version_mac,
'win': "electrum-%s.exe" % version_win,

View File

@ -3,13 +3,17 @@ import os
import subprocess
import io
import zipfile
import requests
import sys
try:
import requests
except ImportError as e:
sys.exit(f"Error: {str(e)}. Try 'sudo python3 -m pip install <module-name>'")
os.chdir(os.path.dirname(os.path.realpath(__file__)))
os.chdir('..')
code_directories = 'gui plugins lib'
cmd = "find {} -type f -name '*.py' -o -name '*.kv'".format(code_directories)
cmd = "find electrum -type f -name '*.py' -o -name '*.kv'"
files = subprocess.check_output(cmd, shell=True)
@ -19,13 +23,13 @@ with open("app.fil", "wb") as f:
print("Found {} files to translate".format(len(files.splitlines())))
# Generate fresh translation template
if not os.path.exists('lib/locale'):
os.mkdir('lib/locale')
cmd = 'xgettext -s --from-code UTF-8 --language Python --no-wrap -f app.fil --output=lib/locale/messages.pot'
if not os.path.exists('electrum/locale'):
os.mkdir('electrum/locale')
cmd = 'xgettext -s --from-code UTF-8 --language Python --no-wrap -f app.fil --output=electrum/locale/messages.pot'
print('Generate template')
os.system(cmd)
os.chdir('lib')
os.chdir('electrum')
crowdin_identifier = 'electrum'
crowdin_file_name = 'files[electrum-client/messages.pot]'
@ -55,7 +59,7 @@ if crowdin_api_key:
# Download & unzip
print('Download translations')
s = requests.request('GET', 'https://crowdin.com/download/project/' + crowdin_identifier + '.zip').content
s = requests.request('GET', 'https://crowdin.com/backend/download/project/' + crowdin_identifier + '.zip').content
zfobj = zipfile.ZipFile(io.BytesIO(s))
print('Unzip translations')

View File

@ -1,13 +1,10 @@
#!/bin/bash
contrib=$(dirname "$0")
test -n "$contrib" -a -d "$contrib" || exit
CONTRIB="$(dirname "$0")"
test -n "$CONTRIB" -a -d "$CONTRIB" || exit
whereis pip3
if [ $? -ne 0 ] ; then echo "Install pip3" ; exit ; fi
rm "$contrib"/../packages/ -r
rm "$CONTRIB"/../packages/ -r
#Install pure python modules in electrum directory
pip3 install -r $contrib/deterministic-build/requirements.txt -t $contrib/../packages
python3 -m pip install -r "$CONTRIB"/deterministic-build/requirements.txt -t "$CONTRIB"/../packages

View File

@ -1 +1,31 @@
python3 setup.py sdist --format=zip,gztar
#!/bin/bash
set -e
CONTRIB="$(dirname "$(readlink -e "$0")")"
ROOT_FOLDER="$CONTRIB"/..
PACKAGES="$ROOT_FOLDER"/packages/
LOCALE="$ROOT_FOLDER"/electrum/locale/
if [ ! -d "$LOCALE" ]; then
echo "Run make_locale first!"
exit 1
fi
if [ ! -d "$PACKAGES" ]; then
echo "Run make_packages first!"
exit 1
fi
(
cd "$ROOT_FOLDER"
echo "'git clean -fx' would delete the following files: >>>"
git clean -fx --dry-run
echo "<<<"
# we could build the kivy atlas potentially?
#(cd electrum/gui/kivy/; make theming) || echo "building kivy atlas failed! skipping."
python3 setup.py --quiet sdist --format=zip,gztar
)

@ -0,0 +1 @@
Subproject commit 59dfc03272751cd29ee311456fa34c40f7ebb7c0

66
contrib/osx/README.md Normal file
View File

@ -0,0 +1,66 @@
Building Mac OS binaries
========================
This guide explains how to build Electrum binaries for macOS systems.
## 1. Building the binary
This needs to be done on a system running macOS or OS X. We use El Capitan (10.11.6) as building it
on High Sierra (or later)
makes the binaries [incompatible with older versions](https://github.com/pyinstaller/pyinstaller/issues/1191).
Before starting, make sure that the Xcode command line tools are installed (e.g. you have `git`).
#### 1.1a Get Xcode
Building the QR scanner (CalinsQRReader) requires full Xcode (not just command line tools).
The last Xcode version compatible with El Capitan is Xcode 8.2.1
Get it from [here](https://developer.apple.com/download/more/).
Unfortunately, you need an "Apple ID" account.
After downloading, uncompress it.
Make sure it is the "selected" xcode (e.g.):
sudo xcode-select -s $HOME/Downloads/Xcode.app/Contents/Developer/
#### 1.1b Build QR scanner separately on newer Mac
Alternatively, you can try building just the QR scanner on newer macOS.
On newer Mac, run:
pushd contrib/osx/CalinsQRReader; xcodebuild; popd
cp -r contrib/osx/CalinsQRReader/build prebuilt_qr
Move `prebuilt_qr` to El Capitan: `contrib/osx/CalinsQRReader/prebuilt_qr`.
#### 1.2 Build Electrum
cd electrum
./contrib/osx/make_osx
This creates both a folder named Electrum.app and the .dmg file.
## 2. Building the image deterministically (WIP)
The usual way to distribute macOS applications is to use image files containing the
application. Although these images can be created on a Mac with the built-in `hdiutil`,
they are not deterministic.
Instead, we use the toolchain that Bitcoin uses: genisoimage and libdmg-hfsplus.
These tools do not work on macOS, so you need a separate Linux machine (or VM).
Copy the Electrum.app directory over and install the dependencies, e.g.:
apt install libcap-dev cmake make gcc faketime
Then you can just invoke `package.sh` with the path to the app:
cd electrum
./contrib/osx/package.sh ~/Electrum.app/

23
contrib/osx/base.sh Normal file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env bash
. $(dirname "$0")/../build_tools_util.sh
function DoCodeSignMaybe { # ARGS: infoName fileOrDirName codesignIdentity
infoName="$1"
file="$2"
identity="$3"
deep=""
if [ -z "$identity" ]; then
# we are ok with them not passing anything; master script calls us unconditionally even if no identity is specified
return
fi
if [ -d "$file" ]; then
deep="--deep"
fi
if [ -z "$infoName" ] || [ -z "$file" ] || [ -z "$identity" ] || [ ! -e "$file" ]; then
fail "Argument error to internal function DoCodeSignMaybe()"
fi
info "Code signing ${infoName}..."
codesign -f -v $deep -s "$identity" "$file" || fail "Could not code sign ${infoName}"
}

143
contrib/osx/make_osx Executable file
View File

@ -0,0 +1,143 @@
#!/usr/bin/env bash
# Parameterize
PYTHON_VERSION=3.6.4
BUILDDIR=/tmp/electrum-build
PACKAGE=Electrum
GIT_REPO=https://github.com/spesmilo/electrum
LIBSECP_VERSION="b408c6a8b287003d1ade5709e6f7bc3c7f1d5be7"
. $(dirname "$0")/base.sh
src_dir=$(dirname "$0")
cd $src_dir/../..
export PYTHONHASHSEED=22
VERSION=`git describe --tags --dirty --always`
which brew > /dev/null 2>&1 || fail "Please install brew from https://brew.sh/ to continue"
which xcodebuild > /dev/null 2>&1 || fail "Please install Xcode and xcode command line tools to continue"
# Code Signing: See https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html
APP_SIGN=""
if [ -n "$1" ]; then
# Test the identity is valid for signing by doing this hack. There is no other way to do this.
cp -f /bin/ls ./CODESIGN_TEST
codesign -s "$1" --dryrun -f ./CODESIGN_TEST > /dev/null 2>&1
res=$?
rm -f ./CODESIGN_TEST
if ((res)); then
fail "Code signing identity \"$1\" appears to be invalid."
fi
unset res
APP_SIGN="$1"
info "Code signing enabled using identity \"$APP_SIGN\""
else
warn "Code signing DISABLED. Specify a valid macOS Developer identity installed on the system as the first argument to this script to enable signing."
fi
info "Installing Python $PYTHON_VERSION"
export PATH="~/.pyenv/bin:~/.pyenv/shims:~/Library/Python/3.6/bin:$PATH"
if [ -d "~/.pyenv" ]; then
pyenv update
else
curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash > /dev/null 2>&1
fi
PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install -s $PYTHON_VERSION && \
pyenv global $PYTHON_VERSION || \
fail "Unable to use Python $PYTHON_VERSION"
info "install dependencies specific to binaries"
# note that this also installs pinned versions of both pip and setuptools
python3 -m pip install -Ir ./contrib/deterministic-build/requirements-binaries.txt --user \
|| fail "Could not install pyinstaller"
info "Installing pyinstaller"
python3 -m pip install -I --user pyinstaller==3.4 --no-use-pep517 || fail "Could not install pyinstaller"
info "Using these versions for building $PACKAGE:"
sw_vers
python3 --version
echo -n "Pyinstaller "
pyinstaller --version
rm -rf ./dist
git submodule init
git submodule update
rm -rf $BUILDDIR > /dev/null 2>&1
mkdir $BUILDDIR
cp -R ./contrib/deterministic-build/electrum-locale/locale/ ./electrum/locale/
info "Downloading libusb..."
curl https://homebrew.bintray.com/bottles/libusb-1.0.22.el_capitan.bottle.tar.gz | \
tar xz --directory $BUILDDIR
cp $BUILDDIR/libusb/1.0.22/lib/libusb-1.0.dylib contrib/osx
echo "82c368dfd4da017ceb32b12ca885576f325503428a4966cc09302cbd62702493 contrib/osx/libusb-1.0.dylib" | \
shasum -a 256 -c || fail "libusb checksum mismatched"
info "Building libsecp256k1"
brew install autoconf automake libtool
git clone https://github.com/bitcoin-core/secp256k1 $BUILDDIR/secp256k1
pushd $BUILDDIR/secp256k1
git reset --hard $LIBSECP_VERSION
git clean -f -x -q
./autogen.sh
./configure --enable-module-recovery --enable-experimental --enable-module-ecdh --disable-jni
make
popd
cp $BUILDDIR/secp256k1/.libs/libsecp256k1.0.dylib contrib/osx
info "Building CalinsQRReader..."
d=contrib/osx/CalinsQRReader
pushd $d
rm -fr build
# prefer building using xcode ourselves. otherwise fallback to prebuilt binary
xcodebuild || cp -r prebuilt_qr build || fail "Could not build CalinsQRReader"
popd
DoCodeSignMaybe "CalinsQRReader.app" "${d}/build/Release/CalinsQRReader.app" "$APP_SIGN" # If APP_SIGN is empty will be a noop
info "Installing requirements..."
python3 -m pip install -Ir ./contrib/deterministic-build/requirements.txt --user || \
fail "Could not install requirements"
info "Installing hardware wallet requirements..."
python3 -m pip install -Ir ./contrib/deterministic-build/requirements-hw.txt --user || \
fail "Could not install hardware wallet requirements"
info "Building $PACKAGE..."
python3 -m pip install --user . > /dev/null || fail "Could not build $PACKAGE"
info "Faking timestamps..."
for d in ~/Library/Python/ ~/.pyenv .; do
pushd $d
find . -exec touch -t '200101220000' {} +
popd
done
info "Building binary"
APP_SIGN="$APP_SIGN" pyinstaller --noconfirm --ascii --clean --name $VERSION contrib/osx/osx.spec || fail "Could not build binary"
info "Adding bitcoin URI types to Info.plist"
plutil -insert 'CFBundleURLTypes' \
-xml '<array><dict> <key>CFBundleURLName</key> <string>bitcoin</string> <key>CFBundleURLSchemes</key> <array><string>bitcoin</string></array> </dict></array>' \
-- dist/$PACKAGE.app/Contents/Info.plist \
|| fail "Could not add keys to Info.plist. Make sure the program 'plutil' exists and is installed."
DoCodeSignMaybe "app bundle" "dist/${PACKAGE}.app" "$APP_SIGN" # If APP_SIGN is empty will be a noop
info "Creating .DMG"
hdiutil create -fs HFS+ -volname $PACKAGE -srcfolder dist/$PACKAGE.app dist/electrum-$VERSION.dmg || fail "Could not create .DMG"
DoCodeSignMaybe ".DMG" "dist/electrum-${VERSION}.dmg" "$APP_SIGN" # If APP_SIGN is empty will be a noop
if [ -z "$APP_SIGN" ]; then
warn "App was built successfully but was not code signed. Users may get security warnings from macOS."
warn "Specify a valid code signing identity as the first argument to this script to enable code signing."
fi

162
contrib/osx/osx.spec Normal file
View File

@ -0,0 +1,162 @@
# -*- mode: python -*-
from PyInstaller.utils.hooks import collect_data_files, collect_submodules, collect_dynamic_libs
import sys, os
PACKAGE='Electrum'
PYPKG='electrum'
MAIN_SCRIPT='run_electrum'
ICONS_FILE=PYPKG + '/gui/icons/electrum.icns'
APP_SIGN = os.environ.get('APP_SIGN', '')
def fail(*msg):
RED='\033[0;31m'
NC='\033[0m' # No Color
print("\r🗯 {}ERROR:{}".format(RED, NC), *msg)
sys.exit(1)
def codesign(identity, binary):
d = os.path.dirname(binary)
saved_dir=None
if d:
# switch to directory of the binary so codesign verbose messages don't include long path
saved_dir = os.path.abspath(os.path.curdir)
os.chdir(d)
binary = os.path.basename(binary)
os.system("codesign -v -f -s '{}' '{}'".format(identity, binary))==0 or fail("Could not code sign " + binary)
if saved_dir:
os.chdir(saved_dir)
def monkey_patch_pyinstaller_for_codesigning(identity):
# Monkey-patch PyInstaller so that we app-sign all binaries *after* they are modified by PyInstaller
# If we app-sign before that point, the signature will be invalid because PyInstaller modifies
# @loader_path in the Mach-O loader table.
try:
import PyInstaller.depend.dylib
_saved_func = PyInstaller.depend.dylib.mac_set_relative_dylib_deps
except (ImportError, NameError, AttributeError):
# Hmm. Likely wrong PyInstaller version.
fail("Could not monkey-patch PyInstaller for code signing. Please ensure that you are using PyInstaller 3.4.")
_signed = set()
def my_func(fn, distname):
_saved_func(fn, distname)
if (fn, distname) not in _signed:
codesign(identity, fn)
_signed.add((fn,distname)) # remember we signed it so we don't sign again
PyInstaller.depend.dylib.mac_set_relative_dylib_deps = my_func
for i, x in enumerate(sys.argv):
if x == '--name':
VERSION = sys.argv[i+1]
break
else:
raise Exception('no version')
electrum = os.path.abspath(".") + "/"
block_cipher = None
# see https://github.com/pyinstaller/pyinstaller/issues/2005
hiddenimports = []
hiddenimports += collect_submodules('trezorlib')
hiddenimports += collect_submodules('safetlib')
hiddenimports += collect_submodules('btchip')
hiddenimports += collect_submodules('keepkeylib')
hiddenimports += collect_submodules('websocket')
hiddenimports += collect_submodules('ckcc')
# safetlib imports PyQt5.Qt. We use a local updated copy of pinmatrix.py until they
# release a new version that includes https://github.com/archos-safe-t/python-safet/commit/b1eab3dba4c04fdfc1fcf17b66662c28c5f2380e
hiddenimports.remove('safetlib.qt.pinmatrix')
datas = [
(electrum + PYPKG + '/*.json', PYPKG),
(electrum + PYPKG + '/wordlist/english.txt', PYPKG + '/wordlist'),
(electrum + PYPKG + '/locale', PYPKG + '/locale'),
(electrum + PYPKG + '/plugins', PYPKG + '/plugins'),
(electrum + PYPKG + '/gui/icons', PYPKG + '/gui/icons'),
]
datas += collect_data_files('trezorlib')
datas += collect_data_files('safetlib')
datas += collect_data_files('btchip')
datas += collect_data_files('keepkeylib')
datas += collect_data_files('ckcc')
# Add the QR Scanner helper app
datas += [(electrum + "contrib/osx/CalinsQRReader/build/Release/CalinsQRReader.app", "./contrib/osx/CalinsQRReader/build/Release/CalinsQRReader.app")]
# Add libusb so Trezor and Safe-T mini will work
binaries = [(electrum + "contrib/osx/libusb-1.0.dylib", ".")]
binaries += [(electrum + "contrib/osx/libsecp256k1.0.dylib", ".")]
# Workaround for "Retro Look":
binaries += [b for b in collect_dynamic_libs('PyQt5') if 'macstyle' in b[0]]
# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
a = Analysis([electrum+ MAIN_SCRIPT,
electrum+'electrum/gui/qt/main_window.py',
electrum+'electrum/gui/text.py',
electrum+'electrum/util.py',
electrum+'electrum/wallet.py',
electrum+'electrum/simple_config.py',
electrum+'electrum/bitcoin.py',
electrum+'electrum/dnssec.py',
electrum+'electrum/commands.py',
electrum+'electrum/plugins/cosigner_pool/qt.py',
electrum+'electrum/plugins/email_requests/qt.py',
electrum+'electrum/plugins/trezor/qt.py',
electrum+'electrum/plugins/safe_t/client.py',
electrum+'electrum/plugins/safe_t/qt.py',
electrum+'electrum/plugins/keepkey/qt.py',
electrum+'electrum/plugins/ledger/qt.py',
electrum+'electrum/plugins/coldcard/qt.py',
],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[])
# http://stackoverflow.com/questions/19055089/pyinstaller-onefile-warning-pyconfig-h-when-importing-scipy-or-scipy-signal
for d in a.datas:
if 'pyconfig' in d[0]:
a.datas.remove(d)
break
# Strip out parts of Qt that we never use. Reduces binary size by tens of MBs. see #4815
qt_bins2remove=('qtweb', 'qt3d', 'qtgame', 'qtdesigner', 'qtquick', 'qtlocation', 'qttest', 'qtxml')
print("Removing Qt binaries:", *qt_bins2remove)
for x in a.binaries.copy():
for r in qt_bins2remove:
if x[0].lower().startswith(r):
a.binaries.remove(x)
print('----> Removed x =', x)
# If code signing, monkey-patch in a code signing step to pyinstaller. See: https://github.com/spesmilo/electrum/issues/4994
if APP_SIGN:
monkey_patch_pyinstaller_for_codesigning(APP_SIGN)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.datas,
name=PACKAGE,
debug=False,
strip=False,
upx=True,
icon=electrum+ICONS_FILE,
console=False)
app = BUNDLE(exe,
version = VERSION,
name=PACKAGE + '.app',
icon=electrum+ICONS_FILE,
bundle_identifier=None,
info_plist={
'NSHighResolutionCapable': 'True',
'NSSupportsAutomaticGraphicsSwitching': 'True'
}
)

View File

@ -85,4 +85,4 @@ dmg dmg Electrum_uncompressed.dmg electrum-$VERSION.dmg || fail "Unable to creat
rm Electrum_uncompressed.dmg
echo "Done."
md5sum electrum-$VERSION.dmg
sha256sum electrum-$VERSION.dmg

View File

@ -1,6 +1,8 @@
Cython>=0.27
trezor[hidapi]>=0.9.0
trezor[hidapi]>=0.11.1
safet[hidapi]>=0.1.0
keepkey
btchip-python
btchip-python>=0.1.26
ckcc-protocol>=0.7.2
websocket-client
hidapi

View File

@ -1,10 +1,12 @@
pyaes>=0.1a1
ecdsa>=0.9
pbkdf2
requests
qrcode
protobuf
dnspython
jsonrpclib-pelix
PySocks>=1.6.6
qdarkstyle<3.0
qdarkstyle<2.6
aiorpcx>=0.9,<0.11
aiohttp>=3.3.0
aiohttp_socks
certifi
pylibscrypt==1.7.1

4
contrib/sign_version Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
version=`python3 -c "import electrum; print(electrum.version.ELECTRUM_VERSION)"`
sig=`./run_electrum -w $SIGNING_WALLET signmessage $SIGNING_ADDRESS $version`
echo "{ \"version\":\"$version\", \"signatures\":{ \"$SIGNING_ADDRESS\":\"$sig\"}}"

View File

@ -17,11 +17,11 @@ if [ -e ./env/bin/activate ]; then
else
virtualenv env -p `which python3`
source ./env/bin/activate
python3 setup.py install
python3 -m pip install .[fast]
fi
export PYTHONPATH="/usr/local/lib/python${PYTHON_VER}/site-packages:$PYTHONPATH"
./electrum "$@"
./run_electrum "$@"
deactivate

View File

@ -1,16 +0,0 @@
# Configuration file for the Electrum client
# Settings defined here are shared across wallets
#
# copy this file to /etc/electrum.conf if you want read-only settings
[client]
server = electrum.novit.ro:50001:t
proxy = None
gap_limit = 5
# booleans use python syntax
use_change = True
gui = qt
num_zeros = 2
# default transaction fee is in Satoshis
fee = 10000
winpos-qt = [799, 226, 877, 435]

View File

@ -3,19 +3,20 @@
[Desktop Entry]
Comment=Lightweight Bitcoin Client
Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\" electrum %u"
Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\"; electrum %u"
GenericName[en_US]=Bitcoin Wallet
GenericName=Bitcoin Wallet
Icon=electrum
Name[en_US]=Electrum Bitcoin Wallet
Name=Electrum Bitcoin Wallet
Categories=Finance;Network;
StartupNotify=false
StartupNotify=true
StartupWMClass=electrum
Terminal=false
Type=Application
MimeType=x-scheme-handler/bitcoin;
Actions=Testnet;
[Desktop Action Testnet]
Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\" electrum --testnet %u"
Exec=sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\"; electrum --testnet %u"
Name=Testnet mode

Binary file not shown.

View File

@ -1,14 +1,17 @@
from .version import ELECTRUM_VERSION
from .util import format_satoshis, print_msg, print_error, set_verbosity
from .wallet import Synchronizer, Wallet
from .wallet import Wallet
from .storage import WalletStorage
from .coinchooser import COIN_CHOOSERS
from .network import Network, pick_random_server
from .interface import Connection, Interface
from .interface import Interface
from .simple_config import SimpleConfig, get_config, set_config
from . import bitcoin
from . import transaction
from . import daemon
from .transaction import Transaction
from .plugins import BasePlugin
from .plugin import BasePlugin
from .commands import Commands, known_commands
__version__ = ELECTRUM_VERSION

View File

@ -0,0 +1,877 @@
# Electrum - lightweight Bitcoin client
# Copyright (C) 2018 The Electrum Developers
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import threading
import asyncio
import itertools
from collections import defaultdict
from typing import TYPE_CHECKING, Dict, Optional
from . import bitcoin
from .bitcoin import COINBASE_MATURITY, TYPE_ADDRESS, TYPE_PUBKEY
from .util import PrintError, profiler, bfh, TxMinedInfo
from .transaction import Transaction, TxOutput
from .synchronizer import Synchronizer
from .verifier import SPV
from .blockchain import hash_header
from .i18n import _
if TYPE_CHECKING:
from .storage import WalletStorage
from .network import Network
TX_HEIGHT_LOCAL = -2
TX_HEIGHT_UNCONF_PARENT = -1
TX_HEIGHT_UNCONFIRMED = 0
class AddTransactionException(Exception):
pass
class UnrelatedTransactionException(AddTransactionException):
def __str__(self):
return _("Transaction is unrelated to this wallet.")
class AddressSynchronizer(PrintError):
"""
inherited by wallet
"""
def __init__(self, storage: 'WalletStorage'):
self.storage = storage
self.network = None # type: Network
# verifier (SPV) and synchronizer are started in start_network
self.synchronizer = None # type: Synchronizer
self.verifier = None # type: SPV
# locks: if you need to take multiple ones, acquire them in the order they are defined here!
self.lock = threading.RLock()
self.transaction_lock = threading.RLock()
# address -> list(txid, height)
self.history = storage.get('addr_history',{})
# Verified transactions. txid -> TxMinedInfo. Access with self.lock.
verified_tx = storage.get('verified_tx3', {})
self.verified_tx = {} # type: Dict[str, TxMinedInfo]
for txid, (height, timestamp, txpos, header_hash, flodata) in verified_tx.items():
self.verified_tx[txid] = TxMinedInfo(height=height,
conf=None,
timestamp=timestamp,
txpos=txpos,
header_hash=header_hash,
flodata=flodata)
# Transactions pending verification. txid -> tx_height. Access with self.lock.
self.unverified_tx = defaultdict(int)
# true when synchronized
self.up_to_date = False
# thread local storage for caching stuff
self.threadlocal_cache = threading.local()
self.load_and_cleanup()
def with_transaction_lock(func):
def func_wrapper(self, *args, **kwargs):
with self.transaction_lock:
return func(self, *args, **kwargs)
return func_wrapper
def load_and_cleanup(self):
self.load_transactions()
self.load_local_history()
self.check_history()
self.load_unverified_transactions()
self.remove_local_transactions_we_dont_have()
def is_mine(self, address):
return address in self.history
def get_addresses(self):
return sorted(self.history.keys())
def get_address_history(self, addr):
h = []
# we need self.transaction_lock but get_tx_height will take self.lock
# so we need to take that too here, to enforce order of locks
with self.lock, self.transaction_lock:
related_txns = self._history_local.get(addr, set())
for tx_hash in related_txns:
tx_height = self.get_tx_height(tx_hash).height
h.append((tx_hash, tx_height))
return h
def get_address_history_len(self, addr: str) -> int:
"""Return number of transactions where address is involved."""
return len(self._history_local.get(addr, ()))
def get_txin_address(self, txi):
addr = txi.get('address')
if addr and addr != "(pubkey)":
return addr
prevout_hash = txi.get('prevout_hash')
prevout_n = txi.get('prevout_n')
dd = self.txo.get(prevout_hash, {})
for addr, l in dd.items():
for n, v, is_cb in l:
if n == prevout_n:
return addr
return None
def get_txout_address(self, txo: TxOutput):
if txo.type == TYPE_ADDRESS:
addr = txo.address
elif txo.type == TYPE_PUBKEY:
addr = bitcoin.public_key_to_p2pkh(bfh(txo.address))
else:
addr = None
return addr
def load_unverified_transactions(self):
# review transactions that are in the history
for addr, hist in self.history.items():
for tx_hash, tx_height in hist:
# add it in case it was previously unconfirmed
self.add_unverified_tx(tx_hash, tx_height)
def start_network(self, network):
self.network = network
if self.network is not None:
self.synchronizer = Synchronizer(self)
self.verifier = SPV(self.network, self)
def stop_threads(self, write_to_disk=True):
if self.network:
if self.synchronizer:
asyncio.run_coroutine_threadsafe(self.synchronizer.stop(), self.network.asyncio_loop)
self.synchronizer = None
if self.verifier:
asyncio.run_coroutine_threadsafe(self.verifier.stop(), self.network.asyncio_loop)
self.verifier = None
self.storage.put('stored_height', self.get_local_height())
if write_to_disk:
self.save_transactions()
self.save_verified_tx()
self.storage.write()
def add_address(self, address):
if address not in self.history:
self.history[address] = []
self.set_up_to_date(False)
if self.synchronizer:
self.synchronizer.add(address)
def get_conflicting_transactions(self, tx_hash, tx):
"""Returns a set of transaction hashes from the wallet history that are
directly conflicting with tx, i.e. they have common outpoints being
spent with tx. If the tx is already in wallet history, that will not be
reported as a conflict.
"""
conflicting_txns = set()
with self.transaction_lock:
for txin in tx.inputs():
if txin['type'] == 'coinbase':
continue
prevout_hash = txin['prevout_hash']
prevout_n = txin['prevout_n']
spending_tx_hash = self.spent_outpoints[prevout_hash].get(prevout_n)
if spending_tx_hash is None:
continue
# this outpoint has already been spent, by spending_tx
assert spending_tx_hash in self.transactions
conflicting_txns |= {spending_tx_hash}
if tx_hash in conflicting_txns:
# this tx is already in history, so it conflicts with itself
if len(conflicting_txns) > 1:
raise Exception('Found conflicting transactions already in wallet history.')
conflicting_txns -= {tx_hash}
return conflicting_txns
def add_transaction(self, tx_hash, tx, allow_unrelated=False):
assert tx_hash, tx_hash
assert tx, tx
assert tx.is_complete()
# assert tx_hash == tx.txid() # disabled as expensive; test done by Synchronizer.
# we need self.transaction_lock but get_tx_height will take self.lock
# so we need to take that too here, to enforce order of locks
with self.lock, self.transaction_lock:
# NOTE: returning if tx in self.transactions might seem like a good idea
# BUT we track is_mine inputs in a txn, and during subsequent calls
# of add_transaction tx, we might learn of more-and-more inputs of
# being is_mine, as we roll the gap_limit forward
is_coinbase = tx.inputs()[0]['type'] == 'coinbase'
tx_height = self.get_tx_height(tx_hash).height
if not allow_unrelated:
# note that during sync, if the transactions are not properly sorted,
# it could happen that we think tx is unrelated but actually one of the inputs is is_mine.
# this is the main motivation for allow_unrelated
is_mine = any([self.is_mine(self.get_txin_address(txin)) for txin in tx.inputs()])
is_for_me = any([self.is_mine(self.get_txout_address(txo)) for txo in tx.outputs()])
if not is_mine and not is_for_me:
raise UnrelatedTransactionException()
# Find all conflicting transactions.
# In case of a conflict,
# 1. confirmed > mempool > local
# 2. this new txn has priority over existing ones
# When this method exits, there must NOT be any conflict, so
# either keep this txn and remove all conflicting (along with dependencies)
# or drop this txn
conflicting_txns = self.get_conflicting_transactions(tx_hash, tx)
if conflicting_txns:
existing_mempool_txn = any(
self.get_tx_height(tx_hash2).height in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT)
for tx_hash2 in conflicting_txns)
existing_confirmed_txn = any(
self.get_tx_height(tx_hash2).height > 0
for tx_hash2 in conflicting_txns)
if existing_confirmed_txn and tx_height <= 0:
# this is a non-confirmed tx that conflicts with confirmed txns; drop.
return False
if existing_mempool_txn and tx_height == TX_HEIGHT_LOCAL:
# this is a local tx that conflicts with non-local txns; drop.
return False
# keep this txn and remove all conflicting
to_remove = set()
to_remove |= conflicting_txns
for conflicting_tx_hash in conflicting_txns:
to_remove |= self.get_depending_transactions(conflicting_tx_hash)
for tx_hash2 in to_remove:
self.remove_transaction(tx_hash2)
# add inputs
def add_value_from_prev_output():
dd = self.txo.get(prevout_hash, {})
# note: this nested loop takes linear time in num is_mine outputs of prev_tx
for addr, outputs in dd.items():
# note: instead of [(n, v, is_cb), ...]; we could store: {n -> (v, is_cb)}
for n, v, is_cb in outputs:
if n == prevout_n:
if addr and self.is_mine(addr):
if d.get(addr) is None:
d[addr] = set()
d[addr].add((ser, v))
return
self.txi[tx_hash] = d = {}
for txi in tx.inputs():
if txi['type'] == 'coinbase':
continue
prevout_hash = txi['prevout_hash']
prevout_n = txi['prevout_n']
ser = prevout_hash + ':%d' % prevout_n
self.spent_outpoints[prevout_hash][prevout_n] = tx_hash
add_value_from_prev_output()
# add outputs
self.txo[tx_hash] = d = {}
for n, txo in enumerate(tx.outputs()):
v = txo[2]
ser = tx_hash + ':%d'%n
addr = self.get_txout_address(txo)
if addr and self.is_mine(addr):
if d.get(addr) is None:
d[addr] = []
d[addr].append((n, v, is_coinbase))
# give v to txi that spends me
next_tx = self.spent_outpoints[tx_hash].get(n)
if next_tx is not None:
dd = self.txi.get(next_tx, {})
if dd.get(addr) is None:
dd[addr] = set()
if (ser, v) not in dd[addr]:
dd[addr].add((ser, v))
self._add_tx_to_local_history(next_tx)
# add to local history
self._add_tx_to_local_history(tx_hash)
# save
self.transactions[tx_hash] = tx
return True
def remove_transaction(self, tx_hash):
def remove_from_spent_outpoints():
# undo spends in spent_outpoints
if tx is not None: # if we have the tx, this branch is faster
for txin in tx.inputs():
if txin['type'] == 'coinbase':
continue
prevout_hash = txin['prevout_hash']
prevout_n = txin['prevout_n']
self.spent_outpoints[prevout_hash].pop(prevout_n, None)
if not self.spent_outpoints[prevout_hash]:
self.spent_outpoints.pop(prevout_hash)
else: # expensive but always works
for prevout_hash, d in list(self.spent_outpoints.items()):
for prevout_n, spending_txid in d.items():
if spending_txid == tx_hash:
self.spent_outpoints[prevout_hash].pop(prevout_n, None)
if not self.spent_outpoints[prevout_hash]:
self.spent_outpoints.pop(prevout_hash)
# Remove this tx itself; if nothing spends from it.
# It is not so clear what to do if other txns spend from it, but it will be
# removed when those other txns are removed.
if not self.spent_outpoints[tx_hash]:
self.spent_outpoints.pop(tx_hash)
with self.transaction_lock:
self.print_error("removing tx from history", tx_hash)
tx = self.transactions.pop(tx_hash, None)
remove_from_spent_outpoints()
self._remove_tx_from_local_history(tx_hash)
self.txi.pop(tx_hash, None)
self.txo.pop(tx_hash, None)
def get_depending_transactions(self, tx_hash):
"""Returns all (grand-)children of tx_hash in this wallet."""
children = set()
for other_hash in self.spent_outpoints[tx_hash].values():
children.add(other_hash)
children |= self.get_depending_transactions(other_hash)
return children
def receive_tx_callback(self, tx_hash, tx, tx_height):
self.add_unverified_tx(tx_hash, tx_height)
self.add_transaction(tx_hash, tx, allow_unrelated=True)
def receive_history_callback(self, addr, hist, tx_fees):
with self.lock:
old_hist = self.get_address_history(addr)
for tx_hash, height in old_hist:
if (tx_hash, height) not in hist:
# make tx local
self.unverified_tx.pop(tx_hash, None)
self.verified_tx.pop(tx_hash, None)
if self.verifier:
self.verifier.remove_spv_proof_for_tx(tx_hash)
self.history[addr] = hist
for tx_hash, tx_height in hist:
# add it in case it was previously unconfirmed
self.add_unverified_tx(tx_hash, tx_height)
# if addr is new, we have to recompute txi and txo
tx = self.transactions.get(tx_hash)
if tx is None:
continue
self.add_transaction(tx_hash, tx, allow_unrelated=True)
# Store fees
self.tx_fees.update(tx_fees)
@profiler
def load_transactions(self):
# load txi, txo, tx_fees
# bookkeeping data of is_mine inputs of transactions
self.txi = self.storage.get('txi', {}) # txid -> address -> (prev_outpoint, value)
for txid, d in list(self.txi.items()):
for addr, lst in d.items():
self.txi[txid][addr] = set([tuple(x) for x in lst])
# bookkeeping data of is_mine outputs of transactions
self.txo = self.storage.get('txo', {}) # txid -> address -> (output_index, value, is_coinbase)
self.tx_fees = self.storage.get('tx_fees', {})
tx_list = self.storage.get('transactions', {})
# load transactions
self.transactions = {}
for tx_hash, raw in tx_list.items():
tx = Transaction(raw)
self.transactions[tx_hash] = tx
if self.txi.get(tx_hash) is None and self.txo.get(tx_hash) is None:
self.print_error("removing unreferenced tx", tx_hash)
self.transactions.pop(tx_hash)
# load spent_outpoints
_spent_outpoints = self.storage.get('spent_outpoints', {})
self.spent_outpoints = defaultdict(dict)
for prevout_hash, d in _spent_outpoints.items():
for prevout_n_str, spending_txid in d.items():
prevout_n = int(prevout_n_str)
if spending_txid not in self.transactions:
continue # only care about txns we have
self.spent_outpoints[prevout_hash][prevout_n] = spending_txid
@profiler
def load_local_history(self):
self._history_local = {} # address -> set(txid)
self._address_history_changed_events = defaultdict(asyncio.Event) # address -> Event
for txid in itertools.chain(self.txi, self.txo):
self._add_tx_to_local_history(txid)
@profiler
def check_history(self):
save = False
hist_addrs_mine = list(filter(lambda k: self.is_mine(k), self.history.keys()))
hist_addrs_not_mine = list(filter(lambda k: not self.is_mine(k), self.history.keys()))
for addr in hist_addrs_not_mine:
self.history.pop(addr)
save = True
for addr in hist_addrs_mine:
hist = self.history[addr]
for tx_hash, tx_height in hist:
if self.txi.get(tx_hash) or self.txo.get(tx_hash):
continue
tx = self.transactions.get(tx_hash)
if tx is not None:
self.add_transaction(tx_hash, tx, allow_unrelated=True)
save = True
if save:
self.save_transactions()
def remove_local_transactions_we_dont_have(self):
txid_set = set(self.txi) | set(self.txo)
for txid in txid_set:
tx_height = self.get_tx_height(txid).height
if tx_height == TX_HEIGHT_LOCAL and txid not in self.transactions:
self.remove_transaction(txid)
@profiler
def save_transactions(self, write=False):
with self.transaction_lock:
tx = {}
for k,v in self.transactions.items():
tx[k] = str(v)
self.storage.put('transactions', tx)
self.storage.put('txi', self.txi)
self.storage.put('txo', self.txo)
self.storage.put('tx_fees', self.tx_fees)
self.storage.put('addr_history', self.history)
self.storage.put('spent_outpoints', self.spent_outpoints)
if write:
self.storage.write()
def save_verified_tx(self, write=False):
with self.lock:
verified_tx_to_save = {}
for txid, tx_info in self.verified_tx.items():
verified_tx_to_save[txid] = (tx_info.height, tx_info.timestamp,
tx_info.txpos, tx_info.header_hash, tx_info.flodata)
self.storage.put('verified_tx3', verified_tx_to_save)
if write:
self.storage.write()
def clear_history(self):
with self.lock:
with self.transaction_lock:
self.txi = {}
self.txo = {}
self.tx_fees = {}
self.spent_outpoints = defaultdict(dict)
self.history = {}
self.verified_tx = {}
self.transactions = {} # type: Dict[str, Transaction]
self.save_transactions()
def get_txpos(self, tx_hash):
"""Returns (height, txpos) tuple, even if the tx is unverified."""
with self.lock:
if tx_hash in self.verified_tx:
info = self.verified_tx[tx_hash]
return info.height, info.txpos
elif tx_hash in self.unverified_tx:
height = self.unverified_tx[tx_hash]
return (height, 0) if height > 0 else ((1e9 - height), 0)
else:
return (1e9+1, 0)
def with_local_height_cached(func):
# get local height only once, as it's relatively expensive.
# take care that nested calls work as expected
def f(self, *args, **kwargs):
orig_val = getattr(self.threadlocal_cache, 'local_height', None)
self.threadlocal_cache.local_height = orig_val or self.get_local_height()
try:
return func(self, *args, **kwargs)
finally:
self.threadlocal_cache.local_height = orig_val
return f
@with_local_height_cached
def get_history(self, domain=None):
# get domain
if domain is None:
domain = self.history.keys()
domain = set(domain)
# 1. Get the history of each address in the domain, maintain the
# delta of a tx as the sum of its deltas on domain addresses
tx_deltas = defaultdict(int)
for addr in domain:
h = self.get_address_history(addr)
for tx_hash, height in h:
delta = self.get_tx_delta(tx_hash, addr)
if delta is None or tx_deltas[tx_hash] is None:
tx_deltas[tx_hash] = None
else:
tx_deltas[tx_hash] += delta
# 2. create sorted history
history = []
for tx_hash in tx_deltas:
delta = tx_deltas[tx_hash]
tx_mined_status = self.get_tx_height(tx_hash)
history.append((tx_hash, tx_mined_status, delta))
history.sort(key = lambda x: self.get_txpos(x[0]), reverse=True)
# 3. add balance
c, u, x = self.get_balance(domain)
balance = c + u + x
h2 = []
for tx_hash, tx_mined_status, delta in history:
h2.append((tx_hash, tx_mined_status, delta, balance))
if balance is None or delta is None:
balance = None
else:
balance -= delta
h2.reverse()
# fixme: this may happen if history is incomplete
if balance not in [None, 0]:
self.print_error("Error: history not synchronized")
return []
return h2
def _add_tx_to_local_history(self, txid):
with self.transaction_lock:
for addr in itertools.chain(self.txi.get(txid, []), self.txo.get(txid, [])):
cur_hist = self._history_local.get(addr, set())
cur_hist.add(txid)
self._history_local[addr] = cur_hist
self._mark_address_history_changed(addr)
def _remove_tx_from_local_history(self, txid):
with self.transaction_lock:
for addr in itertools.chain(self.txi.get(txid, []), self.txo.get(txid, [])):
cur_hist = self._history_local.get(addr, set())
try:
cur_hist.remove(txid)
except KeyError:
pass
else:
self._history_local[addr] = cur_hist
def _mark_address_history_changed(self, addr: str) -> None:
# history for this address changed, wake up coroutines:
self._address_history_changed_events[addr].set()
# clear event immediately so that coroutines can wait() for the next change:
self._address_history_changed_events[addr].clear()
async def wait_for_address_history_to_change(self, addr: str) -> None:
"""Wait until the server tells us about a new transaction related to addr.
Unconfirmed and confirmed transactions are not distinguished, and so e.g. SPV
is not taken into account.
"""
assert self.is_mine(addr), "address needs to be is_mine to be watched"
await self._address_history_changed_events[addr].wait()
def add_unverified_tx(self, tx_hash, tx_height):
if tx_hash in self.verified_tx:
if tx_height in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT):
with self.lock:
self.verified_tx.pop(tx_hash)
if self.verifier:
self.verifier.remove_spv_proof_for_tx(tx_hash)
else:
with self.lock:
# tx will be verified only if height > 0
self.unverified_tx[tx_hash] = tx_height
def remove_unverified_tx(self, tx_hash, tx_height):
with self.lock:
new_height = self.unverified_tx.get(tx_hash)
if new_height == tx_height:
self.unverified_tx.pop(tx_hash, None)
def add_verified_tx(self, tx_hash: str, info: TxMinedInfo):
# Remove from the unverified map and add to the verified map
with self.lock:
self.unverified_tx.pop(tx_hash, None)
self.verified_tx[tx_hash] = info
tx_mined_status = self.get_tx_height(tx_hash)
self.network.trigger_callback('verified', self, tx_hash, tx_mined_status)
def get_unverified_txs(self):
'''Returns a map from tx hash to transaction height'''
with self.lock:
return dict(self.unverified_tx) # copy
def undo_verifications(self, blockchain, height):
'''Used by the verifier when a reorg has happened'''
txs = set()
with self.lock:
for tx_hash, info in list(self.verified_tx.items()):
tx_height = info.height
if tx_height >= height:
header = blockchain.read_header(tx_height)
if not header or hash_header(header) != info.header_hash:
self.verified_tx.pop(tx_hash, None)
# NOTE: we should add these txns to self.unverified_tx,
# but with what height?
# If on the new fork after the reorg, the txn is at the
# same height, we will not get a status update for the
# address. If the txn is not mined or at a diff height,
# we should get a status update. Unless we put tx into
# unverified_tx, it will turn into local. So we put it
# into unverified_tx with the old height, and if we get
# a status update, that will overwrite it.
self.unverified_tx[tx_hash] = tx_height
txs.add(tx_hash)
return txs
def get_local_height(self):
""" return last known height if we are offline """
cached_local_height = getattr(self.threadlocal_cache, 'local_height', None)
if cached_local_height is not None:
return cached_local_height
return self.network.get_local_height() if self.network else self.storage.get('stored_height', 0)
def get_tx_height(self, tx_hash: str) -> TxMinedInfo:
with self.lock:
if tx_hash in self.verified_tx:
info = self.verified_tx[tx_hash]
conf = max(self.get_local_height() - info.height + 1, 0)
return info._replace(conf=conf)
elif tx_hash in self.unverified_tx:
height = self.unverified_tx[tx_hash]
return TxMinedInfo(height=height, conf=0)
else:
# local transaction
return TxMinedInfo(height=TX_HEIGHT_LOCAL, conf=0)
def get_flodata(self, tx_hash: str):
""" Given a transaction, returns flodata """
with self.lock:
if tx_hash in self.verified_tx:
info = self.verified_tx[tx_hash]
flodata = info[5]
return flodata
elif tx_hash in self.unverified_tx:
tx = self.transactions.get(tx_hash)
flodata = tx.flodata[5:]
return flodata
else:
# local transaction
tx = self.transactions.get(tx_hash)
flodata = tx.flodata[5:]
return flodata
def set_up_to_date(self, up_to_date):
with self.lock:
self.up_to_date = up_to_date
if self.network:
self.network.notify('status')
if up_to_date:
self.save_transactions(write=True)
# if the verifier is also up to date, persist that too;
# otherwise it will persist its results when it finishes
if self.verifier and self.verifier.is_up_to_date():
self.save_verified_tx(write=True)
def is_up_to_date(self):
with self.lock: return self.up_to_date
@with_transaction_lock
def get_tx_delta(self, tx_hash, address):
"""effect of tx on address"""
delta = 0
# substract the value of coins sent from address
d = self.txi.get(tx_hash, {}).get(address, [])
for n, v in d:
delta -= v
# add the value of the coins received at address
d = self.txo.get(tx_hash, {}).get(address, [])
for n, v, cb in d:
delta += v
return delta
@with_transaction_lock
def get_tx_value(self, txid):
"""effect of tx on the entire domain"""
delta = 0
for addr, d in self.txi.get(txid, {}).items():
for n, v in d:
delta -= v
for addr, d in self.txo.get(txid, {}).items():
for n, v, cb in d:
delta += v
return delta
def get_wallet_delta(self, tx: Transaction):
""" effect of tx on wallet """
is_relevant = False # "related to wallet?"
is_mine = False
is_pruned = False
is_partial = False
v_in = v_out = v_out_mine = 0
for txin in tx.inputs():
addr = self.get_txin_address(txin)
if self.is_mine(addr):
is_mine = True
is_relevant = True
d = self.txo.get(txin['prevout_hash'], {}).get(addr, [])
for n, v, cb in d:
if n == txin['prevout_n']:
value = v
break
else:
value = None
if value is None:
is_pruned = True
else:
v_in += value
else:
is_partial = True
if not is_mine:
is_partial = False
for o in tx.outputs():
v_out += o.value
if self.is_mine(o.address):
v_out_mine += o.value
is_relevant = True
if is_pruned:
# some inputs are mine:
fee = None
if is_mine:
v = v_out_mine - v_out
else:
# no input is mine
v = v_out_mine
else:
v = v_out_mine - v_in
if is_partial:
# some inputs are mine, but not all
fee = None
else:
# all inputs are mine
fee = v_in - v_out
if not is_mine:
fee = None
return is_relevant, is_mine, v, fee
def get_tx_fee(self, tx: Transaction) -> Optional[int]:
if not tx:
return None
if hasattr(tx, '_cached_fee'):
return tx._cached_fee
with self.lock, self.transaction_lock:
is_relevant, is_mine, v, fee = self.get_wallet_delta(tx)
if fee is None:
txid = tx.txid()
fee = self.tx_fees.get(txid)
# only cache non-None, as None can still change while syncing
if fee is not None:
tx._cached_fee = fee
return fee
def get_addr_io(self, address):
with self.lock, self.transaction_lock:
h = self.get_address_history(address)
received = {}
sent = {}
for tx_hash, height in h:
l = self.txo.get(tx_hash, {}).get(address, [])
for n, v, is_cb in l:
received[tx_hash + ':%d'%n] = (height, v, is_cb)
for tx_hash, height in h:
l = self.txi.get(tx_hash, {}).get(address, [])
for txi, v in l:
sent[txi] = height
return received, sent
def get_addr_utxo(self, address):
coins, spent = self.get_addr_io(address)
for txi in spent:
coins.pop(txi)
out = {}
for txo, v in coins.items():
tx_height, value, is_cb = v
prevout_hash, prevout_n = txo.split(':')
x = {
'address':address,
'value':value,
'prevout_n':int(prevout_n),
'prevout_hash':prevout_hash,
'height':tx_height,
'coinbase':is_cb
}
out[txo] = x
return out
# return the total amount ever received by an address
def get_addr_received(self, address):
received, sent = self.get_addr_io(address)
return sum([v for height, v, is_cb in received.values()])
@with_local_height_cached
def get_addr_balance(self, address):
"""Return the balance of a FLO address:
confirmed and matured, unconfirmed, unmatured
"""
received, sent = self.get_addr_io(address)
c = u = x = 0
local_height = self.get_local_height()
for txo, (tx_height, v, is_cb) in received.items():
if is_cb and tx_height + COINBASE_MATURITY > local_height:
x += v
elif tx_height > 0:
c += v
else:
u += v
if txo in sent:
if sent[txo] > 0:
c -= v
else:
u -= v
return c, u, x
@with_local_height_cached
def get_utxos(self, domain=None, excluded=None, mature=False, confirmed_only=False, nonlocal_only=False):
coins = []
if domain is None:
domain = self.get_addresses()
domain = set(domain)
if excluded:
domain = set(domain) - excluded
for addr in domain:
utxos = self.get_addr_utxo(addr)
for x in utxos.values():
if confirmed_only and x['height'] <= 0:
continue
if nonlocal_only and x['height'] == TX_HEIGHT_LOCAL:
continue
if mature and x['coinbase'] and x['height'] + COINBASE_MATURITY > self.get_local_height():
continue
coins.append(x)
continue
return coins
def get_balance(self, domain=None):
if domain is None:
domain = self.get_addresses()
domain = set(domain)
cc = uu = xx = 0
for addr in domain:
c, u, x = self.get_addr_balance(addr)
cc += c
uu += u
xx += x
return cc, uu, xx
def is_used(self, address):
h = self.history.get(address,[])
return len(h) != 0
def is_empty(self, address):
c, u, x = self.get_addr_balance(address)
return c+u+x == 0
def synchronize(self):
pass

View File

@ -19,6 +19,7 @@
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import asyncio
import json
import locale
import traceback
@ -26,13 +27,13 @@ import subprocess
import sys
import os
import requests
from electrum import ELECTRUM_VERSION, constants
from electrum.i18n import _
from .version import ELECTRUM_VERSION
from . import constants
from .i18n import _
from .util import make_aiohttp_session
class BaseCrashReporter(object):
class BaseCrashReporter:
report_server = "https://crashhub.electrum.org"
config_key = "show_crash_reporter"
issue_template = """<h2>Traceback</h2>
@ -59,16 +60,22 @@ class BaseCrashReporter(object):
def __init__(self, exctype, value, tb):
self.exc_args = (exctype, value, tb)
def send_report(self, endpoint="/crash"):
def send_report(self, asyncio_loop, proxy, endpoint="/crash"):
if constants.net.GENESIS[-4:] not in ["4943", "e26f"] and ".electrum.org" in BaseCrashReporter.report_server:
# Gah! Some kind of altcoin wants to send us crash reports.
raise Exception(_("Missing report URL."))
report = self.get_traceback_info()
report.update(self.get_additional_info())
report = json.dumps(report)
response = requests.post(BaseCrashReporter.report_server + endpoint, data=report)
coro = self.do_post(proxy, BaseCrashReporter.report_server + endpoint, data=report)
response = asyncio.run_coroutine_threadsafe(coro, asyncio_loop).result(5)
return response
async def do_post(self, proxy, url, data):
async with make_aiohttp_session(proxy) as session:
async with session.post(url, data=data) as resp:
return await resp.text()
def get_traceback_info(self):
exc_string = str(self.exc_args[1])
stack = traceback.extract_tb(self.exc_args[2])

View File

@ -27,14 +27,24 @@ import os
import sys
import traceback
from functools import partial
from typing import List, TYPE_CHECKING, Tuple, NamedTuple, Any
from . import bitcoin
from . import keystore
from .bip32 import is_bip32_derivation, xpub_type
from .keystore import bip44_derivation, purpose48_derivation
from .wallet import Imported_Wallet, Standard_Wallet, Multisig_Wallet, wallet_types, Wallet
from .storage import STO_EV_USER_PW, STO_EV_XPUB_PW, get_derivation_used_for_hw_device_encryption
from .wallet import (Imported_Wallet, Standard_Wallet, Multisig_Wallet,
wallet_types, Wallet, Abstract_Wallet)
from .storage import (WalletStorage, STO_EV_USER_PW, STO_EV_XPUB_PW,
get_derivation_used_for_hw_device_encryption)
from .i18n import _
from .util import UserCancelled, InvalidPassword
from .util import UserCancelled, InvalidPassword, WalletFileException
from .simple_config import SimpleConfig
from .plugin import Plugins
if TYPE_CHECKING:
from .plugin import DeviceInfo
# hardware device setup purpose
HWD_SETUP_NEW_WALLET, HWD_SETUP_DECRYPT_WALLET = range(0, 2)
@ -46,15 +56,21 @@ class ScriptTypeNotSupported(Exception): pass
class GoBack(Exception): pass
class WizardStackItem(NamedTuple):
action: Any
args: Any
storage_data: dict
class BaseWizard(object):
def __init__(self, config, plugins, storage):
def __init__(self, config: SimpleConfig, plugins: Plugins, storage: WalletStorage):
super(BaseWizard, self).__init__()
self.config = config
self.plugins = plugins
self.storage = storage
self.wallet = None
self.stack = []
self.wallet = None # type: Abstract_Wallet
self._stack = [] # type: List[WizardStackItem]
self.plugin = None
self.keystores = []
self.is_kivy = config.get('gui') == 'kivy'
@ -66,7 +82,8 @@ class BaseWizard(object):
def run(self, *args):
action = args[0]
args = args[1:]
self.stack.append((action, args))
storage_data = self.storage.get_all_data()
self._stack.append(WizardStackItem(action, args, storage_data))
if not action:
return
if type(action) is tuple:
@ -81,19 +98,25 @@ class BaseWizard(object):
raise Exception("unknown action", action)
def can_go_back(self):
return len(self.stack)>1
return len(self._stack) > 1
def go_back(self):
if not self.can_go_back():
return
self.stack.pop()
action, args = self.stack.pop()
self.run(action, *args)
# pop 'current' frame
self._stack.pop()
# pop 'previous' frame
stack_item = self._stack.pop()
# try to undo side effects since we last entered 'previous' frame
# FIXME only self.storage is properly restored
self.storage.overwrite_all_data(stack_item.storage_data)
# rerun 'previous' frame
self.run(stack_item.action, *stack_item.args)
def reset_stack(self):
self._stack = []
def new(self):
if not self.storage.file_writable():
self.show_error(_("Wallet file not writable"))
return
name = os.path.basename(self.storage.path)
title = _("Create") + ' ' + name
message = '\n'.join([
@ -101,18 +124,27 @@ class BaseWizard(object):
])
wallet_kinds = [
('standard', _("Standard wallet")),
('2fa', _("Wallet with two-factor authentication")),
('multisig', _("Multi-signature wallet")),
('imported', _("Import Bitcoin addresses or private keys")),
('imported', _("Import FLO addresses or private keys")),
]
choices = [pair for pair in wallet_kinds if pair[0] in wallet_types]
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type)
def upgrade_storage(self):
exc = None
def on_finished():
self.wallet = Wallet(self.storage)
self.terminate()
self.waiting_dialog(partial(self.storage.upgrade), _('Upgrading wallet format...'), on_finished=on_finished)
if exc is None:
self.wallet = Wallet(self.storage)
self.terminate()
else:
raise exc
def do_upgrade():
nonlocal exc
try:
self.storage.upgrade()
except Exception as e:
exc = e
self.waiting_dialog(do_upgrade, _('Upgrading wallet format...'), on_finished=on_finished)
def load_2fa(self):
self.storage.put('wallet_type', '2fa')
@ -134,8 +166,8 @@ class BaseWizard(object):
def choose_multisig(self):
def on_multisig(m, n):
self.multisig_type = "%dof%d"%(m, n)
self.storage.put('wallet_type', self.multisig_type)
multisig_type = "%dof%d" % (m, n)
self.storage.put('wallet_type', multisig_type)
self.n = n
self.run('choose_keystore')
self.multisig_dialog(run_next=on_multisig)
@ -166,8 +198,8 @@ class BaseWizard(object):
def import_addresses_or_keys(self):
v = lambda x: keystore.is_address_list(x) or keystore.is_private_key_list(x)
title = _("Import Bitcoin Addresses")
message = _("Enter a list of Bitcoin addresses (this will create a watching-only wallet), or a list of private keys.")
title = _("Import FLO Addresses")
message = _("Enter a list of FLO addresses (this will create a watching-only wallet), or a list of private keys.")
self.add_xpub_dialog(title=title, message=message, run_next=self.on_import,
is_valid=v, allow_multi=True, show_wif_help=True)
@ -176,17 +208,23 @@ class BaseWizard(object):
# will be reflected on self.storage
if keystore.is_address_list(text):
w = Imported_Wallet(self.storage)
for x in text.split():
w.import_address(x)
addresses = text.split()
good_inputs, bad_inputs = w.import_addresses(addresses, write_to_disk=False)
elif keystore.is_private_key_list(text):
k = keystore.Imported_KeyStore({})
self.storage.put('keystore', k.dump())
w = Imported_Wallet(self.storage)
for x in keystore.get_private_keys(text):
w.import_private_key(x, None)
keys = keystore.get_private_keys(text)
good_inputs, bad_inputs = w.import_private_keys(keys, None, write_to_disk=False)
self.keystores.append(w.keystore)
else:
return self.terminate()
if bad_inputs:
msg = "\n".join(f"{key[:10]}... ({msg})" for key, msg in bad_inputs[:10])
if len(bad_inputs) > 10: msg += '\n...'
self.show_error(_("The following inputs could not be imported")
+ f' ({len(bad_inputs)}):\n' + msg)
# FIXME what if len(good_inputs) == 0 ?
return self.run('create_wallet')
def restore_from_key(self):
@ -209,33 +247,48 @@ class BaseWizard(object):
def choose_hw_device(self, purpose=HWD_SETUP_NEW_WALLET):
title = _('Hardware Keystore')
# check available plugins
support = self.plugins.get_hardware_support()
if not support:
msg = '\n'.join([
_('No hardware wallet support found on your system.'),
_('Please install the relevant libraries (eg python-trezor for Trezor).'),
])
self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device(purpose))
return
# scan devices
devices = []
supported_plugins = self.plugins.get_hardware_support()
devices = [] # type: List[Tuple[str, DeviceInfo]]
devmgr = self.plugins.device_manager
debug_msg = ''
def failed_getting_device_infos(name, e):
nonlocal debug_msg
devmgr.print_error(f'error getting device infos for {name}: {e}')
indented_error_msg = ' '.join([''] + str(e).splitlines(keepends=True))
debug_msg += f' {name}: (error getting device infos)\n{indented_error_msg}\n'
# scan devices
try:
scanned_devices = devmgr.scan_devices()
except BaseException as e:
devmgr.print_error('error scanning devices: {}'.format(e))
devmgr.print_error('error scanning devices: {}'.format(repr(e)))
debug_msg = ' {}:\n {}'.format(_('Error scanning devices'), e)
else:
debug_msg = ''
for name, description, plugin in support:
for splugin in supported_plugins:
name, plugin = splugin.name, splugin.plugin
# plugin init errored?
if not plugin:
e = splugin.exception
indented_error_msg = ' '.join([''] + str(e).splitlines(keepends=True))
debug_msg += f' {name}: (error during plugin init)\n'
debug_msg += ' {}\n'.format(_('You might have an incompatible library.'))
debug_msg += f'{indented_error_msg}\n'
continue
# see if plugin recognizes 'scanned_devices'
try:
# FIXME: side-effect: unpaired_device_info sets client.handler
u = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices)
device_infos = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices,
include_failing_clients=True)
except BaseException as e:
devmgr.print_error('error getting device infos for {}: {}'.format(name, e))
debug_msg += ' {}:\n {}\n'.format(plugin.name, e)
traceback.print_exc()
failed_getting_device_infos(name, e)
continue
devices += list(map(lambda x: (name, x), u))
device_infos_failing = list(filter(lambda di: di.exception is not None, device_infos))
for di in device_infos_failing:
failed_getting_device_infos(name, di.exception)
device_infos_working = list(filter(lambda di: di.exception is None, device_infos))
devices += list(map(lambda x: (name, x), device_infos_working))
if not debug_msg:
debug_msg = ' {}'.format(_('No exceptions encountered.'))
if not devices:
@ -255,7 +308,9 @@ class BaseWizard(object):
for name, info in devices:
state = _("initialized") if info.initialized else _("wiped")
label = info.label or _("An unnamed {}").format(name)
descr = "%s [%s, %s]" % (label, name, state)
try: transport_str = info.device.transport_ui_string[:20]
except: transport_str = 'unknown transport'
descr = f"{label} [{name}, {state}, {transport_str}]"
choices.append(((name, info), descr))
msg = _('Select a device') + ':'
self.choice_dialog(title=title, message=msg, choices=choices, run_next= lambda *args: self.on_device(*args, purpose=purpose))
@ -311,12 +366,14 @@ class BaseWizard(object):
# There is no general standard for HD multisig.
# For legacy, this is partially compatible with BIP45; assumes index=0
# For segwit, a custom path is used, as there is no standard at all.
default_choice_idx = 2
choices = [
('standard', 'legacy multisig (p2sh)', "m/45'/0"),
('p2wsh-p2sh', 'p2sh-segwit multisig (p2wsh-p2sh)', purpose48_derivation(0, xtype='p2wsh-p2sh')),
('p2wsh', 'native segwit multisig (p2wsh)', purpose48_derivation(0, xtype='p2wsh')),
]
else:
default_choice_idx = 2
choices = [
('standard', 'legacy (p2pkh)', bip44_derivation(0, bip43_purpose=44)),
('p2wpkh-p2sh', 'p2sh-segwit (p2wpkh-p2sh)', bip44_derivation(0, bip43_purpose=49)),
@ -326,7 +383,8 @@ class BaseWizard(object):
try:
self.choice_and_line_dialog(
run_next=f, title=_('Script type and Derivation path'), message1=message1,
message2=message2, choices=choices, test_text=bitcoin.is_bip32_derivation)
message2=message2, choices=choices, test_text=is_bip32_derivation,
default_choice_idx=default_choice_idx)
return
except ScriptTypeNotSupported as e:
self.show_error(e)
@ -339,6 +397,7 @@ class BaseWizard(object):
except ScriptTypeNotSupported:
raise # this is handled in derivation_dialog
except BaseException as e:
traceback.print_exc(file=sys.stderr)
self.show_error(e)
return
d = {
@ -351,7 +410,7 @@ class BaseWizard(object):
k = hardware_keystore(d)
self.on_keystore(k)
def passphrase_dialog(self, run_next):
def passphrase_dialog(self, run_next, is_restoring=False):
title = _('Seed extension')
message = '\n'.join([
_('You may extend your seed with custom words.'),
@ -361,7 +420,10 @@ class BaseWizard(object):
_('Note that this is NOT your encryption password.'),
_('If you do not know what this is, leave this field empty.'),
])
self.line_dialog(title=title, message=message, warning=warning, default='', test=lambda x:True, run_next=run_next)
warn_issue4566 = is_restoring and self.seed_type == 'bip39'
self.line_dialog(title=title, message=message, warning=warning,
default='', test=lambda x:True, run_next=run_next,
warn_issue4566=warn_issue4566)
def restore_from_seed(self):
self.opt_bip39 = True
@ -374,13 +436,13 @@ class BaseWizard(object):
self.seed_type = 'bip39' if is_bip39 else bitcoin.seed_type(seed)
if self.seed_type == 'bip39':
f = lambda passphrase: self.on_restore_bip39(seed, passphrase)
self.passphrase_dialog(run_next=f) if is_ext else f('')
self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('')
elif self.seed_type in ['standard', 'segwit']:
f = lambda passphrase: self.run('create_keystore', seed, passphrase)
self.passphrase_dialog(run_next=f) if is_ext else f('')
self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('')
elif self.seed_type == 'old':
self.run('create_keystore', seed, '')
elif self.seed_type == '2fa':
elif bitcoin.is_any_2fa_seed_type(self.seed_type):
self.load_2fa()
self.run('on_restore_seed', seed, is_ext)
else:
@ -402,7 +464,6 @@ class BaseWizard(object):
def on_keystore(self, k):
has_xpub = isinstance(k, keystore.Xpub)
if has_xpub:
from .bitcoin import xpub_type
t1 = xpub_type(k.xpub)
if self.wallet_type == 'standard':
if has_xpub and t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']:
@ -430,7 +491,7 @@ class BaseWizard(object):
self.keystores.append(k)
if len(self.keystores) == 1:
xpub = k.get_master_public_key()
self.stack = []
self.reset_stack()
self.run('show_xpub_and_add_cosigners', xpub)
elif len(self.keystores) < self.n:
self.run('choose_keystore')
@ -474,6 +535,7 @@ class BaseWizard(object):
def on_password(self, password, *, encrypt_storage,
storage_enc_version=STO_EV_USER_PW, encrypt_keystore):
assert not self.storage.file_exists(), "file was created too soon! plaintext keys might have been written to disk"
self.storage.set_keystore_encryption(bool(password) and encrypt_keystore)
if encrypt_storage:
self.storage.set_password(password, enc_version=storage_enc_version)
@ -503,18 +565,16 @@ class BaseWizard(object):
def show_xpub_and_add_cosigners(self, xpub):
self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore'))
def choose_seed_type(self):
def choose_seed_type(self, message=None, choices=None):
title = _('Choose Seed type')
message = ' '.join([
_("The type of addresses used by your wallet will depend on your seed."),
_("Segwit wallets use bech32 addresses, defined in BIP173."),
_("Please note that websites and other wallets may not support these addresses yet."),
_("Thus, you might want to keep using a non-segwit wallet in order to be able to receive bitcoins during the transition period.")
])
choices = [
('create_standard_seed', _('Standard')),
('create_segwit_seed', _('Segwit')),
]
if message is None:
message = ' '.join([
_("The type of addresses used by your wallet will depend on your seed.")
])
if choices is None:
choices = [
('create_standard_seed', _('Legacy')),
]
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
def create_segwit_seed(self): self.create_seed('segwit')

269
electrum/bip32.py Normal file
View File

@ -0,0 +1,269 @@
# Copyright (C) 2018 The Electrum developers
# Distributed under the MIT software license, see the accompanying
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
import hashlib
from typing import List
from .util import bfh, bh2u, BitcoinException, print_error
from . import constants
from . import ecc
from .crypto import hash_160, hmac_oneshot
from .bitcoin import rev_hex, int_to_hex, EncodeBase58Check, DecodeBase58Check
BIP32_PRIME = 0x80000000
def protect_against_invalid_ecpoint(func):
def func_wrapper(*args):
n = args[-1]
while True:
is_prime = n & BIP32_PRIME
try:
return func(*args[:-1], n=n)
except ecc.InvalidECPointException:
print_error('bip32 protect_against_invalid_ecpoint: skipping index')
n += 1
is_prime2 = n & BIP32_PRIME
if is_prime != is_prime2: raise OverflowError()
return func_wrapper
# Child private key derivation function (from master private key)
# k = master private key (32 bytes)
# c = master chain code (extra entropy for key derivation) (32 bytes)
# n = the index of the key we want to derive. (only 32 bits will be used)
# If n is hardened (i.e. the 32nd bit is set), the resulting private key's
# corresponding public key can NOT be determined without the master private key.
# However, if n is not hardened, the resulting private key's corresponding
# public key can be determined without the master private key.
@protect_against_invalid_ecpoint
def CKD_priv(k, c, n):
if n < 0: raise ValueError('the bip32 index needs to be non-negative')
is_prime = n & BIP32_PRIME
return _CKD_priv(k, c, bfh(rev_hex(int_to_hex(n,4))), is_prime)
def _CKD_priv(k, c, s, is_prime):
try:
keypair = ecc.ECPrivkey(k)
except ecc.InvalidECPointException as e:
raise BitcoinException('Impossible xprv (not within curve order)') from e
cK = keypair.get_public_key_bytes(compressed=True)
data = bytes([0]) + k + s if is_prime else cK + s
I = hmac_oneshot(c, data, hashlib.sha512)
I_left = ecc.string_to_number(I[0:32])
k_n = (I_left + ecc.string_to_number(k)) % ecc.CURVE_ORDER
if I_left >= ecc.CURVE_ORDER or k_n == 0:
raise ecc.InvalidECPointException()
k_n = ecc.number_to_string(k_n, ecc.CURVE_ORDER)
c_n = I[32:]
return k_n, c_n
# Child public key derivation function (from public key only)
# K = master public key
# c = master chain code
# n = index of key we want to derive
# This function allows us to find the nth public key, as long as n is
# not hardened. If n is hardened, we need the master private key to find it.
@protect_against_invalid_ecpoint
def CKD_pub(cK, c, n):
if n < 0: raise ValueError('the bip32 index needs to be non-negative')
if n & BIP32_PRIME: raise Exception()
return _CKD_pub(cK, c, bfh(rev_hex(int_to_hex(n,4))))
# helper function, callable with arbitrary string.
# note: 's' does not need to fit into 32 bits here! (c.f. trustedcoin billing)
def _CKD_pub(cK, c, s):
I = hmac_oneshot(c, cK + s, hashlib.sha512)
pubkey = ecc.ECPrivkey(I[0:32]) + ecc.ECPubkey(cK)
if pubkey.is_at_infinity():
raise ecc.InvalidECPointException()
cK_n = pubkey.get_public_key_bytes(compressed=True)
c_n = I[32:]
return cK_n, c_n
def xprv_header(xtype, *, net=None):
if net is None:
net = constants.net
return bfh("%08x" % net.XPRV_HEADERS[xtype])
def xpub_header(xtype, *, net=None):
if net is None:
net = constants.net
return bfh("%08x" % net.XPUB_HEADERS[xtype])
def serialize_xprv(xtype, c, k, depth=0, fingerprint=b'\x00'*4,
child_number=b'\x00'*4, *, net=None):
if not ecc.is_secret_within_curve_range(k):
raise BitcoinException('Impossible xprv (not within curve order)')
xprv = xprv_header(xtype, net=net) \
+ bytes([depth]) + fingerprint + child_number + c + bytes([0]) + k
return EncodeBase58Check(xprv)
def serialize_xpub(xtype, c, cK, depth=0, fingerprint=b'\x00'*4,
child_number=b'\x00'*4, *, net=None):
xpub = xpub_header(xtype, net=net) \
+ bytes([depth]) + fingerprint + child_number + c + cK
return EncodeBase58Check(xpub)
class InvalidMasterKeyVersionBytes(BitcoinException): pass
def deserialize_xkey(xkey, prv, *, net=None):
if net is None:
net = constants.net
xkey = DecodeBase58Check(xkey)
if len(xkey) != 78:
raise BitcoinException('Invalid length for extended key: {}'
.format(len(xkey)))
depth = xkey[4]
fingerprint = xkey[5:9]
child_number = xkey[9:13]
c = xkey[13:13+32]
header = int.from_bytes(xkey[0:4], byteorder='big')
headers = net.XPRV_HEADERS if prv else net.XPUB_HEADERS
if header not in headers.values():
raise InvalidMasterKeyVersionBytes('Invalid extended key format: {}'
.format(hex(header)))
xtype = list(headers.keys())[list(headers.values()).index(header)]
n = 33 if prv else 32
K_or_k = xkey[13+n:]
if prv and not ecc.is_secret_within_curve_range(K_or_k):
raise BitcoinException('Impossible xprv (not within curve order)')
return xtype, depth, fingerprint, child_number, c, K_or_k
def deserialize_xpub(xkey, *, net=None):
return deserialize_xkey(xkey, False, net=net)
def deserialize_xprv(xkey, *, net=None):
return deserialize_xkey(xkey, True, net=net)
def xpub_type(x):
return deserialize_xpub(x)[0]
def is_xpub(text):
try:
deserialize_xpub(text)
return True
except:
return False
def is_xprv(text):
try:
deserialize_xprv(text)
return True
except:
return False
def xpub_from_xprv(xprv):
xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)
cK = ecc.ECPrivkey(k).get_public_key_bytes(compressed=True)
return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
def bip32_root(seed, xtype):
I = hmac_oneshot(b"Bitcoin seed", seed, hashlib.sha512)
master_k = I[0:32]
master_c = I[32:]
# create xprv first, as that will check if master_k is within curve order
xprv = serialize_xprv(xtype, master_c, master_k)
cK = ecc.ECPrivkey(master_k).get_public_key_bytes(compressed=True)
xpub = serialize_xpub(xtype, master_c, cK)
return xprv, xpub
def xpub_from_pubkey(xtype, cK):
if cK[0] not in (0x02, 0x03):
raise ValueError('Unexpected first byte: {}'.format(cK[0]))
return serialize_xpub(xtype, b'\x00'*32, cK)
def bip32_derivation(s: str) -> int:
if not s.startswith('m/'):
raise ValueError('invalid bip32 derivation path: {}'.format(s))
s = s[2:]
for n in s.split('/'):
if n == '': continue
i = int(n[:-1]) + BIP32_PRIME if n[-1] == "'" else int(n)
yield i
def convert_bip32_path_to_list_of_uint32(n: str) -> List[int]:
"""Convert bip32 path to list of uint32 integers with prime flags
m/0/-1/1' -> [0, 0x80000001, 0x80000001]
based on code in trezorlib
"""
path = []
for x in n.split('/')[1:]:
if x == '': continue
prime = 0
if x.endswith("'"):
x = x.replace('\'', '')
prime = BIP32_PRIME
if x.startswith('-'):
prime = BIP32_PRIME
path.append(abs(int(x)) | prime)
return path
def is_bip32_derivation(x: str) -> bool:
try:
[ i for i in bip32_derivation(x)]
return True
except :
return False
def bip32_private_derivation(xprv, branch, sequence):
if not sequence.startswith(branch):
raise ValueError('incompatible branch ({}) and sequence ({})'
.format(branch, sequence))
if branch == sequence:
return xprv, xpub_from_xprv(xprv)
xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)
sequence = sequence[len(branch):]
for n in sequence.split('/'):
if n == '': continue
i = int(n[:-1]) + BIP32_PRIME if n[-1] == "'" else int(n)
parent_k = k
k, c = CKD_priv(k, c, i)
depth += 1
parent_cK = ecc.ECPrivkey(parent_k).get_public_key_bytes(compressed=True)
fingerprint = hash_160(parent_cK)[0:4]
child_number = bfh("%08X"%i)
cK = ecc.ECPrivkey(k).get_public_key_bytes(compressed=True)
xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
xprv = serialize_xprv(xtype, c, k, depth, fingerprint, child_number)
return xprv, xpub
def bip32_public_derivation(xpub, branch, sequence):
xtype, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub)
if not sequence.startswith(branch):
raise ValueError('incompatible branch ({}) and sequence ({})'
.format(branch, sequence))
sequence = sequence[len(branch):]
for n in sequence.split('/'):
if n == '': continue
i = int(n)
parent_cK = cK
cK, c = CKD_pub(cK, c, i)
depth += 1
fingerprint = hash_160(parent_cK)[0:4]
child_number = bfh("%08X"%i)
return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
def bip32_private_key(sequence, k, chain):
for i in sequence:
k, chain = CKD_priv(k, chain, i)
return k

View File

@ -24,14 +24,17 @@
# SOFTWARE.
import hashlib
import hmac
from typing import List, Tuple, TYPE_CHECKING, Optional, Union
from .util import bfh, bh2u, BitcoinException, print_error, assert_bytes, to_bytes, inv_dict
from .util import bfh, bh2u, BitcoinException, assert_bytes, to_bytes, inv_dict
from . import version
from . import segwit_addr
from . import constants
from . import ecc
from .crypto import Hash, sha256, hash_160, hmac_oneshot
from .crypto import sha256d, sha256, hash_160, hmac_oneshot
if TYPE_CHECKING:
from .network import Network
################################## transactions
@ -46,7 +49,7 @@ TYPE_PUBKEY = 1
TYPE_SCRIPT = 2
def rev_hex(s):
def rev_hex(s: str) -> str:
return bh2u(bfh(s)[::-1])
@ -57,7 +60,7 @@ def int_to_hex(i: int, length: int=1) -> str:
if not isinstance(i, int):
raise TypeError('{} instead of int'.format(i))
range_size = pow(256, length)
if i < -range_size/2 or i >= range_size:
if i < -(range_size//2) or i >= range_size:
raise OverflowError('cannot convert int {} to hex ({} bytes)'.format(i, length))
if i < 0:
# two's complement
@ -147,19 +150,37 @@ def add_number_to_script(i: int) -> bytes:
return bfh(push_script(script_num_to_hex(i)))
hash_encode = lambda x: bh2u(x[::-1])
hash_decode = lambda x: bfh(x)[::-1]
hmac_sha_512 = lambda x, y: hmac_oneshot(x, y, hashlib.sha512)
def relayfee(network: 'Network'=None) -> int:
from .simple_config import FEERATE_DEFAULT_RELAY
MAX_RELAY_FEE = 50000
f = network.relay_fee if network and network.relay_fee else FEERATE_DEFAULT_RELAY
return min(f, MAX_RELAY_FEE)
def is_new_seed(x, prefix=version.SEED_PREFIX):
def dust_threshold(network: 'Network'=None) -> int:
# Change <= dust threshold is added to the tx fee
return 182 * 3 * relayfee(network) // 1000
def hash_encode(x: bytes) -> str:
return bh2u(x[::-1])
def hash_decode(x: str) -> bytes:
return bfh(x)[::-1]
################################## electrum seeds
def is_new_seed(x: str, prefix=version.SEED_PREFIX) -> bool:
from . import mnemonic
x = mnemonic.normalize_text(x)
s = bh2u(hmac_sha_512(b"Seed version", x.encode('utf8')))
s = bh2u(hmac_oneshot(b"Seed version", x.encode('utf8'), hashlib.sha512))
return s.startswith(prefix)
def is_old_seed(seed):
def is_old_seed(seed: str) -> bool:
from . import old_mnemonic, mnemonic
seed = mnemonic.normalize_text(seed)
words = seed.split()
@ -177,7 +198,7 @@ def is_old_seed(seed):
return is_hex or (uses_electrum_words and (len(words) == 12 or len(words) == 24))
def seed_type(x):
def seed_type(x: str) -> str:
if is_old_seed(x):
return 'old'
elif is_new_seed(x):
@ -186,119 +207,130 @@ def seed_type(x):
return 'segwit'
elif is_new_seed(x, version.SEED_PREFIX_2FA):
return '2fa'
elif is_new_seed(x, version.SEED_PREFIX_2FA_SW):
return '2fa_segwit'
return ''
is_seed = lambda x: bool(seed_type(x))
def is_seed(x: str) -> bool:
return bool(seed_type(x))
def is_any_2fa_seed_type(seed_type):
return seed_type in ['2fa', '2fa_segwit']
############ functions from pywallet #####################
def hash160_to_b58_address(h160: bytes, addrtype):
s = bytes([addrtype])
s += h160
return base_encode(s+Hash(s)[0:4], base=58)
def hash160_to_b58_address(h160: bytes, addrtype: int) -> str:
s = bytes([addrtype]) + h160
s = s + sha256d(s)[0:4]
return base_encode(s, base=58)
def b58_address_to_hash160(addr):
def b58_address_to_hash160(addr: str) -> Tuple[int, bytes]:
addr = to_bytes(addr, 'ascii')
_bytes = base_decode(addr, 25, base=58)
return _bytes[0], _bytes[1:21]
def hash160_to_p2pkh(h160, *, net=None):
if net is None:
net = constants.net
def hash160_to_p2pkh(h160: bytes, *, net=None) -> str:
if net is None: net = constants.net
return hash160_to_b58_address(h160, net.ADDRTYPE_P2PKH)
def hash160_to_p2sh(h160, *, net=None):
if net is None:
net = constants.net
def hash160_to_p2sh(h160: bytes, *, net=None) -> str:
if net is None: net = constants.net
return hash160_to_b58_address(h160, net.ADDRTYPE_P2SH)
def public_key_to_p2pkh(public_key: bytes) -> str:
return hash160_to_p2pkh(hash_160(public_key))
def public_key_to_p2pkh(public_key: bytes, *, net=None) -> str:
if net is None: net = constants.net
return hash160_to_p2pkh(hash_160(public_key), net=net)
def hash_to_segwit_addr(h, witver, *, net=None):
if net is None:
net = constants.net
def hash_to_segwit_addr(h: bytes, witver: int, *, net=None) -> str:
if net is None: net = constants.net
return segwit_addr.encode(net.SEGWIT_HRP, witver, h)
def public_key_to_p2wpkh(public_key):
return hash_to_segwit_addr(hash_160(public_key), witver=0)
def public_key_to_p2wpkh(public_key: bytes, *, net=None) -> str:
if net is None: net = constants.net
return hash_to_segwit_addr(hash_160(public_key), witver=0, net=net)
def script_to_p2wsh(script):
return hash_to_segwit_addr(sha256(bfh(script)), witver=0)
def script_to_p2wsh(script: str, *, net=None) -> str:
if net is None: net = constants.net
return hash_to_segwit_addr(sha256(bfh(script)), witver=0, net=net)
def p2wpkh_nested_script(pubkey):
def p2wpkh_nested_script(pubkey: str) -> str:
pkh = bh2u(hash_160(bfh(pubkey)))
return '00' + push_script(pkh)
def p2wsh_nested_script(witness_script):
def p2wsh_nested_script(witness_script: str) -> str:
wsh = bh2u(sha256(bfh(witness_script)))
return '00' + push_script(wsh)
def pubkey_to_address(txin_type, pubkey):
def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str:
if net is None: net = constants.net
if txin_type == 'p2pkh':
return public_key_to_p2pkh(bfh(pubkey))
return public_key_to_p2pkh(bfh(pubkey), net=net)
elif txin_type == 'p2wpkh':
return public_key_to_p2wpkh(bfh(pubkey))
return public_key_to_p2wpkh(bfh(pubkey), net=net)
elif txin_type == 'p2wpkh-p2sh':
scriptSig = p2wpkh_nested_script(pubkey)
return hash160_to_p2sh(hash_160(bfh(scriptSig)))
return hash160_to_p2sh(hash_160(bfh(scriptSig)), net=net)
else:
raise NotImplementedError(txin_type)
def redeem_script_to_address(txin_type, redeem_script):
def redeem_script_to_address(txin_type: str, redeem_script: str, *, net=None) -> str:
if net is None: net = constants.net
if txin_type == 'p2sh':
return hash160_to_p2sh(hash_160(bfh(redeem_script)))
return hash160_to_p2sh(hash_160(bfh(redeem_script)), net=net)
elif txin_type == 'p2wsh':
return script_to_p2wsh(redeem_script)
return script_to_p2wsh(redeem_script, net=net)
elif txin_type == 'p2wsh-p2sh':
scriptSig = p2wsh_nested_script(redeem_script)
return hash160_to_p2sh(hash_160(bfh(scriptSig)))
return hash160_to_p2sh(hash_160(bfh(scriptSig)), net=net)
else:
raise NotImplementedError(txin_type)
def script_to_address(script, *, net=None):
def script_to_address(script: str, *, net=None) -> str:
from .transaction import get_address_from_output_script
t, addr = get_address_from_output_script(bfh(script), net=net)
assert t == TYPE_ADDRESS
return addr
def address_to_script(addr, *, net=None):
if net is None:
net = constants.net
def address_to_script(addr: str, *, net=None) -> str:
if net is None: net = constants.net
if not is_address(addr, net=net):
raise BitcoinException(f"invalid bitcoin address: {addr}")
witver, witprog = segwit_addr.decode(net.SEGWIT_HRP, addr)
if witprog is not None:
if not (0 <= witver <= 16):
raise BitcoinException('impossible witness version: {}'.format(witver))
raise BitcoinException(f'impossible witness version: {witver}')
OP_n = witver + 0x50 if witver > 0 else 0
script = bh2u(bytes([OP_n]))
script += push_script(bh2u(bytes(witprog)))
return script
addrtype, hash_160 = b58_address_to_hash160(addr)
addrtype, hash_160_ = b58_address_to_hash160(addr)
if addrtype == net.ADDRTYPE_P2PKH:
script = '76a9' # op_dup, op_hash_160
script += push_script(bh2u(hash_160))
script += push_script(bh2u(hash_160_))
script += '88ac' # op_equalverify, op_checksig
elif addrtype == net.ADDRTYPE_P2SH:
script = 'a9' # op_hash_160
script += push_script(bh2u(hash_160))
script += push_script(bh2u(hash_160_))
script += '87' # op_equal
else:
raise BitcoinException('unknown address type: {}'.format(addrtype))
raise BitcoinException(f'unknown address type: {addrtype}')
return script
def address_to_scripthash(addr):
def address_to_scripthash(addr: str) -> str:
script = address_to_script(addr)
return script_to_scripthash(script)
def script_to_scripthash(script):
h = sha256(bytes.fromhex(script))[0:32]
def script_to_scripthash(script: str) -> str:
h = sha256(bfh(script))[0:32]
return bh2u(bytes(reversed(h)))
def public_key_to_p2pk_script(pubkey):
def public_key_to_p2pk_script(pubkey: str) -> str:
script = push_script(pubkey)
script += 'ac' # op_checksig
return script
@ -340,7 +372,7 @@ def base_encode(v: bytes, base: int) -> str:
return result.decode('ascii')
def base_decode(v, length, base):
def base_decode(v: Union[bytes, str], length: Optional[int], base: int) -> Optional[bytes]:
""" decode v into a string of len bytes."""
# assert_bytes(v)
v = to_bytes(v, 'ascii')
@ -378,21 +410,20 @@ class InvalidChecksum(Exception):
pass
def EncodeBase58Check(vchIn):
hash = Hash(vchIn)
def EncodeBase58Check(vchIn: bytes) -> str:
hash = sha256d(vchIn)
return base_encode(vchIn + hash[0:4], base=58)
def DecodeBase58Check(psz):
def DecodeBase58Check(psz: Union[bytes, str]) -> bytes:
vchRet = base_decode(psz, None, base=58)
key = vchRet[0:-4]
csum = vchRet[-4:]
hash = Hash(key)
cs32 = hash[0:4]
if cs32 != csum:
raise InvalidChecksum('expected {}, actual {}'.format(bh2u(cs32), bh2u(csum)))
payload = vchRet[0:-4]
csum_found = vchRet[-4:]
csum_calculated = sha256d(payload)[0:4]
if csum_calculated != csum_found:
raise InvalidChecksum(f'calculated {bh2u(csum_calculated)}, found {bh2u(csum_found)}')
else:
return key
return payload
# backwards compat
@ -409,12 +440,6 @@ WIF_SCRIPT_TYPES = {
WIF_SCRIPT_TYPES_INV = inv_dict(WIF_SCRIPT_TYPES)
PURPOSE48_SCRIPT_TYPES = {
'p2wsh-p2sh': 1, # specifically multisig
'p2wsh': 2, # specifically multisig
}
PURPOSE48_SCRIPT_TYPES_INV = inv_dict(PURPOSE48_SCRIPT_TYPES)
def serialize_privkey(secret: bytes, compressed: bool, txin_type: str,
internal_use: bool=False) -> str:
@ -433,7 +458,7 @@ def serialize_privkey(secret: bytes, compressed: bool, txin_type: str,
return '{}:{}'.format(txin_type, base58_wif)
def deserialize_privkey(key: str) -> (str, bytes, bool):
def deserialize_privkey(key: str) -> Tuple[str, bytes, bool]:
if is_minikey(key):
return 'p2pkh', minikey_to_private_key(key), False
@ -470,36 +495,40 @@ def deserialize_privkey(key: str) -> (str, bytes, bool):
return txin_type, secret_bytes, compressed
def is_compressed(sec):
def is_compressed_privkey(sec: str) -> bool:
return deserialize_privkey(sec)[2]
def address_from_private_key(sec):
def address_from_private_key(sec: str) -> str:
txin_type, privkey, compressed = deserialize_privkey(sec)
public_key = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
return pubkey_to_address(txin_type, public_key)
def is_segwit_address(addr):
def is_segwit_address(addr: str, *, net=None) -> bool:
if net is None: net = constants.net
try:
witver, witprog = segwit_addr.decode(constants.net.SEGWIT_HRP, addr)
witver, witprog = segwit_addr.decode(net.SEGWIT_HRP, addr)
except Exception as e:
return False
return witprog is not None
def is_b58_address(addr):
def is_b58_address(addr: str, *, net=None) -> bool:
if net is None: net = constants.net
try:
addrtype, h = b58_address_to_hash160(addr)
except Exception as e:
return False
if addrtype not in [constants.net.ADDRTYPE_P2PKH, constants.net.ADDRTYPE_P2SH]:
if addrtype not in [net.ADDRTYPE_P2PKH, net.ADDRTYPE_P2SH]:
return False
return addr == hash160_to_b58_address(h, addrtype)
def is_address(addr):
return is_segwit_address(addr) or is_b58_address(addr)
def is_address(addr: str, *, net=None) -> bool:
if net is None: net = constants.net
return is_segwit_address(addr, net=net) \
or is_b58_address(addr, net=net)
def is_private_key(key):
def is_private_key(key: str) -> bool:
try:
k = deserialize_privkey(key)
return k is not False
@ -509,7 +538,7 @@ def is_private_key(key):
########### end pywallet functions #######################
def is_minikey(text):
def is_minikey(text: str) -> bool:
# Minikeys are typically 22 or 30 characters, but this routine
# permits any length of 20 or more provided the minikey is valid.
# A valid minikey must begin with an 'S', be in base58, and when
@ -519,243 +548,5 @@ def is_minikey(text):
and all(ord(c) in __b58chars for c in text)
and sha256(text + '?')[0] == 0x00)
def minikey_to_private_key(text):
def minikey_to_private_key(text: str) -> bytes:
return sha256(text)
###################################### BIP32 ##############################
BIP32_PRIME = 0x80000000
def protect_against_invalid_ecpoint(func):
def func_wrapper(*args):
n = args[-1]
while True:
is_prime = n & BIP32_PRIME
try:
return func(*args[:-1], n=n)
except ecc.InvalidECPointException:
print_error('bip32 protect_against_invalid_ecpoint: skipping index')
n += 1
is_prime2 = n & BIP32_PRIME
if is_prime != is_prime2: raise OverflowError()
return func_wrapper
# Child private key derivation function (from master private key)
# k = master private key (32 bytes)
# c = master chain code (extra entropy for key derivation) (32 bytes)
# n = the index of the key we want to derive. (only 32 bits will be used)
# If n is hardened (i.e. the 32nd bit is set), the resulting private key's
# corresponding public key can NOT be determined without the master private key.
# However, if n is not hardened, the resulting private key's corresponding
# public key can be determined without the master private key.
@protect_against_invalid_ecpoint
def CKD_priv(k, c, n):
if n < 0: raise ValueError('the bip32 index needs to be non-negative')
is_prime = n & BIP32_PRIME
return _CKD_priv(k, c, bfh(rev_hex(int_to_hex(n,4))), is_prime)
def _CKD_priv(k, c, s, is_prime):
try:
keypair = ecc.ECPrivkey(k)
except ecc.InvalidECPointException as e:
raise BitcoinException('Impossible xprv (not within curve order)') from e
cK = keypair.get_public_key_bytes(compressed=True)
data = bytes([0]) + k + s if is_prime else cK + s
I = hmac_oneshot(c, data, hashlib.sha512)
I_left = ecc.string_to_number(I[0:32])
k_n = (I_left + ecc.string_to_number(k)) % ecc.CURVE_ORDER
if I_left >= ecc.CURVE_ORDER or k_n == 0:
raise ecc.InvalidECPointException()
k_n = ecc.number_to_string(k_n, ecc.CURVE_ORDER)
c_n = I[32:]
return k_n, c_n
# Child public key derivation function (from public key only)
# K = master public key
# c = master chain code
# n = index of key we want to derive
# This function allows us to find the nth public key, as long as n is
# not hardened. If n is hardened, we need the master private key to find it.
@protect_against_invalid_ecpoint
def CKD_pub(cK, c, n):
if n < 0: raise ValueError('the bip32 index needs to be non-negative')
if n & BIP32_PRIME: raise Exception()
return _CKD_pub(cK, c, bfh(rev_hex(int_to_hex(n,4))))
# helper function, callable with arbitrary string.
# note: 's' does not need to fit into 32 bits here! (c.f. trustedcoin billing)
def _CKD_pub(cK, c, s):
I = hmac_oneshot(c, cK + s, hashlib.sha512)
pubkey = ecc.ECPrivkey(I[0:32]) + ecc.ECPubkey(cK)
if pubkey.is_at_infinity():
raise ecc.InvalidECPointException()
cK_n = pubkey.get_public_key_bytes(compressed=True)
c_n = I[32:]
return cK_n, c_n
def xprv_header(xtype, *, net=None):
if net is None:
net = constants.net
return bfh("%08x" % net.XPRV_HEADERS[xtype])
def xpub_header(xtype, *, net=None):
if net is None:
net = constants.net
return bfh("%08x" % net.XPUB_HEADERS[xtype])
def serialize_xprv(xtype, c, k, depth=0, fingerprint=b'\x00'*4,
child_number=b'\x00'*4, *, net=None):
if not ecc.is_secret_within_curve_range(k):
raise BitcoinException('Impossible xprv (not within curve order)')
xprv = xprv_header(xtype, net=net) \
+ bytes([depth]) + fingerprint + child_number + c + bytes([0]) + k
return EncodeBase58Check(xprv)
def serialize_xpub(xtype, c, cK, depth=0, fingerprint=b'\x00'*4,
child_number=b'\x00'*4, *, net=None):
xpub = xpub_header(xtype, net=net) \
+ bytes([depth]) + fingerprint + child_number + c + cK
return EncodeBase58Check(xpub)
def deserialize_xkey(xkey, prv, *, net=None):
if net is None:
net = constants.net
xkey = DecodeBase58Check(xkey)
if len(xkey) != 78:
raise BitcoinException('Invalid length for extended key: {}'
.format(len(xkey)))
depth = xkey[4]
fingerprint = xkey[5:9]
child_number = xkey[9:13]
c = xkey[13:13+32]
header = int('0x' + bh2u(xkey[0:4]), 16)
headers = net.XPRV_HEADERS if prv else net.XPUB_HEADERS
if header not in headers.values():
raise BitcoinException('Invalid extended key format: {}'
.format(hex(header)))
xtype = list(headers.keys())[list(headers.values()).index(header)]
n = 33 if prv else 32
K_or_k = xkey[13+n:]
if prv and not ecc.is_secret_within_curve_range(K_or_k):
raise BitcoinException('Impossible xprv (not within curve order)')
return xtype, depth, fingerprint, child_number, c, K_or_k
def deserialize_xpub(xkey, *, net=None):
return deserialize_xkey(xkey, False, net=net)
def deserialize_xprv(xkey, *, net=None):
return deserialize_xkey(xkey, True, net=net)
def xpub_type(x):
return deserialize_xpub(x)[0]
def is_xpub(text):
try:
deserialize_xpub(text)
return True
except:
return False
def is_xprv(text):
try:
deserialize_xprv(text)
return True
except:
return False
def xpub_from_xprv(xprv):
xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)
cK = ecc.ECPrivkey(k).get_public_key_bytes(compressed=True)
return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
def bip32_root(seed, xtype):
I = hmac_oneshot(b"Bitcoin seed", seed, hashlib.sha512)
master_k = I[0:32]
master_c = I[32:]
# create xprv first, as that will check if master_k is within curve order
xprv = serialize_xprv(xtype, master_c, master_k)
cK = ecc.ECPrivkey(master_k).get_public_key_bytes(compressed=True)
xpub = serialize_xpub(xtype, master_c, cK)
return xprv, xpub
def xpub_from_pubkey(xtype, cK):
if cK[0] not in (0x02, 0x03):
raise ValueError('Unexpected first byte: {}'.format(cK[0]))
return serialize_xpub(xtype, b'\x00'*32, cK)
def bip32_derivation(s):
if not s.startswith('m/'):
raise ValueError('invalid bip32 derivation path: {}'.format(s))
s = s[2:]
for n in s.split('/'):
if n == '': continue
i = int(n[:-1]) + BIP32_PRIME if n[-1] == "'" else int(n)
yield i
def is_bip32_derivation(x):
try:
[ i for i in bip32_derivation(x)]
return True
except :
return False
def bip32_private_derivation(xprv, branch, sequence):
if not sequence.startswith(branch):
raise ValueError('incompatible branch ({}) and sequence ({})'
.format(branch, sequence))
if branch == sequence:
return xprv, xpub_from_xprv(xprv)
xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)
sequence = sequence[len(branch):]
for n in sequence.split('/'):
if n == '': continue
i = int(n[:-1]) + BIP32_PRIME if n[-1] == "'" else int(n)
parent_k = k
k, c = CKD_priv(k, c, i)
depth += 1
parent_cK = ecc.ECPrivkey(parent_k).get_public_key_bytes(compressed=True)
fingerprint = hash_160(parent_cK)[0:4]
child_number = bfh("%08X"%i)
cK = ecc.ECPrivkey(k).get_public_key_bytes(compressed=True)
xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
xprv = serialize_xprv(xtype, c, k, depth, fingerprint, child_number)
return xprv, xpub
def bip32_public_derivation(xpub, branch, sequence):
xtype, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub)
if not sequence.startswith(branch):
raise ValueError('incompatible branch ({}) and sequence ({})'
.format(branch, sequence))
sequence = sequence[len(branch):]
for n in sequence.split('/'):
if n == '': continue
i = int(n)
parent_cK = cK
cK, c = CKD_pub(cK, c, i)
depth += 1
fingerprint = hash_160(parent_cK)[0:4]
child_number = bfh("%08X"%i)
return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
def bip32_private_key(sequence, k, chain):
for i in sequence:
k, chain = CKD_priv(k, chain, i)
return k

732
electrum/blockchain.py Normal file
View File

@ -0,0 +1,732 @@
# Electrum - lightweight Bitcoin client
# Copyright (C) 2012 thomasv@ecdsa.org
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os
import threading
from typing import Optional, Dict
from . import util
from .bitcoin import hash_encode, int_to_hex, rev_hex
from .crypto import sha256d
from . import constants
from .util import bfh, bh2u
from .simple_config import SimpleConfig
try:
import pylibscrypt
getPoWHash = lambda x: pylibscrypt.scrypt(password=x, salt=x, N=1024, r=1, p=1, olen=32)
except ImportError:
util.print_msg("Warning: package pylibscrypt not available")
HEADER_SIZE = 80 # bytes
MAX_TARGET = 0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
class MissingHeader(Exception):
pass
class InvalidHeader(Exception):
pass
def serialize_header(header_dict: dict) -> str:
s = int_to_hex(header_dict['version'], 4) \
+ rev_hex(header_dict['prev_block_hash']) \
+ rev_hex(header_dict['merkle_root']) \
+ int_to_hex(int(header_dict['timestamp']), 4) \
+ int_to_hex(int(header_dict['bits']), 4) \
+ int_to_hex(int(header_dict['nonce']), 4)
return s
def deserialize_header(s: bytes, height: int) -> dict:
if not s:
raise InvalidHeader('Invalid header: {}'.format(s))
if len(s) != HEADER_SIZE:
raise InvalidHeader('Invalid header length: {}'.format(len(s)))
hex_to_int = lambda s: int.from_bytes(s, byteorder='little')
h = {}
h['version'] = hex_to_int(s[0:4])
h['prev_block_hash'] = hash_encode(s[4:36])
h['merkle_root'] = hash_encode(s[36:68])
h['timestamp'] = hex_to_int(s[68:72])
h['bits'] = hex_to_int(s[72:76])
h['nonce'] = hex_to_int(s[76:80])
h['block_height'] = height
return h
def hash_header(header: dict) -> str:
if header is None:
return '0' * 64
if header.get('prev_block_hash') is None:
header['prev_block_hash'] = '00'*32
return hash_raw_header(serialize_header(header))
def hash_raw_header(header: str) -> str:
return hash_encode(sha256d(bfh(header)))
# key: blockhash hex at forkpoint
# the chain at some key is the best chain that includes the given hash
blockchains = {} # type: Dict[str, Blockchain]
blockchains_lock = threading.RLock()
def read_blockchains(config: 'SimpleConfig'):
best_chain = Blockchain(config=config,
forkpoint=0,
parent=None,
forkpoint_hash=constants.net.GENESIS,
prev_hash=None)
blockchains[constants.net.GENESIS] = best_chain
# consistency checks
if best_chain.height() > constants.net.max_checkpoint():
header_after_cp = best_chain.read_header(constants.net.max_checkpoint()+1)
if not header_after_cp or not best_chain.can_connect(header_after_cp, check_height=False):
util.print_error("[blockchain] deleting best chain. cannot connect header after last cp to last cp.")
os.unlink(best_chain.path())
best_chain.update_size()
# forks
fdir = os.path.join(util.get_headers_dir(config), 'forks')
util.make_dir(fdir)
# files are named as: fork2_{forkpoint}_{prev_hash}_{first_hash}
l = filter(lambda x: x.startswith('fork2_') and '.' not in x, os.listdir(fdir))
l = sorted(l, key=lambda x: int(x.split('_')[1])) # sort by forkpoint
def delete_chain(filename, reason):
util.print_error(f"[blockchain] deleting chain {filename}: {reason}")
os.unlink(os.path.join(fdir, filename))
def instantiate_chain(filename):
__, forkpoint, prev_hash, first_hash = filename.split('_')
forkpoint = int(forkpoint)
prev_hash = (64-len(prev_hash)) * "0" + prev_hash # left-pad with zeroes
first_hash = (64-len(first_hash)) * "0" + first_hash
# forks below the max checkpoint are not allowed
if forkpoint <= constants.net.max_checkpoint():
delete_chain(filename, "deleting fork below max checkpoint")
return
# find parent (sorting by forkpoint guarantees it's already instantiated)
for parent in blockchains.values():
if parent.check_hash(forkpoint - 1, prev_hash):
break
else:
delete_chain(filename, "cannot find parent for chain")
return
b = Blockchain(config=config,
forkpoint=forkpoint,
parent=parent,
forkpoint_hash=first_hash,
prev_hash=prev_hash)
# consistency checks
h = b.read_header(b.forkpoint)
if first_hash != hash_header(h):
delete_chain(filename, "incorrect first hash for chain")
return
if not b.parent.can_connect(h, check_height=False):
delete_chain(filename, "cannot connect chain to parent")
return
chain_id = b.get_id()
assert first_hash == chain_id, (first_hash, chain_id)
blockchains[chain_id] = b
for filename in l:
instantiate_chain(filename)
def pow_hash_header(header):
return hash_encode(getPoWHash(bfh(serialize_header(header))))
def get_best_chain() -> 'Blockchain':
return blockchains[constants.net.GENESIS]
# block hash -> chain work; up to and including that block
_CHAINWORK_CACHE = {
"0000000000000000000000000000000000000000000000000000000000000000": 0, # virtual block at height -1
} # type: Dict[str, int]
class Blockchain(util.PrintError):
"""
Manages blockchain headers and their verification
"""
def __init__(self, config: SimpleConfig, forkpoint: int, parent: Optional['Blockchain'],
forkpoint_hash: str, prev_hash: Optional[str]):
assert isinstance(forkpoint_hash, str) and len(forkpoint_hash) == 64, forkpoint_hash
assert (prev_hash is None) or (isinstance(prev_hash, str) and len(prev_hash) == 64), prev_hash
# assert (parent is None) == (forkpoint == 0)
if 0 < forkpoint <= constants.net.max_checkpoint():
raise Exception(f"cannot fork below max checkpoint. forkpoint: {forkpoint}")
self.config = config
self.forkpoint = forkpoint # height of first header
self.parent = parent
self._forkpoint_hash = forkpoint_hash # blockhash at forkpoint. "first hash"
self._prev_hash = prev_hash # blockhash immediately before forkpoint
self.lock = threading.RLock()
self.update_size()
def with_lock(func):
def func_wrapper(self, *args, **kwargs):
with self.lock:
return func(self, *args, **kwargs)
return func_wrapper
@property
def checkpoints(self):
return constants.net.CHECKPOINTS
def get_max_child(self) -> Optional[int]:
with blockchains_lock: chains = list(blockchains.values())
children = list(filter(lambda y: y.parent==self, chains))
return max([x.forkpoint for x in children]) if children else None
def get_max_forkpoint(self) -> int:
"""Returns the max height where there is a fork
related to this chain.
"""
mc = self.get_max_child()
return mc if mc is not None else self.forkpoint
@with_lock
def get_branch_size(self) -> int:
return self.height() - self.get_max_forkpoint() + 1
def get_name(self) -> str:
return self.get_hash(self.get_max_forkpoint()).lstrip('0')[0:10]
def check_header(self, header: dict) -> bool:
header_hash = hash_header(header)
height = header.get('block_height')
return self.check_hash(height, header_hash)
def check_hash(self, height: int, header_hash: str) -> bool:
"""Returns whether the hash of the block at given height
is the given hash.
"""
assert isinstance(header_hash, str) and len(header_hash) == 64, header_hash # hex
try:
return header_hash == self.get_hash(height)
except Exception:
return False
def fork(parent, header: dict) -> 'Blockchain':
if not parent.can_connect(header, check_height=False):
raise Exception("forking header does not connect to parent chain")
forkpoint = header.get('block_height')
self = Blockchain(config=parent.config,
forkpoint=forkpoint,
parent=parent,
forkpoint_hash=hash_header(header),
prev_hash=parent.get_hash(forkpoint-1))
open(self.path(), 'w+').close()
self.save_header(header)
# put into global dict. note that in some cases
# save_header might have already put it there but that's OK
chain_id = self.get_id()
with blockchains_lock:
blockchains[chain_id] = self
return self
@with_lock
def height(self) -> int:
return self.forkpoint + self.size() - 1
@with_lock
def size(self) -> int:
return self._size
@with_lock
def update_size(self) -> None:
p = self.path()
self._size = os.path.getsize(p)//HEADER_SIZE if os.path.exists(p) else 0
#def pow_hash_header(header):
# return hash_encode(getPoWHash(bfh(serialize_header(header))))
@classmethod
def verify_header(cls, header: dict, prev_hash: str, target: int, expected_header_hash: str=None) -> None:
_hash = hash_header(header)
_powhash = pow_hash_header(header)
if expected_header_hash and expected_header_hash != _hash:
raise Exception("hash mismatches with expected: {} vs {}".format(expected_header_hash, _hash))
if prev_hash != header.get('prev_block_hash'):
raise Exception("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash')))
if constants.net.TESTNET:
return
bits = cls.target_to_bits(target)
bits = target
if bits != header.get('bits'):
raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits')))
block_hash_as_num = int.from_bytes(bfh(_hash), byteorder='big')
target_val = cls.bits_to_target(bits)
if int('0x' + _powhash, 16) > target_val:
raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target_val))
def verify_chunk(self, index: int, data: bytes) -> None:
num = len(data) // HEADER_SIZE
current_header = index * 2016
print('chunk ' + str(index))
prev_hash = self.get_hash(current_header - 1)
headerLast = None
headerFirst = None
capture = None
lst = []
for i in range(num):
averaging_interval = self.AveragingInterval(current_header)
difficulty_interval = self.DifficultyAdjustmentInterval(current_header)
if current_header < 426000:
target = self.get_target(current_header - 1, headerLast, headerFirst)
try:
expected_header_hash = self.get_hash(current_header)
except MissingHeader:
expected_header_hash = None
raw_header = data[i*HEADER_SIZE : (i+1)*HEADER_SIZE]
header = deserialize_header(raw_header, current_header)
self.verify_header(header, prev_hash, target, expected_header_hash)
prev_hash = hash_header(header)
headerLast = header
if current_header == 0:
headerFirst = header
elif (current_header + averaging_interval + 1) % difficulty_interval == 0:
capture = header
if current_header != 0 and current_header % difficulty_interval == 0:
headerFirst = capture
if current_header >= 425993:
lst.append(headerLast)
current_header = current_header + 1
else:
if len(lst)>6:
headerFirst = lst[0]
target = self.get_target(current_header - 1, headerLast, headerFirst)
try:
expected_header_hash = self.get_hash(current_header)
except MissingHeader:
expected_header_hash = None
raw_header = data[i * HEADER_SIZE: (i + 1) * HEADER_SIZE]
header = deserialize_header(raw_header, current_header)
self.verify_header(header, prev_hash, target, expected_header_hash)
prev_hash = hash_header(header)
headerLast = header
lst.append(header)
if len(lst)>7:
lst.pop(0)
current_header = current_header + 1
@with_lock
def path(self):
d = util.get_headers_dir(self.config)
if self.parent is None:
filename = 'blockchain_headers'
else:
assert self.forkpoint > 0, self.forkpoint
prev_hash = self._prev_hash.lstrip('0')
first_hash = self._forkpoint_hash.lstrip('0')
basename = f'fork2_{self.forkpoint}_{prev_hash}_{first_hash}'
filename = os.path.join('forks', basename)
return os.path.join(d, filename)
@with_lock
def save_chunk(self, index: int, chunk: bytes):
assert index >= 0, index
chunk_within_checkpoint_region = index < len(self.checkpoints)
# chunks in checkpoint region are the responsibility of the 'main chain'
if chunk_within_checkpoint_region and self.parent is not None:
main_chain = get_best_chain()
main_chain.save_chunk(index, chunk)
return
delta_height = (index * 2016 - self.forkpoint)
delta_bytes = delta_height * HEADER_SIZE
# if this chunk contains our forkpoint, only save the part after forkpoint
# (the part before is the responsibility of the parent)
if delta_bytes < 0:
chunk = chunk[-delta_bytes:]
delta_bytes = 0
truncate = not chunk_within_checkpoint_region
self.write(chunk, delta_bytes, truncate)
self.swap_with_parent()
def swap_with_parent(self) -> None:
parent_lock = self.parent.lock if self.parent is not None else threading.Lock()
with parent_lock, self.lock, blockchains_lock: # this order should not deadlock
# do the swap; possibly multiple ones
cnt = 0
while self._swap_with_parent():
cnt += 1
if cnt > len(blockchains): # make sure we are making progress
raise Exception(f'swapping fork with parent too many times: {cnt}')
def _swap_with_parent(self) -> bool:
"""Check if this chain became stronger than its parent, and swap
the underlying files if so. The Blockchain instances will keep
'containing' the same headers, but their ids change and so
they will be stored in different files."""
if self.parent is None:
return False
if self.parent.get_chainwork() >= self.get_chainwork():
return False
self.print_error("swap", self.forkpoint, self.parent.forkpoint)
parent_branch_size = self.parent.height() - self.forkpoint + 1
forkpoint = self.forkpoint # type: Optional[int]
parent = self.parent # type: Optional[Blockchain]
child_old_id = self.get_id()
parent_old_id = parent.get_id()
# swap files
# child takes parent's name
# parent's new name will be something new (not child's old name)
self.assert_headers_file_available(self.path())
child_old_name = self.path()
with open(self.path(), 'rb') as f:
my_data = f.read()
self.assert_headers_file_available(parent.path())
with open(parent.path(), 'rb') as f:
f.seek((forkpoint - parent.forkpoint)*HEADER_SIZE)
parent_data = f.read(parent_branch_size*HEADER_SIZE)
self.write(parent_data, 0)
parent.write(my_data, (forkpoint - parent.forkpoint)*HEADER_SIZE)
# swap parameters
self.parent, parent.parent = parent.parent, self # type: Optional[Blockchain], Optional[Blockchain]
self.forkpoint, parent.forkpoint = parent.forkpoint, self.forkpoint
self._forkpoint_hash, parent._forkpoint_hash = parent._forkpoint_hash, hash_raw_header(bh2u(parent_data[:HEADER_SIZE]))
self._prev_hash, parent._prev_hash = parent._prev_hash, self._prev_hash
# parent's new name
os.replace(child_old_name, parent.path())
self.update_size()
parent.update_size()
# update pointers
blockchains.pop(child_old_id, None)
blockchains.pop(parent_old_id, None)
blockchains[self.get_id()] = self
blockchains[parent.get_id()] = parent
return True
def get_id(self) -> str:
return self._forkpoint_hash
def assert_headers_file_available(self, path):
if os.path.exists(path):
return
elif not os.path.exists(util.get_headers_dir(self.config)):
raise FileNotFoundError('Electrum headers_dir does not exist. Was it deleted while running?')
else:
raise FileNotFoundError('Cannot find headers file but headers_dir is there. Should be at {}'.format(path))
@with_lock
def write(self, data: bytes, offset: int, truncate: bool=True) -> None:
filename = self.path()
self.assert_headers_file_available(filename)
with open(filename, 'rb+') as f:
if truncate and offset != self._size * HEADER_SIZE:
f.seek(offset)
f.truncate()
f.seek(offset)
f.write(data)
f.flush()
os.fsync(f.fileno())
self.update_size()
@with_lock
def save_header(self, header: dict) -> None:
delta = header.get('block_height') - self.forkpoint
data = bfh(serialize_header(header))
# headers are only _appended_ to the end:
assert delta == self.size(), (delta, self.size())
assert len(data) == HEADER_SIZE
self.write(data, delta*HEADER_SIZE)
self.swap_with_parent()
@with_lock
def read_header(self, height: int) -> Optional[dict]:
if height < 0:
return
if height < self.forkpoint:
return self.parent.read_header(height)
if height > self.height():
return
delta = height - self.forkpoint
name = self.path()
self.assert_headers_file_available(name)
with open(name, 'rb') as f:
f.seek(delta * HEADER_SIZE)
h = f.read(HEADER_SIZE)
if len(h) < HEADER_SIZE:
raise Exception('Expected to read a full header. This was only {} bytes'.format(len(h)))
if h == bytes([0])*HEADER_SIZE:
return None
return deserialize_header(h, height)
def header_at_tip(self) -> Optional[dict]:
"""Return latest header."""
height = self.height()
return self.read_header(height)
def get_hash(self, height: int) -> str:
def is_height_checkpoint():
within_cp_range = height <= constants.net.max_checkpoint()
at_chunk_boundary = (height+1) % 2016 == 0
return within_cp_range and at_chunk_boundary
if height == -1:
return '0000000000000000000000000000000000000000000000000000000000000000'
elif height == 0:
return constants.net.GENESIS
elif is_height_checkpoint():
index = height // 2016
h, t = self.checkpoints[index]
return h
else:
header = self.read_header(height)
if header is None:
raise MissingHeader(height)
return hash_header(header)
def get_target(self, index: int, headerLast: dict=None, headerFirst: dict=None) -> int:
# compute target from chunk x, used in chunk x+1
if constants.net.TESTNET:
return 0
# The range is first 90 blocks because FLO's block time was 90 blocks when it started
if -1 <= index <= 88:
return 0x1e0ffff0
if index < len(self.checkpoints):
h, t = self.checkpoints[index]
return t
# new target
if headerLast is None:
headerLast = self.read_header(index)
height = headerLast["block_height"]
# check if the height passes is in range for retargeting
if (height + 1) % self.DifficultyAdjustmentInterval(height + 1) != 0:
return int(headerLast["bits"])
if headerFirst is None:
averagingInterval = self.AveragingInterval(height + 1)
blockstogoback = averagingInterval - 1
# print("Blocks to go back = " + str(blockstogoback))
if (height + 1) != averagingInterval:
blockstogoback = averagingInterval
firstHeight = height - blockstogoback
headerFirst = self.read_header(int(firstHeight))
firstBlockTime = headerFirst["timestamp"]
nMinActualTimespan = int(self.MinActualTimespan(int(headerLast["block_height"]) + 1))
nMaxActualTimespan = int(self.MaxActualTimespan(int(headerLast["block_height"]) + 1))
# Limit adjustment step
nActualTimespan = headerLast["timestamp"] - firstBlockTime
if nActualTimespan < nMinActualTimespan:
nActualTimespan = nMinActualTimespan
if nActualTimespan > nMaxActualTimespan:
nActualTimespan = nMaxActualTimespan
# Retarget
bnNewBits = int(headerLast["bits"])
bnNew = self.bits_to_target(bnNewBits)
bnOld = bnNew
# FLO: intermediate uint256 can overflow by 1 bit
# const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
fShift = bnNew > MAX_TARGET - 1
if (fShift):
bnNew = bnNew >> 1
bnNew = bnNew * nActualTimespan
bnNew = bnNew / self.TargetTimespan(headerLast["block_height"] + 1)
if fShift:
bnNew = bnNew << 1
if bnNew > MAX_TARGET:
bnNew = MAX_TARGET
bnNew = self.target_to_bits(int(bnNew))
return bnNew
@classmethod
def bits_to_target(cls, bits: int) -> int:
bitsN = (bits >> 24) & 0xff
if not (0x03 <= bitsN <= 0x1e):
raise Exception("First part of bits should be in [0x03, 0x1e]")
bitsBase = bits & 0xffffff
if not (0x8000 <= bitsBase <= 0x7fffff):
raise Exception("Second part of bits should be in [0x8000, 0x7fffff]")
return bitsBase << (8 * (bitsN - 3))
@classmethod
def target_to_bits(cls, target: int) -> int:
c = ("%064x" % target)[2:]
while c[:2] == '00' and len(c) > 6:
c = c[2:]
bitsN, bitsBase = len(c) // 2, int.from_bytes(bfh(c[:6]), byteorder='big')
if bitsBase >= 0x800000:
bitsN += 1
bitsBase >>= 8
return bitsN << 24 | bitsBase
def chainwork_of_header_at_height(self, height: int) -> int:
"""work done by single header at given height"""
chunk_idx = height // 2016 - 1
target = self.get_target(chunk_idx)
work = ((2 ** 256 - target - 1) // (target + 1)) + 1
return work
@with_lock
def get_chainwork(self, height=None) -> int:
if height is None:
height = max(0, self.height())
if constants.net.TESTNET:
# On testnet/regtest, difficulty works somewhat different.
# It's out of scope to properly implement that.
return height
last_retarget = height // 2016 * 2016 - 1
cached_height = last_retarget
while _CHAINWORK_CACHE.get(self.get_hash(cached_height)) is None:
if cached_height <= -1:
break
cached_height -= 2016
assert cached_height >= -1, cached_height
running_total = _CHAINWORK_CACHE[self.get_hash(cached_height)]
while cached_height < last_retarget:
cached_height += 2016
work_in_single_header = self.chainwork_of_header_at_height(cached_height)
work_in_chunk = 2016 * work_in_single_header
running_total += work_in_chunk
_CHAINWORK_CACHE[self.get_hash(cached_height)] = running_total
cached_height += 2016
work_in_single_header = self.chainwork_of_header_at_height(cached_height)
work_in_last_partial_chunk = (height % 2016 + 1) * work_in_single_header
return running_total + work_in_last_partial_chunk
def can_connect(self, header: dict, check_height: bool=True) -> bool:
if header is None:
return False
height = header['block_height']
if check_height and self.height() != height - 1:
#self.print_error("cannot connect at height", height)
return False
if height == 0:
return hash_header(header) == constants.net.GENESIS
try:
prev_hash = self.get_hash(height - 1)
except:
return False
if prev_hash != header.get('prev_block_hash'):
return False
try:
target = self.get_target(height - 1)
except MissingHeader:
return False
try:
self.verify_header(header, prev_hash, target)
except BaseException as e:
return False
return True
def connect_chunk(self, idx: int, hexdata: str) -> bool:
assert idx >= 0, idx
try:
data = bfh(hexdata)
self.verify_chunk(idx, data)
#self.print_error("validated chunk %d" % idx)
self.save_chunk(idx, data)
return True
except BaseException as e:
self.print_error(f'verify_chunk idx {idx} failed: {repr(e)}')
return False
def get_checkpoints(self):
# for each chunk, store the hash of the last block and the target after the chunk
cp = []
n = self.height() // 2016
for index in range(n):
h = self.get_hash((index+1) * 2016 -1)
target = self.get_target(index)
cp.append((h, target))
return cp
def AveragingInterval(self, height):
# V1
if height < constants.net.nHeight_Difficulty_Version2:
return constants.net.nAveragingInterval_Version1
# V2
elif height < constants.net.nHeight_Difficulty_Version3:
return constants.net.nAveragingInterval_Version2
# V3
else:
return constants.net.nAveragingInterval_Version3
def MinActualTimespan(self, height):
averagingTargetTimespan = self.AveragingInterval(height) * constants.net.nPowTargetSpacing
# V1
if height < constants.net.nHeight_Difficulty_Version2:
return int(averagingTargetTimespan * (100 - constants.net.nMaxAdjustUp_Version1) / 100)
# V2
elif height < constants.net.nHeight_Difficulty_Version3:
return int(averagingTargetTimespan * (100 - constants.net.nMaxAdjustUp_Version2) / 100)
# V3
else:
return int(averagingTargetTimespan * (100 - constants.net.nMaxAdjustUp_Version3) / 100)
def MaxActualTimespan(self, height):
averagingTargetTimespan = self.AveragingInterval(height) * constants.net.nPowTargetSpacing
# V1
if height < constants.net.nHeight_Difficulty_Version2:
return int(averagingTargetTimespan * (100 + constants.net.nMaxAdjustDown_Version1) / 100)
# V2
elif height < constants.net.nHeight_Difficulty_Version3:
return int(averagingTargetTimespan * (100 + constants.net.nMaxAdjustDown_Version2) / 100)
# V3
else:
return int(averagingTargetTimespan * (100 + constants.net.nMaxAdjustDown_Version3) / 100)
def TargetTimespan(self, height):
# V1
if height < constants.net.nHeight_Difficulty_Version2:
return constants.net.nTargetTimespan_Version1
# V2
if height < constants.net.nHeight_Difficulty_Version3:
return constants.net.nAveragingInterval_Version2 * constants.net.nPowTargetSpacing
# V3
return constants.net.nAveragingInterval_Version3 * constants.net.nPowTargetSpacing
def DifficultyAdjustmentInterval(self, height):
# V1
if height < constants.net.nHeight_Difficulty_Version2:
return constants.net.nInterval_Version1
# V2
if height < constants.net.nHeight_Difficulty_Version3:
return constants.net.nInterval_Version2
# V3
return constants.net.nInterval_Version3
def check_header(header: dict) -> Optional[Blockchain]:
if type(header) is not dict:
return None
with blockchains_lock: chains = list(blockchains.values())
for b in chains:
if b.check_header(header):
return b
return None
def can_connect(header: dict) -> Optional[Blockchain]:
with blockchains_lock: chains = list(blockchains.values())
for b in chains:
if b.can_connect(header):
return b
return None

View File

@ -0,0 +1,3 @@
[
]

View File

@ -0,0 +1,3 @@
[
]

View File

@ -22,11 +22,12 @@
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from collections import defaultdict, namedtuple
from collections import defaultdict
from math import floor, log10
from typing import NamedTuple, List
from .bitcoin import sha256, COIN, TYPE_ADDRESS, is_address
from .transaction import Transaction
from .transaction import Transaction, TxOutput
from .util import NotEnoughFunds, PrintError
@ -68,13 +69,14 @@ class PRNG:
x[i], x[j] = x[j], x[i]
Bucket = namedtuple('Bucket',
['desc',
'weight', # as in BIP-141
'value', # in satoshis
'coins', # UTXOs
'min_height', # min block height where a coin was confirmed
'witness']) # whether any coin uses segwit
class Bucket(NamedTuple):
desc: str
weight: int # as in BIP-141
value: int # in satoshis
coins: List[dict] # UTXOs
min_height: int # min block height where a coin was confirmed
witness: bool # whether any coin uses segwit
def strip_unneeded(bkts, sufficient_funds):
'''Remove buckets that are unnecessary in achieving the spend amount'''
@ -82,8 +84,8 @@ def strip_unneeded(bkts, sufficient_funds):
for i in range(len(bkts)):
if not sufficient_funds(bkts[i + 1:]):
return bkts[i:]
# Shouldn't get here
return bkts
# none of the buckets are needed
return []
class CoinChooserBase(PrintError):
@ -117,7 +119,7 @@ class CoinChooserBase(PrintError):
def change_amounts(self, tx, count, fee_estimator, dust_threshold):
# Break change up if bigger than max_change
output_amounts = [o[2] for o in tx.outputs()]
output_amounts = [o.value for o in tx.outputs()]
# Don't split change of less than 0.02 BTC
max_change = max(max(output_amounts) * 1.25, 0.02 * COIN)
@ -178,14 +180,14 @@ class CoinChooserBase(PrintError):
# size of the change output, add it to the transaction.
dust = sum(amount for amount in amounts if amount < dust_threshold)
amounts = [amount for amount in amounts if amount >= dust_threshold]
change = [(TYPE_ADDRESS, addr, amount)
change = [TxOutput(TYPE_ADDRESS, addr, amount)
for addr, amount in zip(change_addrs, amounts)]
self.print_error('change:', change)
if dust:
self.print_error('not keeping dust', dust)
return change
def make_tx(self, coins, outputs, change_addrs, fee_estimator,
def make_tx(self, coins, inputs, outputs, change_addrs, fee_estimator,
dust_threshold):
"""Select unspent coins to spend to pay outputs. If the change is
greater than dust_threshold (after adding the change output to
@ -200,11 +202,14 @@ class CoinChooserBase(PrintError):
self.p = PRNG(''.join(sorted(utxos)))
# Copy the outputs so when adding change we don't modify "outputs"
tx = Transaction.from_io([], outputs[:])
tx = Transaction.from_io(inputs[:], outputs[:])
input_value = tx.input_value()
# Weight of the transaction with no inputs and no change
# Note: this will use legacy tx serialization as the need for "segwit"
# would be detected from inputs. The only side effect should be that the
# marker and flag are excluded, which is compensated in get_tx_weight()
# FIXME calculation will be off by this (2 wu) in case of RBF batching
base_weight = tx.estimated_weight()
spent_amount = tx.output_value()
@ -228,7 +233,7 @@ class CoinChooserBase(PrintError):
def sufficient_funds(buckets):
'''Given a list of buckets, return True if it has enough
value to pay for the transaction'''
total_input = sum(bucket.value for bucket in buckets)
total_input = input_value + sum(bucket.value for bucket in buckets)
total_weight = get_tx_weight(buckets)
return total_input >= spent_amount + fee_estimator_w(total_weight)
@ -354,9 +359,9 @@ class CoinChooserPrivacy(CoinChooserRandom):
return [coin['address'] for coin in coins]
def penalty_func(self, tx):
min_change = min(o[2] for o in tx.outputs()) * 0.75
max_change = max(o[2] for o in tx.outputs()) * 1.33
spent_amount = sum(o[2] for o in tx.outputs())
min_change = min(o.value for o in tx.outputs()) * 0.75
max_change = max(o.value for o in tx.outputs()) * 1.33
spent_amount = sum(o.value for o in tx.outputs())
def penalty(buckets):
badness = len(buckets) - 1

View File

@ -32,15 +32,26 @@ import ast
import base64
from functools import wraps
from decimal import Decimal
from typing import Optional, TYPE_CHECKING
from .import util, ecc
from .util import bfh, bh2u, format_satoshis, json_decode, print_error, json_encode
from .import bitcoin
from . import bitcoin
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
from . import bip32
from .i18n import _
from .transaction import Transaction, multisig_script
from .transaction import Transaction, multisig_script, TxOutput
from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
from .plugins import run_hook
from .synchronizer import Notifier
from .storage import WalletStorage
from . import keystore
from .wallet import Wallet, Imported_Wallet, Abstract_Wallet
from .mnemonic import Mnemonic
if TYPE_CHECKING:
from .network import Network
from .simple_config import SimpleConfig
known_commands = {}
@ -91,7 +102,8 @@ def command(s):
class Commands:
def __init__(self, config, wallet, network, callback = None):
def __init__(self, config: 'SimpleConfig', wallet: Abstract_Wallet,
network: Optional['Network'], callback=None):
self.config = config
self.wallet = wallet
self.network = network
@ -123,17 +135,80 @@ class Commands:
return ' '.join(sorted(known_commands.keys()))
@command('')
def create(self, segwit=False):
def create(self, passphrase=None, password=None, encrypt_file=True, segwit=False):
"""Create a new wallet"""
raise Exception('Not a JSON-RPC command')
storage = WalletStorage(self.config.get_wallet_path())
if storage.file_exists():
raise Exception("Remove the existing wallet first!")
@command('wn')
def restore(self, text):
seed_type = 'segwit' if segwit else 'standard'
seed = Mnemonic('en').make_seed(seed_type)
k = keystore.from_seed(seed, passphrase)
storage.put('keystore', k.dump())
storage.put('wallet_type', 'standard')
wallet = Wallet(storage)
wallet.update_password(old_pw=None, new_pw=password, encrypt_storage=encrypt_file)
wallet.synchronize()
msg = "Please keep your seed in a safe place; if you lose it, you will not be able to restore your wallet."
wallet.storage.write()
return {'seed': seed, 'path': wallet.storage.path, 'msg': msg}
@command('')
def restore(self, text, passphrase=None, password=None, encrypt_file=True):
"""Restore a wallet from text. Text can be a seed phrase, a master
public key, a master private key, a list of bitcoin addresses
or bitcoin private keys. If you want to be prompted for your
seed, type '?' or ':' (concealed) """
raise Exception('Not a JSON-RPC command')
storage = WalletStorage(self.config.get_wallet_path())
if storage.file_exists():
raise Exception("Remove the existing wallet first!")
text = text.strip()
if keystore.is_address_list(text):
wallet = Imported_Wallet(storage)
addresses = text.split()
good_inputs, bad_inputs = wallet.import_addresses(addresses, write_to_disk=False)
# FIXME tell user about bad_inputs
if not good_inputs:
raise Exception("None of the given addresses can be imported")
elif keystore.is_private_key_list(text, allow_spaces_inside_key=False):
k = keystore.Imported_KeyStore({})
storage.put('keystore', k.dump())
wallet = Imported_Wallet(storage)
keys = keystore.get_private_keys(text)
good_inputs, bad_inputs = wallet.import_private_keys(keys, None, write_to_disk=False)
# FIXME tell user about bad_inputs
if not good_inputs:
raise Exception("None of the given privkeys can be imported")
else:
if keystore.is_seed(text):
k = keystore.from_seed(text, passphrase)
elif keystore.is_master_key(text):
k = keystore.from_master_key(text)
else:
raise Exception("Seed or key not recognized")
storage.put('keystore', k.dump())
storage.put('wallet_type', 'standard')
wallet = Wallet(storage)
assert not storage.file_exists(), "file was created too soon! plaintext keys might have been written to disk"
wallet.update_password(old_pw=None, new_pw=password, encrypt_storage=encrypt_file)
wallet.synchronize()
if self.network:
wallet.start_network(self.network)
print_error("Recovering wallet...")
wallet.wait_until_synchronized()
wallet.stop_threads()
# note: we don't wait for SPV
msg = "Recovery successful" if wallet.is_found() else "Found no history for this wallet"
else:
msg = ("This wallet was restored offline. It may contain more addresses than displayed. "
"Start a daemon (not offline) to sync history.")
wallet.storage.write()
return {'path': wallet.storage.path, 'msg': msg}
@command('wp')
def password(self, password=None, new_password=None):
@ -145,6 +220,11 @@ class Commands:
self.wallet.storage.write()
return {'password':self.wallet.has_password()}
@command('w')
def get(self, key):
"""Return item from wallet storage"""
return self.wallet.storage.get(key)
@command('')
def getconfig(self, key):
"""Return a configuration variable. """
@ -181,13 +261,13 @@ class Commands:
walletless server query, results are not checked by SPV.
"""
sh = bitcoin.address_to_scripthash(address)
return self.network.get_history_for_scripthash(sh)
return self.network.run_from_another_thread(self.network.get_history_for_scripthash(sh))
@command('w')
def listunspent(self):
"""List unspent outputs. Returns the list of unspent transaction
outputs in your wallet."""
l = copy.deepcopy(self.wallet.get_utxos(exclude_frozen=False))
l = copy.deepcopy(self.wallet.get_utxos())
for i in l:
v = i["value"]
i["value"] = str(Decimal(v)/COIN) if v is not None else None
@ -199,7 +279,7 @@ class Commands:
is a walletless server query, results are not checked by SPV.
"""
sh = bitcoin.address_to_scripthash(address)
return self.network.listunspent_for_scripthash(sh)
return self.network.run_from_another_thread(self.network.listunspent_for_scripthash(sh))
@command('')
def serialize(self, jsontx):
@ -226,7 +306,7 @@ class Commands:
txin['signatures'] = [None]
txin['num_sig'] = 1
outputs = [(TYPE_ADDRESS, x['address'], int(x['value'])) for x in outputs]
outputs = [TxOutput(TYPE_ADDRESS, x['address'], int(x['value'])) for x in outputs]
tx = Transaction.from_io(inputs, outputs, locktime=locktime)
tx.sign(keypairs)
return tx.as_dict()
@ -249,13 +329,14 @@ class Commands:
def deserialize(self, tx):
"""Deserialize a serialized transaction"""
tx = Transaction(tx)
return tx.deserialize()
return tx.deserialize(force_full_parse=True)
@command('n')
def broadcast(self, tx):
"""Broadcast a transaction to the network. """
tx = Transaction(tx)
return self.network.broadcast_transaction(tx)
self.network.run_from_another_thread(self.network.broadcast_transaction(tx))
return tx.txid()
@command('')
def createmultisig(self, num, pubkeys):
@ -322,7 +403,7 @@ class Commands:
server query, results are not checked by SPV.
"""
sh = bitcoin.address_to_scripthash(address)
out = self.network.get_balance_for_scripthash(sh)
out = self.network.run_from_another_thread(self.network.get_balance_for_scripthash(sh))
out["confirmed"] = str(Decimal(out["confirmed"])/COIN)
out["unconfirmed"] = str(Decimal(out["unconfirmed"])/COIN)
return out
@ -331,7 +412,7 @@ class Commands:
def getmerkle(self, txid, height):
"""Get Merkle branch of a transaction included in a block. Electrum
uses this to verify transactions (Simple Payment Verification)."""
return self.network.get_merkle_for_transaction(txid, int(height))
return self.network.run_from_another_thread(self.network.get_merkle_for_transaction(txid, int(height)))
@command('n')
def getservers(self):
@ -354,6 +435,16 @@ class Commands:
"""Get master private key. Return your wallet\'s master private key"""
return str(self.wallet.keystore.get_master_private_key(password))
@command('')
def convert_xkey(self, xkey, xtype):
"""Convert xtype of a master key. e.g. xpub -> ypub"""
is_xprv = bip32.is_xprv(xkey)
if not bip32.is_xpub(xkey) and not is_xprv:
raise Exception('xkey should be a master public/private key')
_, depth, fingerprint, child_number, c, cK = bip32.deserialize_xkey(xkey, is_xprv)
serialize = bip32.serialize_xprv if is_xprv else bip32.serialize_xpub
return serialize(xtype, c, cK, depth, fingerprint, child_number)
@command('wp')
def getseed(self, password=None):
"""Get seed phrase. Print the generation seed of your wallet."""
@ -368,7 +459,7 @@ class Commands:
try:
addr = self.wallet.import_private_key(privkey, password)
out = "Keypair imported: " + addr
except BaseException as e:
except Exception as e:
out = "Error: " + str(e)
return out
@ -415,7 +506,7 @@ class Commands:
for address, amount in outputs:
address = self._resolver(address)
amount = satoshis(amount)
final_outputs.append((TYPE_ADDRESS, address, amount))
final_outputs.append(TxOutput(TYPE_ADDRESS, address, amount))
coins = self.wallet.get_spendable_coins(domain, self.config)
tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr)
@ -446,9 +537,15 @@ class Commands:
return tx.as_dict()
@command('w')
def history(self, year=None, show_addresses=False, show_fiat=False):
def history(self, year=None, show_addresses=False, show_fiat=False, show_fees=False,
from_height=None, to_height=None):
"""Wallet history. Returns the transaction history of your wallet."""
kwargs = {'show_addresses': show_addresses}
kwargs = {
'show_addresses': show_addresses,
'show_fees': show_fees,
'from_height': from_height,
'to_height': to_height,
}
if year:
import time
start_date = datetime.datetime(year, 1, 1)
@ -517,7 +614,7 @@ class Commands:
if self.wallet and txid in self.wallet.transactions:
tx = self.wallet.transactions[txid]
else:
raw = self.network.get_transaction(txid)
raw = self.network.run_from_another_thread(self.network.get_transaction(txid))
if raw:
tx = Transaction(raw)
else:
@ -635,20 +732,11 @@ class Commands:
self.wallet.remove_payment_request(k, self.config)
@command('n')
def notify(self, address, URL):
def notify(self, address: str, URL: str):
"""Watch an address. Every time the address changes, a http POST is sent to the URL."""
def callback(x):
import urllib.request
headers = {'content-type':'application/json'}
data = {'address':address, 'status':x.get('result')}
serialized_data = util.to_bytes(json.dumps(data))
try:
req = urllib.request.Request(URL, serialized_data, headers)
response_stream = urllib.request.urlopen(req, timeout=5)
util.print_error('Got Response for %s' % address)
except BaseException as e:
util.print_error(str(e))
self.network.subscribe_to_addresses([address], callback)
if not hasattr(self, "_notifier"):
self._notifier = Notifier(self.network)
self.network.run_from_another_thread(self._notifier.start_watching_queue.put((address, URL)))
return True
@command('wn')
@ -680,6 +768,16 @@ class Commands:
# for the python console
return sorted(known_commands.keys())
def eval_bool(x: str) -> bool:
if x == 'false': return False
if x == 'true': return True
try:
return bool(ast.literal_eval(x))
except:
return bool(x)
param_descriptions = {
'privkey': 'Private key. Type \'?\' to get a prompt.',
'destination': 'Bitcoin address, contact or alias',
@ -702,6 +800,7 @@ param_descriptions = {
command_options = {
'password': ("-W", "Password"),
'new_password':(None, "New Password"),
'encrypt_file':(None, "Whether the file on disk should be encrypted with the provided password"),
'receiving': (None, "Show only receiving addresses"),
'change': (None, "Show only change addresses"),
'frozen': (None, "Show only frozen addresses"),
@ -717,6 +816,7 @@ command_options = {
'nbits': (None, "Number of bits of entropy"),
'segwit': (None, "Create segwit seed"),
'language': ("-L", "Default language for wordlist"),
'passphrase': (None, "Seed extension"),
'privkey': (None, "Private key. Set to '?' to get a prompt."),
'unsigned': ("-u", "Do not sign transaction"),
'rbf': (None, "Replace-by-fee transaction"),
@ -731,9 +831,12 @@ command_options = {
'paid': (None, "Show only paid requests."),
'show_addresses': (None, "Show input and output addresses"),
'show_fiat': (None, "Show fiat value of transactions"),
'show_fees': (None, "Show miner fees paid by transactions"),
'year': (None, "Show history for a given year"),
'fee_method': (None, "Fee estimation method to use"),
'fee_level': (None, "Float between 0.0 and 1.0, representing fee slider position")
'fee_level': (None, "Float between 0.0 and 1.0, representing fee slider position"),
'from_height': (None, "Only show transactions that confirmed after given block height"),
'to_height': (None, "Only show transactions that confirmed before given block height"),
}
@ -745,6 +848,8 @@ arg_types = {
'nbits': int,
'imax': int,
'year': int,
'from_height': int,
'to_height': int,
'tx': tx_from_str,
'pubkeys': json_loads,
'jsontx': json_loads,
@ -755,6 +860,7 @@ arg_types = {
'locktime': int,
'fee_method': str,
'fee_level': json_loads,
'encrypt_file': eval_bool,
}
config_variables = {
@ -826,10 +932,14 @@ def add_network_options(parser):
parser.add_argument("-1", "--oneserver", action="store_true", dest="oneserver", default=None, help="connect to one server only")
parser.add_argument("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)")
parser.add_argument("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
parser.add_argument("--noonion", action="store_true", dest="noonion", default=None, help="do not try to connect to onion servers")
parser.add_argument("--skipmerklecheck", action="store_true", dest="skipmerklecheck", default=False, help="Tolerate invalid merkle proofs from server")
def add_global_options(parser):
group = parser.add_argument_group('global options')
group.add_argument("-v", "--verbose", action="store_true", dest="verbose", default=False, help="Show debugging information")
# const is for when no argument is given to verbosity
# default is for when the flag is missing
group.add_argument("-v", dest="verbosity", help="Set verbosity filter", default='', const='*', nargs='?')
group.add_argument("-D", "--dir", dest="electrum_path", help="electrum directory")
group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory")
group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path")
@ -850,6 +960,7 @@ def get_parser():
parser_gui.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
parser_gui.add_argument("-m", action="store_true", dest="hide_gui", default=False, help="hide GUI on startup")
parser_gui.add_argument("-L", "--lang", dest="language", default=None, help="default language used in GUI")
parser_gui.add_argument("--daemon", action="store_true", dest="daemon", default=False, help="keep daemon running after GUI is closed")
add_network_options(parser_gui)
add_global_options(parser_gui)
# daemon
@ -863,12 +974,10 @@ def get_parser():
cmd = known_commands[cmdname]
p = subparsers.add_parser(cmdname, help=cmd.help, description=cmd.description)
add_global_options(p)
if cmdname == 'restore':
p.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
for optname, default in zip(cmd.options, cmd.defaults):
a, help = command_options[optname]
b = '--' + optname
action = "store_true" if type(default) is bool else 'store'
action = "store_true" if default is False else 'store'
args = (a, b) if a else (b,)
if action == 'store':
_type = arg_types.get(optname, str)

View File

@ -37,62 +37,114 @@ def read_json(filename, default):
return r
class BitcoinMainnet:
class AbstractNet:
@classmethod
def max_checkpoint(cls) -> int:
return max(0, len(cls.CHECKPOINTS) * 2016 - 1)
class BitcoinMainnet(AbstractNet):
TESTNET = False
WIF_PREFIX = 0x80
ADDRTYPE_P2PKH = 0
ADDRTYPE_P2SH = 5
SEGWIT_HRP = "bc"
GENESIS = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
WIF_PREFIX = 0xa3
ADDRTYPE_P2PKH = 35
ADDRTYPE_P2SH = 94
SEGWIT_HRP = "flo"
GENESIS = "09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea"
DEFAULT_PORTS = {'t': '50001', 's': '50002'}
DEFAULT_SERVERS = read_json('servers.json', {})
CHECKPOINTS = read_json('checkpoints.json', [])
XPRV_HEADERS = {
'standard': 0x0488ade4, # xprv
'standard': 0x01343c31, # xprv
'p2wpkh-p2sh': 0x049d7878, # yprv
'p2wsh-p2sh': 0x0295b005, # Yprv
'p2wpkh': 0x04b2430c, # zprv
'p2wsh': 0x02aa7a99, # Zprv
}
XPUB_HEADERS = {
'standard': 0x0488b21e, # xpub
'standard': 0x0134406b, # xpub
'p2wpkh-p2sh': 0x049d7cb2, # ypub
'p2wsh-p2sh': 0x0295b43f, # Ypub
'p2wpkh': 0x04b24746, # zpub
'p2wsh': 0x02aa7ed3, # Zpub
}
BIP44_COIN_TYPE = 0
BIP44_COIN_TYPE = 216
# FLO Network constants
fPowAllowMinDifficultyBlocks = False
fPowNoRetargeting = False
nRuleChangeActivationThreshold = 6048 # 75% of 8064
nMinerConfirmationWindow = 8064
# Difficulty adjustments
nPowTargetSpacing = 40 # 40s block time
# V1
nTargetTimespan_Version1 = 60 * 60
nInterval_Version1 = nTargetTimespan_Version1 / nPowTargetSpacing
nMaxAdjustUp_Version1 = 75
nMaxAdjustDown_Version1 = 300
nAveragingInterval_Version1 = nInterval_Version1
# V2
nHeight_Difficulty_Version2 = 208440
nInterval_Version2 = 15
nMaxAdjustDown_Version2 = 300
nMaxAdjustUp_Version2 = 75
nAveragingInterval_Version2 = nInterval_Version2
# V3
nHeight_Difficulty_Version3 = 426000
nInterval_Version3 = 1
nMaxAdjustDown_Version3 = 3
nMaxAdjustUp_Version3 = 2
nAveragingInterval_Version3 = 6
class BitcoinTestnet:
class BitcoinTestnet(AbstractNet):
TESTNET = True
WIF_PREFIX = 0xef
ADDRTYPE_P2PKH = 111
ADDRTYPE_P2SH = 196
SEGWIT_HRP = "tb"
GENESIS = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
ADDRTYPE_P2PKH = 115
ADDRTYPE_P2SH = 58
SEGWIT_HRP = "tflo"
GENESIS = "9b7bc86236c34b5e3a39367c036b7fe8807a966c22a7a1f0da2a198a27e03731"
DEFAULT_PORTS = {'t': '51001', 's': '51002'}
DEFAULT_SERVERS = read_json('servers_testnet.json', {})
CHECKPOINTS = read_json('checkpoints_testnet.json', [])
XPRV_HEADERS = {
'standard': 0x04358394, # tprv
'standard': 0x01343c23, # tprv
'p2wpkh-p2sh': 0x044a4e28, # uprv
'p2wsh-p2sh': 0x024285b5, # Uprv
'p2wpkh': 0x045f18bc, # vprv
'p2wsh': 0x02575048, # Vprv
}
XPUB_HEADERS = {
'standard': 0x043587cf, # tpub
'standard': 0x013440e2, # tpub
'p2wpkh-p2sh': 0x044a5262, # upub
'p2wsh-p2sh': 0x024289ef, # Upub
'p2wpkh': 0x045f1cf6, # vpub
'p2wsh': 0x02575483, # Vpub
}
BIP44_COIN_TYPE = 1
#Difficulty adjustments
nPowTargetSpacing = 40 # 40 block time
# V1
nTargetTimespan_Version1 = 60 * 60
nInterval_Version1 = nTargetTimespan_Version1 / nPowTargetSpacing;
nMaxAdjustUp_Version1 = 75
nMaxAdjustDown_Version1 = 300
nAveragingInterval_Version1 = nInterval_Version1
# V2
nHeight_Difficulty_Version2 = 50000
nInterval_Version2 = 15
nMaxAdjustDown_Version2 = 300
nMaxAdjustUp_Version2 = 75
nAveragingInterval_Version2 = nInterval_Version2
# V3
nHeight_Difficulty_Version3 = 60000
nInterval_Version3 = 1
nMaxAdjustDown_Version3 = 3
nMaxAdjustUp_Version3 = 2
nAveragingInterval_Version3 = 6
class BitcoinRegtest(BitcoinTestnet):

View File

@ -21,11 +21,9 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import re
import dns
from dns.exception import DNSException
import json
import traceback
import sys
from . import bitcoin
from . import dnssec
@ -67,8 +65,9 @@ class Contacts(dict):
def pop(self, key):
if key in self.keys():
dict.pop(self, key)
res = dict.pop(self, key)
self.save()
return res
def resolve(self, k):
if bitcoin.is_address(k):
@ -100,7 +99,7 @@ class Contacts(dict):
try:
records, validated = dnssec.query(url, dns.rdatatype.TXT)
except DNSException as e:
print_error('Error resolving openalias: ', str(e))
print_error(f'Error resolving openalias: {repr(e)}')
return None
prefix = 'btc'
for record in records:

216
electrum/crypto.py Normal file
View File

@ -0,0 +1,216 @@
# -*- coding: utf-8 -*-
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2018 The Electrum developers
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import base64
import os
import hashlib
import hmac
from typing import Union
import pyaes
from .util import assert_bytes, InvalidPassword, to_bytes, to_string, WalletFileException
from .i18n import _
try:
from Cryptodome.Cipher import AES
except:
AES = None
class InvalidPadding(Exception):
pass
def append_PKCS7_padding(data: bytes) -> bytes:
assert_bytes(data)
padlen = 16 - (len(data) % 16)
return data + bytes([padlen]) * padlen
def strip_PKCS7_padding(data: bytes) -> bytes:
assert_bytes(data)
if len(data) % 16 != 0 or len(data) == 0:
raise InvalidPadding("invalid length")
padlen = data[-1]
if not (0 < padlen <= 16):
raise InvalidPadding("invalid padding byte (out of range)")
for i in data[-padlen:]:
if i != padlen:
raise InvalidPadding("invalid padding byte (inconsistent)")
return data[0:-padlen]
def aes_encrypt_with_iv(key: bytes, iv: bytes, data: bytes) -> bytes:
assert_bytes(key, iv, data)
data = append_PKCS7_padding(data)
if AES:
e = AES.new(key, AES.MODE_CBC, iv).encrypt(data)
else:
aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)
aes = pyaes.Encrypter(aes_cbc, padding=pyaes.PADDING_NONE)
e = aes.feed(data) + aes.feed() # empty aes.feed() flushes buffer
return e
def aes_decrypt_with_iv(key: bytes, iv: bytes, data: bytes) -> bytes:
assert_bytes(key, iv, data)
if AES:
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.decrypt(data)
else:
aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)
aes = pyaes.Decrypter(aes_cbc, padding=pyaes.PADDING_NONE)
data = aes.feed(data) + aes.feed() # empty aes.feed() flushes buffer
try:
return strip_PKCS7_padding(data)
except InvalidPadding:
raise InvalidPassword()
def EncodeAES_base64(secret: bytes, msg: bytes) -> bytes:
"""Returns base64 encoded ciphertext."""
e = EncodeAES_bytes(secret, msg)
return base64.b64encode(e)
def EncodeAES_bytes(secret: bytes, msg: bytes) -> bytes:
assert_bytes(msg)
iv = bytes(os.urandom(16))
ct = aes_encrypt_with_iv(secret, iv, msg)
return iv + ct
def DecodeAES_base64(secret: bytes, ciphertext_b64: Union[bytes, str]) -> bytes:
ciphertext = bytes(base64.b64decode(ciphertext_b64))
return DecodeAES_bytes(secret, ciphertext)
def DecodeAES_bytes(secret: bytes, ciphertext: bytes) -> bytes:
assert_bytes(ciphertext)
iv, e = ciphertext[:16], ciphertext[16:]
s = aes_decrypt_with_iv(secret, iv, e)
return s
PW_HASH_VERSION_LATEST = 1
KNOWN_PW_HASH_VERSIONS = (1, 2, )
SUPPORTED_PW_HASH_VERSIONS = (1, )
assert PW_HASH_VERSION_LATEST in KNOWN_PW_HASH_VERSIONS
assert PW_HASH_VERSION_LATEST in SUPPORTED_PW_HASH_VERSIONS
class UnexpectedPasswordHashVersion(InvalidPassword, WalletFileException):
def __init__(self, version):
self.version = version
def __str__(self):
return "{unexpected}: {version}\n{instruction}".format(
unexpected=_("Unexpected password hash version"),
version=self.version,
instruction=_('You are most likely using an outdated version of Electrum. Please update.'))
class UnsupportedPasswordHashVersion(InvalidPassword, WalletFileException):
def __init__(self, version):
self.version = version
def __str__(self):
return "{unsupported}: {version}\n{instruction}".format(
unsupported=_("Unsupported password hash version"),
version=self.version,
instruction=f"To open this wallet, try 'git checkout password_v{self.version}'.\n"
"Alternatively, restore from seed.")
def _hash_password(password: Union[bytes, str], *, version: int) -> bytes:
pw = to_bytes(password, 'utf8')
if version not in SUPPORTED_PW_HASH_VERSIONS:
raise UnsupportedPasswordHashVersion(version)
if version == 1:
return sha256d(pw)
else:
assert version not in KNOWN_PW_HASH_VERSIONS
raise UnexpectedPasswordHashVersion(version)
def pw_encode(data: str, password: Union[bytes, str, None], *, version: int) -> str:
if not password:
return data
if version not in KNOWN_PW_HASH_VERSIONS:
raise UnexpectedPasswordHashVersion(version)
# derive key from password
secret = _hash_password(password, version=version)
# encrypt given data
ciphertext = EncodeAES_bytes(secret, to_bytes(data, "utf8"))
ciphertext_b64 = base64.b64encode(ciphertext)
return ciphertext_b64.decode('utf8')
def pw_decode(data: str, password: Union[bytes, str, None], *, version: int) -> str:
if password is None:
return data
if version not in KNOWN_PW_HASH_VERSIONS:
raise UnexpectedPasswordHashVersion(version)
data_bytes = bytes(base64.b64decode(data))
# derive key from password
secret = _hash_password(password, version=version)
# decrypt given data
try:
d = to_string(DecodeAES_bytes(secret, data_bytes), "utf8")
except Exception as e:
raise InvalidPassword() from e
return d
def sha256(x: Union[bytes, str]) -> bytes:
x = to_bytes(x, 'utf8')
return bytes(hashlib.sha256(x).digest())
def sha256d(x: Union[bytes, str]) -> bytes:
x = to_bytes(x, 'utf8')
out = bytes(sha256(sha256(x)))
return out
def hash_160(x: bytes) -> bytes:
try:
md = hashlib.new('ripemd160')
md.update(sha256(x))
return md.digest()
except BaseException:
from . import ripemd
md = ripemd.new(sha256(x))
return md.digest()
def hmac_oneshot(key: bytes, msg: bytes, digest) -> bytes:
if hasattr(hmac, 'digest'):
# requires python 3.7+; faster
return hmac.digest(key, msg, digest)
else:
return hmac.new(key, msg, digest).digest()

44
electrum/currencies.json Normal file
View File

@ -0,0 +1,44 @@
{
"CoinMarketcap": [
"AED",
"ALL",
"ARS",
"AUD",
"BHD",
"BOB",
"BRL",
"KHR",
"CAD",
"CLP",
"CNY",
"COP",
"CUP",
"CZK",
"EGP",
"EUR",
"HKD",
"HUF",
"ISK",
"INR",
"IDR",
"IQD",
"ILS",
"JPY",
"LBP",
"MYR",
"MXN",
"NPR",
"NZD",
"NGN",
"NOK",
"GBP",
"QAR",
"RUB",
"SGD",
"SEK",
"CHF",
"THB",
"USD",
"VND"
]
}

View File

@ -22,29 +22,31 @@
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import asyncio
import ast
import os
import time
import traceback
import sys
import threading
from typing import Dict, Optional, Tuple
# from jsonrpc import JSONRPCResponseManager
import jsonrpclib
from .jsonrpc import VerifyingJSONRPCServer
from .jsonrpc import VerifyingJSONRPCServer
from .version import ELECTRUM_VERSION
from .network import Network
from .util import json_decode, DaemonThread
from .util import print_error, to_string
from .wallet import Wallet
from .util import (json_decode, DaemonThread, print_error, to_string,
create_and_start_event_loop, profiler, standardize_path)
from .wallet import Wallet, Abstract_Wallet
from .storage import WalletStorage
from .commands import known_commands, Commands
from .simple_config import SimpleConfig
from .exchange_rate import FxThread
from .plugins import run_hook
from .plugin import run_hook
def get_lockfile(config):
def get_lockfile(config: SimpleConfig):
return os.path.join(config.path, 'daemon')
@ -52,7 +54,7 @@ def remove_lockfile(lockfile):
os.unlink(lockfile)
def get_fd_or_server(config):
def get_fd_or_server(config: SimpleConfig):
'''Tries to create the lockfile, using O_EXCL to
prevent races. If it succeeds it returns the FD.
Otherwise try and connect to the server specified in the lockfile.
@ -71,7 +73,7 @@ def get_fd_or_server(config):
remove_lockfile(lockfile)
def get_server(config):
def get_server(config: SimpleConfig) -> Optional[jsonrpclib.Server]:
lockfile = get_lockfile(config)
while True:
create_time = None
@ -90,14 +92,14 @@ def get_server(config):
server.ping()
return server
except Exception as e:
print_error("[get_server]", e)
print_error(f"failed to connect to JSON-RPC server: {e}")
if not create_time or create_time < time.time() - 1.0:
return None
# Sleep a bit and try again; it might have just been started
time.sleep(1.0)
def get_rpc_credentials(config):
def get_rpc_credentials(config: SimpleConfig) -> Tuple[str, str]:
rpc_user = config.get('rpcuser', None)
rpc_password = config.get('rpcpassword', None)
if rpc_user is None or rpc_password is None:
@ -119,26 +121,33 @@ def get_rpc_credentials(config):
class Daemon(DaemonThread):
def __init__(self, config, fd, is_gui):
@profiler
def __init__(self, config: SimpleConfig, fd=None, *, listen_jsonrpc=True):
DaemonThread.__init__(self)
self.config = config
if fd is None and listen_jsonrpc:
fd, server = get_fd_or_server(config)
if fd is None: raise Exception('failed to lock daemon; already running?')
self.asyncio_loop, self._stop_loop, self._loop_thread = create_and_start_event_loop()
if config.get('offline'):
self.network = None
else:
self.network = Network(config)
self.network.start()
self.network._loop_thread = self._loop_thread
self.fx = FxThread(config, self.network)
if self.network:
self.network.add_jobs([self.fx])
self.network.start([self.fx.run])
self.gui = None
self.wallets = {}
self.wallets = {} # type: Dict[str, Abstract_Wallet]
# Setup JSONRPC server
self.init_server(config, fd, is_gui)
self.server = None
if listen_jsonrpc:
self.init_server(config, fd)
self.start()
def init_server(self, config, fd, is_gui):
def init_server(self, config: SimpleConfig, fd):
host = config.get('rpchost', '127.0.0.1')
port = config.get('rpcport', 0)
rpc_user, rpc_password = get_rpc_credentials(config)
try:
server = VerifyingJSONRPCServer((host, port), logRequests=False,
@ -153,19 +162,18 @@ class Daemon(DaemonThread):
self.server = server
server.timeout = 0.1
server.register_function(self.ping, 'ping')
if is_gui:
server.register_function(self.run_gui, 'gui')
else:
server.register_function(self.run_daemon, 'daemon')
self.cmd_runner = Commands(self.config, None, self.network)
for cmdname in known_commands:
server.register_function(getattr(self.cmd_runner, cmdname), cmdname)
server.register_function(self.run_cmdline, 'run_cmdline')
server.register_function(self.run_gui, 'gui')
server.register_function(self.run_daemon, 'daemon')
self.cmd_runner = Commands(self.config, None, self.network)
for cmdname in known_commands:
server.register_function(getattr(self.cmd_runner, cmdname), cmdname)
server.register_function(self.run_cmdline, 'run_cmdline')
def ping(self):
return True
def run_daemon(self, config_options):
asyncio.set_event_loop(self.asyncio_loop)
config = SimpleConfig(config_options)
sub = config.get('subcommand')
assert sub in [None, 'start', 'stop', 'status', 'load_wallet', 'close_wallet']
@ -187,18 +195,18 @@ class Daemon(DaemonThread):
response = False
elif sub == 'status':
if self.network:
p = self.network.get_parameters()
net_params = self.network.get_parameters()
current_wallet = self.cmd_runner.wallet
current_wallet_path = current_wallet.storage.path \
if current_wallet else None
response = {
'path': self.network.config.path,
'server': p[0],
'server': net_params.host,
'blockchain_height': self.network.get_local_height(),
'server_height': self.network.get_server_height(),
'spv_nodes': len(self.network.get_interfaces()),
'connected': self.network.is_connected(),
'auto_connect': p[4],
'auto_connect': net_params.auto_connect,
'version': ELECTRUM_VERSION,
'wallets': {k: w.is_up_to_date()
for k, w in self.wallets.items()},
@ -215,18 +223,19 @@ class Daemon(DaemonThread):
def run_gui(self, config_options):
config = SimpleConfig(config_options)
if self.gui:
#if hasattr(self.gui, 'new_window'):
# path = config.get_wallet_path()
# self.gui.new_window(path, config.get('url'))
# response = "ok"
#else:
# response = "error: current GUI does not support multiple windows"
response = "error: Electrum GUI already running"
if hasattr(self.gui, 'new_window'):
config.open_last_wallet()
path = config.get_wallet_path()
self.gui.new_window(path, config.get('url'))
response = "ok"
else:
response = "error: current GUI does not support multiple windows"
else:
response = "Error: Electrum is running in daemon mode. Please stop the daemon first."
return response
def load_wallet(self, path, password):
def load_wallet(self, path, password) -> Optional[Abstract_Wallet]:
path = standardize_path(path)
# wizard will be launched if we return
if path in self.wallets:
wallet = self.wallets[path]
@ -243,22 +252,31 @@ class Daemon(DaemonThread):
if storage.get_action():
return
wallet = Wallet(storage)
wallet.start_threads(self.network)
wallet.start_network(self.network)
self.wallets[path] = wallet
return wallet
def add_wallet(self, wallet):
def add_wallet(self, wallet: Abstract_Wallet):
path = wallet.storage.path
self.wallets[path] = wallet
def get_wallet(self, path):
return self.wallets.get(path)
def delete_wallet(self, path):
self.stop_wallet(path)
if os.path.exists(path):
os.unlink(path)
return True
return False
def stop_wallet(self, path):
wallet = self.wallets.pop(path)
wallet = self.wallets.pop(path, None)
if not wallet: return
wallet.stop_threads()
def run_cmdline(self, config_options):
asyncio.set_event_loop(self.asyncio_loop)
password = config_options.get('password')
new_password = config_options.get('new_password')
config = SimpleConfig(config_options)
@ -284,30 +302,39 @@ class Daemon(DaemonThread):
kwargs[x] = (config_options.get(x) if x in ['password', 'new_password'] else config.get(x))
cmd_runner = Commands(config, wallet, self.network)
func = getattr(cmd_runner, cmd.name)
result = func(*args, **kwargs)
try:
result = func(*args, **kwargs)
except TypeError as e:
raise Exception("Wrapping TypeError to prevent JSONRPC-Pelix from hiding traceback") from e
return result
def run(self):
while self.is_running():
self.server.handle_request() if self.server else time.sleep(0.1)
# stop network/wallets
for k, wallet in self.wallets.items():
wallet.stop_threads()
if self.network:
self.print_error("shutting down network")
self.network.stop()
self.network.join()
# stop event loop
self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1)
self._loop_thread.join(timeout=1)
self.on_stop()
def stop(self):
if self.gui:
self.gui.stop()
self.print_error("stopping, removing lockfile")
remove_lockfile(get_lockfile(self.config))
DaemonThread.stop(self)
def init_gui(self, config, plugins):
threading.current_thread().setName('GUI')
gui_name = config.get('gui', 'qt')
if gui_name in ['lite', 'classic']:
gui_name = 'qt'
gui = __import__('electrum_gui.' + gui_name, fromlist=['electrum_gui'])
gui = __import__('electrum.gui.' + gui_name, fromlist=['electrum'])
self.gui = gui.ElectrumGui(config, self, plugins)
try:
self.gui.main()

View File

@ -110,11 +110,9 @@ def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
if rrsig.algorithm == ECDSAP256SHA256:
curve = ecdsa.curves.NIST256p
key_len = 32
digest_len = 32
elif rrsig.algorithm == ECDSAP384SHA384:
curve = ecdsa.curves.NIST384p
key_len = 48
digest_len = 48
else:
# shouldn't happen
raise ValidationFailure('unknown ECDSA curve')
@ -141,7 +139,7 @@ def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
rrnamebuf = rrname.to_digestable(origin)
rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
rrsig.original_ttl)
rrlist = sorted(rdataset);
rrlist = sorted(rdataset)
for rr in rrlist:
hash.update(rrnamebuf)
hash.update(rrfixed)

View File

@ -24,10 +24,8 @@
# SOFTWARE.
import base64
import hmac
import hashlib
from typing import Union
from typing import Union, Tuple
import ecdsa
from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1
@ -36,8 +34,10 @@ from ecdsa.ellipticcurve import Point
from ecdsa.util import string_to_number, number_to_string
from .util import bfh, bh2u, assert_bytes, print_error, to_bytes, InvalidPassword, profiler
from .crypto import (Hash, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot)
from .crypto import (sha256d, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot)
from .ecc_fast import do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1
from . import msqr
from . import constants
do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()
@ -53,31 +53,31 @@ def point_at_infinity():
return ECPubkey(None)
def sig_string_from_der_sig(der_sig, order=CURVE_ORDER):
def sig_string_from_der_sig(der_sig: bytes, order=CURVE_ORDER) -> bytes:
r, s = ecdsa.util.sigdecode_der(der_sig, order)
return ecdsa.util.sigencode_string(r, s, order)
def der_sig_from_sig_string(sig_string, order=CURVE_ORDER):
def der_sig_from_sig_string(sig_string: bytes, order=CURVE_ORDER) -> bytes:
r, s = ecdsa.util.sigdecode_string(sig_string, order)
return ecdsa.util.sigencode_der_canonize(r, s, order)
def der_sig_from_r_and_s(r, s, order=CURVE_ORDER):
def der_sig_from_r_and_s(r: int, s: int, order=CURVE_ORDER) -> bytes:
return ecdsa.util.sigencode_der_canonize(r, s, order)
def get_r_and_s_from_der_sig(der_sig, order=CURVE_ORDER):
def get_r_and_s_from_der_sig(der_sig: bytes, order=CURVE_ORDER) -> Tuple[int, int]:
r, s = ecdsa.util.sigdecode_der(der_sig, order)
return r, s
def get_r_and_s_from_sig_string(sig_string, order=CURVE_ORDER):
def get_r_and_s_from_sig_string(sig_string: bytes, order=CURVE_ORDER) -> Tuple[int, int]:
r, s = ecdsa.util.sigdecode_string(sig_string, order)
return r, s
def sig_string_from_r_and_s(r, s, order=CURVE_ORDER):
def sig_string_from_r_and_s(r: int, s: int, order=CURVE_ORDER) -> bytes:
return ecdsa.util.sigencode_string_canonize(r, s, order)
@ -94,23 +94,22 @@ def point_to_ser(P, compressed=True) -> bytes:
return bfh('04'+('%064x' % x)+('%064x' % y))
def get_y_coord_from_x(x, odd=True):
def get_y_coord_from_x(x: int, odd: bool=True) -> int:
curve = curve_secp256k1
_p = curve.p()
_a = curve.a()
_b = curve.b()
for offset in range(128):
Mx = x + offset
My2 = pow(Mx, 3, _p) + _a * pow(Mx, 2, _p) + _b % _p
My = pow(My2, (_p + 1) // 4, _p)
if curve.contains_point(Mx, My):
if odd == bool(My & 1):
return My
return _p - My
raise Exception('ECC_YfromX: No Y found')
x = x % _p
y2 = (pow(x, 3, _p) + _a * x + _b) % _p
y = msqr.modular_sqrt(y2, _p)
if curve.contains_point(x, y):
if odd == bool(y & 1):
return y
return _p - y
raise InvalidECPointException()
def ser_to_point(ser: bytes) -> (int, int):
def ser_to_point(ser: bytes) -> Tuple[int, int]:
if ser[0] not in (0x02, 0x03, 0x04):
raise ValueError('Unexpected first byte: {}'.format(ser[0]))
if ser[0] == 0x04:
@ -227,7 +226,7 @@ class ECPubkey(object):
def get_public_key_hex(self, compressed=True):
return bh2u(self.get_public_key_bytes(compressed))
def point(self) -> (int, int):
def point(self) -> Tuple[int, int]:
return self._pubkey.point.x(), self._pubkey.point.y()
def __mul__(self, other: int):
@ -254,7 +253,7 @@ class ECPubkey(object):
def verify_message_for_address(self, sig65: bytes, message: bytes) -> None:
assert_bytes(message)
h = Hash(msg_magic(message))
h = sha256d(msg_magic(message))
public_key, compressed = self.from_signature65(sig65, h)
# check public key
if public_key != self:
@ -296,6 +295,14 @@ class ECPubkey(object):
def is_at_infinity(self):
return self == point_at_infinity()
@classmethod
def is_pubkey_bytes(cls, b: bytes):
try:
ECPubkey(b)
return True
except:
return False
def msg_magic(message: bytes) -> bytes:
from .bitcoin import var_int
@ -303,16 +310,17 @@ def msg_magic(message: bytes) -> bytes:
return b"\x18Bitcoin Signed Message:\n" + length + message
def verify_message_with_address(address: str, sig65: bytes, message: bytes):
def verify_message_with_address(address: str, sig65: bytes, message: bytes, *, net=None):
from .bitcoin import pubkey_to_address
assert_bytes(sig65, message)
if net is None: net = constants.net
try:
h = Hash(msg_magic(message))
h = sha256d(msg_magic(message))
public_key, compressed = ECPubkey.from_signature65(sig65, h)
# check public key using the address
pubkey_hex = public_key.get_public_key_hex(compressed)
for txin_type in ['p2pkh','p2wpkh','p2wpkh-p2sh']:
addr = pubkey_to_address(txin_type, pubkey_hex)
addr = pubkey_to_address(txin_type, pubkey_hex, net=net)
if address == addr:
break
else:
@ -321,7 +329,7 @@ def verify_message_with_address(address: str, sig65: bytes, message: bytes):
public_key.verify_message_hash(sig65[1:], h)
return True
except Exception as e:
print_error("Verification error: {0}".format(e))
print_error(f"Verification error: {repr(e)}")
return False
@ -397,14 +405,14 @@ class ECPrivkey(ECPubkey):
raise Exception("error: cannot sign message. no recid fits..")
message = to_bytes(message, 'utf8')
msg_hash = Hash(msg_magic(message))
msg_hash = sha256d(msg_magic(message))
sig_string = self.sign(msg_hash,
sigencode=sig_string_from_r_and_s,
sigdecode=get_r_and_s_from_sig_string)
sig65, recid = bruteforce_recid(sig_string)
return sig65
def decrypt_message(self, encrypted, magic=b'BIE1'):
def decrypt_message(self, encrypted: Tuple[str, bytes], magic: bytes=b'BIE1') -> bytes:
encrypted = base64.b64decode(encrypted)
if len(encrypted) < 85:
raise Exception('invalid ciphertext: length')
@ -429,6 +437,6 @@ class ECPrivkey(ECPubkey):
return aes_decrypt_with_iv(key_e, iv, ciphertext)
def construct_sig65(sig_string, recid, is_compressed):
def construct_sig65(sig_string: bytes, recid: int, is_compressed: bool) -> bytes:
comp = 4 if is_compressed else 0
return bytes([27 + recid + comp]) + sig_string

View File

@ -72,6 +72,9 @@ def load_library():
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
@ -181,7 +184,9 @@ def _prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
def do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
if not _libsecp256k1:
print_error('[ecc] warning: libsecp256k1 library not available, falling back to python-ecdsa')
# FIXME print_error will always print as 'verbosity' is not yet initialised
print_error('[ecc] info: libsecp256k1 library not available, falling back to python-ecdsa. '
'This means signing operations will be slower.')
return
if not _patched_functions.prepared_to_patch:
raise Exception("can't patch python-ecdsa without preparations")

1
electrum/electrum Symbolic link
View File

@ -0,0 +1 @@
../run_electrum

View File

@ -1,18 +1,23 @@
import asyncio
from datetime import datetime
import inspect
import requests
import sys
import os
import json
from threading import Thread
import time
import csv
import decimal
from decimal import Decimal
import concurrent.futures
import traceback
from typing import Sequence
from .bitcoin import COIN
from .i18n import _
from .util import PrintError, ThreadJob, make_dir
from .util import (PrintError, ThreadJob, make_dir, log_exceptions,
make_aiohttp_session, resource_path)
from .network import Network
from .simple_config import SimpleConfig
# See https://en.wikipedia.org/wiki/ISO_4217
@ -32,34 +37,42 @@ class ExchangeBase(PrintError):
self.on_quotes = on_quotes
self.on_history = on_history
def get_json(self, site, get_string):
async def get_raw(self, site, get_string):
# APIs must have https
url = ''.join(['https://', site, get_string])
response = requests.request('GET', url, headers={'User-Agent' : 'Electrum'}, timeout=10)
return response.json()
async with make_aiohttp_session(Network.get_instance().proxy) as session:
async with session.get(url) as response:
return await response.text()
def get_csv(self, site, get_string):
async def get_json(self, site, get_string):
# APIs must have https
url = ''.join(['https://', site, get_string])
response = requests.request('GET', url, headers={'User-Agent' : 'Electrum'})
reader = csv.DictReader(response.content.decode().split('\n'))
async with make_aiohttp_session(Network.get_instance().proxy) as session:
async with session.get(url) as response:
# set content_type to None to disable checking MIME type
return await response.json(content_type=None)
async def get_csv(self, site, get_string):
raw = await self.get_raw(site, get_string)
reader = csv.DictReader(raw.split('\n'))
return list(reader)
def name(self):
return self.__class__.__name__
def update_safe(self, ccy):
@log_exceptions
async def update_safe(self, ccy):
try:
self.print_error("getting fx quotes for", ccy)
self.quotes = self.get_rates(ccy)
self.quotes = await self.get_rates(ccy)
self.print_error("received fx quotes")
except BaseException as e:
self.print_error("failed fx quotes:", e)
self.print_error("failed fx quotes:", repr(e))
self.quotes = {}
self.on_quotes()
def update(self, ccy):
t = Thread(target=self.update_safe, args=(ccy,))
t.setDaemon(True)
t.start()
asyncio.get_event_loop().create_task(self.update_safe(ccy))
def read_historical_rates(self, ccy, cache_dir):
filename = os.path.join(cache_dir, self.name() + '_'+ ccy)
@ -78,13 +91,15 @@ class ExchangeBase(PrintError):
self.on_history()
return h
def get_historical_rates_safe(self, ccy, cache_dir):
@log_exceptions
async def get_historical_rates_safe(self, ccy, cache_dir):
try:
self.print_error("requesting fx history for", ccy)
h = self.request_history(ccy)
h = await self.request_history(ccy)
self.print_error("received fx history for", ccy)
except BaseException as e:
self.print_error("failed fx history:", e)
#traceback.print_exc()
return
filename = os.path.join(cache_dir, self.name() + '_' + ccy)
with open(filename, 'w', encoding='utf-8') as f:
@ -100,9 +115,7 @@ class ExchangeBase(PrintError):
if h is None:
h = self.read_historical_rates(ccy, cache_dir)
if h is None or h['timestamp'] < time.time() - 24*3600:
t = Thread(target=self.get_historical_rates_safe, args=(ccy, cache_dir))
t.setDaemon(True)
t.start()
asyncio.get_event_loop().create_task(self.get_historical_rates_safe(ccy, cache_dir))
def history_ccys(self):
return []
@ -116,8 +129,8 @@ class ExchangeBase(PrintError):
class BitcoinAverage(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('apiv2.bitcoinaverage.com', '/indices/global/ticker/short')
async def get_rates(self, ccy):
json = await self.get_json('apiv2.bitcoinaverage.com', '/indices/global/ticker/short')
return dict([(r.replace("BTC", ""), Decimal(json[r]['last']))
for r in json if r != 'timestamp'])
@ -126,8 +139,8 @@ class BitcoinAverage(ExchangeBase):
'MXN', 'NOK', 'NZD', 'PLN', 'RON', 'RUB', 'SEK', 'SGD', 'USD',
'ZAR']
def request_history(self, ccy):
history = self.get_csv('apiv2.bitcoinaverage.com',
async def request_history(self, ccy):
history = await self.get_csv('apiv2.bitcoinaverage.com',
"/indices/global/history/BTC%s?period=alltime&format=csv" % ccy)
return dict([(h['DateTime'][:10], h['Average'])
for h in history])
@ -135,8 +148,8 @@ class BitcoinAverage(ExchangeBase):
class Bitcointoyou(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('bitcointoyou.com', "/API/ticker.aspx")
async def get_rates(self, ccy):
json = await self.get_json('bitcointoyou.com', "/API/ticker.aspx")
return {'BRL': Decimal(json['ticker']['last'])}
def history_ccys(self):
@ -145,8 +158,8 @@ class Bitcointoyou(ExchangeBase):
class BitcoinVenezuela(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.bitcoinvenezuela.com', '/')
async def get_rates(self, ccy):
json = await self.get_json('api.bitcoinvenezuela.com', '/')
rates = [(r, json['BTC'][r]) for r in json['BTC']
if json['BTC'][r] is not None] # Giving NULL for LTC
return dict(rates)
@ -154,99 +167,99 @@ class BitcoinVenezuela(ExchangeBase):
def history_ccys(self):
return ['ARS', 'EUR', 'USD', 'VEF']
def request_history(self, ccy):
return self.get_json('api.bitcoinvenezuela.com',
"/historical/index.php?coin=BTC")[ccy +'_BTC']
async def request_history(self, ccy):
json = await self.get_json('api.bitcoinvenezuela.com',
"/historical/index.php?coin=BTC")
return json[ccy +'_BTC']
class Bitbank(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('public.bitbank.cc', '/btc_jpy/ticker')
async def get_rates(self, ccy):
json = await self.get_json('public.bitbank.cc', '/btc_jpy/ticker')
return {'JPY': Decimal(json['data']['last'])}
class BitFlyer(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('bitflyer.jp', '/api/echo/price')
async def get_rates(self, ccy):
json = await self.get_json('bitflyer.jp', '/api/echo/price')
return {'JPY': Decimal(json['mid'])}
class Bitmarket(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('www.bitmarket.pl', '/json/BTCPLN/ticker.json')
async def get_rates(self, ccy):
json = await self.get_json('www.bitmarket.pl', '/json/BTCPLN/ticker.json')
return {'PLN': Decimal(json['last'])}
class BitPay(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('bitpay.com', '/api/rates')
async def get_rates(self, ccy):
json = await self.get_json('bitpay.com', '/api/rates')
return dict([(r['code'], Decimal(r['rate'])) for r in json])
class Bitso(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.bitso.com', '/v2/ticker')
async def get_rates(self, ccy):
json = await self.get_json('api.bitso.com', '/v2/ticker')
return {'MXN': Decimal(json['last'])}
class BitStamp(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('www.bitstamp.net', '/api/ticker/')
async def get_rates(self, ccy):
json = await self.get_json('www.bitstamp.net', '/api/ticker/')
return {'USD': Decimal(json['last'])}
class Bitvalor(ExchangeBase):
def get_rates(self,ccy):
json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
async def get_rates(self,ccy):
json = await self.get_json('api.bitvalor.com', '/v1/ticker.json')
return {'BRL': Decimal(json['ticker_1h']['total']['last'])}
class BlockchainInfo(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('blockchain.info', '/ticker')
async def get_rates(self, ccy):
json = await self.get_json('blockchain.info', '/ticker')
return dict([(r, Decimal(json[r]['15m'])) for r in json])
class BTCChina(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('data.btcchina.com', '/data/ticker')
async def get_rates(self, ccy):
json = await self.get_json('data.btcchina.com', '/data/ticker')
return {'CNY': Decimal(json['ticker']['last'])}
class BTCParalelo(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('btcparalelo.com', '/api/price')
async def get_rates(self, ccy):
json = await self.get_json('btcparalelo.com', '/api/price')
return {'VEF': Decimal(json['price'])}
class Coinbase(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('coinbase.com',
'/api/v1/currencies/exchange_rates')
return dict([(r[7:].upper(), Decimal(json[r]))
for r in json if r.startswith('btc_to_')])
async def get_rates(self, ccy):
json = await self.get_json('api.coinbase.com',
'/v2/exchange-rates?currency=BTC')
return {ccy: Decimal(rate) for (ccy, rate) in json["data"]["rates"].items()}
class CoinDesk(ExchangeBase):
def get_currencies(self):
dicts = self.get_json('api.coindesk.com',
async def get_currencies(self):
dicts = await self.get_json('api.coindesk.com',
'/v1/bpi/supported-currencies.json')
return [d['currency'] for d in dicts]
def get_rates(self, ccy):
json = self.get_json('api.coindesk.com',
async def get_rates(self, ccy):
json = await self.get_json('api.coindesk.com',
'/v1/bpi/currentprice/%s.json' % ccy)
result = {ccy: Decimal(json['bpi'][ccy]['rate_float'])}
return result
@ -257,35 +270,43 @@ class CoinDesk(ExchangeBase):
def history_ccys(self):
return self.history_starts().keys()
def request_history(self, ccy):
async def request_history(self, ccy):
start = self.history_starts()[ccy]
end = datetime.today().strftime('%Y-%m-%d')
# Note ?currency and ?index don't work as documented. Sigh.
query = ('/v1/bpi/historical/close.json?start=%s&end=%s'
% (start, end))
json = self.get_json('api.coindesk.com', query)
json = await self.get_json('api.coindesk.com', query)
return json['bpi']
class CoinMarketcap(ExchangeBase):
async def get_rates(self, ccy):
json = await self.get_json('pro-api.coinmarketcap.com','/v1/cryptocurrency/quotes/latest?symbol=FLO&CMC_PRO_API_KEY=194ee8a1-5a58-4f3e-ba07-a1d6bc633210&convert=%s' % ccy)
result = {ccy: Decimal(json['data']['FLO']['quote'][ccy]['price'])}
return result
class Coinsecure(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.coinsecure.in', '/v0/noauth/newticker')
async def get_rates(self, ccy):
json = await self.get_json('api.coinsecure.in', '/v0/noauth/newticker')
return {'INR': Decimal(json['lastprice'] / 100.0 )}
class Foxbit(ExchangeBase):
def get_rates(self,ccy):
json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
async def get_rates(self,ccy):
json = await self.get_json('api.bitvalor.com', '/v1/ticker.json')
return {'BRL': Decimal(json['ticker_1h']['exchanges']['FOX']['last'])}
class itBit(ExchangeBase):
def get_rates(self, ccy):
async def get_rates(self, ccy):
ccys = ['USD', 'EUR', 'SGD']
json = self.get_json('api.itbit.com', '/v1/markets/XBT%s/ticker' % ccy)
json = await self.get_json('api.itbit.com', '/v1/markets/XBT%s/ticker' % ccy)
result = dict.fromkeys(ccys)
if ccy in ccys:
result[ccy] = Decimal(json['lastPrice'])
@ -294,10 +315,10 @@ class itBit(ExchangeBase):
class Kraken(ExchangeBase):
def get_rates(self, ccy):
async def get_rates(self, ccy):
ccys = ['EUR', 'USD', 'CAD', 'GBP', 'JPY']
pairs = ['XBT%s' % c for c in ccys]
json = self.get_json('api.kraken.com',
json = await self.get_json('api.kraken.com',
'/0/public/Ticker?pair=%s' % ','.join(pairs))
return dict((k[-3:], Decimal(float(v['c'][0])))
for k, v in json['result'].items())
@ -305,45 +326,45 @@ class Kraken(ExchangeBase):
class LocalBitcoins(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('localbitcoins.com',
async def get_rates(self, ccy):
json = await self.get_json('localbitcoins.com',
'/bitcoinaverage/ticker-all-currencies/')
return dict([(r, Decimal(json[r]['rates']['last'])) for r in json])
class MercadoBitcoin(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
async def get_rates(self, ccy):
json = await self.get_json('api.bitvalor.com', '/v1/ticker.json')
return {'BRL': Decimal(json['ticker_1h']['exchanges']['MBT']['last'])}
class NegocieCoins(ExchangeBase):
def get_rates(self,ccy):
json = self.get_json('api.bitvalor.com', '/v1/ticker.json')
async def get_rates(self,ccy):
json = await self.get_json('api.bitvalor.com', '/v1/ticker.json')
return {'BRL': Decimal(json['ticker_1h']['exchanges']['NEG']['last'])}
class TheRockTrading(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.therocktrading.com',
async def get_rates(self, ccy):
json = await self.get_json('api.therocktrading.com',
'/v1/funds/BTCEUR/ticker')
return {'EUR': Decimal(json['last'])}
class Unocoin(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('www.unocoin.com', 'trade?buy')
async def get_rates(self, ccy):
json = await self.get_json('www.unocoin.com', 'trade?buy')
return {'INR': Decimal(json)}
class WEX(ExchangeBase):
def get_rates(self, ccy):
json_eur = self.get_json('wex.nz', '/api/3/ticker/btc_eur')
json_rub = self.get_json('wex.nz', '/api/3/ticker/btc_rur')
json_usd = self.get_json('wex.nz', '/api/3/ticker/btc_usd')
async def get_rates(self, ccy):
json_eur = await self.get_json('wex.nz', '/api/3/ticker/btc_eur')
json_rub = await self.get_json('wex.nz', '/api/3/ticker/btc_rur')
json_usd = await self.get_json('wex.nz', '/api/3/ticker/btc_usd')
return {'EUR': Decimal(json_eur['btc_eur']['last']),
'RUB': Decimal(json_rub['btc_rur']['last']),
'USD': Decimal(json_usd['btc_usd']['last'])}
@ -351,15 +372,15 @@ class WEX(ExchangeBase):
class Winkdex(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('winkdex.com', '/api/v0/price')
async def get_rates(self, ccy):
json = await self.get_json('winkdex.com', '/api/v0/price')
return {'USD': Decimal(json['price'] / 100.0)}
def history_ccys(self):
return ['USD']
def request_history(self, ccy):
json = self.get_json('winkdex.com',
async def request_history(self, ccy):
json = await self.get_json('winkdex.com',
"/api/v0/series?start_time=1342915200")
history = json['series'][0]['results']
return dict([(h['timestamp'][:10], h['price'] / 100.0)
@ -367,8 +388,8 @@ class Winkdex(ExchangeBase):
class Zaif(ExchangeBase):
def get_rates(self, ccy):
json = self.get_json('api.zaif.jp', '/api/1/last_price/btc_jpy')
async def get_rates(self, ccy):
json = await self.get_json('api.zaif.jp', '/api/1/last_price/btc_jpy')
return {'JPY': Decimal(json['last_price'])}
@ -381,8 +402,7 @@ def dictinvert(d):
return inv
def get_exchanges_and_currencies():
import os, json
path = os.path.join(os.path.dirname(__file__), 'currencies.json')
path = resource_path('currencies.json')
try:
with open(path, 'r', encoding='utf-8') as f:
return json.loads(f.read())
@ -423,48 +443,67 @@ def get_exchanges_by_ccy(history=True):
class FxThread(ThreadJob):
def __init__(self, config, network):
def __init__(self, config: SimpleConfig, network: Network):
self.config = config
self.network = network
if self.network:
self.network.register_callback(self.set_proxy, ['proxy_set'])
self.ccy = self.get_currency()
self.history_used_spot = False
self.ccy_combo = None
self.hist_checkbox = None
self.cache_dir = os.path.join(config.path, 'cache')
self._trigger = asyncio.Event()
self._trigger.set()
self.set_exchange(self.config_exchange())
make_dir(self.cache_dir)
def get_currencies(self, h):
d = get_exchanges_by_ccy(h)
def set_proxy(self, trigger_name, *args):
self._trigger.set()
@staticmethod
def get_currencies(history: bool) -> Sequence[str]:
d = get_exchanges_by_ccy(history)
return sorted(d.keys())
def get_exchanges_by_ccy(self, ccy, h):
d = get_exchanges_by_ccy(h)
@staticmethod
def get_exchanges_by_ccy(ccy: str, history: bool) -> Sequence[str]:
d = get_exchanges_by_ccy(history)
return d.get(ccy, [])
@staticmethod
def remove_thousands_separator(text):
return text.replace(',', '') # FIXME use THOUSAND_SEPARATOR in util
def ccy_amount_str(self, amount, commas):
prec = CCY_PRECISIONS.get(self.ccy, 2)
fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec))
fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec)) # FIXME use util.THOUSAND_SEPARATOR and util.DECIMAL_POINT
try:
rounded_amount = round(amount, prec)
except decimal.InvalidOperation:
rounded_amount = amount
return fmt_str.format(rounded_amount)
def run(self):
# This runs from the plugins thread which catches exceptions
if self.is_enabled():
if self.timeout ==0 and self.show_history():
self.exchange.get_historical_rates(self.ccy, self.cache_dir)
if self.timeout <= time.time():
self.timeout = time.time() + 150
async def run(self):
while True:
try:
await asyncio.wait_for(self._trigger.wait(), 150)
except concurrent.futures.TimeoutError:
pass
else:
self._trigger.clear()
if self.is_enabled():
if self.show_history():
self.exchange.get_historical_rates(self.ccy, self.cache_dir)
if self.is_enabled():
self.exchange.update(self.ccy)
def is_enabled(self):
return bool(self.config.get('use_exchange_rate'))
def set_enabled(self, b):
return self.config.set_key('use_exchange_rate', bool(b))
self.config.set_key('use_exchange_rate', bool(b))
self.trigger_update()
def get_history_config(self):
return bool(self.config.get('history_rates'))
@ -489,7 +528,7 @@ class FxThread(ThreadJob):
return self.config.get("currency", "EUR")
def config_exchange(self):
return self.config.get('use_exchange', 'BitcoinAverage')
return self.config.get('use_exchange', 'CoinMarketcap')
def show_history(self):
return self.is_enabled() and self.get_history_config() and self.ccy in self.exchange.history_ccys()
@ -497,9 +536,13 @@ class FxThread(ThreadJob):
def set_currency(self, ccy):
self.ccy = ccy
self.config.set_key('currency', ccy, True)
self.timeout = 0 # Because self.ccy changes
self.trigger_update()
self.on_quotes()
def trigger_update(self):
if self.network:
self.network.asyncio_loop.call_soon_threadsafe(self._trigger.set)
def set_exchange(self, name):
class_ = globals().get(name, BitcoinAverage)
self.print_error("using exchange", name)
@ -508,7 +551,7 @@ class FxThread(ThreadJob):
self.exchange = class_(self.on_quotes, self.on_history)
# A new exchange means new fx quotes, initially empty. Force
# a quote refresh
self.timeout = 0
self.trigger_update()
self.exchange.read_historical_rates(self.ccy, self.cache_dir)
def on_quotes(self):
@ -519,8 +562,8 @@ class FxThread(ThreadJob):
if self.network:
self.network.trigger_callback('on_history')
def exchange_rate(self):
'''Returns None, or the exchange rate as a Decimal'''
def exchange_rate(self) -> Decimal:
"""Returns the exchange rate as a Decimal"""
rate = self.exchange.quotes.get(self.ccy)
if rate is None:
return Decimal('NaN')

View File

@ -1,5 +1,5 @@
# To create a new GUI, please add its code to this directory.
# Three objects are passed to the ElectrumGui: config, daemon and plugins
# The Wallet object is instanciated by the GUI
# The Wallet object is instantiated by the GUI
# Notifications about network events are sent to the GUI by using network.register_callback()

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Some files were not shown because too many files have changed in this diff Show More