Compare commits

..

2 Commits

Author SHA1 Message Date
Marko Bencun
49a5b8f2e8
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-07-27 16:26:05 +02:00
Marko Bencun
9cd5c2a300
add BitBox02 support 2020-07-27 16:26:02 +02:00
42 changed files with 482 additions and 713 deletions

11
.gitignore vendored
View File

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

View File

@ -68,8 +68,6 @@ 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

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>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,8 +6,6 @@ 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
@ -91,15 +89,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 |
@ -111,7 +109,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 | Yes | Yes | Yes | Yes | Yes |
| Mixed Segwit and Non-Segwit Inputs | N/A | N/A | Yes | N/A | Yes | Yes | Yes | Yes |
| Display on device screen | Yes | Yes | Yes | Yes | N/A | Yes | Yes | Yes |
## Using with Bitcoin Core

View File

@ -21,6 +21,7 @@ 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,21 +33,14 @@ pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
exclude_binaries=True,
name='hwi',
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.2.1'
__version__ = '1.1.2'

View File

@ -62,16 +62,15 @@ def find_device(password='', device_type=None, fingerprint=None, expert=False):
try:
client = get_client(d['type'], d['path'], password, expert)
if fingerprint:
master_fpr = d.get('fingerprint', None)
if master_fpr is None:
master_fpr = client.get_master_fingerprint_hex()
master_fpr = d.get('fingerprint', None)
if master_fpr is None:
master_fpr = client.get_master_fingerprint_hex()
if master_fpr != fingerprint:
client.close()
continue
if fingerprint and master_fpr != fingerprint:
client.close()
continue
return client
except Exception:
except:
if client:
client.close()
pass # Ignore things we wouldn't get fingerprints for
@ -240,18 +239,15 @@ 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:
path += descriptor.path_suffix[i]
xpubs_descriptor = True
path += ','
return {'error': 'Multisig descriptor must include all pubkeys', 'code': BAD_ARGUMENT}
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, descriptor=descriptor if xpubs_descriptor else None)
return client.display_address(path, descriptor.sh_wpkh or descriptor.sh_wsh, descriptor.wpkh or descriptor.wsh, redeem_script)
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,4 +1,3 @@
# mypy: ignore-errors
import re
# From: https://github.com/bitcoin/bitcoin/blob/master/src/script/descriptor.cpp
@ -83,12 +82,6 @@ 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):
@ -154,9 +147,6 @@ 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,15 +14,20 @@ 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, Descriptor
from ..hwwclient import HardwareWalletClient
from ..serializations import (
PSBT,
CTxOut,
is_p2pkh,
is_p2sh,
is_p2wpkh,
is_p2wsh,
is_witness,
ser_uint256,
ser_sig_der,
)
@ -39,6 +44,8 @@ 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 (
@ -58,19 +65,19 @@ from bitbox02.communication.bitbox_api_protocol import (
)
class BitBox02Error(UnavailableActionError):
def __init__(self, msg: str):
class KeypathError(UnavailableActionError):
def __init__(self, keypath: str, is_testnet: bool):
"""
BitBox02 unexpected error. The BitBox02 does not return give granular error messages,
so we give hints to as what could be wrong.
Keypath error exception with formatting and docs.
"""
msg = "Input error: {}. A keypath might be invalid. Supported keypaths are: ".format(
msg
network = "testnet" if is_testnet else "mainnet"
msg = "The BitBox02 does not support the keypath {} on {}. Supported keypaths are:\n".format(
keypath, network
)
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 += "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 += "For address keypaths, append /0/<address index> for a receive and /1/<change index> for a change address."
super().__init__(msg)
@ -133,38 +140,8 @@ class CLINoiseConfig(util.BitBoxAppNoiseConfig):
)
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 _keypath_check_account(bip44_account: int) -> bool:
return HARDENED <= bip44_account <= HARDENED + 99
def enumerate(password: str = "") -> List[Dict[str, object]]:
@ -176,10 +153,6 @@ 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
)
@ -192,32 +165,29 @@ def enumerate(password: str = "") -> List[Dict[str, object]]:
assert isinstance(edition, BitBox02Edition)
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,
}
)
d_data = {
"type": "bitbox02",
"path": path,
"model": {
BitBox02Edition.MULTI: "bitbox02_multi",
BitBox02Edition.BTCONLY: "bitbox02_btconly",
}[edition],
"needs_pin_sent": False,
"needs_passphrase_sent": False,
}
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()
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()
result.append(d_data)
@ -242,7 +212,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 BitBox02Error(str(exc))
raise BadArgumentError("invalid input")
raise exc
except FirmwareVersionOutdatedException as exc:
raise DeviceNotReadyError(str(exc))
@ -257,7 +227,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."
)
@ -275,7 +245,7 @@ class Bitbox02Client(HardwareWalletClient):
def set_noise_config(self, noise_config: BitBoxNoiseConfig) -> None:
self.noise_config = noise_config
def init(self, expect_initialized: Optional[bool] = True) -> bitbox02.BitBox02:
def init(self) -> bitbox02.BitBox02:
if self.bb02 is not None:
return self.bb02
@ -292,19 +262,6 @@ 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)
@ -319,6 +276,8 @@ 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]]:
@ -326,7 +285,7 @@ class Bitbox02Client(HardwareWalletClient):
"The BitBox02 does not need a PIN sent from the host"
)
def send_pin(self, pin: str) -> Dict[str, Union[bool, str, int]]:
def send_pin(self) -> Dict[str, Union[bool, str, int]]:
raise UnavailableActionError(
"The BitBox02 does not need a PIN sent from the host"
)
@ -336,24 +295,82 @@ class Bitbox02Client(HardwareWalletClient):
return bitbox02.btc.TBTC
return bitbox02.btc.BTC
def _get_xpub(self, keypath: Sequence[int]) -> str:
xpub_type = (
bitbox02.btc.BTCPubRequest.TPUB
if self.is_testnet
else bitbox02.btc.BTCPubRequest.XPUB
)
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()
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 Bitbox02Exception as exc:
raise BitBox02Error(str(exc))
except ValueError:
raise KeypathError(bip32_path, self.is_testnet)
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,
@ -361,25 +378,30 @@ 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,
@ -388,6 +410,9 @@ 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]]]:
@ -600,9 +625,7 @@ class Bitbox02Client(HardwareWalletClient):
return {"psbt": psbt.serialize()}
def sign_message(
self, message: Union[str, bytes], bip32_path: str
) -> Dict[str, str]:
def sign_message(self, message: str, bip32_path: str) -> Dict[str, str]:
raise UnavailableActionError("The BitBox02 does not support 'signmessage'")
@bitbox02_exception
@ -624,7 +647,9 @@ class Bitbox02Client(HardwareWalletClient):
"Passphrase not needed when setting up a BitBox02."
)
bb02 = self.init(expect_initialized=False)
bb02 = self.init()
if bb02.device_info()["initialized"]:
raise UnavailableActionError("The BitBox02 must be wiped before setup.")
if label:
bb02.set_device_name(label)
@ -651,7 +676,9 @@ class Bitbox02Client(HardwareWalletClient):
def restore_device(
self, label: str = "", word_count: int = 24
) -> Dict[str, Union[bool, str, int]]:
bb02 = self.init(expect_initialized=False)
bb02 = self.init()
if bb02.device_info()["initialized"]:
raise UnavailableActionError("The BitBox02 must be wiped before restore.")
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 _ in range(numInputs):
for i 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 _ in range(numOutputs):
for i 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 Exception:
except:
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 Exception:
except:
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 Exception:
except:
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 Exception:
except:
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 Exception:
except:
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:
except CCProtoError as e:
if expect_errors: raise
raise
except Exception:
except:
#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, _ = decode_xpub(expected_xpub)
pubkey, chaincode = 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,8 +376,7 @@ class UnixSimulatorPipe:
self.pipe.close()
try:
os.unlink(self.pipe_name)
except Exception:
pass
except: 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 _ in range(dfu_prefix.targets):
for idx in range(dfu_prefix.targets):
prefix = consume(fd, 'Target', '<6sBI255s2I',
'signature altsetting named name size elements')
#print("target%d: %r" % (idx, prefix))
for _ in range(prefix.elements):
for ei in range(prefix.elements):
# Decode target prefix
# < little endian
# I uint32_t element address

View File

@ -1,7 +1,5 @@
# Coldcard interaction script
from typing import Dict, Union
from ..hwwclient import HardwareWalletClient
from ..errors import (
ActionCanceledError,
@ -124,7 +122,7 @@ class ColdcardClient(HardwareWalletClient):
if our_keys > passes:
passes = our_keys
for _ in range(passes):
for i in range(0, passes):
# Get psbt in hex and then make binary
fd = io.BytesIO(base64.b64decode(tx.serialize()))
@ -176,18 +174,15 @@ 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: Union[str, bytes], keypath: str) -> Dict[str, str]:
def sign_message(self, message, keypath):
self.device.check_mitm()
keypath = keypath.replace('h', '\'')
keypath = keypath.replace('H', '\'')
msg = message
if not isinstance(message, bytes):
msg = message.encode()
ok = self.device.send_recv(
CCProtocolPacker.sign_message(msg, keypath, AF_CLASSIC), timeout=None
)
ok = self.device.send_recv(CCProtocolPacker.sign_message(message.encode(), keypath, AF_CLASSIC), timeout=None)
assert ok is None
if self.device.is_simulator:
self.device.send_recv(CCProtocolPacker.sim_keypress(b'y'))
@ -203,14 +198,14 @@ class ColdcardClient(HardwareWalletClient):
if len(done) != 2:
raise DeviceFailureError('Failed: %r' % done)
_, raw = done
addr, 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, descriptor=None):
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None):
self.device.check_mitm()
keypath = keypath.replace('h', '\'')
keypath = keypath.replace('H', '\'')

View File

@ -13,7 +13,6 @@ import logging
import socket
import sys
import time
from typing import Dict, Union
from ..hwwclient import HardwareWalletClient
from ..errors import (
@ -513,15 +512,14 @@ 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: Union[str, bytes], keypath: str) -> Dict[str, str]:
def sign_message(self, message, keypath):
to_hash = b""
to_hash += self.message_magic
to_hash += ser_compact_size(len(message))
if isinstance(message, bytes):
to_hash += message
else:
to_hash += message.encode()
to_hash += message.encode()
hashed_message = hash256(to_hash)
@ -551,7 +549,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, descriptor=None):
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None):
raise UnavailableActionError('The Digital Bitbox does not have a screen to display addresses on')
# Setup a new device
@ -631,7 +629,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 Exception:
except:
pass
for d in devices:
if ('interface_number' in d and d['interface_number'] == 0

View File

@ -1,7 +1,5 @@
# Ledger interaction script
from typing import Dict, Union
from ..hwwclient import HardwareWalletClient
from ..errors import (
ActionCanceledError,
@ -40,14 +38,10 @@ import re
SIMULATOR_PATH = 'tcp:127.0.0.1:9999'
LEDGER_VENDOR_ID = 0x2c97
LEDGER_MODEL_IDS = {
0x10: "ledger_nano_s",
0x40: "ledger_nano_x"
}
LEDGER_LEGACY_PRODUCT_IDS = {
0x0001: "ledger_nano_s",
0x0004: "ledger_nano_x"
}
LEDGER_DEVICE_IDS = [
0x0001, # Ledger Nano S
0x0004, # Ledger Nano X
]
# minimal checking of string keypath
def check_keypath(key_path):
@ -317,14 +311,13 @@ 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: Union[str, bytes], keypath: str) -> Dict[str, str]:
def sign_message(self, message, keypath):
if not check_keypath(keypath):
raise BadArgumentError("Invalid keypath")
if isinstance(message, str):
message = bytearray(message, 'utf-8')
else:
message = bytearray(message)
message = bytearray(message, 'utf-8')
keypath = keypath[2:]
# First display on screen what address you're signing for
self.app.getWalletPublicKey(keypath, True)
@ -347,7 +340,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, descriptor=None):
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None):
if not check_keypath(keypath):
raise BadArgumentError("Invalid keypath")
if redeem_script is not None:
@ -390,8 +383,9 @@ class LedgerClient(HardwareWalletClient):
def enumerate(password=''):
results = []
devices = []
devices.extend(hid.enumerate(LEDGER_VENDOR_ID, 0))
devices.append({'path': SIMULATOR_PATH.encode(), 'interface_number': 0, 'product_id': 0x1000})
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})
for d in devices:
if ('interface_number' in d and d['interface_number'] == 0
@ -400,13 +394,7 @@ def enumerate(password=''):
path = d['path'].decode()
d_data['type'] = 'ledger'
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['model'] = 'ledger_nano_x' if d['product_id'] == 0x0004 else 'ledger_nano_s'
d_data['path'] = path
if path == SIMULATOR_PATH:

View File

@ -1,7 +1,5 @@
# Trezor interaction script
from typing import Dict, Union
from ..hwwclient import HardwareWalletClient
from ..errors import (
ActionCanceledError,
@ -259,7 +257,7 @@ class TrezorClient(HardwareWalletClient):
if is_ms:
# Add to txinputtype
txinputtype.multisig = multisig
if not is_wit:
if not psbt_in.witness_utxo:
if utxo.is_p2sh:
txinputtype.script_type = proto.InputScriptType.SPENDMULTISIG
else:
@ -401,8 +399,10 @@ 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: Union[str, bytes], keypath: str) -> Dict[str, str]:
def sign_message(self, message, keypath):
self._check_unlocked()
path = tools.parse_path(keypath)
result = btc.sign_message(self.client, self.coin_name, path, message)
@ -410,20 +410,11 @@ class TrezorClient(HardwareWalletClient):
# Display address of specified type on the device.
@trezor_exception
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None, descriptor=None):
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=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
elif redeem_script:
if redeem_script:
# Get multisig object required by Trezor's get_address
multisig = parse_multisig(bytes.fromhex(redeem_script))
if not multisig[0]:
@ -460,7 +451,7 @@ class TrezorClient(HardwareWalletClient):
multisig=multisig,
)
return {'address': address}
except Exception:
except:
pass
raise BadArgumentError("No path supplied matched device keys")
@ -543,7 +534,7 @@ class TrezorClient(HardwareWalletClient):
self._check_unlocked()
try:
device.apply_settings(self.client, use_passphrase=not self.client.features.passphrase_protection)
except Exception:
except:
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,10 +15,9 @@
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import binascii
from typing import Union
from . import messages
from .tools import expect, normalize_nfc, session
from .tools import CallException, expect, normalize_nfc, session
@expect(messages.PublicKey)
@ -63,11 +62,7 @@ def get_address(
@expect(messages.MessageSignature)
def sign_message(
client,
coin_name,
n,
message: Union[str, bytes],
script_type=messages.InputScriptType.SPENDADDRESS,
client, coin_name, n, message, script_type=messages.InputScriptType.SPENDADDRESS
):
message = normalize_nfc(message)
return client.call(
@ -76,7 +71,6 @@ 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.debug("creating client instance for device: {}".format(transport.get_path()))
LOG.info("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 Exception:
except:
self.call_raw(messages.Cancel())
raise

View File

@ -14,7 +14,6 @@
# 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
@ -26,8 +25,6 @@ from .transport import enumerate_devices, get_transport
RECOVERY_BACK = "\x08" # backspace character, sent literally
LOG = logging.getLogger(__name__)
class TrezorDevice:
"""
@ -195,7 +192,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, _, fflags in self.get_fields().values():
for fname, ftype, 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, Union
from typing import List, NewType
from .exceptions import TrezorFailure
@ -181,13 +181,13 @@ def parse_path(nstr: str) -> Address:
raise ValueError("Invalid BIP32 path", nstr)
def normalize_nfc(txt: Union[str, bytes]) -> bytes:
def normalize_nfc(txt):
"""
Normalize message to NFC and return bytes suitable for protobuf.
This seems to be bitcoin-qt standard of doing things.
"""
if isinstance(txt, bytes):
return txt
txt = txt.decode()
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.debug("Enumerating {}: found {} devices".format(name, len(found)))
LOG.info("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.debug(
LOG.info(
"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.warning("HID transport is disabled: {}".format(e))
LOG.info("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 Exception:
except:
pass
return devices

View File

@ -3,7 +3,6 @@
import json
import logging
import sys
import time
from typing import Callable
from . import commands, __version__
@ -221,11 +220,6 @@ 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)
@ -237,11 +231,7 @@ class BitBox02NoiseConfig(bitbox02.util.BitBoxAppNoiseConfig):
dialog = BitBox02PairingDialog(code, device_response)
dialog.show()
# render the window since the next operation is blocking
while True:
QCoreApplication.processEvents()
if dialog.painted:
break
time.sleep(0.1)
QCoreApplication.processEvents()
if not device_response():
return False
dialog.enable_buttons()
@ -250,8 +240,7 @@ 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,7 +1,6 @@
from typing import Dict, Optional, Union
from .base58 import get_xpub_fingerprint_hex
from .descriptor import Descriptor
from .serializations import PSBT
@ -56,14 +55,10 @@ class HardwareWalletClient(object):
raise NotImplementedError("The HardwareWalletClient base class "
"does not implement this method")
def sign_message(
self, message: Union[str, bytes], bip32_path: str
) -> Dict[str, str]:
def sign_message(self, message: str, bip32_path: str) -> Dict[str, str]:
"""Sign a message (bitcoin message signing).
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.
Sign the message according to the bitcoin message signing standard.
Retrieve the signing key at the specified BIP32 derivation path.
@ -78,7 +73,6 @@ 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.
@ -162,7 +156,7 @@ class HardwareWalletClient(object):
raise NotImplementedError("The HardwareWalletClient base class "
"does not implement this method")
def send_pin(self, pin: str) -> Dict[str, Union[bool, str, int]]:
def send_pin(self) -> Dict[str, Union[bool, str, int]]:
"""Send PIN.
Must return a dictionary with the "success" key,

View File

View File

@ -103,7 +103,7 @@ def deser_uint256(f: Readable) -> int:
def ser_uint256(u: int) -> bytes:
rs = b""
for _ in range(8):
for i 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 _ in range(nit):
for i 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 _ in range(nit):
for i 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 _ in range(len(self.wit.vtxinwit), len(self.vin)):
for i 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,12 +1,9 @@
# 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"
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"

200
poetry.lock generated
View File

@ -17,62 +17,6 @@ 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+"
@ -181,17 +125,6 @@ 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"
@ -212,18 +145,6 @@ 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"
@ -240,14 +161,6 @@ 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"
@ -289,14 +202,6 @@ 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"
@ -305,14 +210,6 @@ 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+"
@ -338,8 +235,7 @@ testing = ["jaraco.itertools", "func-timeout"]
qt = ["pyside2"]
[metadata]
content-hash = "fc39b2be42f870113feb38f56c177164fb7302eb07616d850f5a1b9e3e8509e1"
lock-version = "1.0"
content-hash = "bce2677f0c45cb74f03eecfab2094577eed8fb16fe305a21c924128c4faa1b47"
python-versions = "^3.6,<3.9"
[metadata.files]
@ -350,65 +246,6 @@ 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"},
@ -456,35 +293,12 @@ 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"},
]
@ -492,10 +306,6 @@ 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"},
@ -515,10 +325,6 @@ 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"},
@ -527,10 +333,6 @@ 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.2.1"
version = "1.1.2"
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/", "hwilib/py.typed"]
include = ["hwilib/**/*.py", "udev/"]
packages = [
{ include = "hwi.py" },
{ include = "hwi-qt.py" },
@ -24,7 +24,6 @@ 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,17 +13,6 @@ 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',
@ -42,8 +31,6 @@ 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',
@ -62,8 +49,6 @@ 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',
@ -82,8 +67,6 @@ 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',
@ -102,8 +85,6 @@ 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',
@ -117,13 +98,13 @@ package_data = \
modules = \
['hwi', 'hwi-qt']
install_requires = \
['bitbox02>=4.1.0',
'ecdsa>=0.13.0,<0.14.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']
'typing-extensions>=3.7,<4.0',
'bitbox02>=4.1.0']
extras_require = \
{'qt': ['pyside2>=5.14.0,<6.0.0']}
@ -133,9 +114,9 @@ entry_points = \
setup_kwargs = {
'name': 'hwi',
'version': '1.2.1',
'version': '1.1.2',
'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\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",
'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",
'author': 'Andrew Chow',
'author_email': 'andrew@achow101.com',
'maintainer': None,

View File

@ -1,4 +1,4 @@
From 8793fbaa9b32f3c67f289a05194e68acc5c61b7d Mon Sep 17 00:00:00 2001
From cadbd3d25306b43060fd06eed589947d537a5ced 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..6d301d4 100644
index 0313c3e..e2c3d71 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..6d301d4 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, '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'}],
+ ['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'}],
+ ]
sim_defaults['fee_limit'] = -1
if '--xfp' in sys.argv:
--
2.28.0
2.27.0

View File

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

View File

@ -16,18 +16,6 @@ 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)
@ -40,18 +28,6 @@ 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,12 +13,8 @@ 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):
@ -68,7 +64,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, '--testnet']
self.dev_args = ['-t', self.type, '-d', self.path]
if emulator:
self.emulator = emulator
else:
@ -117,12 +113,6 @@ 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()
@ -176,64 +166,77 @@ class TestDeviceConnect(DeviceTestCase):
class TestGetKeypool(DeviceTestCase):
def setUp(self):
super().setUp()
self.setup_wallets()
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_getkeypool(self):
pkh_keypool_desc = self.do_command(self.dev_args + ['getkeypool', '0', '20'])
import_result = self.wrpc.importdescriptors(pkh_keypool_desc)
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'])
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/"))
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)
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))
shwpkh_keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--sh_wpkh', '0', '20'])
import_result = self.wrpc.importdescriptors(shwpkh_keypool_desc)
import_result = self.wrpc.importmulti(shwpkh_keypool_desc)
self.assertTrue(import_result[0]['success'])
for _ in range(0, 21):
for i in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress('', 'p2sh-segwit'))
self.assertTrue(addr_info['hdkeypath'].startswith("m/49'/1'/0'/0/"))
self.assertEqual(addr_info['hdkeypath'], "m/49'/1'/0'/0/{}".format(i))
addr_info = self.wrpc.getaddressinfo(self.wrpc.getrawchangeaddress('p2sh-segwit'))
self.assertTrue(addr_info['hdkeypath'].startswith("m/49'/1'/0'/1/"))
self.assertEqual(addr_info['hdkeypath'], "m/49'/1'/0'/1/{}".format(i))
wpkh_keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--wpkh', '0', '20'])
import_result = self.wrpc.importdescriptors(wpkh_keypool_desc)
import_result = self.wrpc.importmulti(wpkh_keypool_desc)
self.assertTrue(import_result[0]['success'])
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/"))
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))
# 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.importdescriptors(keypool_desc)
import_result = self.wrpc.importmulti(keypool_desc)
self.assertTrue(import_result[0]['success'])
for _ in range(0, 21):
for i in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress('', 'p2sh-segwit'))
self.assertTrue(addr_info['hdkeypath'].startswith("m/49'/1'/3'/0/"))
self.assertEqual(addr_info['hdkeypath'], "m/49'/1'/3'/0/{}".format(i))
addr_info = self.wrpc.getaddressinfo(self.wrpc.getrawchangeaddress('p2sh-segwit'))
self.assertTrue(addr_info['hdkeypath'].startswith("m/49'/1'/3'/1/"))
self.assertEqual(addr_info['hdkeypath'], "m/49'/1'/3'/1/{}".format(i))
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--wpkh', '--account', '3', '0', '20'])
import_result = self.wrpc.importdescriptors(keypool_desc)
import_result = self.wrpc.importmulti(keypool_desc)
self.assertTrue(import_result[0]['success'])
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/"))
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))
keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--path', 'm/0h/0h/4h/*', '0', '20'])
import_result = self.wrpc.importdescriptors(keypool_desc)
import_result = self.wrpc.importmulti(keypool_desc)
self.assertTrue(import_result[0]['success'])
for _ in range(0, 21):
addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress('', 'legacy'))
self.assertTrue(addr_info['hdkeypath'].startswith("m/0'/0'/4'/"))
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))
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/')
@ -243,6 +246,12 @@ 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()
@ -266,8 +275,14 @@ class TestGetDescriptors(DeviceTestCase):
class TestSignTx(DeviceTestCase):
def setUp(self):
super().setUp()
self.setup_wallets()
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 _generate_and_finalize(self, unknown_inputs, psbt):
if not unknown_inputs:
@ -318,50 +333,49 @@ 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', '--all', '30', '50'])
import_result = self.wrpc.importdescriptors(keypool_desc)
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)
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)
sh_multi_desc, sh_multi_addr, sh_wsh_multi_desc, sh_wsh_multi_addr, wsh_multi_desc, wsh_multi_addr = self._make_multisigs()
# 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_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.importdescriptors([sh_multi_import, sh_wsh_multi_import, wsh_multi_import])
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").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
@ -403,9 +417,9 @@ class TestSignTx(DeviceTestCase):
# Test wrapper to avoid mixed-inputs signing for Ledger
def test_signtx(self):
supports_mixed = {'coldcard', 'trezor_1', 'digitalbitbox', 'keepkey', 'trezor_t'}
supports_mixed = {'coldcard', 'trezor_1', 'digitalbitbox', 'keepkey'}
supports_multisig = {'ledger', 'trezor_1', 'digitalbitbox', 'keepkey', 'coldcard', 'trezor_t'}
supports_external = {'ledger', 'trezor_1', 'digitalbitbox', 'keepkey', 'coldcard', 'trezor_t'}
supports_external = {'ledger', 'trezor_1', 'digitalbitbox', 'keepkey', 'coldcard'}
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:
@ -441,6 +455,16 @@ 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)
@ -514,77 +538,152 @@ class TestDisplayAddress(DeviceTestCase):
self.assertIn('code', result)
self.assertEqual(result['code'], -7)
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])
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)
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 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)
self.assertEqual(self.rpc.deriveaddresses(desc)[0], ms_info["address"])
pubkeys = [sh_wpkh_info['desc'][8:-11],
wpkh_info['desc'][5:-10],
pkh_info['desc'][4:-10]]
path = "{},{},{}".format(sorted_pubkeys[0][1], sorted_pubkeys[1][1], sorted_pubkeys[2][1])
# 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']
return ms_info["address"], desc, ms_info["redeemScript"], path
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'])
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 = 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]
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)
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']
if use_desc:
args = ['displayaddress', '--desc', desc]
else:
args = ['displayaddress', '--path', path, '--redeem_script', rs]
if addrtype != "pkh":
args.append("--{}".format(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")
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])
# 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])
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(addr[4:58], result['address'][2:56])
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])
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 Exception:
except:
pass
time.sleep(0.5)
# Cleanup
@ -133,9 +133,6 @@ 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,9 +99,6 @@ 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, _, files in walk(self.INSTALLATION_FOLDER, topdown=False):
for root, dirs, 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 _, _, files in walk(self.INSTALLATION_FOLDER, topdown=False):
for root, dirs, 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)