Compare commits

..

64 Commits

Author SHA1 Message Date
Craig Raw
f988d4fffc Merge branch 'master' of github.com:sparrowwallet/HWI 2020-11-26 07:42:20 +02:00
Andrew Chow
d86368ff7d release: Bump to version 1.2.1 2020-11-25 20:37:51 -05:00
Andrew Chow
def565b5ac release: Bump to 1.2.1-rc.1 2020-11-24 17:30:23 -05:00
Andrew Chow
1b65d782e2
Merge #406: [1.2.x] Backports for 1.2.1
b45607c1cf bitbox02: unlock during enumerate() (Marko Bencun)
070d5eafbf bitbox02: fix enumerate regression (Marko Bencun)
087bc691f2 Add back support for Ledger legacy USB product IDs (TamtamHero)
b8f0901752 Upgrade USB enumeration logic for Ledger products (TamtamHero)

Pull request description:

  Backports #403 #399 #404 to the 1.x branch for a 1.2.1 release.

Top commit has no ACKs.

Tree-SHA512: 98803ebe742b0277762f7ae008dc70bddfcc98c72ff0eedbe265637ae8e117e0585a9a7df617cb03e078fcbf9b3a31a2e12f6827a975d9042994a4f616e52048
2020-11-24 17:28:55 -05:00
Marko Bencun
b45607c1cf
bitbox02: unlock during enumerate()
Make it consistent with other HW wallets. Nunchuk relies on having an
unlocked device after enumerate() finishes.

Github-Pull: #404
Rebased-From: e1cc1937179a7fdb8e9807a0ef9e6f9b8ece8df0
2020-11-24 14:46:23 -05:00
Marko Bencun
070d5eafbf
bitbox02: fix enumerate regression
6a6c46f6834b4dac5e67d0c92a9ef791619e9dce checks that a password must
exist instead of that a password must not exist.

That commit does not describe the reason for the change. Password
cannot be None anyway, as the type of password is `str`, not `Optional[str]`.

Github-Pull: #399
Rebased-From: b8cf8578836cf92918c619f6f763c69697926810
2020-11-24 14:46:17 -05:00
TamtamHero
087bc691f2
Add back support for Ledger legacy USB product IDs
Github-Pull: #403
Rebased-From: 1080756258477b15c4e61025635e5b7b2dc947d4
2020-11-24 14:44:59 -05:00
TamtamHero
b8f0901752
Upgrade USB enumeration logic for Ledger products
Github-Pull: #403
Rebased-From: a7407e268b817515fd78b1c2225753538410070a
2020-11-24 14:44:55 -05:00
Craig Raw
663169d754 Merge branch 'master' of https://github.com/sparrowwallet/HWI 2020-10-16 11:54:31 +02:00
Andrew Chow
afec2cd8fc release: Bump to 1.2.0 2020-10-09 14:16:08 -04:00
Andrew Chow
7dbcc9ad79 release: Bump to 1.2.0-rc.1 2020-10-02 14:41:55 -04:00
Andrew Chow
085817746b
Merge #380: ignored .vscode folder
2f56cd3839 ignored .vscode folder (fametrano)

Pull request description:

  This is to ignore the folder where Visual Studio Code save its own stuff

ACKs for top commit:
  instagibbs:
    ACK 2f56cd3839
  achow101:
    ACK 2f56cd3839

Tree-SHA512: 1f8e22febe5cc3ab5d6a5937b0df51c56569335cc0382ef463539784fd6d12db699b3449c2f332850080e6880ad718828ea43d9f05578ca494d46b89b17c3e6c
2020-09-01 16:05:11 -04:00
Andrew Chow
f83c966012
Merge #383: Fix README features table rendering
0683dc246a Fix README features table rendering (benk10)

Pull request description:

  The HWW features table seems to have been broken with #363 due to a small issue (needed to add another `:---:|` at the table separator list), so just a small PR to fix that (and remove duplicated spaces).

ACKs for top commit:
  achow101:
    ACK 0683dc246a
  instagibbs:
    utACK 0683dc246a

Tree-SHA512: 36a7b9e039d397bb2a48ac34841727f4c2bfe67aaaabd5fd651f3b058eb52a50a2a395f404e86ae8ab31042d89e5ceb3bb0f3b9ceb16d13e147995262ba38bf3
2020-08-31 23:22:03 -04:00
benk10
0683dc246a
Fix README features table rendering 2020-09-01 05:15:38 +02:00
Gregory Sanders
17b8c8bf3c
Merge #363: add BitBox02 support
b24f2aaa2d hwilib/gui: make sure the bb02 pairing window is painted (Marko Bencun)
5010844a27 hwwclient: fix send_pin signature (Marko Bencun)
6169788aa7 bitbox02: consistent errs when the device is (not) initialized (Marko Bencun)
d255229639 commands/find_device: only get the device fingerprint if required (Marko Bencun)
4df060d0c6 bitbox02: use local copy of parse_path (Marko Bencun)
ed1125115f typecheck bitbox02.py (Marko Bencun)
4e4455d2b2 add BitBox02 support (Marko Bencun)
1d0bbb6281 add bitbox02 dependency (Marko Bencun)

Pull request description:

  This adds cli and gui support for the BitBox02. For now, only single sig support is added, with the plan to add multisig support soon after this is merged.

  This PR has had an internal review over here before: https://github.com/benma/hwi/pull/1, and I tested it quite extensively.

ACKs for top commit:
  achow101:
    ACK b24f2aaa2d
  instagibbs:
    utACK b24f2aaa2d

Tree-SHA512: 0dd7c48f0381c4f72c0ea033dd9e64212f6c44c4c9c2711441500d7816b7327379dfb314494bbf8301a87f05601c1ed78a4c1d4397fa2d309ccb613faa847946
2020-08-31 21:44:55 -04:00
Marko Bencun
b24f2aaa2d
hwilib/gui: make sure the bb02 pairing window is painted
Sometimes, one processEvents() was not enough to have Qt5 finish
painting the window before proceeding onto the blocking call to show
the pairing code on the bitbox02 screen.

This is an attempt at making this more robust.
2020-08-31 13:02:07 +02:00
fametrano
2f56cd3839 ignored .vscode folder 2020-08-27 11:23:54 +02:00
Marko Bencun
5010844a27
hwwclient: fix send_pin signature
Was missing the pin argument.
2020-08-26 17:55:31 +02:00
Marko Bencun
6169788aa7
bitbox02: consistent errs when the device is (not) initialized
Running e.g. getxpub when the device is not initialized results in the
device returning with 'BAD_STATE', which is not so user friendly.
2020-08-26 17:55:31 +02:00
Marko Bencun
d255229639
commands/find_device: only get the device fingerprint if required
If the fingerprint is not specified on the CLI, the fingerprint does
not need to be queried from the device.

This is helpful for uninitialized devices, to invoke the 'setup' or
'restore' commands, at which point the device does not have a keystore
and fingerprint yet.
2020-08-26 17:55:03 +02:00
Marko Bencun
4df060d0c6
bitbox02: use local copy of parse_path
Device libraries should not cross-reference each other and stay
independent.
2020-08-26 17:55:02 +02:00
Marko Bencun
ed1125115f
typecheck bitbox02.py
Since trezorlib is imported also by bitbox02, it would be type-checked
by mypy. Since it contains no types, it is easiest to ignore trezorlib
in mypy.

--implicit-reexport is disabled by --strict, but that is a bit too
strict. The bitbox02 package relies on implicit reexports.
2020-08-26 17:54:54 +02:00
Marko Bencun
4e4455d2b2
add BitBox02 support 2020-08-26 17:54:52 +02:00
Marko Bencun
1d0bbb6281
add bitbox02 dependency 2020-08-26 16:38:51 +02:00
Andrew Chow
db672d048b
Merge #373: Avoided bare except
44dabcfa80 Avoided bare expect (Ferdinando Ametrano)

Pull request description:

  Using a bare `except:` is not a good coding practice: it catches all exceptions inheriting from `BaseException`, including `SystemExit`, `KeyboardInterrupt`, and `GeneratorExit` (which is not an error and should not normally be caught by user code).
  ```
  BaseException
    - Exception
    - GeneratorExit
    - KeyboardInterrupt
    - SystemExit
  ```
  This patch is sub-optimal because only specific exceptions should be catched... anyway, in situations where one wants to catch all “normal” exceptions, at least restrict to explicitly catch the `Exception` base class, instead of implicitly catching any `BaseException`.

ACKs for top commit:
  achow101:
    ACK 44dabcfa80

Tree-SHA512: 220dc4be1c47958e3727205d9cbb03069a49108ce697983c1967cf04f980cf3f607399e9a2c9e511d270f13154aa85a1cf9c92cfd0681f7ba56622134f663dd1
2020-08-25 13:36:05 -04:00
Ferdinando Ametrano
44dabcfa80 Avoided bare expect 2020-08-25 11:23:27 +02:00
Andrew Chow
550161b937
Merge #378: tests: Descriptor wallets, multisig fixes, and general cleanups
2c381fa2dc tests: Add trezor t back to mixed and external signing tests (Andrew Chow)
2ea932ed78 tests: Use m/48' for multisig tests (Andrew Chow)
4791841d63 tests: Use descriptor wallets (Andrew Chow)
c5a64885bc tests: Refactor multisig signtx to be able to use a separate path (Andrew Chow)
f0cef6ea39 tests: Combine multisig displayaddress path and descriptor tests (Andrew Chow)
10f094a0b0 tests: move xpub multisig displayaddress to separate test case (Andrew Chow)
84eacd63e1 tests: Properly skip multisig displayaddress for unsupported devices (Andrew Chow)
c6038cbbd9 tests: Use a unique wallet name for each test case (Andrew Chow)
e8316657a5 test: Remove wallet setup from displayaddress tests (Andrew Chow)
b9931556b1 tests: Refactor multisig setup in multisig displayaddress (Andrew Chow)
66182f0ca7 tests: Refactor wallet creation to parent class function (Andrew Chow)
4498c3f347 tests: Use parent class setUp and don't re-set self.rpc (Andrew Chow)
79f74a78fc tests: refactor --testnet in self.dev_args to __init__ (Andrew Chow)

Pull request description:

  * Refactor and cleanup wallet, RPC, and `--testnet` argument setup
    * A new wallet is made for each individual test case for now. Funds still provided by the default wallet.
  * Multisig setups are refactored and updated to not use the wallet
    * Common functionality is refactored into a separate function to deduplicate a bunch of code.
    * The setup itself is simplified to not require the use of the wallet.
  * The multisig descriptor with xpub test added in #353 is moved to its own function
  * The multisig `displayaddress` tests for descriptor and paths have been combined as they are basically the same
  * The wallet we import stuff into is now a descriptor wallet and we use `importdescriptors` now. This is necessary to fix the Trezor 1 test failure.
  * Change multisigs to use the `m/48'` path that is supposedly a standard but not really documented anywhere. This is necessary to fix the Trezor 1 test failure.

ACKs for top commit:
  instagibbs:
    reACK 2c381fa2dc

Tree-SHA512: 035c369fba908ba2836ad6af9e5d50b15d89ea23210be6d97a9bf5dd47d7f21a581ab6f2a59d9eb46d3732ac568c07b77e35962b48daf1bc26925edf7b9f9e49
2020-08-20 16:42:16 -04:00
Andrew Chow
2c381fa2dc tests: Add trezor t back to mixed and external signing tests 2020-08-20 15:27:26 -04:00
Andrew Chow
2ea932ed78 tests: Use m/48' for multisig tests
Apparently m/48' (where 48 is the purpose number in a BIP 44 path) is
the standard path for use with multisigs. This is mistakenly called BIP
48 in many places, but no such BIP 48 actually exists. Anyways, Trezor
requires multisigs use m/48' for multisigs, so we need to use them in
our tests.
2020-08-20 15:27:26 -04:00
Andrew Chow
4791841d63 tests: Use descriptor wallets
We are really targeting descriptor wallets, so we should be testing
those.

Because descriptor wallets also change how the imports will work w.r.t
rescans and the next address to give out, the getkeypool path checks are
changed to just check that the begin with the correct path prefix.
2020-08-20 15:27:26 -04:00
Andrew Chow
22f6d7d62e
Merge #379: Update docs regarding Trezor T mixed input support
991a14473d Update docs wrt Trezor T mixed input support (Martin Milata)

Pull request description:

  AFAICT Trezor T does support mixed segwit and non-segwit inputs, there's couple of tests for that: https://github.com/trezor/trezor-firmware/blob/core/v2.3.2/tests/device_tests/test_msg_signtx_mixed_inputs.py

ACKs for top commit:
  achow101:
    ACK 991a14473d

Tree-SHA512: 78549007f0bfa2075e5f37a47d5b5a06910e776827a2193f59dbd969718430964544e0f57e178e197d76df3adec2ae28c0a0a722c67be69a003841b58ce6e0da
2020-08-20 13:03:35 -04:00
Martin Milata
991a14473d Update docs wrt Trezor T mixed input support 2020-08-20 12:55:23 +02:00
Andrew Chow
c5a64885bc tests: Refactor multisig signtx to be able to use a separate path 2020-08-19 17:40:10 -04:00
Andrew Chow
f0cef6ea39 tests: Combine multisig displayaddress path and descriptor tests
Combine these because they're basically the same. Also uses subtests.
2020-08-19 16:30:19 -04:00
Andrew Chow
10f094a0b0 tests: move xpub multisig displayaddress to separate test case 2020-08-19 16:30:19 -04:00
Andrew Chow
84eacd63e1 tests: Properly skip multisig displayaddress for unsupported devices 2020-08-19 16:30:19 -04:00
Andrew Chow
c6038cbbd9 tests: Use a unique wallet name for each test case
Instead of a single wallet for all tests, make a wallet for each
individual test case (i.e. test function).
2020-08-19 16:30:19 -04:00
Andrew Chow
e8316657a5 test: Remove wallet setup from displayaddress tests
This is no longer needed.
2020-08-19 16:30:19 -04:00
Andrew Chow
b9931556b1 tests: Refactor multisig setup in multisig displayaddress
The path and descriptor tests do basically the same thing to setup the
multisig. Refactor that out to be separate and to not rely on the
wallet.
2020-08-19 16:30:19 -04:00
Andrew Chow
66182f0ca7 tests: Refactor wallet creation to parent class function 2020-08-19 14:00:40 -04:00
Andrew Chow
4498c3f347 tests: Use parent class setUp and don't re-set self.rpc
self.rpc is already setup when the test starts, no need to set it again.
Additionally, use the parent class setUp so we aren't duplicating as
much code.
2020-08-19 13:31:48 -04:00
Andrew Chow
79f74a78fc tests: refactor --testnet in self.dev_args to __init__ 2020-08-19 13:22:49 -04:00
Andrew Chow
8751d1c725
Merge #372: Cleaned up log messages
5e296ca4dc Cleaned up log messages (Ferdinando M. Ametrano)

Pull request description:

  In my team, we comment out that line to avoid Trezor writing to our LOG.info

  As they say "your mileage may vary", but a final user's LOG.info should not be polluted by third party library.

ACKs for top commit:
  achow101:
    ACK 5e296ca4dc

Tree-SHA512: bfde661893c2cd966f8656964b782d16a8190008c4a74d8c3bc2a4491cd5248062f8ed0e5ea022d440cb1f2674b1724011858c6431c67f643fae21ec22cd7922
2020-08-18 11:34:50 -04:00
Ferdinando M. Ametrano
5e296ca4dc Cleaned up log messages 2020-08-18 08:48:34 +02:00
Andrew Chow
f7776fcfb3
Merge #371: added gitignore directives for virtual environments
7a896e792b added gitignore directives for virtual environments (Ferdinando M. Ametrano)

Pull request description:

  virtual environments are routinely created in the root project (sub)folder(s) and should be ignored by Git

ACKs for top commit:
  achow101:
    ACK 7a896e792b

Tree-SHA512: fd5ae7cc430871a96d4db8336dad9f2206005fa9b05911445bc72363603b13fee97ef55c5fe3a65520d3a9203814014e71511b20d2c4cbd8eeff7fde85d14ca5
2020-08-17 12:08:17 -04:00
Andrew Chow
3adaa3c483
Merge #369: added py.typed so that static type checkers use type hints (PEP 561)
287bce9894 added py.typed so that static type checkers use package stubs (PEP 561) (Ferdinando Ametrano)

Pull request description:

  - Problem: mypy can't find type hints when importing from hwi
  - Solution: add a py.typed file to make hwi PEP-561-compatible

  At the moment hwi has type hints, but they are useless to packages importing from hwi because there is no py.typed file to tell mypy they're there.

  PEP 561 specifies that packages which contain either inline type hints or type stubs should indicate their support for type hints via including a blank file named py.typed in the root of the package. Package maintainers who wish to support type checking of their code MUST add it to their package supporting typing.

ACKs for top commit:
  achow101:
    ACK 287bce9894

Tree-SHA512: 536c3022a0ab71673c542eaf238f231e812dd8beda92269e61647ec8e22762de720f89a83c9711e648d687fc67bf8fcee627db80ade8f4b0829ea6bbba081f98
2020-08-17 12:04:23 -04:00
Ferdinando M. Ametrano
7a896e792b
added gitignore directives for virtual environments
virtual environments are routinely created in the root project (sub)folder(s) and should be ignored by Git
2020-08-17 15:03:22 +02:00
Ferdinando Ametrano
287bce9894 added py.typed so that static type checkers use package stubs (PEP 561) 2020-08-17 10:52:51 +02:00
Andrew Chow
914ad3ea66
Merge #353: Support multisig xpub descriptors on Trezor
78a8801f94 Support multisig xpub descriptors (benk10)

Pull request description:

  Trezor Model T has an option when displaying a multisig address to also show all xpubs used as cosigners of the multisig. Currently, the display address function did not support using xpubs, so here I added support in Trezor for such descriptors, which will make xpubs show up correctly.

ACKs for top commit:
  achow101:
    ACK 78a8801f94

Tree-SHA512: 707e56b3cbac5f21ccdaeba912e751f23fe5fff637c18366cbe76b618840d4ba38fd2442919e2ff61f770c8381e59a6147d385a44d2def5dad07d89a09730eed
2020-08-15 17:50:25 -04:00
benk10
78a8801f94 Support multisig xpub descriptors 2020-08-15 21:21:45 +02:00
Andrew Chow
2fd8012be0
Merge #367: avoided 'unused variables' namespace pollution
85e5723289 removed unused variables in the devices folder codebase (Ferdinando Ametrano)
56f38aae8d avoided 'unused variables' namespace pollution (Ferdinando Ametrano)

Pull request description:

ACKs for top commit:
  achow101:
    ACK 85e5723289

Tree-SHA512: 1925a84291dd9e29f9fe2bb22b74e0fef3035675599d06f3f7fbe605871144f616734969b2771ebaa3f49e91766f0c6c629b8470f063c5b0dd6969cb4c57cf77
2020-08-14 14:25:02 -04:00
Andrew Chow
42ad6f8b95
Merge #366: extended sign_message to also sign bytes, not only string
d16bdf794f extended sign_message to also sign bytes, not only string (Ferdinando Ametrano)

Pull request description:

  Bitcoin message signing is usually used to sign an input string which, as first step, is encoded to bytes. The rest of the implementation assumes bytes, operates on bytes, ecdsa-signs bytes.

  This PR proposes to skip the string encoding if bytes, instead of a string, are provided as input: this allow to "bitcoin message sign" an arbitrary byte sequence (note that arbitrary byte sequences cannot be always decoded to strings).

  The changes to trezorlib/btc.py are only cosmetic and that file could actually be left untouched

ACKs for top commit:
  achow101:
    ACK d16bdf794f

Tree-SHA512: 9f6ddebcb19eebb3d9164051bbc00360275d8437793130ec4394c0af463ad252db5261beee243ec30f1fe458d3ac23d7f6ce69d88e1e875cf00b53c9ed5f3d2a
2020-08-14 14:24:00 -04:00
Andrew Chow
85958afcc6
Merge #368: Add disclaimer to README about (lack of) endorsement
3016d4a861 Add disclaimer to README about (lack of) endorsement (Gregory Sanders)

Pull request description:

  We should be saying something like this since day 1, and now with discussion of newer devices even more prudent.

ACKs for top commit:
  achow101:
    ACK 3016d4a861

Tree-SHA512: d0146ebab41ac32a8400c6acdeb1acf8eb6fcaae418fdc9a042f538345545f1fc88e48ae321a2caa470ba82059a74bf941a507e80b46d9e4c558f6dc0bc656e8
2020-08-14 14:20:53 -04:00
Gregory Sanders
3016d4a861 Add disclaimer to README about (lack of) endorsement 2020-08-11 10:03:36 -04:00
Ferdinando Ametrano
85e5723289 removed unused variables in the devices folder codebase 2020-08-08 16:46:45 +02:00
Ferdinando Ametrano
56f38aae8d avoided 'unused variables' namespace pollution 2020-08-08 16:35:52 +02:00
Ferdinando Ametrano
d16bdf794f extended sign_message to also sign bytes, not only string 2020-08-08 16:19:34 +02:00
Andrew Chow
c663a21ec8
Merge #354: Update Ledger udev rules
dfb3117053 Update Ledger udev rules (Andrew Chow)

Pull request description:

  From https://github.com/LedgerHQ/udev-rules/blob/master/20-hw1.rules

Top commit has no ACKs.

Tree-SHA512: 68507b7502aaf4066144d17eb2dd58fcd5fb6c3a18a4cb8da6abcd6aa8bc12d1d9d8886ad86e754efb878d71097c0183cd7da6fa161085871b23706a64c164e1
2020-08-07 20:27:42 -04:00
Andrew Chow
488e9f2876
Merge #365: Fix descriptor fingerprint parsing when no derivation used
67a9d7a322 Fix descriptor fingerprint parsing when no derivation used (benk10)

Pull request description:

  This is a small fix for parsing a descriptor that specifies origin fingerprint but no origin derivation (depth=0).
  E.g. `[90abcdef]xpub.../0/*`.

  Currently, it will set origin_fingerprint to `None`, while the correct behavior would be to save the fingerprint.

ACKs for top commit:
  achow101:
    ACK 67a9d7a322

Tree-SHA512: 1fdf093dd8c65431067afb7ddd8770ed07ed99e2d6185bc363a0e6f4512dbe0735b4e09c0e8a9b4da62f8498a766ff4c9c390fc7ae8a83cd052c862bb336f235
2020-08-07 20:25:43 -04:00
benk10
67a9d7a322 Fix descriptor fingerprint parsing when no derivation used 2020-08-07 23:59:48 +03:00
Craig Raw
d427c22235 onedir build for catalina 2020-08-07 18:26:28 +02:00
Andrew Chow
4be273bf45
Merge #361: add is_witness check before is_p2sh for non-witness utxos
a78f8d68f6 add not is_wit check to trezor (Kevin Mulcrone)

Pull request description:

  Fixes #360

  Correctly assigns the script type of an input when an input has a witnessScript but no witnessUtxo.

ACKs for top commit:
  achow101:
    ACK a78f8d68f6

Tree-SHA512: 4fbb61f327e8feb94ff69f09a094c8b9427168ac13ae311859a15074a351b7a4b5bd8d65d723d64f60232ad407e31cc24f6ac427c48f25745ad714f76a04685b
2020-07-31 14:40:31 -04:00
Kevin Mulcrone
a78f8d68f6 add not is_wit check to trezor 2020-07-28 23:20:02 -06:00
Andrew Chow
dfb3117053 Update Ledger udev rules
From https://github.com/LedgerHQ/udev-rules/blob/master/20-hw1.rules
2020-07-03 13:41:43 -04:00
42 changed files with 712 additions and 481 deletions

11
.gitignore vendored
View File

@ -14,3 +14,14 @@ hwilib/ui/ui_*.py
*.stderr
*.stdout
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
.vscode

View File

@ -68,6 +68,8 @@ jobs:
stage: lint
install:
- pip install mypy
- pip install poetry
- poetry install
script: mypy --implicit-reexport --strict hwilib/base58.py hwilib/errors.py hwilib/serializations.py hwilib/hwwclient.py hwilib/devices/bitbox02.py
- name: Run non-device tests only
stage: test

16
HWI.entitlements Normal file
View File

@ -0,0 +1,16 @@
<?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>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>

View File

@ -6,6 +6,8 @@ The Bitcoin Hardware Wallet Interface is a Python library and command line tool
It provides a standard way for software to work with hardware wallets without needing to implement device specific drivers.
Python software can use the provided library (`hwilib`). Software in other languages can execute the `hwi` tool.
Caveat emptor: Inclusion of a specific hardware wallet vendor does not imply any endorsement of quality or security.
## Prerequisites
Python 3 is required. The libraries and [udev rules](hwilib/udev/README.md) for each device must also be installed. Some libraries will need to be installed
@ -89,15 +91,15 @@ The below table lists what devices and features are supported for each device.
Please also see [docs](docs/) for additional information about each device.
| Feature \ Device | Ledger Nano X | Ledger Nano S | Trezor One | Trezor Model T | BitBox01 | BitBox02 | KeepKey | Coldcard |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| Support Planned | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Implemented | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| xpub retrieval | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Message Signing | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes |
| Device Setup | N/A | N/A | Yes | Yes | Yes | Yes | Yes | N/A |
| Device Wipe | N/A | N/A | Yes | Yes | Yes | Yes | Yes | N/A |
| Device Recovery | N/A | N/A | Yes | Yes | N/A | Yes | Yes | N/A |
| Device Backup | N/A | N/A | N/A | N/A | Yes | Yes | N/A | Yes |
| Device Recovery | N/A | N/A | Yes | Yes | N/A | Yes | Yes | N/A |
| Device Backup | N/A | N/A | N/A | N/A | Yes | Yes | N/A | Yes |
| P2PKH Inputs | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes |
| P2SH-P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
@ -109,7 +111,7 @@ Please also see [docs](docs/) for additional information about each device.
| Arbitrary redeemScript Inputs | Yes | Yes | N/A | N/A | Yes | N/A | N/A | N/A |
| Arbitrary witnessScript Inputs | Yes | Yes | N/A | N/A | Yes | N/A | N/A | N/A |
| Non-wallet inputs | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes |
| Mixed Segwit and Non-Segwit Inputs | N/A | N/A | Yes | N/A | Yes | Yes | Yes | Yes |
| Mixed Segwit and Non-Segwit Inputs | N/A | N/A | Yes | Yes | Yes | Yes | Yes | Yes |
| Display on device screen | Yes | Yes | Yes | Yes | N/A | Yes | Yes | Yes |
## Using with Bitcoin Core

View File

@ -21,7 +21,6 @@ Due to the limitations of the Trezor, some transactions cannot be signed by a Tr
- Multisig inputs are limited to at most n-of-15 multisigs. This is a firmware limitation.
* Transactions with arbitrary input scripts (scriptPubKey, redeemScript, or witnessScript) and arbitrary output scripts cannot be signed. This is a firmware limitation.
* Send-to-self transactions will result in no prompt for outputs as all outputs will be detected as change.
- For **Trezor T**, a transaction cannot contain both segwit and non-segwit inputs
## Note on `backup`

View File

@ -33,14 +33,21 @@ pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='hwi',
exclude_binaries=True,
name='hwi',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
runtime_tmpdir=None,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='hwi')

View File

@ -1 +1 @@
__version__ = '1.1.2'
__version__ = '1.2.1'

View File

@ -62,15 +62,16 @@ def find_device(password='', device_type=None, fingerprint=None, expert=False):
try:
client = get_client(d['type'], d['path'], password, expert)
master_fpr = d.get('fingerprint', None)
if master_fpr is None:
master_fpr = client.get_master_fingerprint_hex()
if fingerprint:
master_fpr = d.get('fingerprint', None)
if master_fpr is None:
master_fpr = client.get_master_fingerprint_hex()
if fingerprint and master_fpr != fingerprint:
client.close()
continue
if master_fpr != fingerprint:
client.close()
continue
return client
except:
except Exception:
if client:
client.close()
pass # Ignore things we wouldn't get fingerprints for
@ -239,15 +240,18 @@ def displayaddress(client, path=None, desc=None, sh_wpkh=False, wpkh=False, rede
if descriptor.sh or descriptor.sh_wsh or descriptor.wsh:
path = ''
redeem_script = format(80 + int(descriptor.multisig_M), 'x')
xpubs_descriptor = False
for i in range(0, descriptor.multisig_N):
path += descriptor.origin_fingerprint[i] + descriptor.origin_path[i] + ','
path += descriptor.origin_fingerprint[i] + descriptor.origin_path[i]
if not descriptor.path_suffix[i]:
redeem_script += '21' + descriptor.base_key[i]
else:
return {'error': 'Multisig descriptor must include all pubkeys', 'code': BAD_ARGUMENT}
path += descriptor.path_suffix[i]
xpubs_descriptor = True
path += ','
path = path[0:-1]
redeem_script += format(80 + descriptor.multisig_N, 'x') + 'ae'
return client.display_address(path, descriptor.sh_wpkh or descriptor.sh_wsh, descriptor.wpkh or descriptor.wsh, redeem_script)
return client.display_address(path, descriptor.sh_wpkh or descriptor.sh_wsh, descriptor.wpkh or descriptor.wsh, redeem_script, descriptor=descriptor if xpubs_descriptor else None)
if descriptor.m_path is None:
return {'error': 'Descriptor missing origin info: ' + desc, 'code': BAD_ARGUMENT}
if descriptor.origin_fingerprint != client.get_master_fingerprint_hex():

View File

@ -1,3 +1,4 @@
# mypy: ignore-errors
import re
# From: https://github.com/bitcoin/bitcoin/blob/master/src/script/descriptor.cpp
@ -82,6 +83,12 @@ class Descriptor:
if origin_path and not isinstance(origin_path, list):
self.m_path_base = "m" + origin_path
self.m_path = "m" + origin_path + (path_suffix or "")
elif isinstance(origin_path, list):
self.m_path_base = []
self.m_path = []
for i in range(0, len(origin_path)):
self.m_path_base.append("m" + origin_path[i])
self.m_path.append("m" + origin_path[i] + (path_suffix[i] or ""))
@classmethod
def parse(cls, desc, testnet=False):
@ -147,6 +154,9 @@ class Descriptor:
origin_path = match.group(2)
# Replace h with '
origin_path = origin_path.replace('h', '\'')
else:
origin_fingerprint = origin
origin_path = ''
base_key_and_path_match = re.search(r"\[.*\](\w+)([\d'\/\*]*)", key)
else:

View File

@ -14,20 +14,15 @@ from binascii import unhexlify
import struct
import builtins
import sys
import os
import json
from pathlib import Path
from functools import wraps
from ..hwwclient import HardwareWalletClient
from ..hwwclient import HardwareWalletClient, Descriptor
from ..serializations import (
PSBT,
CTxOut,
is_p2pkh,
is_p2sh,
is_p2wpkh,
is_p2wsh,
is_witness,
ser_uint256,
ser_sig_der,
)
@ -44,8 +39,6 @@ from ..errors import (
import hid # type: ignore
from .trezorlib.tools import parse_path
from bitbox02 import util
from bitbox02 import bitbox02
from bitbox02.communication import (
@ -65,19 +58,19 @@ from bitbox02.communication.bitbox_api_protocol import (
)
class KeypathError(UnavailableActionError):
def __init__(self, keypath: str, is_testnet: bool):
class BitBox02Error(UnavailableActionError):
def __init__(self, msg: str):
"""
Keypath error exception with formatting and docs.
BitBox02 unexpected error. The BitBox02 does not return give granular error messages,
so we give hints to as what could be wrong.
"""
network = "testnet" if is_testnet else "mainnet"
msg = "The BitBox02 does not support the keypath {} on {}. Supported keypaths are:\n".format(
keypath, network
msg = "Input error: {}. A keypath might be invalid. Supported keypaths are: ".format(
msg
)
msg += "m/49'/0'/<account'> for p2wpkh-p2sh\n"
msg += "m/84'/0'/<account'> for p2wpkh\n"
msg += "m/48'/0'/<account'>/2' for p2wsh multisig\n"
msg += "account can be between 0' and 99'\n"
msg += "m/49'/0'/<account'> for p2wpkh-p2sh; "
msg += "m/84'/0'/<account'> for p2wpkh; "
msg += "m/48'/0'/<account'>/2' for p2wsh multisig; "
msg += "account can be between 0' and 99'; "
msg += "For address keypaths, append /0/<address index> for a receive and /1/<change index> for a change address."
super().__init__(msg)
@ -140,8 +133,38 @@ class CLINoiseConfig(util.BitBoxAppNoiseConfig):
)
def _keypath_check_account(bip44_account: int) -> bool:
return HARDENED <= bip44_account <= HARDENED + 99
def _parse_path(nstr: str) -> Sequence[int]:
"""
Adapted from trezorlib.tools.parse_path.
Convert BIP32 path string to list of uint32 integers with hardened flags.
Several conventions are supported to set the hardened flag: -1, 1', 1h
e.g.: "0/1h/1" -> [0, 0x80000001, 1]
:param nstr: path string
:return: list of integers
"""
if not nstr:
return []
n = nstr.split("/")
# m/a/b/c => a/b/c
if n[0] == "m":
n = n[1:]
def str_to_harden(x: str) -> int:
if x.startswith("-"):
return abs(int(x)) + HARDENED
elif x.endswith(("h", "'")):
return int(x[:-1]) + HARDENED
else:
return int(x)
try:
return [str_to_harden(x) for x in n]
except Exception:
raise ValueError("Invalid BIP32 path", nstr)
def enumerate(password: str = "") -> List[Dict[str, object]]:
@ -153,6 +176,10 @@ def enumerate(password: str = "") -> List[Dict[str, object]]:
path = device_info["path"].decode()
client = Bitbox02Client(path)
client.set_noise_config(SilentNoiseConfig())
d_data: Dict[str, object] = {}
bb02 = None
with handle_errors(common_err_msgs["enumerate"], d_data):
bb02 = client.init(expect_initialized=None)
version, platform, edition, unlocked = bitbox02.BitBox02.get_info(
client.transport
)
@ -165,29 +192,32 @@ def enumerate(password: str = "") -> List[Dict[str, object]]:
assert isinstance(edition, BitBox02Edition)
d_data = {
"type": "bitbox02",
"path": path,
"model": {
BitBox02Edition.MULTI: "bitbox02_multi",
BitBox02Edition.BTCONLY: "bitbox02_btconly",
}[edition],
"needs_pin_sent": False,
"needs_passphrase_sent": False,
}
d_data.update(
{
"type": "bitbox02",
"path": path,
"model": {
BitBox02Edition.MULTI: "bitbox02_multi",
BitBox02Edition.BTCONLY: "bitbox02_btconly",
}[edition],
"needs_pin_sent": False,
"needs_passphrase_sent": False,
}
)
with handle_errors(common_err_msgs["enumerate"], d_data):
if not unlocked:
raise DeviceNotReadyError(
"Please load wallet to unlock."
if _using_external_gui
else "Please use any subcommand to unlock"
)
bb02 = client.init()
info = bb02.device_info()
if not info["initialized"]:
raise HWWError("Not initialized", DEVICE_NOT_INITIALIZED)
d_data["fingerprint"] = client.get_master_fingerprint_hex()
if bb02 is not None:
with handle_errors(common_err_msgs["enumerate"], d_data):
if not bb02.device_info()["initialized"]:
raise DeviceNotReadyError(
"BitBox02 is not initialized. Please initialize it using the BitBoxApp."
)
elif not unlocked:
raise DeviceNotReadyError(
"Please load wallet to unlock."
if _using_external_gui
else "Please use any subcommand to unlock"
)
d_data["fingerprint"] = client.get_master_fingerprint_hex()
result.append(d_data)
@ -212,7 +242,7 @@ def bitbox02_exception(f: T) -> T:
raise ActionCanceledError("{} canceled".format(f.__name__))
except Bitbox02Exception as exc:
if exc.code in (ERR_GENERIC, ERR_INVALID_INPUT):
raise BadArgumentError("invalid input")
raise BitBox02Error(str(exc))
raise exc
except FirmwareVersionOutdatedException as exc:
raise DeviceNotReadyError(str(exc))
@ -227,7 +257,7 @@ class Bitbox02Client(HardwareWalletClient):
Initializes a new BitBox02 client instance.
"""
super().__init__(path, password=password, expert=expert)
if password != "":
if password:
raise BadArgumentError(
"The BitBox02 does not accept a passphrase from the host. Please enable the passphrase option and enter the passphrase on the device during unlock."
)
@ -245,7 +275,7 @@ class Bitbox02Client(HardwareWalletClient):
def set_noise_config(self, noise_config: BitBoxNoiseConfig) -> None:
self.noise_config = noise_config
def init(self) -> bitbox02.BitBox02:
def init(self, expect_initialized: Optional[bool] = True) -> bitbox02.BitBox02:
if self.bb02 is not None:
return self.bb02
@ -262,6 +292,19 @@ class Bitbox02Client(HardwareWalletClient):
sys.stderr.write("WARNING: {}\n".format(exc))
raise
self.bb02 = bb02
is_initialized = bb02.device_info()["initialized"]
if expect_initialized is not None:
if expect_initialized:
if not is_initialized:
raise HWWError(
"The BitBox02 must be initialized first.",
DEVICE_NOT_INITIALIZED,
)
elif is_initialized:
raise UnavailableActionError(
"The BitBox02 must be wiped before setup."
)
return bb02
raise Exception(
"Could not find the hid device info for path {}".format(self.device_path)
@ -276,8 +319,6 @@ class Bitbox02Client(HardwareWalletClient):
The BitBox02 does not support querying arbitrary keypaths, but has an api call return the fingerprint at m/.
"""
bb02 = self.init()
if not bb02.device_info()["initialized"]:
raise UnavailableActionError("Not initialized")
return bb02.root_fingerprint().hex()
def prompt_pin(self) -> Dict[str, Union[bool, str, int]]:
@ -285,7 +326,7 @@ class Bitbox02Client(HardwareWalletClient):
"The BitBox02 does not need a PIN sent from the host"
)
def send_pin(self) -> Dict[str, Union[bool, str, int]]:
def send_pin(self, pin: str) -> Dict[str, Union[bool, str, int]]:
raise UnavailableActionError(
"The BitBox02 does not need a PIN sent from the host"
)
@ -295,82 +336,24 @@ class Bitbox02Client(HardwareWalletClient):
return bitbox02.btc.TBTC
return bitbox02.btc.BTC
def _get_xpub(self, keypath: List[int]) -> str:
expected_coin = 1 + HARDENED if self.is_testnet else 0 + HARDENED
if len(keypath) == 3:
# singlesig
purpose, coin, account = keypath
if coin != expected_coin or not _keypath_check_account(account):
raise ValueError()
try:
# Actually we want to show ypub... or zpub... for segwit-p2sh or native segwit,
# but some downstream projects using HWI can just parse xpub and tpub.
# Maybe we can change it to Electrum compatible xpub formats someday.
xpub_type = {
False: {
PURPOSE_P2WPKH_P2SH: bitbox02.btc.BTCPubRequest.XPUB, # bitbox02.btc.BTCPubRequest.YPUB,
PURPOSE_P2WPKH: bitbox02.btc.BTCPubRequest.XPUB, # bitbox02.btc.BTCPubRequest.ZPUB,
},
True: {
PURPOSE_P2WPKH_P2SH: bitbox02.btc.BTCPubRequest.TPUB, # bitbox02.btc.BTCPubRequest.UPUB,
PURPOSE_P2WPKH: bitbox02.btc.BTCPubRequest.TPUB, # bitbox02.btc.BTCPubRequest.VPUB,
},
}[self.is_testnet][purpose]
except KeyError:
raise ValueError()
elif len(keypath) == 4:
# multisig
purpose, coin, account, script_type = keypath
if (
purpose != PURPOSE_MULTISIG_P2WSH
or coin != expected_coin
or not _keypath_check_account(account)
or script_type != 2 + HARDENED
):
raise ValueError()
if self.is_testnet:
xpub_type = (
bitbox02.btc.BTCPubRequest.TPUB
) # bitbox02.btc.BTCPubRequest.CAPITAL_VPUB
else:
xpub_type = (
bitbox02.btc.BTCPubRequest.XPUB
) # bitbox02.btc.BTCPubRequest.CAPITAL_ZPUB
else:
raise ValueError()
def _get_xpub(self, keypath: Sequence[int]) -> str:
xpub_type = (
bitbox02.btc.BTCPubRequest.TPUB
if self.is_testnet
else bitbox02.btc.BTCPubRequest.XPUB
)
return self.init().btc_xpub(
keypath, coin=self._get_coin(), xpub_type=xpub_type, display=False
)
def get_pubkey_at_path(self, bip32_path: str) -> Dict[str, str]:
path_uint32s = parse_path(bip32_path)
path_uint32s = _parse_path(bip32_path)
try:
xpub = self._get_xpub(path_uint32s)
except ValueError:
raise KeypathError(bip32_path, self.is_testnet)
except Bitbox02Exception as exc:
raise BitBox02Error(str(exc))
return {"xpub": xpub}
def _check_address_keypath_simple(
self, bip32_path: str, expected_purpose: int
) -> bool:
path_uint32s = parse_path(bip32_path)
if len(path_uint32s) != 5:
return False
purpose, coin, account, change, address = path_uint32s
if purpose != expected_purpose:
return False
expected_coin = 1 + HARDENED if self.is_testnet else 0 + HARDENED
if coin != expected_coin:
return False
if not _keypath_check_account(account):
return False
if change not in (0, 1):
return False
if not (0 <= address <= 9999):
return False
return True
@bitbox02_exception
def display_address(
self,
@ -378,30 +361,25 @@ class Bitbox02Client(HardwareWalletClient):
p2sh_p2wpkh: bool,
bech32: bool,
redeem_script: Optional[str] = None,
descriptor: Optional[Descriptor] = None,
) -> Dict[str, str]:
if redeem_script:
raise NotImplementedError("BitBox02 multisig not integrated into HWI yet")
keypath_exception = KeypathError(bip32_path, self.is_testnet)
if p2sh_p2wpkh:
script_config = bitbox02.btc.BTCScriptConfig(
simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH
)
if not self._check_address_keypath_simple(bip32_path, PURPOSE_P2WPKH_P2SH):
raise keypath_exception
elif bech32:
script_config = bitbox02.btc.BTCScriptConfig(
simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH
)
if not self._check_address_keypath_simple(bip32_path, PURPOSE_P2WPKH):
raise keypath_exception
else:
raise UnavailableActionError(
"The BitBox02 does not support legacy p2pkh addresses"
)
address = self.init().btc_address(
parse_path(bip32_path),
_parse_path(bip32_path),
coin=self._get_coin(),
script_config=script_config,
display=True,
@ -410,9 +388,6 @@ class Bitbox02Client(HardwareWalletClient):
@bitbox02_exception
def sign_tx(self, psbt: PSBT) -> Dict[str, str]:
SCRIPT_CONFIG_INDEX_P2WPKH = 0
SCRIPT_CONFIG_INDEX_P2WPKH_P2SH = 1
def find_our_key(
keypaths: Dict[bytes, Sequence[int]]
) -> Tuple[Optional[bytes], Optional[Sequence[int]]]:
@ -625,7 +600,9 @@ class Bitbox02Client(HardwareWalletClient):
return {"psbt": psbt.serialize()}
def sign_message(self, message: str, bip32_path: str) -> Dict[str, str]:
def sign_message(
self, message: Union[str, bytes], bip32_path: str
) -> Dict[str, str]:
raise UnavailableActionError("The BitBox02 does not support 'signmessage'")
@bitbox02_exception
@ -647,9 +624,7 @@ class Bitbox02Client(HardwareWalletClient):
"Passphrase not needed when setting up a BitBox02."
)
bb02 = self.init()
if bb02.device_info()["initialized"]:
raise UnavailableActionError("The BitBox02 must be wiped before setup.")
bb02 = self.init(expect_initialized=False)
if label:
bb02.set_device_name(label)
@ -676,9 +651,7 @@ class Bitbox02Client(HardwareWalletClient):
def restore_device(
self, label: str = "", word_count: int = 24
) -> Dict[str, Union[bool, str, int]]:
bb02 = self.init()
if bb02.device_info()["initialized"]:
raise UnavailableActionError("The BitBox02 must be wiped before restore.")
bb02 = self.init(expect_initialized=False)
if label:
bb02.set_device_name(label)

View File

@ -101,14 +101,14 @@ class bitcoinTransaction:
inputSize = readVarint(data, offset)
offset += inputSize['size']
numInputs = inputSize['value']
for i in range(numInputs):
for _ in range(numInputs):
tmp = { 'buffer': data, 'offset' : offset}
self.inputs.append(bitcoinInput(tmp))
offset = tmp['offset']
outputSize = readVarint(data, offset)
offset += outputSize['size']
numOutputs = outputSize['value']
for i in range(numOutputs):
for _ in range(numOutputs):
tmp = { 'buffer': data, 'offset' : offset}
self.outputs.append(bitcoinOutput(tmp))
offset = tmp['offset']

View File

@ -84,7 +84,7 @@ class btchip:
self.scriptBlockLength = 50
else:
self.scriptBlockLength = 255
except:
except Exception:
pass
def getWalletPublicKey(self, path, showOnScreen=False, segwit=False, segwitNative=False, cashAddr=False):
@ -271,7 +271,7 @@ class btchip:
response = self.dongle.exchange(bytearray(apdu))
offset += dataLength
alternateEncoding = True
except:
except Exception:
pass
if not alternateEncoding:
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_FINALIZE, 0x02, 0x00 ]

View File

@ -142,7 +142,7 @@ class HIDDongleHIDAPI(Dongle, DongleWait):
if self.opened:
try:
self.device.close()
except:
except Exception:
pass
self.opened = False
@ -155,7 +155,7 @@ class DongleServer(Dongle):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
self.socket.connect((self.server, self.port))
except:
except Exception:
raise BTChipException("Proxy connection failed")
def exchange(self, apdu, timeout=20000):
@ -175,5 +175,5 @@ class DongleServer(Dongle):
def close(self):
try:
self.socket.close()
except:
except Exception:
pass

View File

@ -159,10 +159,10 @@ class ColdcardDevice:
print("Rx [%2d]: %r" % (len(resp), b2a_hex(bytes(resp))))
return CCProtocolUnpacker.decode(resp)
except CCProtoError as e:
except CCProtoError:
if expect_errors: raise
raise
except:
except Exception:
#print("Corrupt response: %r" % resp)
raise
@ -258,7 +258,7 @@ class ColdcardDevice:
# If Pycoin is not available, do it using ecdsa
from ecdsa import BadSignatureError, SECP256k1, VerifyingKey
pubkey, chaincode = decode_xpub(expected_xpub)
pubkey, _ = decode_xpub(expected_xpub)
vk = VerifyingKey.from_string(get_pubkey_string(pubkey), curve=SECP256k1)
try:
ok = vk.verify_digest(sig[1:], self.session_key)
@ -376,7 +376,8 @@ class UnixSimulatorPipe:
self.pipe.close()
try:
os.unlink(self.pipe_name)
except: pass
except Exception:
pass
def get_serial_number_string(self):
return 'simulator'

View File

@ -24,14 +24,14 @@ def dfu_parse(fd):
assert dfu_prefix.signature == b'DfuSe', "Not a DFU file (bad magic)"
for idx in range(dfu_prefix.targets):
for _ in range(dfu_prefix.targets):
prefix = consume(fd, 'Target', '<6sBI255s2I',
'signature altsetting named name size elements')
#print("target%d: %r" % (idx, prefix))
for ei in range(prefix.elements):
for _ in range(prefix.elements):
# Decode target prefix
# < little endian
# I uint32_t element address

View File

@ -1,5 +1,7 @@
# Coldcard interaction script
from typing import Dict, Union
from ..hwwclient import HardwareWalletClient
from ..errors import (
ActionCanceledError,
@ -122,7 +124,7 @@ class ColdcardClient(HardwareWalletClient):
if our_keys > passes:
passes = our_keys
for i in range(0, passes):
for _ in range(passes):
# Get psbt in hex and then make binary
fd = io.BytesIO(base64.b64decode(tx.serialize()))
@ -174,15 +176,18 @@ class ColdcardClient(HardwareWalletClient):
tx.deserialize(base64.b64encode(result).decode())
return {'psbt': tx.serialize()}
# Must return a base64 encoded string with the signed message
# The message can be any string. keypath is the bip 32 derivation path for the key to sign with
@coldcard_exception
def sign_message(self, message, keypath):
def sign_message(self, message: Union[str, bytes], keypath: str) -> Dict[str, str]:
self.device.check_mitm()
keypath = keypath.replace('h', '\'')
keypath = keypath.replace('H', '\'')
ok = self.device.send_recv(CCProtocolPacker.sign_message(message.encode(), keypath, AF_CLASSIC), timeout=None)
msg = message
if not isinstance(message, bytes):
msg = message.encode()
ok = self.device.send_recv(
CCProtocolPacker.sign_message(msg, keypath, AF_CLASSIC), timeout=None
)
assert ok is None
if self.device.is_simulator:
self.device.send_recv(CCProtocolPacker.sim_keypress(b'y'))
@ -198,14 +203,14 @@ class ColdcardClient(HardwareWalletClient):
if len(done) != 2:
raise DeviceFailureError('Failed: %r' % done)
addr, raw = done
_, raw = done
sig = str(base64.b64encode(raw), 'ascii').replace('\n', '')
return {"signature": sig}
# Display address of specified type on the device.
@coldcard_exception
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None):
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None, descriptor=None):
self.device.check_mitm()
keypath = keypath.replace('h', '\'')
keypath = keypath.replace('H', '\'')

View File

@ -13,6 +13,7 @@ import logging
import socket
import sys
import time
from typing import Dict, Union
from ..hwwclient import HardwareWalletClient
from ..errors import (
@ -512,14 +513,15 @@ class DigitalbitboxClient(HardwareWalletClient):
return {'psbt': tx.serialize()}
# Must return a base64 encoded string with the signed message
# The message can be any string
@digitalbitbox_exception
def sign_message(self, message, keypath):
def sign_message(self, message: Union[str, bytes], keypath: str) -> Dict[str, str]:
to_hash = b""
to_hash += self.message_magic
to_hash += ser_compact_size(len(message))
to_hash += message.encode()
if isinstance(message, bytes):
to_hash += message
else:
to_hash += message.encode()
hashed_message = hash256(to_hash)
@ -549,7 +551,7 @@ class DigitalbitboxClient(HardwareWalletClient):
return {"signature": base64.b64encode(compact_sig).decode('utf-8')}
# Display address of specified type on the device.
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None):
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None, descriptor=None):
raise UnavailableActionError('The Digital Bitbox does not have a screen to display addresses on')
# Setup a new device
@ -629,7 +631,7 @@ def enumerate(password=''):
dev.send_recv(b'{"device" : "info"}')
devices.append({'path': b'udp:127.0.0.1:35345', 'interface_number': 0})
dev.close()
except:
except Exception:
pass
for d in devices:
if ('interface_number' in d and d['interface_number'] == 0

View File

@ -1,5 +1,7 @@
# Ledger interaction script
from typing import Dict, Union
from ..hwwclient import HardwareWalletClient
from ..errors import (
ActionCanceledError,
@ -38,10 +40,14 @@ import re
SIMULATOR_PATH = 'tcp:127.0.0.1:9999'
LEDGER_VENDOR_ID = 0x2c97
LEDGER_DEVICE_IDS = [
0x0001, # Ledger Nano S
0x0004, # Ledger Nano X
]
LEDGER_MODEL_IDS = {
0x10: "ledger_nano_s",
0x40: "ledger_nano_x"
}
LEDGER_LEGACY_PRODUCT_IDS = {
0x0001: "ledger_nano_s",
0x0004: "ledger_nano_x"
}
# minimal checking of string keypath
def check_keypath(key_path):
@ -311,13 +317,14 @@ class LedgerClient(HardwareWalletClient):
# Send PSBT back
return {'psbt': tx.serialize()}
# Must return a base64 encoded string with the signed message
# The message can be any string
@ledger_exception
def sign_message(self, message, keypath):
def sign_message(self, message: Union[str, bytes], keypath: str) -> Dict[str, str]:
if not check_keypath(keypath):
raise BadArgumentError("Invalid keypath")
message = bytearray(message, 'utf-8')
if isinstance(message, str):
message = bytearray(message, 'utf-8')
else:
message = bytearray(message)
keypath = keypath[2:]
# First display on screen what address you're signing for
self.app.getWalletPublicKey(keypath, True)
@ -340,7 +347,7 @@ class LedgerClient(HardwareWalletClient):
# Display address of specified type on the device. Only supports single-key based addresses.
@ledger_exception
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None):
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None, descriptor=None):
if not check_keypath(keypath):
raise BadArgumentError("Invalid keypath")
if redeem_script is not None:
@ -383,9 +390,8 @@ class LedgerClient(HardwareWalletClient):
def enumerate(password=''):
results = []
devices = []
for device_id in LEDGER_DEVICE_IDS:
devices.extend(hid.enumerate(LEDGER_VENDOR_ID, device_id))
devices.append({'path': SIMULATOR_PATH.encode(), 'interface_number': 0, 'product_id': 1})
devices.extend(hid.enumerate(LEDGER_VENDOR_ID, 0))
devices.append({'path': SIMULATOR_PATH.encode(), 'interface_number': 0, 'product_id': 0x1000})
for d in devices:
if ('interface_number' in d and d['interface_number'] == 0
@ -394,7 +400,13 @@ def enumerate(password=''):
path = d['path'].decode()
d_data['type'] = 'ledger'
d_data['model'] = 'ledger_nano_x' if d['product_id'] == 0x0004 else 'ledger_nano_s'
model = d['product_id'] >> 8
if model in LEDGER_MODEL_IDS.keys():
d_data['model'] = LEDGER_MODEL_IDS[model]
elif d['product_id'] in LEDGER_LEGACY_PRODUCT_IDS.keys():
d_data['model'] = LEDGER_LEGACY_PRODUCT_IDS[d['product_id']]
else:
continue
d_data['path'] = path
if path == SIMULATOR_PATH:

View File

@ -1,5 +1,7 @@
# Trezor interaction script
from typing import Dict, Union
from ..hwwclient import HardwareWalletClient
from ..errors import (
ActionCanceledError,
@ -257,7 +259,7 @@ class TrezorClient(HardwareWalletClient):
if is_ms:
# Add to txinputtype
txinputtype.multisig = multisig
if not psbt_in.witness_utxo:
if not is_wit:
if utxo.is_p2sh:
txinputtype.script_type = proto.InputScriptType.SPENDMULTISIG
else:
@ -399,10 +401,8 @@ class TrezorClient(HardwareWalletClient):
return {'psbt': tx.serialize()}
# Must return a base64 encoded string with the signed message
# The message can be any string
@trezor_exception
def sign_message(self, message, keypath):
def sign_message(self, message: Union[str, bytes], keypath: str) -> Dict[str, str]:
self._check_unlocked()
path = tools.parse_path(keypath)
result = btc.sign_message(self.client, self.coin_name, path, message)
@ -410,11 +410,20 @@ class TrezorClient(HardwareWalletClient):
# Display address of specified type on the device.
@trezor_exception
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None):
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None, descriptor=None):
self._check_unlocked()
# descriptor means multisig with xpubs
if descriptor:
pubkeys = []
xpub = ExtendedKey()
for i in range(0, descriptor.multisig_N):
xpub.deserialize(descriptor.base_key[i])
hd_node = proto.HDNodeType(depth=xpub.depth, fingerprint=int.from_bytes(xpub.parent_fingerprint, 'big'), child_num=xpub.child_num, chain_code=xpub.chaincode, public_key=xpub.pubkey)
pubkeys.append(proto.HDNodePathType(node=hd_node, address_n=tools.parse_path('m' + descriptor.path_suffix[i])))
multisig = proto.MultisigRedeemScriptType(m=int(descriptor.multisig_M), signatures=[b''] * int(descriptor.multisig_N), pubkeys=pubkeys)
# redeem_script means p2sh/multisig
if redeem_script:
elif redeem_script:
# Get multisig object required by Trezor's get_address
multisig = parse_multisig(bytes.fromhex(redeem_script))
if not multisig[0]:
@ -451,7 +460,7 @@ class TrezorClient(HardwareWalletClient):
multisig=multisig,
)
return {'address': address}
except:
except Exception:
pass
raise BadArgumentError("No path supplied matched device keys")
@ -534,7 +543,7 @@ class TrezorClient(HardwareWalletClient):
self._check_unlocked()
try:
device.apply_settings(self.client, use_passphrase=not self.client.features.passphrase_protection)
except:
except Exception:
if self.type == 'Keepkey':
print('Confirm the action by entering your PIN', file=sys.stderr)
print('Use \'sendpin\' to provide the number positions for the PIN as displayed on your device\'s screen', file=sys.stderr)

View File

@ -15,9 +15,10 @@
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import binascii
from typing import Union
from . import messages
from .tools import CallException, expect, normalize_nfc, session
from .tools import expect, normalize_nfc, session
@expect(messages.PublicKey)
@ -62,7 +63,11 @@ def get_address(
@expect(messages.MessageSignature)
def sign_message(
client, coin_name, n, message, script_type=messages.InputScriptType.SPENDADDRESS
client,
coin_name,
n,
message: Union[str, bytes],
script_type=messages.InputScriptType.SPENDADDRESS,
):
message = normalize_nfc(message)
return client.call(
@ -71,6 +76,7 @@ def sign_message(
)
)
@session
def sign_tx(client, coin_name, inputs, outputs, details=None, prev_txes=None):
this_tx = messages.TransactionType(inputs=inputs, outputs=outputs)

View File

@ -72,7 +72,7 @@ class TrezorClient:
"""
def __init__(self, transport, ui=None, state=None):
LOG.info("creating client instance for device: {}".format(transport.get_path()))
LOG.debug("creating client instance for device: {}".format(transport.get_path()))
self.transport = transport
self.ui = ui
self.state = state
@ -149,7 +149,7 @@ class TrezorClient:
try:
passphrase = self.ui.get_passphrase()
except:
except Exception:
self.call_raw(messages.Cancel())
raise

View File

@ -14,6 +14,7 @@
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import logging
import os
import time
import warnings
@ -25,6 +26,8 @@ from .transport import enumerate_devices, get_transport
RECOVERY_BACK = "\x08" # backspace character, sent literally
LOG = logging.getLogger(__name__)
class TrezorDevice:
"""
@ -192,7 +195,7 @@ def reset(
raise RuntimeError("Invalid response, expected EntropyRequest")
external_entropy = os.urandom(32)
# LOG.debug("Computer generated entropy: " + external_entropy.hex())
LOG.debug("Computer generated entropy: " + external_entropy.hex())
ret = client.call(proto.EntropyAck(entropy=external_entropy))
client.init_device()
return ret

View File

@ -158,7 +158,7 @@ class MessageType:
def _fill_missing(self):
# fill missing fields
for fname, ftype, fflags in self.get_fields().values():
for fname, _, fflags in self.get_fields().values():
if not hasattr(self, fname):
if fflags & FLAG_REPEATED:
setattr(self, fname, [])

View File

@ -19,7 +19,7 @@ import hashlib
import re
import struct
import unicodedata
from typing import List, NewType
from typing import List, NewType, Union
from .exceptions import TrezorFailure
@ -181,13 +181,13 @@ def parse_path(nstr: str) -> Address:
raise ValueError("Invalid BIP32 path", nstr)
def normalize_nfc(txt):
def normalize_nfc(txt: Union[str, bytes]) -> bytes:
"""
Normalize message to NFC and return bytes suitable for protobuf.
This seems to be bitcoin-qt standard of doing things.
"""
if isinstance(txt, bytes):
txt = txt.decode()
return txt
return unicodedata.normalize("NFC", txt).encode()

View File

@ -121,7 +121,7 @@ def enumerate_devices() -> Iterable[Transport]:
name = transport.__name__
try:
found = list(transport.enumerate())
LOG.info("Enumerating {}: found {} devices".format(name, len(found)))
LOG.debug("Enumerating {}: found {} devices".format(name, len(found)))
devices.extend(found)
except NotImplementedError:
LOG.error("{} does not implement device enumeration".format(name))
@ -144,7 +144,7 @@ def get_transport(path: str = None, prefix_search: bool = False) -> Transport:
def match_prefix(a: str, b: str) -> bool:
return a.startswith(b) or b.startswith(a)
LOG.info(
LOG.debug(
"looking for device by {}: {}".format(
"prefix" if prefix_search else "full path", path
)

View File

@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__)
try:
import hid
except Exception as e:
LOG.info("HID transport is disabled: {}".format(e))
LOG.warning("HID transport is disabled: {}".format(e))
hid = None

View File

@ -129,7 +129,7 @@ class WebUsbTransport(ProtocolBasedTransport):
# non-functional.
dev.getProduct()
devices.append(WebUsbTransport(dev))
except:
except Exception:
pass
return devices

View File

@ -3,6 +3,7 @@
import json
import logging
import sys
import time
from typing import Callable
from . import commands, __version__
@ -220,6 +221,11 @@ class BitBox02PairingDialog(QDialog):
self.ui.pairingCode.setText(pairing_code.replace("\n", "<br>"))
self.ui.buttonBox.setEnabled(False)
self.device_response = device_response
self.painted = False
def paintEvent(self, ev):
super().paintEvent(ev)
self.painted = True
def enable_buttons(self):
self.ui.buttonBox.setEnabled(True)
@ -231,7 +237,11 @@ class BitBox02NoiseConfig(bitbox02.util.BitBoxAppNoiseConfig):
dialog = BitBox02PairingDialog(code, device_response)
dialog.show()
# render the window since the next operation is blocking
QCoreApplication.processEvents()
while True:
QCoreApplication.processEvents()
if dialog.painted:
break
time.sleep(0.1)
if not device_response():
return False
dialog.enable_buttons()
@ -240,7 +250,8 @@ class BitBox02NoiseConfig(bitbox02.util.BitBoxAppNoiseConfig):
def attestation_check(self, result: bool) -> None:
if not result:
QMessageBox.warning(None,
QMessageBox.warning(
None,
"BitBox02 attestation check",
"BitBox02 attestation check failed. Your BitBox02 might not be genuine. Please contact support@shiftcrypto.ch if the problem persists.",
)

View File

@ -1,6 +1,7 @@
from typing import Dict, Optional, Union
from .base58 import get_xpub_fingerprint_hex
from .descriptor import Descriptor
from .serializations import PSBT
@ -55,10 +56,14 @@ class HardwareWalletClient(object):
raise NotImplementedError("The HardwareWalletClient base class "
"does not implement this method")
def sign_message(self, message: str, bip32_path: str) -> Dict[str, str]:
def sign_message(
self, message: Union[str, bytes], bip32_path: str
) -> Dict[str, str]:
"""Sign a message (bitcoin message signing).
Sign the message according to the bitcoin message signing standard.
Sign the message according to the bitcoin message signing standard:
usually, the message is a string that is encoded to bytes;
anyway, if the message is already bytes it is processed untouched.
Retrieve the signing key at the specified BIP32 derivation path.
@ -73,6 +78,7 @@ class HardwareWalletClient(object):
p2sh_p2wpkh: bool,
bech32: bool,
redeem_script: Optional[str] = None,
descriptor: Optional[Descriptor] = None,
) -> Dict[str, str]:
"""Display and return the address of specified type.
@ -156,7 +162,7 @@ class HardwareWalletClient(object):
raise NotImplementedError("The HardwareWalletClient base class "
"does not implement this method")
def send_pin(self) -> Dict[str, Union[bool, str, int]]:
def send_pin(self, pin: str) -> Dict[str, Union[bool, str, int]]:
"""Send PIN.
Must return a dictionary with the "success" key,

0
hwilib/py.typed Normal file
View File

View File

@ -103,7 +103,7 @@ def deser_uint256(f: Readable) -> int:
def ser_uint256(u: int) -> bytes:
rs = b""
for i in range(8):
for _ in range(8):
rs += struct.pack("<I", u & 0xFFFFFFFF)
u >>= 32
return rs
@ -121,7 +121,7 @@ D = TypeVar("D", bound=Deserializable)
def deser_vector(f: Readable, c: Callable[[], D]) -> List[D]:
nit = deser_compact_size(f)
r = []
for i in range(nit):
for _ in range(nit):
t = c()
t.deserialize(f)
r.append(t)
@ -138,7 +138,7 @@ def ser_vector(v: Sequence[Serializable]) -> bytes:
def deser_string_vector(f: Readable) -> List[bytes]:
nit = deser_compact_size(f)
r = []
for i in range(nit):
for _ in range(nit):
t = deser_string(f)
r.append(t)
return r
@ -456,7 +456,7 @@ class CTransaction(object):
if (len(self.wit.vtxinwit) != len(self.vin)):
# vtxinwit must have the same length as vin
self.wit.vtxinwit = self.wit.vtxinwit[:len(self.vin)]
for i in range(len(self.wit.vtxinwit), len(self.vin)):
for _ in range(len(self.wit.vtxinwit), len(self.vin)):
self.wit.vtxinwit.append(CTxInWitness())
r += self.wit.serialize()
r += struct.pack("<I", self.nLockTime)

View File

@ -1,9 +1,12 @@
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1b7c", MODE="0660", GROUP="plugdev"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="2b7c", MODE="0660", GROUP="plugdev"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="3b7c", MODE="0660", GROUP="plugdev"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="4b7c", MODE="0660", GROUP="plugdev"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1807", MODE="0660", GROUP="plugdev"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1808", MODE="0660", GROUP="plugdev"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0000", MODE="0660", GROUP="plugdev"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0001", MODE="0660", GROUP="plugdev"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0004", MODE="0660", GROUP="plugdev"
# HW.1 / Nano
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1b7c|2b7c|3b7c|4b7c", TAG+="uaccess", TAG+="udev-acl"
# Blue
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0000|0000|0001|0002|0003|0004|0005|0006|0007|0008|0009|000a|000b|000c|000d|000e|000f|0010|0011|0012|0013|0014|0015|0016|0017|0018|0019|001a|001b|001c|001d|001e|001f", TAG+="uaccess", TAG+="udev-acl"
# Nano S
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0001|1000|1001|1002|1003|1004|1005|1006|1007|1008|1009|100a|100b|100c|100d|100e|100f|1010|1011|1012|1013|1014|1015|1016|1017|1018|1019|101a|101b|101c|101d|101e|101f", TAG+="uaccess", TAG+="udev-acl"
# Aramis
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0002|2000|2001|2002|2003|2004|2005|2006|2007|2008|2009|200a|200b|200c|200d|200e|200f|2010|2011|2012|2013|2014|2015|2016|2017|2018|2019|201a|201b|201c|201d|201e|201f", TAG+="uaccess", TAG+="udev-acl"
# HW2
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0003|3000|3001|3002|3003|3004|3005|3006|3007|3008|3009|300a|300b|300c|300d|300e|300f|3010|3011|3012|3013|3014|3015|3016|3017|3018|3019|301a|301b|301c|301d|301e|301f", TAG+="uaccess", TAG+="udev-acl"
# Nano X
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0004|4000|4001|4002|4003|4004|4005|4006|4007|4008|4009|400a|400b|400c|400d|400e|400f|4010|4011|4012|4013|4014|4015|4016|4017|4018|4019|401a|401b|401c|401d|401e|401f", TAG+="uaccess", TAG+="udev-acl"

200
poetry.lock generated
View File

@ -17,6 +17,62 @@ version = "1.5.2"
[package.dependencies]
pycodestyle = ">=2.5.0"
[[package]]
category = "main"
description = "Base58 and Base58Check implementation"
name = "base58"
optional = false
python-versions = ">=3.5"
version = "2.0.1"
[[package]]
category = "main"
description = "Python library for bitbox02 communication"
name = "bitbox02"
optional = false
python-versions = ">=3.6"
version = "4.1.0"
[package.dependencies]
base58 = ">=2.0.0"
ecdsa = ">=0.13"
hidapi = ">=0.7.99.post21"
noiseprotocol = ">=0.3"
protobuf = ">=3.7"
semver = ">=2.8.1"
typing-extensions = ">=3.7.4"
[[package]]
category = "main"
description = "Foreign Function Interface for Python calling C code."
name = "cffi"
optional = false
python-versions = "*"
version = "1.14.1"
[package.dependencies]
pycparser = "*"
[[package]]
category = "main"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
name = "cryptography"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "3.0"
[package.dependencies]
cffi = ">=1.8,<1.11.3 || >1.11.3"
six = ">=1.4.1"
[package.extras]
docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0,<3.1.0 || >3.1.0,<3.1.1 || >3.1.1)", "sphinx-rtd-theme"]
docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
idna = ["idna (>=2.1)"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"]
[[package]]
category = "dev"
description = "Python 2.7 backport of the \"dis\" module from Python 3.5+"
@ -125,6 +181,17 @@ version = "0.18"
[package.dependencies]
pbkdf2 = "*"
[[package]]
category = "main"
description = "Implementation of Noise Protocol Framework"
name = "noiseprotocol"
optional = false
python-versions = "~=3.5"
version = "0.3.1"
[package.dependencies]
cryptography = ">=2.8"
[[package]]
category = "main"
description = "PKCS#5 v2.0 PBKDF2 Module"
@ -145,6 +212,18 @@ version = "2019.4.18"
[package.dependencies]
future = "*"
[[package]]
category = "main"
description = "Protocol Buffers"
name = "protobuf"
optional = false
python-versions = "*"
version = "3.12.4"
[package.dependencies]
setuptools = "*"
six = ">=1.9"
[[package]]
category = "main"
description = "Pure-Python Implementation of the AES block-cipher and common modes of operation"
@ -161,6 +240,14 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.6.0"
[[package]]
category = "main"
description = "C parser in Python"
name = "pycparser"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.20"
[[package]]
category = "dev"
description = "passive checker of Python programs"
@ -202,6 +289,14 @@ optional = false
python-versions = "*"
version = "0.2.0"
[[package]]
category = "main"
description = "Python helper for Semantic Versioning (http://semver.org/)"
name = "semver"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.10.2"
[[package]]
category = "main"
description = "Python / C++ bindings helper module"
@ -210,6 +305,14 @@ optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <3.9"
version = "5.14.2.1"
[[package]]
category = "main"
description = "Python 2 and 3 compatibility utilities"
name = "six"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
version = "1.15.0"
[[package]]
category = "main"
description = "Backported and Experimental Type Hints for Python 3.5+"
@ -235,7 +338,8 @@ testing = ["jaraco.itertools", "func-timeout"]
qt = ["pyside2"]
[metadata]
content-hash = "bce2677f0c45cb74f03eecfab2094577eed8fb16fe305a21c924128c4faa1b47"
content-hash = "fc39b2be42f870113feb38f56c177164fb7302eb07616d850f5a1b9e3e8509e1"
lock-version = "1.0"
python-versions = "^3.6,<3.9"
[metadata.files]
@ -246,6 +350,65 @@ altgraph = [
autopep8 = [
{file = "autopep8-1.5.2.tar.gz", hash = "sha256:152fd8fe47d02082be86e05001ec23d6f420086db56b17fc883f3f965fb34954"},
]
base58 = [
{file = "base58-2.0.1-py3-none-any.whl", hash = "sha256:447adc750d6b642987ffc6d397ecd15a799852d5f6a1d308d384500243825058"},
{file = "base58-2.0.1.tar.gz", hash = "sha256:365c9561d9babac1b5f18ee797508cd54937a724b6e419a130abad69cec5ca79"},
]
bitbox02 = [
{file = "bitbox02-4.1.0-py3-none-any.whl", hash = "sha256:1af95952d67b74c80ccc0588e0aee983c764960da637bd24bc41a1cb89d5e127"},
{file = "bitbox02-4.1.0.tar.gz", hash = "sha256:73a35594162f32897dd2b1880f0cfaa42922acd1c2d7f4cf3d94b8333329c931"},
]
cffi = [
{file = "cffi-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:66dd45eb9530e3dde8f7c009f84568bc7cac489b93d04ac86e3111fb46e470c2"},
{file = "cffi-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:4f53e4128c81ca3212ff4cf097c797ab44646a40b42ec02a891155cd7a2ba4d8"},
{file = "cffi-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:833401b15de1bb92791d7b6fb353d4af60dc688eaa521bd97203dcd2d124a7c1"},
{file = "cffi-1.14.1-cp27-cp27m-win32.whl", hash = "sha256:26f33e8f6a70c255767e3c3f957ccafc7f1f706b966e110b855bfe944511f1f9"},
{file = "cffi-1.14.1-cp27-cp27m-win_amd64.whl", hash = "sha256:b87dfa9f10a470eee7f24234a37d1d5f51e5f5fa9eeffda7c282e2b8f5162eb1"},
{file = "cffi-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:effd2ba52cee4ceff1a77f20d2a9f9bf8d50353c854a282b8760ac15b9833168"},
{file = "cffi-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bac0d6f7728a9cc3c1e06d4fcbac12aaa70e9379b3025b27ec1226f0e2d404cf"},
{file = "cffi-1.14.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d6033b4ffa34ef70f0b8086fd4c3df4bf801fee485a8a7d4519399818351aa8e"},
{file = "cffi-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8416ed88ddc057bab0526d4e4e9f3660f614ac2394b5e019a628cdfff3733849"},
{file = "cffi-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:892daa86384994fdf4856cb43c93f40cbe80f7f95bb5da94971b39c7f54b3a9c"},
{file = "cffi-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:c991112622baee0ae4d55c008380c32ecfd0ad417bcd0417ba432e6ba7328caa"},
{file = "cffi-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fcf32bf76dc25e30ed793145a57426064520890d7c02866eb93d3e4abe516948"},
{file = "cffi-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f960375e9823ae6a07072ff7f8a85954e5a6434f97869f50d0e41649a1c8144f"},
{file = "cffi-1.14.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a6d28e7f14ecf3b2ad67c4f106841218c8ab12a0683b1528534a6c87d2307af3"},
{file = "cffi-1.14.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cda422d54ee7905bfc53ee6915ab68fe7b230cacf581110df4272ee10462aadc"},
{file = "cffi-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:4a03416915b82b81af5502459a8a9dd62a3c299b295dcdf470877cb948d655f2"},
{file = "cffi-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:4ce1e995aeecf7cc32380bc11598bfdfa017d592259d5da00fc7ded11e61d022"},
{file = "cffi-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e23cb7f1d8e0f93addf0cae3c5b6f00324cccb4a7949ee558d7b6ca973ab8ae9"},
{file = "cffi-1.14.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ddff0b2bd7edcc8c82d1adde6dbbf5e60d57ce985402541cd2985c27f7bec2a0"},
{file = "cffi-1.14.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f90c2267101010de42f7273c94a1f026e56cbc043f9330acd8a80e64300aba33"},
{file = "cffi-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:3cd2c044517f38d1b577f05927fb9729d3396f1d44d0c659a445599e79519792"},
{file = "cffi-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fa72a52a906425416f41738728268072d5acfd48cbe7796af07a923236bcf96"},
{file = "cffi-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:267adcf6e68d77ba154334a3e4fc921b8e63cbb38ca00d33d40655d4228502bc"},
{file = "cffi-1.14.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d3148b6ba3923c5850ea197a91a42683f946dba7e8eb82dfa211ab7e708de939"},
{file = "cffi-1.14.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:98be759efdb5e5fa161e46d404f4e0ce388e72fbf7d9baf010aff16689e22abe"},
{file = "cffi-1.14.1-cp38-cp38-win32.whl", hash = "sha256:6923d077d9ae9e8bacbdb1c07ae78405a9306c8fd1af13bfa06ca891095eb995"},
{file = "cffi-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:b1d6ebc891607e71fd9da71688fcf332a6630b7f5b7f5549e6e631821c0e5d90"},
{file = "cffi-1.14.1.tar.gz", hash = "sha256:b2a2b0d276a136146e012154baefaea2758ef1f56ae9f4e01c612b0831e0bd2f"},
]
cryptography = [
{file = "cryptography-3.0-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:ab49edd5bea8d8b39a44b3db618e4783ef84c19c8b47286bf05dfdb3efb01c83"},
{file = "cryptography-3.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:124af7255ffc8e964d9ff26971b3a6153e1a8a220b9a685dc407976ecb27a06a"},
{file = "cryptography-3.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:51e40123083d2f946794f9fe4adeeee2922b581fa3602128ce85ff813d85b81f"},
{file = "cryptography-3.0-cp27-cp27m-win32.whl", hash = "sha256:dea0ba7fe6f9461d244679efa968d215ea1f989b9c1957d7f10c21e5c7c09ad6"},
{file = "cryptography-3.0-cp27-cp27m-win_amd64.whl", hash = "sha256:8ecf9400d0893836ff41b6f977a33972145a855b6efeb605b49ee273c5e6469f"},
{file = "cryptography-3.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c608ff4d4adad9e39b5057de43657515c7da1ccb1807c3a27d4cf31fc923b4b"},
{file = "cryptography-3.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:bec7568c6970b865f2bcebbe84d547c52bb2abadf74cefce396ba07571109c67"},
{file = "cryptography-3.0-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:0cbfed8ea74631fe4de00630f4bb592dad564d57f73150d6f6796a24e76c76cd"},
{file = "cryptography-3.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:a09fd9c1cca9a46b6ad4bea0a1f86ab1de3c0c932364dbcf9a6c2a5eeb44fa77"},
{file = "cryptography-3.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:ce82cc06588e5cbc2a7df3c8a9c778f2cb722f56835a23a68b5a7264726bb00c"},
{file = "cryptography-3.0-cp35-cp35m-win32.whl", hash = "sha256:9367d00e14dee8d02134c6c9524bb4bd39d4c162456343d07191e2a0b5ec8b3b"},
{file = "cryptography-3.0-cp35-cp35m-win_amd64.whl", hash = "sha256:384d7c681b1ab904fff3400a6909261cae1d0939cc483a68bdedab282fb89a07"},
{file = "cryptography-3.0-cp36-cp36m-win32.whl", hash = "sha256:4d355f2aee4a29063c10164b032d9fa8a82e2c30768737a2fd56d256146ad559"},
{file = "cryptography-3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:45741f5499150593178fc98d2c1a9c6722df88b99c821ad6ae298eff0ba1ae71"},
{file = "cryptography-3.0-cp37-cp37m-win32.whl", hash = "sha256:8ecef21ac982aa78309bb6f092d1677812927e8b5ef204a10c326fc29f1367e2"},
{file = "cryptography-3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4b9303507254ccb1181d1803a2080a798910ba89b1a3c9f53639885c90f7a756"},
{file = "cryptography-3.0-cp38-cp38-win32.whl", hash = "sha256:8713ddb888119b0d2a1462357d5946b8911be01ddbf31451e1d07eaa5077a261"},
{file = "cryptography-3.0-cp38-cp38-win_amd64.whl", hash = "sha256:bea0b0468f89cdea625bb3f692cd7a4222d80a6bdafd6fb923963f2b9da0e15f"},
{file = "cryptography-3.0.tar.gz", hash = "sha256:8e924dbc025206e97756e8903039662aa58aa9ba357d8e1d8fc29e3092322053"},
]
dis3 = [
{file = "dis3-0.1.3-py2-none-any.whl", hash = "sha256:61f7720dd0d8749d23fda3d7227ce74d73da11c2fade993a67ab2f9852451b14"},
{file = "dis3-0.1.3-py3-none-any.whl", hash = "sha256:30b6412d33d738663e8ded781b138f4b01116437f0872aa56aa3adba6aeff218"},
@ -293,12 +456,35 @@ mccabe = [
mnemonic = [
{file = "mnemonic-0.18.tar.gz", hash = "sha256:02a7306a792370f4a0c106c2cf1ce5a0c84b9dbd7e71c6792fdb9ad88a727f1d"},
]
noiseprotocol = [
{file = "noiseprotocol-0.3.1-py3-none-any.whl", hash = "sha256:2e1a603a38439636cf0ffd8b3e8b12cee27d368a28b41be7dbe568b2abb23111"},
]
pbkdf2 = [
{file = "pbkdf2-1.3.tar.gz", hash = "sha256:ac6397369f128212c43064a2b4878038dab78dab41875364554aaf2a684e6979"},
]
pefile = [
{file = "pefile-2019.4.18.tar.gz", hash = "sha256:a5d6e8305c6b210849b47a6174ddf9c452b2888340b8177874b862ba6c207645"},
]
protobuf = [
{file = "protobuf-3.12.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3d59825cba9447e8f4fcacc1f3c892cafd28b964e152629b3f420a2fb5918b5a"},
{file = "protobuf-3.12.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:6009f3ebe761fad319b52199a49f1efa7a3729302947a78a3f5ea8e7e89e3ac2"},
{file = "protobuf-3.12.4-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:e2bd5c98952db3f1bb1af2e81b6a208909d3b8a2d32f7525c5cc10a6338b6593"},
{file = "protobuf-3.12.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2becd0e238ae34caf96fa7365b87f65b88aebcf7864dfe5ab461c5005f4256d9"},
{file = "protobuf-3.12.4-cp35-cp35m-win32.whl", hash = "sha256:ef991cbe34d7bb935ba6349406a210d3558b9379c21621c6ed7b99112af7350e"},
{file = "protobuf-3.12.4-cp35-cp35m-win_amd64.whl", hash = "sha256:a7b6cf201e67132ca99b8a6c4812fab541fdce1ceb54bb6f66bc336ab7259138"},
{file = "protobuf-3.12.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4794a7748ee645d2ae305f3f4f0abd459e789c973b5bc338008960f83e0c554b"},
{file = "protobuf-3.12.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f1796e0eb911bf5b08e76b753953effbeb6bc42c95c16597177f627eaa52c375"},
{file = "protobuf-3.12.4-cp36-cp36m-win32.whl", hash = "sha256:c0c8d7c8f07eacd9e98a907941b56e57883cf83de069cfaeaa7e02c582f72ddb"},
{file = "protobuf-3.12.4-cp36-cp36m-win_amd64.whl", hash = "sha256:2db6940c1914fa3fbfabc0e7c8193d9e18b01dbb4650acac249b113be3ba8d9e"},
{file = "protobuf-3.12.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6842284bb15f1b19c50c5fd496f1e2a4cfefdbdfa5d25c02620cb82793295a7"},
{file = "protobuf-3.12.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0b00429b87821f1e6f3d641327864e6f271763ae61799f7540bc58a352825fe2"},
{file = "protobuf-3.12.4-cp37-cp37m-win32.whl", hash = "sha256:f10ba89f9cd508dc00e469918552925ef7cba38d101ca47af1e78f2f9982c6b3"},
{file = "protobuf-3.12.4-cp37-cp37m-win_amd64.whl", hash = "sha256:2636c689a6a2441da9a2ef922a21f9b8bfd5dfe676abd77d788db4b36ea86bee"},
{file = "protobuf-3.12.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:50b7bb2124f6a1fb0ddc6a44428ae3a21e619ad2cdf08130ac6c00534998ef07"},
{file = "protobuf-3.12.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e77ca4e1403b363a88bde9e31c11d093565e925e1685f40b29385a52f2320794"},
{file = "protobuf-3.12.4-py2.py3-none-any.whl", hash = "sha256:32f0bcdf85e0040f36b4f548c71177027f2a618cab00ba235197fa9e230b7289"},
{file = "protobuf-3.12.4.tar.gz", hash = "sha256:c99e5aea75b6f2b29c8d8da5bdc5f5ed8d9a5b4f15115c8316a3f0a850f94656"},
]
pyaes = [
{file = "pyaes-1.6.1.tar.gz", hash = "sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"},
]
@ -306,6 +492,10 @@ pycodestyle = [
{file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"},
{file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"},
]
pycparser = [
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
]
pyflakes = [
{file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"},
{file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"},
@ -325,6 +515,10 @@ pywin32-ctypes = [
{file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"},
{file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"},
]
semver = [
{file = "semver-2.10.2-py2.py3-none-any.whl", hash = "sha256:21e80ca738975ed513cba859db0a0d2faca2380aef1962f48272ebf9a8a44bd4"},
{file = "semver-2.10.2.tar.gz", hash = "sha256:c0a4a9d1e45557297a722ee9bac3de2ec2ea79016b6ffcaca609b0bc62cf4276"},
]
shiboken2 = [
{file = "shiboken2-5.14.2.1-5.14.2-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:d285d476a76f254bff69cc58c1d4385df295b42de1a818d4a8d11694c2d728fc"},
{file = "shiboken2-5.14.2.1-5.14.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:73d03e74f542204e351539e42ab3e3727a69408e1497af4c6e84fb66c3e706d8"},
@ -333,6 +527,10 @@ shiboken2 = [
{file = "shiboken2-5.14.2.1-5.14.2-cp35.cp36.cp37.cp38-none-win32.whl", hash = "sha256:fe4d0cf6737f1d01944be4cf3b401d74015c515ab84622bf04f47d64ffcd39f9"},
{file = "shiboken2-5.14.2.1-5.14.2-cp35.cp36.cp37.cp38-none-win_amd64.whl", hash = "sha256:c022203b7cf01df6ad0bb190d286c2965958243a16e47bee8c5e6bbb9d0cd475"},
]
six = [
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
]
typing-extensions = [
{file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"},
{file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"},

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "hwi"
version = "1.1.2"
version = "1.2.1"
description = "A library for working with Bitcoin hardware wallets"
authors = ["Andrew Chow <andrew@achow101.com>"]
license = "MIT"
@ -8,7 +8,7 @@ readme = "README.md"
repository = "https://github.com/bitcoin-core/HWI"
homepage = "https://github.com/bitcoin-core/HWI"
exclude = ["docs/", "test/"]
include = ["hwilib/**/*.py", "udev/"]
include = ["hwilib/**/*.py", "udev/", "hwilib/py.typed"]
packages = [
{ include = "hwi.py" },
{ include = "hwi-qt.py" },
@ -24,6 +24,7 @@ mnemonic = "^0.18.0"
typing-extensions = "^3.7"
libusb1 = "^1.7"
pyside2 = { version = "^5.14.0", optional = true }
bitbox02 = ">=4.1.0"
[tool.poetry.extras]
qt = ["pyside2"]

View File

@ -13,6 +13,17 @@ packages = \
package_data = \
{'': ['*'],
'hwilib': ['udev/*',
'ui/bitbox02pairing.ui',
'ui/bitbox02pairing.ui',
'ui/bitbox02pairing.ui',
'ui/bitbox02pairing.ui',
'ui/bitbox02pairing.ui',
'ui/bitbox02pairing.ui',
'ui/bitbox02pairing.ui',
'ui/bitbox02pairing.ui',
'ui/bitbox02pairing.ui',
'ui/bitbox02pairing.ui',
'ui/displayaddressdialog.ui',
'ui/displayaddressdialog.ui',
'ui/displayaddressdialog.ui',
'ui/displayaddressdialog.ui',
@ -31,6 +42,8 @@ package_data = \
'ui/getkeypooloptionsdialog.ui',
'ui/getkeypooloptionsdialog.ui',
'ui/getkeypooloptionsdialog.ui',
'ui/getkeypooloptionsdialog.ui',
'ui/getxpubdialog.ui',
'ui/getxpubdialog.ui',
'ui/getxpubdialog.ui',
'ui/getxpubdialog.ui',
@ -49,6 +62,8 @@ package_data = \
'ui/hwiqt.pyproject',
'ui/hwiqt.pyproject',
'ui/hwiqt.pyproject',
'ui/hwiqt.pyproject',
'ui/mainwindow.ui',
'ui/mainwindow.ui',
'ui/mainwindow.ui',
'ui/mainwindow.ui',
@ -67,6 +82,8 @@ package_data = \
'ui/sendpindialog.ui',
'ui/sendpindialog.ui',
'ui/sendpindialog.ui',
'ui/sendpindialog.ui',
'ui/setpassphrasedialog.ui',
'ui/setpassphrasedialog.ui',
'ui/setpassphrasedialog.ui',
'ui/setpassphrasedialog.ui',
@ -85,6 +102,8 @@ package_data = \
'ui/signmessagedialog.ui',
'ui/signmessagedialog.ui',
'ui/signmessagedialog.ui',
'ui/signmessagedialog.ui',
'ui/signpsbtdialog.ui',
'ui/signpsbtdialog.ui',
'ui/signpsbtdialog.ui',
'ui/signpsbtdialog.ui',
@ -98,13 +117,13 @@ package_data = \
modules = \
['hwi', 'hwi-qt']
install_requires = \
['ecdsa>=0.13.0,<0.14.0',
['bitbox02>=4.1.0',
'ecdsa>=0.13.0,<0.14.0',
'hidapi>=0.7.99,<0.8.0',
'libusb1>=1.7,<2.0',
'mnemonic>=0.18.0,<0.19.0',
'pyaes>=1.6,<2.0',
'typing-extensions>=3.7,<4.0',
'bitbox02>=4.1.0']
'typing-extensions>=3.7,<4.0']
extras_require = \
{'qt': ['pyside2>=5.14.0,<6.0.0']}
@ -114,9 +133,9 @@ entry_points = \
setup_kwargs = {
'name': 'hwi',
'version': '1.1.2',
'version': '1.2.1',
'description': 'A library for working with Bitcoin hardware wallets',
'long_description': "# Bitcoin Hardware Wallet Interface\n\n[![Build Status](https://travis-ci.org/bitcoin-core/HWI.svg?branch=master)](https://travis-ci.org/bitcoin-core/HWI)\n\nThe Bitcoin Hardware Wallet Interface is a Python library and command line tool for interacting with hardware wallets.\nIt provides a standard way for software to work with hardware wallets without needing to implement device specific drivers.\nPython software can use the provided library (`hwilib`). Software in other languages can execute the `hwi` tool.\n\n## Prerequisites\n\nPython 3 is required. The libraries and [udev rules](hwilib/udev/README.md) for each device must also be installed. Some libraries will need to be installed\n\nFor Ubuntu/Debian:\n```\nsudo apt install libusb-1.0-0-dev libudev-dev python3-dev\n```\n\nFor Centos:\n```\nsudo yum -y install python3-devel libusbx-devel systemd-devel\n```\n\nFor macOS:\n```\nbrew install libusb\n```\n\n## Install\n\n```\ngit clone https://github.com/bitcoin-core/HWI.git\ncd HWI\npoetry install # or 'pip3 install .' or 'python3 setup.py install'\n```\n\nThis project uses the [Poetry](https://github.com/sdispater/poetry) dependency manager. HWI and its dependencies can be installed via poetry by executing the following in the root source directory:\n\n```\npoetry install\n```\n\nPip can also be used to automatically install HWI and its dependencies using the `setup.py` file (which is usually in sync with `pyproject.toml`):\n\n```\npip3 install .\n```\n\nThe `setup.py` file can be used to install HWI and its dependencies so long as `setuptools` is also installed:\n\n```\npip3 install -U setuptools\npython3 setup.py install\n```\n\n## Dependencies\n\nSee `pyproject.toml` for all dependencies. Dependencies under `[tool.poetry.dependecies]` are user dependencies, and `[tool.poetry.dev-dependencies]` for development based dependencies. These dependencies will be installed with any of the three above installation methods.\n\n## Usage\n\nTo use, first enumerate all devices and find the one that you want to use with\n\n```\n./hwi.py enumerate\n```\n\nOnce the device type and device path is known, issue commands to it like so:\n\n```\n./hwi.py -t <type> -d <path> <command> <command args>\n```\n\nAll output will be in JSON form and sent to `stdout`.\nAdditional information or prompts will be sent to `stderr` and will not necessarily be in JSON.\nThis additional information is for debugging purposes.\n\nTo see a complete list of available commands and global parameters, run\n`./hwi.py --help`. To see options specific to a particular command,\npass the `--help` parameter after the command name; for example:\n\n```\n./hwi.py getdescriptors --help\n```\n\n## Device Support\n\nThe below table lists what devices and features are supported for each device.\n\nPlease also see [docs](docs/) for additional information about each device.\n\n| Feature \\ Device | Ledger Nano X | Ledger Nano S | Trezor One | Trezor Model T | Digital BitBox | KeepKey | Coldcard |\n|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n| Support Planned | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| Implemented | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| xpub retrieval | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| Message Signing | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| Device Setup | N/A | N/A | Yes | Yes | Yes | Yes | N/A |\n| Device Wipe | N/A | N/A | Yes | Yes | Yes | Yes | N/A |\n| Device Recovery | N/A | N/A | Yes | Yes | N/A | Yes | N/A |\n| Device Backup | N/A | N/A | N/A | N/A | Yes | N/A | Yes |\n| P2PKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| P2SH-P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| P2SH Multisig Inputs | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| P2SH-P2WSH Multisig Inputs | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| P2WSH Multisig Inputs | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| Bare Multisig Inputs | Yes | Yes | N/A | N/A | Yes | N/A | N/A |\n| Arbitrary scriptPubKey Inputs | Yes | Yes | N/A | N/A | Yes | N/A | N/A |\n| Arbitrary redeemScript Inputs | Yes | Yes | N/A | N/A | Yes | N/A | N/A |\n| Arbitrary witnessScript Inputs | Yes | Yes | N/A | N/A | Yes | N/A | N/A |\n| Non-wallet inputs | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| Mixed Segwit and Non-Segwit Inputs | N/A | N/A | Yes | N/A | Yes | Yes | Yes |\n| Display on device screen | Yes | Yes | Yes | Yes | N/A | Yes | Yes |\n\n## Using with Bitcoin Core\n\nSee [Using Bitcoin Core with Hardware Wallets](docs/bitcoin-core-usage.md).\n\n## License\n\nThis project is available under the MIT License, Copyright Andrew Chow.\n",
'long_description': "# Bitcoin Hardware Wallet Interface\n\n[![Build Status](https://travis-ci.org/bitcoin-core/HWI.svg?branch=master)](https://travis-ci.org/bitcoin-core/HWI)\n\nThe Bitcoin Hardware Wallet Interface is a Python library and command line tool for interacting with hardware wallets.\nIt provides a standard way for software to work with hardware wallets without needing to implement device specific drivers.\nPython software can use the provided library (`hwilib`). Software in other languages can execute the `hwi` tool.\n\nCaveat emptor: Inclusion of a specific hardware wallet vendor does not imply any endorsement of quality or security.\n\n## Prerequisites\n\nPython 3 is required. The libraries and [udev rules](hwilib/udev/README.md) for each device must also be installed. Some libraries will need to be installed\n\nFor Ubuntu/Debian:\n```\nsudo apt install libusb-1.0-0-dev libudev-dev python3-dev\n```\n\nFor Centos:\n```\nsudo yum -y install python3-devel libusbx-devel systemd-devel\n```\n\nFor macOS:\n```\nbrew install libusb\n```\n\n## Install\n\n```\ngit clone https://github.com/bitcoin-core/HWI.git\ncd HWI\npoetry install # or 'pip3 install .' or 'python3 setup.py install'\n```\n\nThis project uses the [Poetry](https://github.com/sdispater/poetry) dependency manager. HWI and its dependencies can be installed via poetry by executing the following in the root source directory:\n\n```\npoetry install\n```\n\nPip can also be used to automatically install HWI and its dependencies using the `setup.py` file (which is usually in sync with `pyproject.toml`):\n\n```\npip3 install .\n```\n\nThe `setup.py` file can be used to install HWI and its dependencies so long as `setuptools` is also installed:\n\n```\npip3 install -U setuptools\npython3 setup.py install\n```\n\n## Dependencies\n\nSee `pyproject.toml` for all dependencies. Dependencies under `[tool.poetry.dependecies]` are user dependencies, and `[tool.poetry.dev-dependencies]` for development based dependencies. These dependencies will be installed with any of the three above installation methods.\n\n## Usage\n\nTo use, first enumerate all devices and find the one that you want to use with\n\n```\n./hwi.py enumerate\n```\n\nOnce the device type and device path is known, issue commands to it like so:\n\n```\n./hwi.py -t <type> -d <path> <command> <command args>\n```\n\nAll output will be in JSON form and sent to `stdout`.\nAdditional information or prompts will be sent to `stderr` and will not necessarily be in JSON.\nThis additional information is for debugging purposes.\n\nTo see a complete list of available commands and global parameters, run\n`./hwi.py --help`. To see options specific to a particular command,\npass the `--help` parameter after the command name; for example:\n\n```\n./hwi.py getdescriptors --help\n```\n\n## Device Support\n\nThe below table lists what devices and features are supported for each device.\n\nPlease also see [docs](docs/) for additional information about each device.\n\n| Feature \\ Device | Ledger Nano X | Ledger Nano S | Trezor One | Trezor Model T | BitBox01 | BitBox02 | KeepKey | Coldcard |\n|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n| Support Planned | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| Implemented | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| xpub retrieval | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| Message Signing | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes |\n| Device Setup | N/A | N/A | Yes | Yes | Yes | Yes | Yes | N/A |\n| Device Wipe | N/A | N/A | Yes | Yes | Yes | Yes | Yes | N/A |\n| Device Recovery | N/A | N/A | Yes | Yes | N/A | Yes | Yes | N/A |\n| Device Backup | N/A | N/A | N/A | N/A | Yes | Yes | N/A | Yes |\n| P2PKH Inputs | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes |\n| P2SH-P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| P2SH Multisig Inputs | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes |\n| P2SH-P2WSH Multisig Inputs | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes |\n| P2WSH Multisig Inputs | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |\n| Bare Multisig Inputs | Yes | Yes | N/A | N/A | Yes | N/A | N/A | N/A |\n| Arbitrary scriptPubKey Inputs | Yes | Yes | N/A | N/A | Yes | N/A | N/A | N/A |\n| Arbitrary redeemScript Inputs | Yes | Yes | N/A | N/A | Yes | N/A | N/A | N/A |\n| Arbitrary witnessScript Inputs | Yes | Yes | N/A | N/A | Yes | N/A | N/A | N/A |\n| Non-wallet inputs | Yes | Yes | Yes | Yes | Yes | N/A | Yes | Yes |\n| Mixed Segwit and Non-Segwit Inputs | N/A | N/A | Yes | Yes | Yes | Yes | Yes | Yes |\n| Display on device screen | Yes | Yes | Yes | Yes | N/A | Yes | Yes | Yes |\n\n## Using with Bitcoin Core\n\nSee [Using Bitcoin Core with Hardware Wallets](docs/bitcoin-core-usage.md).\n\n## License\n\nThis project is available under the MIT License, Copyright Andrew Chow.\n",
'author': 'Andrew Chow',
'author_email': 'andrew@achow101.com',
'maintainer': None,

View File

@ -1,4 +1,4 @@
From cadbd3d25306b43060fd06eed589947d537a5ced Mon Sep 17 00:00:00 2001
From 8793fbaa9b32f3c67f289a05194e68acc5c61b7d Mon Sep 17 00:00:00 2001
From: Andrew Chow <achow101-github@achow101.com>
Date: Tue, 17 Dec 2019 17:56:05 -0500
Subject: [PATCH 2/2] Change default simulator multisig
@ -8,7 +8,7 @@ Subject: [PATCH 2/2] Change default simulator multisig
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/unix/frozen-modules/sim_settings.py b/unix/frozen-modules/sim_settings.py
index 0313c3e..e2c3d71 100644
index 0313c3e..6d301d4 100644
--- a/unix/frozen-modules/sim_settings.py
+++ b/unix/frozen-modules/sim_settings.py
@@ -68,7 +68,11 @@ if '--ms' in sys.argv:
@ -17,13 +17,13 @@ index 0313c3e..e2c3d71 100644
# P2SH: 2of4 using BIP39 passwords: "Me", "Myself", "and I", and (empty string) on simulator
- sim_defaults['multisig'] = [['MeMyself', [2, 4], [[3503269483, 'tpubD9429UXFGCTKJ9NdiNK4rC5ygqSUkginycYHccqSg5gkmyQ7PZRHNjk99M6a6Y3NY8ctEUUJvCu6iCCui8Ju3xrHRu3Ez1CKB4ZFoRZDdP9'], [2389277556, 'tpubD97nVL37v5tWyMf9ofh5rznwhh1593WMRg6FT4o6MRJkKWANtwAMHYLrcJFsFmPfYbY1TE1LLQ4KBb84LBPt1ubvFwoosvMkcWJtMwvXgSc'], [3190206587, 'tpubD9ArfXowvGHnuECKdGXVKDMfZVGdephVWg8fWGWStH3VKHzT4ph3A4ZcgXWqFu1F5xGTfxncmrnf3sLC86dup2a8Kx7z3xQ3AgeNTQeFxPa'], [1130956047, 'tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n']], {'ch': 'XTN', 'pp': "45'"}]]
+ sim_defaults['multisig'] = [
+ ['mstest', [2, 3], [[1130956047, 'tpubDCDqt7XXvhAYY9HSwrCXB7BXqYM4RXB8WFtKgtTXGa6u3U6EV1NJJRFTcuTRyhSY5Vreg1LP8aPdyiAPQGrDJLikkHoc7VQg6DA9NtUxHtj'], [1130956047, 'tpubDCDqt7XXvhAYY9HSwrCXB7BXqYM4RXB8WFtKgtTXGa6u3U6EV1NJJRFTcuTRyhSY5Vreg1LP8aPdyiAPQGrDJLikkHoc7VQg6DA9NtUxHtj'], [1130956047, 'tpubDCDqt7XXvhAYY9HSwrCXB7BXqYM4RXB8WFtKgtTXGa6u3U6EV1NJJRFTcuTRyhSY5Vreg1LP8aPdyiAPQGrDJLikkHoc7VQg6DA9NtUxHtj']], {'ft': 8, 'ch': 'XTN'}],
+ ['mstest1', [2, 3], [[1130956047, 'tpubDCDqt7XXvhAYY9HSwrCXB7BXqYM4RXB8WFtKgtTXGa6u3U6EV1NJJRFTcuTRyhSY5Vreg1LP8aPdyiAPQGrDJLikkHoc7VQg6DA9NtUxHtj'], [1130956047, 'tpubDCDqt7XXvhAYY9HSwrCXB7BXqYM4RXB8WFtKgtTXGa6u3U6EV1NJJRFTcuTRyhSY5Vreg1LP8aPdyiAPQGrDJLikkHoc7VQg6DA9NtUxHtj'], [1130956047, 'tpubDCDqt7XXvhAYY9HSwrCXB7BXqYM4RXB8WFtKgtTXGa6u3U6EV1NJJRFTcuTRyhSY5Vreg1LP8aPdyiAPQGrDJLikkHoc7VQg6DA9NtUxHtj']], {'ft': 14, 'ch': 'XTN'}],
+ ['mstest2', [2, 3], [[1130956047, 'tpubDCDqt7XXvhAYY9HSwrCXB7BXqYM4RXB8WFtKgtTXGa6u3U6EV1NJJRFTcuTRyhSY5Vreg1LP8aPdyiAPQGrDJLikkHoc7VQg6DA9NtUxHtj'], [1130956047, 'tpubDCDqt7XXvhAYY9HSwrCXB7BXqYM4RXB8WFtKgtTXGa6u3U6EV1NJJRFTcuTRyhSY5Vreg1LP8aPdyiAPQGrDJLikkHoc7VQg6DA9NtUxHtj'], [1130956047, 'tpubDCDqt7XXvhAYY9HSwrCXB7BXqYM4RXB8WFtKgtTXGa6u3U6EV1NJJRFTcuTRyhSY5Vreg1LP8aPdyiAPQGrDJLikkHoc7VQg6DA9NtUxHtj']], {'ft': 26, 'ch': 'XTN'}],
+ ['mstest', [2, 3], [[1130956047, 'tpubDCp1a2CuSdeVLYbjKRF6H1oSU2hubMm6oV4tXYFkh5u7BwhJ1P5ZwntWGfNCx92BUWpbYPcbgApbYHNTEED49vFdscWgg6KpJepKdgBB92U'], [1130956047, 'tpubDCp1a2CuSdeVQxVNMuLWW4GDnSYqzaPwYRfb8uveDQmwYaPBXERNPWUR8GvyfSLDsbr9MwQxLsKeAAjSsigKpmgrkLdHJM77C3us7t5QFL2'], [1130956047, 'tpubDCp1a2CuSdeVS6h1LsXGqpALo6ZhvWEFDYDv8qTgcnBZYdAPsZ7QL25UKdUqKsMcc8eMQyZA9zjvUMaJjdSVcfDftzgmdvJfH5MnrZoxzFG']], {'ft': 8, 'ch': 'XTN'}],
+ ['mstest1', [2, 3], [[1130956047, 'tpubDCp1a2CuSdeVLYbjKRF6H1oSU2hubMm6oV4tXYFkh5u7BwhJ1P5ZwntWGfNCx92BUWpbYPcbgApbYHNTEED49vFdscWgg6KpJepKdgBB92U'], [1130956047, 'tpubDCp1a2CuSdeVQxVNMuLWW4GDnSYqzaPwYRfb8uveDQmwYaPBXERNPWUR8GvyfSLDsbr9MwQxLsKeAAjSsigKpmgrkLdHJM77C3us7t5QFL2'], [1130956047, 'tpubDCp1a2CuSdeVS6h1LsXGqpALo6ZhvWEFDYDv8qTgcnBZYdAPsZ7QL25UKdUqKsMcc8eMQyZA9zjvUMaJjdSVcfDftzgmdvJfH5MnrZoxzFG']], {'ft': 14, 'ch': 'XTN'}],
+ ['mstest2', [2, 3], [[1130956047, 'tpubDCp1a2CuSdeVLYbjKRF6H1oSU2hubMm6oV4tXYFkh5u7BwhJ1P5ZwntWGfNCx92BUWpbYPcbgApbYHNTEED49vFdscWgg6KpJepKdgBB92U'], [1130956047, 'tpubDCp1a2CuSdeVQxVNMuLWW4GDnSYqzaPwYRfb8uveDQmwYaPBXERNPWUR8GvyfSLDsbr9MwQxLsKeAAjSsigKpmgrkLdHJM77C3us7t5QFL2'], [1130956047, 'tpubDCp1a2CuSdeVS6h1LsXGqpALo6ZhvWEFDYDv8qTgcnBZYdAPsZ7QL25UKdUqKsMcc8eMQyZA9zjvUMaJjdSVcfDftzgmdvJfH5MnrZoxzFG']], {'ft': 26, 'ch': 'XTN'}],
+ ]
sim_defaults['fee_limit'] = -1
if '--xfp' in sys.argv:
--
2.27.0
2.28.0

View File

@ -31,7 +31,7 @@ def coldcard_test_suite(simulator, rpc, userpass, interface):
break
if found:
break
except:
except Exception:
pass
time.sleep(0.5)
# Cleanup

View File

@ -16,6 +16,18 @@ class TestDescriptor(unittest.TestCase):
self.assertEqual(desc.testnet, True)
self.assertEqual(desc.m_path, "m/84'/1'/0'/0/0")
def test_parse_multisig_descriptor_with_origin(self):
desc = Descriptor.parse("wsh(multi(2,[00000001/48'/0'/0'/2']tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/0,[00000002/48'/0'/0'/2']tpubDFHiBJDeNvqPWNJbzzxqDVXmJZoNn2GEtoVcFhMjXipQiorGUmps3e5ieDGbRrBPTFTh9TXEKJCwbAGW9uZnfrVPbMxxbFohuFzfT6VThty/0/0))", True)
self.assertIsNotNone(desc)
self.assertEqual(desc.wsh, True)
self.assertEqual(desc.origin_fingerprint, ["00000001", "00000002"])
self.assertEqual(desc.origin_path, ["/48'/0'/0'/2'", "/48'/0'/0'/2'"])
self.assertEqual(desc.base_key, ["tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B", "tpubDFHiBJDeNvqPWNJbzzxqDVXmJZoNn2GEtoVcFhMjXipQiorGUmps3e5ieDGbRrBPTFTh9TXEKJCwbAGW9uZnfrVPbMxxbFohuFzfT6VThty"])
self.assertEqual(desc.path_suffix, ["/0/0", "/0/0"])
self.assertEqual(desc.testnet, True)
self.assertEqual(desc.m_path_base, ["m/48'/0'/0'/2'", "m/48'/0'/0'/2'"])
self.assertEqual(desc.m_path, ["m/48'/0'/0'/2'/0/0", "m/48'/0'/0'/2'/0/0"])
def test_parse_descriptor_without_origin(self):
desc = Descriptor.parse("wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/0)", True)
self.assertIsNotNone(desc)
@ -28,6 +40,18 @@ class TestDescriptor(unittest.TestCase):
self.assertEqual(desc.testnet, True)
self.assertEqual(desc.m_path, None)
def test_parse_descriptor_with_origin_fingerprint_only(self):
desc = Descriptor.parse("wpkh([00000001]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/0)", True)
self.assertIsNotNone(desc)
self.assertEqual(desc.wpkh, True)
self.assertEqual(desc.sh_wpkh, None)
self.assertEqual(desc.origin_fingerprint, "00000001")
self.assertEqual(desc.origin_path, "")
self.assertEqual(desc.base_key, "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B")
self.assertEqual(desc.path_suffix, "/0/0")
self.assertEqual(desc.testnet, True)
self.assertEqual(desc.m_path, None)
def test_parse_descriptor_with_key_at_end_with_origin(self):
desc = Descriptor.parse("wpkh([00000001/84'/1'/0'/0/0]0297dc3f4420402e01a113984311bf4a1b8de376cac0bdcfaf1b3ac81f13433c7)", True)
self.assertIsNotNone(desc)

View File

@ -13,8 +13,12 @@ import unittest
from authproxy import AuthServiceProxy, JSONRPCException
from hwilib.base58 import xpub_to_pub_hex
from hwilib.cli import process_commands
from hwilib.descriptor import AddChecksum
from hwilib.serializations import PSBT
SUPPORTS_MS_DISPLAY = {'trezor_1', 'keepkey', 'coldcard', 'trezor_t'}
SUPPORTS_XPUB_MS_DISPLAY = {'trezor_t'}
# Class for emulator control
class DeviceEmulator():
def start(self):
@ -64,7 +68,7 @@ class DeviceTestCase(unittest.TestCase):
self.fingerprint = fingerprint
self.master_xpub = master_xpub
self.password = password
self.dev_args = ['-t', self.type, '-d', self.path]
self.dev_args = ['-t', self.type, '-d', self.path, '--testnet']
if emulator:
self.emulator = emulator
else:
@ -113,6 +117,12 @@ class DeviceTestCase(unittest.TestCase):
def __repr__(self):
return '{}: {}'.format(self.full_type, super().__repr__())
def setup_wallets(self):
wallet_name = '{}_{}_test'.format(self.full_type, self.id())
self.rpc.createwallet(wallet_name=wallet_name, disable_private_keys=True, descriptors=True)
self.wrpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/{}'.format(self.rpc_userpass, wallet_name))
self.wpk_rpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/'.format(self.rpc_userpass))
def setUp(self):
self.emulator.start()
@ -166,77 +176,64 @@ class TestDeviceConnect(DeviceTestCase):
class TestGetKeypool(DeviceTestCase):
def setUp(self):
self.rpc = AuthServiceProxy('http://{}@127.0.0.1:18443'.format(self.rpc_userpass))
if '{}_test'.format(self.full_type) not in self.rpc.listwallets():
self.rpc.createwallet('{}_test'.format(self.full_type), True)
self.wrpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/{}_test'.format(self.rpc_userpass, self.full_type))
self.wpk_rpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/'.format(self.rpc_userpass))
if '--testnet' not in self.dev_args:
self.dev_args.append('--testnet')
self.emulator.start()
super().setUp()
self.setup_wallets()
def test_getkeypool(self):
non_keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--nokeypool', '0', '20'])
import_result = self.wpk_rpc.importmulti(non_keypool_desc)
self.assertTrue(import_result[0]['success'])
pkh_keypool_desc = self.do_command(self.dev_args + ['getkeypool', '0', '20'])
import_result = self.wpk_rpc.importmulti(pkh_keypool_desc)
self.assertFalse(import_result[0]['success'])
import_result = self.wrpc.importmulti(pkh_keypool_desc)
import_result = self.wrpc.importdescriptors(pkh_keypool_desc)
self.assertTrue(import_result[0]['success'])
for i in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress())
self.assertEqual(addr_info['hdkeypath'], "m/44'/1'/0'/0/{}".format(i))
addr_info = self.wrpc.getaddressinfo(self.wrpc.getrawchangeaddress())
self.assertEqual(addr_info['hdkeypath'], "m/44'/1'/0'/1/{}".format(i))
for _ in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress('', 'legacy'))
self.assertTrue(addr_info['hdkeypath'].startswith("m/44'/1'/0'/0/"))
addr_info = self.wrpc.getaddressinfo(self.wrpc.getrawchangeaddress('legacy'))
self.assertTrue(addr_info['hdkeypath'].startswith("m/44'/1'/0'/1/"))
shwpkh_keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--sh_wpkh', '0', '20'])
import_result = self.wrpc.importmulti(shwpkh_keypool_desc)
import_result = self.wrpc.importdescriptors(shwpkh_keypool_desc)
self.assertTrue(import_result[0]['success'])
for i in range(0, 21):
for _ in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress('', 'p2sh-segwit'))
self.assertEqual(addr_info['hdkeypath'], "m/49'/1'/0'/0/{}".format(i))
self.assertTrue(addr_info['hdkeypath'].startswith("m/49'/1'/0'/0/"))
addr_info = self.wrpc.getaddressinfo(self.wrpc.getrawchangeaddress('p2sh-segwit'))
self.assertEqual(addr_info['hdkeypath'], "m/49'/1'/0'/1/{}".format(i))
self.assertTrue(addr_info['hdkeypath'].startswith("m/49'/1'/0'/1/"))
wpkh_keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--wpkh', '0', '20'])
import_result = self.wrpc.importmulti(wpkh_keypool_desc)
import_result = self.wrpc.importdescriptors(wpkh_keypool_desc)
self.assertTrue(import_result[0]['success'])
for i in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress())
self.assertEqual(addr_info['hdkeypath'], "m/84'/1'/0'/0/{}".format(i))
addr_info = self.wrpc.getaddressinfo(self.wrpc.getrawchangeaddress())
self.assertEqual(addr_info['hdkeypath'], "m/84'/1'/0'/1/{}".format(i))
for _ in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress('', 'bech32'))
self.assertTrue(addr_info['hdkeypath'].startswith("m/84'/1'/0'/0/"))
addr_info = self.wrpc.getaddressinfo(self.wrpc.getrawchangeaddress('bech32'))
self.assertTrue(addr_info['hdkeypath'].startswith("m/84'/1'/0'/1/"))
# Test that `--all` option gives the "concatenation" of previous three calls
all_keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--all', '0', '20'])
self.assertEqual(all_keypool_desc, pkh_keypool_desc + wpkh_keypool_desc + shwpkh_keypool_desc)
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--sh_wpkh', '--account', '3', '0', '20'])
import_result = self.wrpc.importmulti(keypool_desc)
import_result = self.wrpc.importdescriptors(keypool_desc)
self.assertTrue(import_result[0]['success'])
for i in range(0, 21):
for _ in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress('', 'p2sh-segwit'))
self.assertEqual(addr_info['hdkeypath'], "m/49'/1'/3'/0/{}".format(i))
self.assertTrue(addr_info['hdkeypath'].startswith("m/49'/1'/3'/0/"))
addr_info = self.wrpc.getaddressinfo(self.wrpc.getrawchangeaddress('p2sh-segwit'))
self.assertEqual(addr_info['hdkeypath'], "m/49'/1'/3'/1/{}".format(i))
self.assertTrue(addr_info['hdkeypath'].startswith("m/49'/1'/3'/1/"))
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--wpkh', '--account', '3', '0', '20'])
import_result = self.wrpc.importmulti(keypool_desc)
import_result = self.wrpc.importdescriptors(keypool_desc)
self.assertTrue(import_result[0]['success'])
for i in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress())
self.assertEqual(addr_info['hdkeypath'], "m/84'/1'/3'/0/{}".format(i))
addr_info = self.wrpc.getaddressinfo(self.wrpc.getrawchangeaddress())
self.assertEqual(addr_info['hdkeypath'], "m/84'/1'/3'/1/{}".format(i))
for _ in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress('', 'bech32'))
self.assertTrue(addr_info['hdkeypath'].startswith("m/84'/1'/3'/0/"))
addr_info = self.wrpc.getaddressinfo(self.wrpc.getrawchangeaddress('bech32'))
self.assertTrue(addr_info['hdkeypath'].startswith("m/84'/1'/3'/1/"))
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--path', 'm/0h/0h/4h/*', '0', '20'])
import_result = self.wrpc.importmulti(keypool_desc)
import_result = self.wrpc.importdescriptors(keypool_desc)
self.assertTrue(import_result[0]['success'])
for i in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress())
self.assertEqual(addr_info['hdkeypath'], "m/0'/0'/4'/{}".format(i))
for _ in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress('', 'legacy'))
self.assertTrue(addr_info['hdkeypath'].startswith("m/0'/0'/4'/"))
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--path', '/0h/0h/4h/*', '0', '20'])
self.assertEqual(keypool_desc['error'], 'Path must start with m/')
@ -246,12 +243,6 @@ class TestGetKeypool(DeviceTestCase):
self.assertEqual(keypool_desc['code'], -7)
class TestGetDescriptors(DeviceTestCase):
def setUp(self):
self.rpc = AuthServiceProxy('http://{}@127.0.0.1:18443'.format(self.rpc_userpass))
if '--testnet' not in self.dev_args:
self.dev_args.append('--testnet')
self.emulator.start()
def tearDown(self):
self.emulator.stop()
@ -275,14 +266,8 @@ class TestGetDescriptors(DeviceTestCase):
class TestSignTx(DeviceTestCase):
def setUp(self):
self.rpc = AuthServiceProxy('http://{}@127.0.0.1:18443'.format(self.rpc_userpass))
if '{}_test'.format(self.full_type) not in self.rpc.listwallets():
self.rpc.createwallet('{}_test'.format(self.full_type), True)
self.wrpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/{}_test'.format(self.rpc_userpass, self.full_type))
self.wpk_rpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/'.format(self.rpc_userpass))
if '--testnet' not in self.dev_args:
self.dev_args.append('--testnet')
self.emulator.start()
super().setUp()
self.setup_wallets()
def _generate_and_finalize(self, unknown_inputs, psbt):
if not unknown_inputs:
@ -333,49 +318,50 @@ class TestSignTx(DeviceTestCase):
self.assertTrue(self.wrpc.testmempoolaccept([finalize_res['hex']])[0]["allowed"])
return finalize_res['hex']
def _make_multisigs(self):
desc_pubkeys = []
sorted_pubkeys = []
for i in range(0, 3):
path = "/48h/1h/{}h/0/0".format(i)
origin = '{}{}'.format(self.fingerprint, path)
xpub = self.do_command(self.dev_args + ["--expert", "getxpub", "m{}".format(path)])
desc_pubkeys.append("[{}]{}".format(origin, xpub["pubkey"]))
sorted_pubkeys.append(xpub["pubkey"])
sorted_pubkeys.sort()
sh_desc = AddChecksum("sh(sortedmulti(2,{},{},{}))".format(desc_pubkeys[0], desc_pubkeys[1], desc_pubkeys[2]))
sh_ms_info = self.rpc.createmultisig(2, sorted_pubkeys, "legacy")
self.assertEqual(self.rpc.deriveaddresses(sh_desc)[0], sh_ms_info["address"])
sh_wsh_desc = AddChecksum("sh(wsh(sortedmulti(2,{},{},{})))".format(desc_pubkeys[1], desc_pubkeys[2], desc_pubkeys[0]))
sh_wsh_ms_info = self.rpc.createmultisig(2, sorted_pubkeys, "p2sh-segwit")
self.assertEqual(self.rpc.deriveaddresses(sh_wsh_desc)[0], sh_wsh_ms_info["address"])
wsh_desc = AddChecksum("wsh(sortedmulti(2,{},{},{}))".format(desc_pubkeys[2], desc_pubkeys[1], desc_pubkeys[0]))
wsh_ms_info = self.rpc.createmultisig(2, sorted_pubkeys, "bech32")
self.assertEqual(self.rpc.deriveaddresses(wsh_desc)[0], wsh_ms_info["address"])
return sh_desc, sh_ms_info["address"], sh_wsh_desc, sh_wsh_ms_info["address"], wsh_desc, wsh_ms_info["address"]
def _test_signtx(self, input_type, multisig, external):
# Import some keys to the watch only wallet and send coins to them
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--sh_wpkh', '30', '50'])
import_result = self.wrpc.importmulti(keypool_desc)
self.assertTrue(import_result[0]['success'])
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--sh_wpkh', '--internal', '30', '50'])
import_result = self.wrpc.importmulti(keypool_desc)
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--all', '30', '50'])
import_result = self.wrpc.importdescriptors(keypool_desc)
self.assertTrue(import_result[0]['success'])
sh_wpkh_addr = self.wrpc.getnewaddress('', 'p2sh-segwit')
wpkh_addr = self.wrpc.getnewaddress('', 'bech32')
pkh_addr = self.wrpc.getnewaddress('', 'legacy')
self.wrpc.importaddress(wpkh_addr)
self.wrpc.importaddress(pkh_addr)
# pubkeys to construct 2-of-3 multisig descriptors for import
sh_wpkh_info = self.wrpc.getaddressinfo(sh_wpkh_addr)
wpkh_info = self.wrpc.getaddressinfo(wpkh_addr)
pkh_info = self.wrpc.getaddressinfo(pkh_addr)
# Get origin info/key pair so wallet doesn't forget how to
# sign with keys post-import
pubkeys = [sh_wpkh_info['desc'][8:-11],
wpkh_info['desc'][5:-10],
pkh_info['desc'][4:-10]]
# Get the descriptors with their checksums
sh_multi_desc = self.wrpc.getdescriptorinfo('sh(sortedmulti(2,' + pubkeys[0] + ',' + pubkeys[1] + ',' + pubkeys[2] + '))')['descriptor']
sh_wsh_multi_desc = self.wrpc.getdescriptorinfo('sh(wsh(sortedmulti(2,' + pubkeys[0] + ',' + pubkeys[1] + ',' + pubkeys[2] + ')))')['descriptor']
wsh_multi_desc = self.wrpc.getdescriptorinfo('wsh(sortedmulti(2,' + pubkeys[2] + ',' + pubkeys[1] + ',' + pubkeys[0] + '))')['descriptor']
sh_multi_desc, sh_multi_addr, sh_wsh_multi_desc, sh_wsh_multi_addr, wsh_multi_desc, wsh_multi_addr = self._make_multisigs()
sh_multi_import = {'desc': sh_multi_desc, "timestamp": "now", "label": "shmulti"}
sh_wsh_multi_import = {'desc': sh_wsh_multi_desc, "timestamp": "now", "label": "shwshmulti"}
# re-order pubkeys to allow import without "already have private keys" error
wsh_multi_import = {'desc': wsh_multi_desc, "timestamp": "now", "label": "wshmulti"}
multi_result = self.wrpc.importmulti([sh_multi_import, sh_wsh_multi_import, wsh_multi_import])
multi_result = self.wrpc.importdescriptors([sh_multi_import, sh_wsh_multi_import, wsh_multi_import])
self.assertTrue(multi_result[0]['success'])
self.assertTrue(multi_result[1]['success'])
self.assertTrue(multi_result[2]['success'])
sh_multi_addr = self.wrpc.getaddressesbylabel("shmulti").popitem()[0]
sh_wsh_multi_addr = self.wrpc.getaddressesbylabel("shwshmulti").popitem()[0]
wsh_multi_addr = self.wrpc.getaddressesbylabel("wshmulti").popitem()[0]
in_amt = 3
out_amt = in_amt // 3
number_inputs = 0
@ -417,9 +403,9 @@ class TestSignTx(DeviceTestCase):
# Test wrapper to avoid mixed-inputs signing for Ledger
def test_signtx(self):
supports_mixed = {'coldcard', 'trezor_1', 'digitalbitbox', 'keepkey'}
supports_mixed = {'coldcard', 'trezor_1', 'digitalbitbox', 'keepkey', 'trezor_t'}
supports_multisig = {'ledger', 'trezor_1', 'digitalbitbox', 'keepkey', 'coldcard', 'trezor_t'}
supports_external = {'ledger', 'trezor_1', 'digitalbitbox', 'keepkey', 'coldcard'}
supports_external = {'ledger', 'trezor_1', 'digitalbitbox', 'keepkey', 'coldcard', 'trezor_t'}
self._test_signtx("legacy", self.full_type in supports_multisig, self.full_type in supports_external)
self._test_signtx("segwit", self.full_type in supports_multisig, self.full_type in supports_external)
if self.full_type in supports_mixed:
@ -455,16 +441,6 @@ class TestSignTx(DeviceTestCase):
pass
class TestDisplayAddress(DeviceTestCase):
def setUp(self):
self.rpc = AuthServiceProxy('http://{}@127.0.0.1:18443'.format(self.rpc_userpass))
if '{}_test'.format(self.full_type) not in self.rpc.listwallets():
self.rpc.createwallet('{}_test'.format(self.full_type), True)
self.wrpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/{}_test'.format(self.rpc_userpass, self.full_type))
self.wpk_rpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/'.format(self.rpc_userpass))
if '--testnet' not in self.dev_args:
self.dev_args.append('--testnet')
self.emulator.start()
def test_display_address_bad_args(self):
result = self.do_command(self.dev_args + ['displayaddress', '--sh_wpkh', '--wpkh', '--path', 'm/49h/1h/0h/0/0'])
self.assertIn('error', result)
@ -538,152 +514,77 @@ class TestDisplayAddress(DeviceTestCase):
self.assertIn('code', result)
self.assertEqual(result['code'], -7)
def test_display_address_multisig_path(self):
supports_multisig = {'trezor_1', 'keepkey', 'coldcard', 'trezor_t'}
if self.full_type not in supports_multisig:
return
# Import some keys to the watch only wallet and get multisig address
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--sh_wpkh', '40', '50'])
import_result = self.wrpc.importmulti(keypool_desc)
self.assertTrue(import_result[0]['success'])
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--sh_wpkh', '--internal', '40', '50'])
import_result = self.wrpc.importmulti(keypool_desc)
self.assertTrue(import_result[0]['success'])
sh_wpkh_addr = self.wrpc.getnewaddress('', 'p2sh-segwit')
wpkh_addr = self.wrpc.getnewaddress('', 'bech32')
pkh_addr = self.wrpc.getnewaddress('', 'legacy')
self.wrpc.importaddress(wpkh_addr)
self.wrpc.importaddress(pkh_addr)
def _make_single_multisig(self, addrtype):
desc_pubkeys = []
sorted_pubkeys = []
for i in range(0, 3):
path = "/48h/1h/{}h/0/0".format(i)
origin = '{}{}'.format(self.fingerprint, path)
xpub = self.do_command(self.dev_args + ["--expert", "getxpub", "m{}".format(path)])
desc_pubkeys.append("[{}]{}".format(origin, xpub["pubkey"]))
sorted_pubkeys.append((xpub["pubkey"], origin))
sorted_pubkeys.sort(key=lambda tup: tup[0])
# pubkeys to construct 2-of-3 multisig descriptors for import
sh_wpkh_info = self.wrpc.getaddressinfo(sh_wpkh_addr)
wpkh_info = self.wrpc.getaddressinfo(wpkh_addr)
pkh_info = self.wrpc.getaddressinfo(pkh_addr)
if addrtype == "pkh":
desc = AddChecksum("sh(sortedmulti(2,{},{},{}))".format(desc_pubkeys[0], desc_pubkeys[1], desc_pubkeys[2]))
ms_info = self.rpc.createmultisig(2, [x[0] for x in sorted_pubkeys], "legacy")
elif addrtype == "sh_wpkh":
desc = AddChecksum("sh(wsh(sortedmulti(2,{},{},{})))".format(desc_pubkeys[1], desc_pubkeys[2], desc_pubkeys[0]))
ms_info = self.rpc.createmultisig(2, [x[0] for x in sorted_pubkeys], "p2sh-segwit")
elif addrtype == "wpkh":
desc = AddChecksum("wsh(sortedmulti(2,{},{},{}))".format(desc_pubkeys[2], desc_pubkeys[1], desc_pubkeys[0]))
ms_info = self.rpc.createmultisig(2, [x[0] for x in sorted_pubkeys], "bech32")
else:
self.fail("Oops the test is broken")
pubkeys = [sh_wpkh_info['desc'][8:-11],
wpkh_info['desc'][5:-10],
pkh_info['desc'][4:-10]]
self.assertEqual(self.rpc.deriveaddresses(desc)[0], ms_info["address"])
# Get the descriptors with their checksums
sh_multi_desc = self.wrpc.getdescriptorinfo('sh(sortedmulti(2,' + pubkeys[0] + ',' + pubkeys[1] + ',' + pubkeys[2] + '))')['descriptor']
sh_wsh_multi_desc = self.wrpc.getdescriptorinfo('sh(wsh(sortedmulti(2,' + pubkeys[0] + ',' + pubkeys[1] + ',' + pubkeys[2] + ')))')['descriptor']
wsh_multi_desc = self.wrpc.getdescriptorinfo('wsh(sortedmulti(2,' + pubkeys[2] + ',' + pubkeys[1] + ',' + pubkeys[0] + '))')['descriptor']
path = "{},{},{}".format(sorted_pubkeys[0][1], sorted_pubkeys[1][1], sorted_pubkeys[2][1])
sh_multi_import = {'desc': sh_multi_desc, "timestamp": "now", "label": "shmulti-display"}
sh_wsh_multi_import = {'desc': sh_wsh_multi_desc, "timestamp": "now", "label": "shwshmulti-display"}
# re-order pubkeys to allow import without "already have private keys" error
wsh_multi_import = {'desc': wsh_multi_desc, "timestamp": "now", "label": "wshmulti-display"}
multi_result = self.wrpc.importmulti([sh_multi_import, sh_wsh_multi_import, wsh_multi_import])
self.assertTrue(multi_result[0]['success'])
self.assertTrue(multi_result[1]['success'])
self.assertTrue(multi_result[2]['success'])
return ms_info["address"], desc, ms_info["redeemScript"], path
sh_multi_addr = self.wrpc.getaddressesbylabel("shmulti-display").popitem()[0]
sh_wsh_multi_addr = self.wrpc.getaddressesbylabel("shwshmulti-display").popitem()[0]
wsh_multi_addr = self.wrpc.getaddressesbylabel("wshmulti-display").popitem()[0]
def test_display_address_multisig(self):
if self.full_type not in SUPPORTS_MS_DISPLAY:
raise unittest.SkipTest("{} does not support multisig display".format(self.full_type))
sh_multi_addr_redeem_script = self.wrpc.getaddressinfo(sh_multi_addr)['hex']
sh_wsh_multi_addr_redeem_script = self.wrpc.getaddressinfo(sh_multi_addr)['hex']
wsh_multi_addr_redeem_script = self.wrpc.getaddressinfo(sh_multi_addr)['hex']
for addrtype in ["pkh", "sh_wpkh", "wpkh"]:
for use_desc in [True, False]:
with self.subTest(addrtype=addrtype, use_desc=use_desc):
addr, desc, rs, path = self._make_single_multisig(addrtype)
path = pubkeys[2][1:24] + ',' + pubkeys[1][1:24] + ',' + pubkeys[0][1:24]
# need to replace `'` with `h` for stdin option to work
path = path.replace("'", "h")
if use_desc:
args = ['displayaddress', '--desc', desc]
else:
args = ['displayaddress', '--path', path, '--redeem_script', rs]
if addrtype != "pkh":
args.append("--{}".format(addrtype))
# legacy
result = self.do_command(self.dev_args + ['displayaddress', '--path', path, '--redeem_script', sh_multi_addr_redeem_script])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
self.assertEqual(sh_multi_addr, result['address'])
# wrapped segwit
result = self.do_command(self.dev_args + ['displayaddress', '--sh_wpkh', '--path', path, '--redeem_script', sh_wsh_multi_addr_redeem_script])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
self.assertEqual(sh_wsh_multi_addr, result['address'])
# native setwit
result = self.do_command(self.dev_args + ['displayaddress', '--wpkh', '--path', path, '--redeem_script', wsh_multi_addr_redeem_script])
result = self.do_command(self.dev_args + args)
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
if addrtype == "wpkh":
# removes prefix and checksum since regtest gives
# prefix `bcrt` on Bitcoin Core while wallets return testnet `tb` prefix
self.assertEqual(addr[4:58], result['address'][2:56])
else:
self.assertEqual(addr, result['address'])
def test_display_address_xpub_multisig(self):
if self.full_type not in SUPPORTS_XPUB_MS_DISPLAY:
raise unittest.SkipTest("{} does not support multsig display with xpubs".format(self.full_type))
account_xpub = self.do_command(self.dev_args + ['getxpub', 'm/48h/1h/0h'])['xpub']
desc = 'wsh(multi(2,[' + self.fingerprint + '/48h/1h/0h]' + account_xpub + '/0/0,[' + self.fingerprint + '/48h/1h/0h]' + account_xpub + '/1/0))'
result = self.do_command(self.dev_args + ['displayaddress', '--desc', desc])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
addr = self.rpc.deriveaddresses(AddChecksum(desc))[0]
# removes prefix and checksum since regtest gives
# prefix `bcrt` on Bitcoin Core while wallets return testnet `tb` prefix
self.assertEqual(wsh_multi_addr[4:58], result['address'][2:56])
def test_display_address_multisig_descriptor(self):
supports_multisig = {'trezor_1', 'keepkey', 'coldcard', 'trezor_t'}
if self.full_type not in supports_multisig:
return
# Import some keys to the watch only wallet and get multisig address
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--sh_wpkh', '50', '60'])
import_result = self.wrpc.importmulti(keypool_desc)
self.assertTrue(import_result[0]['success'])
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--sh_wpkh', '--internal', '50', '60'])
import_result = self.wrpc.importmulti(keypool_desc)
self.assertTrue(import_result[0]['success'])
sh_wpkh_addr = self.wrpc.getnewaddress('', 'p2sh-segwit')
wpkh_addr = self.wrpc.getnewaddress('', 'bech32')
pkh_addr = self.wrpc.getnewaddress('', 'legacy')
self.wrpc.importaddress(wpkh_addr)
self.wrpc.importaddress(pkh_addr)
# pubkeys to construct 2-of-3 multisig descriptors for import
sh_wpkh_info = self.wrpc.getaddressinfo(sh_wpkh_addr)
wpkh_info = self.wrpc.getaddressinfo(wpkh_addr)
pkh_info = self.wrpc.getaddressinfo(pkh_addr)
pubkeys = [sh_wpkh_info['desc'][8:-11],
wpkh_info['desc'][5:-10],
pkh_info['desc'][4:-10]]
# Get the descriptors with their checksums
sh_multi_desc = self.wrpc.getdescriptorinfo('sh(sortedmulti(2,' + pubkeys[0] + ',' + pubkeys[1] + ',' + pubkeys[2] + '))')['descriptor']
sh_wsh_multi_desc = self.wrpc.getdescriptorinfo('sh(wsh(sortedmulti(2,' + pubkeys[0] + ',' + pubkeys[1] + ',' + pubkeys[2] + ')))')['descriptor']
wsh_multi_desc = self.wrpc.getdescriptorinfo('wsh(sortedmulti(2,' + pubkeys[2] + ',' + pubkeys[1] + ',' + pubkeys[0] + '))')['descriptor']
sh_multi_import = {'desc': sh_multi_desc, "timestamp": "now", "label": "shmulti-display-desc"}
sh_wsh_multi_import = {'desc': sh_wsh_multi_desc, "timestamp": "now", "label": "shwshmulti-display-desc"}
# re-order pubkeys to allow import without "already have private keys" error
wsh_multi_import = {'desc': wsh_multi_desc, "timestamp": "now", "label": "wshmulti-display-desc"}
multi_result = self.wrpc.importmulti([sh_multi_import, sh_wsh_multi_import, wsh_multi_import])
self.assertTrue(multi_result[0]['success'])
self.assertTrue(multi_result[1]['success'])
self.assertTrue(multi_result[2]['success'])
sh_multi_addr = self.wrpc.getaddressesbylabel("shmulti-display-desc").popitem()[0]
sh_wsh_multi_addr = self.wrpc.getaddressesbylabel("shwshmulti-display-desc").popitem()[0]
wsh_multi_addr = self.wrpc.getaddressesbylabel("wshmulti-display-desc").popitem()[0]
# need to replace `'` with `h` and to remove checksome for the stdin option to work
sh_multi_desc = sh_multi_desc.replace("'", "h").split('#')[0]
sh_wsh_multi_desc = sh_wsh_multi_desc.replace("'", "h").split('#')[0]
wsh_multi_desc = wsh_multi_desc.replace("'", "h").split('#')[0]
# legacy
result = self.do_command(self.dev_args + ['displayaddress', '--desc', sh_multi_desc])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
self.assertEqual(sh_multi_addr, result['address'])
# wrapped segwit
result = self.do_command(self.dev_args + ['displayaddress', '--desc', sh_wsh_multi_desc])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
self.assertEqual(sh_wsh_multi_addr, result['address'])
# native setwit
result = self.do_command(self.dev_args + ['displayaddress', '--desc', wsh_multi_desc])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
# removes prefix and checksum since regtest gives
# prefix `bcrt` on Bitcoin Core while wallets return testnet `tb` prefix
self.assertEqual(wsh_multi_addr[4:58], result['address'][2:56])
self.assertEqual(addr[4:58], result['address'][2:56])
class TestSignMessage(DeviceTestCase):
def test_sign_msg(self):

View File

@ -28,7 +28,7 @@ def digitalbitbox_test_suite(simulator, rpc, userpass, interface):
reply = send_plain(b'{"password":"0000"}', dev)
if 'error' not in reply:
break
except:
except Exception:
pass
time.sleep(0.5)
# Cleanup
@ -133,6 +133,9 @@ def digitalbitbox_test_suite(simulator, rpc, userpass, interface):
self.assertTrue(result['success'])
class TestBitboxGetXpub(DeviceTestCase):
def setUp(self):
self.dev_args.remove('--testnet')
def test_getxpub(self):
result = self.do_command(self.dev_args + ['--expert', 'getxpub', 'm/44h/0h/0h/3'])
self.assertEqual(result['xpub'], 'xpub6Du9e5Cz1NZWz3dvsvM21tsj4xEdbAb7AcbysFL42Y3yr8PLMnsaxhetHxurTpX5Rp5RbnFFwP1wct8K3gErCUSwcxFhxThsMBSxdmkhTNf')

View File

@ -99,6 +99,9 @@ def ledger_test_suite(emulator, rpc, userpass, interface):
self.assertEqual(result['code'], -9)
class TestLedgerGetXpub(DeviceTestCase):
def setUp(self):
self.dev_args.remove("--testnet")
def test_getxpub(self):
result = self.do_command(self.dev_args + ['--expert', 'getxpub', 'm/44h/0h/0h/3'])
self.assertEqual(result['xpub'], 'xpub6DqTtMuqBiBsSPb5UxB1qgJ3ViXuhoyZYhw3zTK4MywLB6psioW4PN1SAbhxVVirKQojnTBsjG5gXiiueRBgWmUuN43dpbMSgMCQHVqx2bR')

View File

@ -16,7 +16,7 @@ class TestUdevRulesInstaller(unittest.TestCase):
@classmethod
def tearDownClass(self):
for root, dirs, files in walk(self.INSTALLATION_FOLDER, topdown=False):
for root, _, files in walk(self.INSTALLATION_FOLDER, topdown=False):
for name in files:
remove(path.join(root, name))
removedirs(self.INSTALLATION_FOLDER)
@ -28,7 +28,7 @@ class TestUdevRulesInstaller(unittest.TestCase):
self.assertEqual(result['error'], 'Need to be root.')
self.assertEqual(result['code'], -16)
# Assert files wre copied
for root, dirs, files in walk(self.INSTALLATION_FOLDER, topdown=False):
for _, _, files in walk(self.INSTALLATION_FOLDER, topdown=False):
for file_name in files:
src = path.join(self.SOURCE_FOLDER, file_name)
tgt = path.join(self.INSTALLATION_FOLDER, file_name)