Compare commits
2 Commits
master
...
benma-bitb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49a5b8f2e8 | ||
|
|
9cd5c2a300 |
11
.gitignore
vendored
11
.gitignore
vendored
@ -14,14 +14,3 @@ hwilib/ui/ui_*.py
|
||||
|
||||
*.stderr
|
||||
*.stdout
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
.vscode
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
10
README.md
10
README.md
@ -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
|
||||
|
||||
@ -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`
|
||||
|
||||
|
||||
15
hwi.spec
15
hwi.spec
@ -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')
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
__version__ = '1.2.1'
|
||||
__version__ = '1.1.2'
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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']
|
||||
|
||||
@ -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 ]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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', '\'')
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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, [])
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -129,7 +129,7 @@ class WebUsbTransport(ProtocolBasedTransport):
|
||||
# non-functional.
|
||||
dev.getProduct()
|
||||
devices.append(WebUsbTransport(dev))
|
||||
except Exception:
|
||||
except:
|
||||
pass
|
||||
return devices
|
||||
|
||||
|
||||
@ -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.",
|
||||
)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
200
poetry.lock
generated
@ -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"},
|
||||
|
||||
@ -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"]
|
||||
|
||||
29
setup.py
29
setup.py
@ -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[](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[](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,
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user