Compare commits

..

No commits in common. "master" and "pr-devfind" have entirely different histories.

29 changed files with 120 additions and 513 deletions

View File

@ -40,7 +40,7 @@ addons:
- cython3 - cython3
- ccache - ccache
install: install:
- pip install pipenv pysdl2 python-bitcoinrpc protobuf poetry==0.12.12 - pip install pipenv pysdl2 python-bitcoinrpc protobuf poetry
# From trezor-mcu to get the correct protobuf version # From trezor-mcu to get the correct protobuf version
- curl -LO "https://github.com/google/protobuf/releases/download/v3.4.0/protoc-3.4.0-linux-x86_64.zip" - curl -LO "https://github.com/google/protobuf/releases/download/v3.4.0/protoc-3.4.0-linux-x86_64.zip"
- unzip "protoc-3.4.0-linux-x86_64.zip" -d protoc - unzip "protoc-3.4.0-linux-x86_64.zip" -d protoc
@ -56,7 +56,7 @@ jobs:
- name: With command line interface - name: With command line interface
script: cd test; poetry run ./run_tests.py --interface=cli script: cd test; poetry run ./run_tests.py --interface=cli
- name: With stdin interface - name: With stdin interface
script: cd test; poetry run ./run_tests.py --interface=stdin script: cd test; ./run_tests.py --interface=stdin
- name: With linux binary distribution command line interface - name: With linux binary distribution command line interface
services: docker services: docker
before_script: before_script:

View File

@ -69,23 +69,23 @@ Please also see [docs](docs/) for additional information about each device.
| Support Planned | Yes | Yes | Yes | Yes | Yes | Yes | | Support Planned | Yes | Yes | Yes | Yes | Yes | Yes |
| Implemented | Yes | Yes | Yes | Yes | Yes | Yes | | Implemented | Yes | Yes | Yes | Yes | Yes | Yes |
| xpub retrieval | Yes | Yes | Yes | Yes | Yes | Yes | | xpub retrieval | Yes | Yes | Yes | Yes | Yes | Yes |
| Message Signing | Yes | Yes | Yes | Yes | Yes | Yes | | Message Signing | Yes | Yes | ? | Yes | Yes | Yes |
| Device Setup | N/A | Yes | Yes | Yes | Yes | N/A | | Device Setup | N/A | Yes | ? | Yes | Yes | N/A |
| Device Wipe | N/A | Yes | Yes | Yes | Yes | N/A | | Device Wipe | N/A | Yes | ? | Yes | Yes | N/A |
| Device Recovery | N/A | Yes | Yes | N/A | Yes | N/A | | Device Recovery | N/A | Yes | ? | N/A | Yes | N/A |
| Device Backup | N/A | N/A | N/A | Yes | N/A | Yes | | Device Backup | N/A | N/A | ? | Yes | N/A | Yes |
| P2PKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes | | P2PKH Inputs | Yes | Yes | ? | Yes | Yes | Yes |
| P2SH-P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes | | P2SH-P2WPKH Inputs | Yes | Yes | ? | Yes | Yes | Yes |
| P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes | | P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes |
| P2SH Multisig Inputs | Yes | Yes | Yes | Yes | Yes | N/A | | P2SH Multisig Inputs | Yes | Yes | ? | Yes | Yes | N/A |
| P2SH-P2WSH Multisig Inputs | Yes | Yes | Yes | Yes | No | N/A | | P2SH-P2WSH Multisig Inputs | Yes | No | ? | Yes | No | N/A |
| P2WSH Multisig Inputs | Yes | Yes | Yes | Yes | Yes | N/A | | P2WSH Multisig Inputs | Yes | No | ? | Yes | Yes | N/A |
| Bare Multisig Inputs | Yes | N/A | N/A | Yes | N/A | N/A | | Bare Multisig Inputs | Yes | N/A | ? | Yes | N/A | N/A |
| Aribtrary scriptPubKey Inputs | Yes | N/A | N/A | Yes | N/A | N/A | | Aribtrary scriptPubKey Inputs | Yes | N/A | ? | Yes | N/A | N/A |
| Aribtrary redeemScript Inputs | Yes | N/A | N/A | Yes | N/A | N/A | | Aribtrary redeemScript Inputs | Yes | N/A | ? | Yes | N/A | N/A |
| Arbitrary witnessScript Inputs | Yes | N/A | N/A | Yes | N/A | N/A | | Arbitrary witnessScript Inputs | Yes | N/A | ? | Yes | N/A | N/A |
| Non-wallet inputs | Yes | Yes | Yes | Yes | Yes | Yes | | Non-wallet inputs | Yes | Yes | ? | Yes | Yes | Yes |
| Mixed Segwit and Non-Segwit Inputs | N/A | Yes | N/A | Yes | Yes | Yes | | Mixed Segwit and Non-Segwit Inputs | N/A | Yes | ? | Yes | Yes | Yes |
| Display on device screen | Yes | Yes | Yes | N/A | Yes | Yes | | Display on device screen | Yes | Yes | Yes | N/A | Yes | Yes |
## Using with Bitcoin Core ## Using with Bitcoin Core

View File

@ -4,7 +4,7 @@
eval "$(pyenv init -)" eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)" eval "$(pyenv virtualenv-init -)"
pip install -U pip pip install -U pip
pip install poetry==0.12.12 pip install poetry
# Setup poetry and install the dependencies # Setup poetry and install the dependencies
poetry install poetry install

View File

@ -4,7 +4,7 @@
eval "$(pyenv init -)" eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)" eval "$(pyenv virtualenv-init -)"
pip install -U pip pip install -U pip
pip install poetry==0.12.12 pip install poetry
# Setup poetry and install the dependencies # Setup poetry and install the dependencies
poetry install poetry install

View File

@ -53,7 +53,7 @@ popd
$PYTHON -m pip install -U pip $PYTHON -m pip install -U pip
# Install Poetry and things needed for pyinstaller # Install Poetry and things needed for pyinstaller
$PYTHON -m pip install poetry==0.12.12 $PYTHON -m pip install poetry
# We also need to change the timestamps of all of the base library files # We also need to change the timestamps of all of the base library files
lib_dir=~/.wine/drive_c/python3/Lib lib_dir=~/.wine/drive_c/python3/Lib

View File

@ -286,7 +286,7 @@ e51392c82e13bbfe714c73361aff14ac1a1637abf37587a562844ae5a4265adf
When the keypools run out, they can be refilled by using the `getkeypool` commands as done in the beginning, but with different starting and ending indexes. For example, to refill my keypools, I would use the following `getkeypool` commands: When the keypools run out, they can be refilled by using the `getkeypool` commands as done in the beginning, but with different starting and ending indexes. For example, to refill my keypools, I would use the following `getkeypool` commands:
``` ```
$ ./hwi.py -f 8038ecd9 getkeypool --wpkh --keypool 1000 2000 $ ./hwi.py -f 8038ecd9 getkeypool --wpkh --keypool --internal 1000 2000
$ ./hwi.py -f 8038ecd9 getkeypool --wpkh --keypool --internal 1000 2000 $ ./hwi.py -f 8038ecd9 getkeypool --wpkh --keypool --internal 1000 2000
``` ```
The output can be imported with `importmulti` as shown in the Setup steps. The output can be imported with `importmulti` as shown in the Setup steps.

View File

@ -1,7 +1,6 @@
# Ledger Nano S # Ledger Nano S
The Ledger Nano S is supported by HWI. The Ledger Nano S is supported by HWI.
Note that the Bitcoin App must be installed and running on the device.
Currently implemented commands: Currently implemented commands:

View File

@ -25,10 +25,6 @@ a = Analysis(['hwi.py'],
win_private_assemblies=False, win_private_assemblies=False,
cipher=block_cipher, cipher=block_cipher,
noarchive=False) noarchive=False)
if platform.system() == 'Linux':
a.datas += Tree('udev', prefix='udev')
pyz = PYZ(a.pure, a.zipped_data, pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher) cipher=block_cipher)
exe = EXE(pyz, exe = EXE(pyz,

View File

@ -2,7 +2,7 @@
from .commands import backup_device, displayaddress, enumerate, find_device, \ from .commands import backup_device, displayaddress, enumerate, find_device, \
get_client, getmasterxpub, getxpub, getkeypool, prompt_pin, restore_device, send_pin, setup_device, \ get_client, getmasterxpub, getxpub, getkeypool, prompt_pin, restore_device, send_pin, setup_device, \
signmessage, signtx, wipe_device, install_udev_rules signmessage, signtx, wipe_device
from .errors import ( from .errors import (
HWWError, HWWError,
NO_DEVICE_PATH, NO_DEVICE_PATH,
@ -63,9 +63,6 @@ def prompt_pin_handler(args, client):
def send_pin_handler(args, client): def send_pin_handler(args, client):
return send_pin(client, pin=args.pin) return send_pin(client, pin=args.pin)
def install_udev_rules_handler(args):
return install_udev_rules(args.source, args.location)
def process_commands(cli_args): def process_commands(cli_args):
parser = argparse.ArgumentParser(description='Hardware Wallet Interface, version {}.\nAccess and send commands to a hardware wallet device. Responses are in JSON format.'.format(__version__), formatter_class=argparse.RawDescriptionHelpFormatter) parser = argparse.ArgumentParser(description='Hardware Wallet Interface, version {}.\nAccess and send commands to a hardware wallet device. Responses are in JSON format.'.format(__version__), formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('--device-path', '-d', help='Specify the device path of the device to connect to') parser.add_argument('--device-path', '-d', help='Specify the device path of the device to connect to')
@ -145,13 +142,6 @@ def process_commands(cli_args):
sendpin_parser.add_argument('pin', help='The numeric positions of the PIN') sendpin_parser.add_argument('pin', help='The numeric positions of the PIN')
sendpin_parser.set_defaults(func=send_pin_handler) sendpin_parser.set_defaults(func=send_pin_handler)
if sys.platform.startswith("linux"):
udevrules_parser = subparsers.add_parser('installudevrules', help='Install and load the udev rule files for the hardware wallet devices')
udevrules_parser.add_argument('--source', help=argparse.SUPPRESS, default='udev')
udevrules_parser.add_argument('--location', help='The path where the udev rules files will be copied', default='/etc/udev/rules.d/')
udevrules_parser.set_defaults(func=install_udev_rules_handler)
if any(arg == '--stdin' for arg in cli_args): if any(arg == '--stdin' for arg in cli_args):
blank_count = 0 blank_count = 0
while True: while True:
@ -187,17 +177,6 @@ def process_commands(cli_args):
if command == 'enumerate': if command == 'enumerate':
return args.func(args) return args.func(args)
# Install the devices udev rules for Linux
if command == 'installudevrules':
try:
result = args.func(args)
except Exception as e:
if args.debug:
import traceback
traceback.print_exc()
result = {'error': str(e), 'code': UNKNOWN_ERROR}
return result
# Auto detect if we are using fingerprint or type to identify device # Auto detect if we are using fingerprint or type to identify device
if args.fingerprint or (args.device_type and not args.device_path): if args.fingerprint or (args.device_type and not args.device_path):
client = find_device(args.device_path, args.password, args.device_type, args.fingerprint) client = find_device(args.device_path, args.password, args.device_type, args.fingerprint)

View File

@ -9,8 +9,6 @@ from .base58 import get_xpub_fingerprint_as_id, get_xpub_fingerprint_hex, xpub_t
from .errors import NoPasswordError, UnavailableActionError, DeviceAlreadyInitError, DeviceAlreadyUnlockedError, UnknownDeviceError, BAD_ARGUMENT, NOT_IMPLEMENTED from .errors import NoPasswordError, UnavailableActionError, DeviceAlreadyInitError, DeviceAlreadyUnlockedError, UnknownDeviceError, BAD_ARGUMENT, NOT_IMPLEMENTED
from .descriptor import Descriptor from .descriptor import Descriptor
from .devices import __all__ as all_devs from .devices import __all__ as all_devs
from .udevinstaller import UDevInstaller
# Get the client for the device # Get the client for the device
def get_client(device_type, device_path, password=''): def get_client(device_type, device_path, password=''):
@ -157,10 +155,6 @@ def displayaddress(client, path=None, desc=None, sh_wpkh=False, wpkh=False):
return {'error':'Both `--wpkh` and `--sh_wpkh` can not be selected at the same time.','code':BAD_ARGUMENT} return {'error':'Both `--wpkh` and `--sh_wpkh` can not be selected at the same time.','code':BAD_ARGUMENT}
return client.display_address(path, sh_wpkh, wpkh) return client.display_address(path, sh_wpkh, wpkh)
elif desc is not None: elif desc is not None:
if client.fingerprint is None:
master_xpub = client.get_pubkey_at_path('m/0h')['xpub']
client.fingerprint = get_xpub_fingerprint_hex(master_xpub)
if sh_wpkh == True or wpkh == True: if sh_wpkh == True or wpkh == True:
return {'error':' `--wpkh` and `--sh_wpkh` can not be combined with --desc','code':BAD_ARGUMENT} return {'error':' `--wpkh` and `--sh_wpkh` can not be combined with --desc','code':BAD_ARGUMENT}
descriptor = Descriptor.parse(desc, client.is_testnet) descriptor = Descriptor.parse(desc, client.is_testnet)
@ -192,6 +186,3 @@ def prompt_pin(client):
def send_pin(client, pin): def send_pin(client, pin):
return client.send_pin(pin) return client.send_pin(pin)
def install_udev_rules(source, location):
return UDevInstaller.install(source, location);

View File

@ -1,7 +1,7 @@
# Coldcard interaction script # Coldcard interaction script
from ..hwwclient import HardwareWalletClient from ..hwwclient import HardwareWalletClient
from ..errors import ActionCanceledError, BadArgumentError, DeviceBusyError, DeviceFailureError, HWWError, UnavailableActionError, UNKNOWN_ERROR from ..errors import ActionCanceledError, BadArgumentError, DeviceBusyError, UnavailableActionError, DeviceFailureError
from .ckcc.client import ColdcardDevice, COINKITE_VID, CKCC_PID from .ckcc.client import ColdcardDevice, COINKITE_VID, CKCC_PID
from .ckcc.protocol import CCProtocolPacker, CCBusyError, CCProtoError, CCUserRefused from .ckcc.protocol import CCProtocolPacker, CCBusyError, CCProtoError, CCUserRefused
from .ckcc.constants import MAX_BLK_LEN, AF_P2WPKH, AF_CLASSIC, AF_P2WPKH_P2SH from .ckcc.constants import MAX_BLK_LEN, AF_P2WPKH, AF_CLASSIC, AF_P2WPKH_P2SH
@ -14,8 +14,6 @@ import hid
import io import io
import sys import sys
import time import time
import struct
from binascii import hexlify
CC_SIMULATOR_SOCK = '/tmp/ckcc-simulator.sock' CC_SIMULATOR_SOCK = '/tmp/ckcc-simulator.sock'
# Using the simulator: https://github.com/Coldcard/firmware/blob/master/unix/README.md # Using the simulator: https://github.com/Coldcard/firmware/blob/master/unix/README.md
@ -58,10 +56,6 @@ class ColdcardClient(HardwareWalletClient):
else: else:
return {'xpub':xpub} return {'xpub':xpub}
def _get_fingerprint_hex(self):
# quick method to get fingerprint of wallet
return hexlify(struct.pack('<I', self.device.master_fingerprint)).decode()
# Must return a hex string with the signed transaction # Must return a hex string with the signed transaction
# The tx must be in the combined unsigned transaction format # The tx must be in the combined unsigned transaction format
@coldcard_exception @coldcard_exception
@ -225,35 +219,29 @@ def enumerate(password=''):
path = d['path'].decode() path = d['path'].decode()
d_data['type'] = 'coldcard' d_data['type'] = 'coldcard'
d_data['path'] = path d_data['path'] = path
d_data['needs_passphrase'] = False
client = None client = None
try: try:
client = ColdcardClient(path) client = ColdcardClient(path)
d_data['fingerprint'] = client._get_fingerprint_hex() master_xpub = client.get_pubkey_at_path('m/0h')['xpub']
except HWWError as e: d_data['fingerprint'] = get_xpub_fingerprint_hex(master_xpub)
d_data['error'] = "Could not open client or get fingerprint information: " + e.get_msg()
d_data['code'] = e.get_code()
except Exception as e: except Exception as e:
d_data['error'] = "Could not open client or get fingerprint information: " + str(e) d_data['error'] = "Could not open client or get fingerprint information: " + str(e)
d_data['code'] = UNKNOWN_ERROR
if client: if client:
client.close() client.close()
results.append(d_data) results.append(d_data)
# Check if the simulator is there # Check if the simulator is there
client = None client = None
try: try:
client = ColdcardClient(CC_SIMULATOR_SOCK) client = ColdcardClient(CC_SIMULATOR_SOCK)
master_xpub = client.get_pubkey_at_path('m/0h')['xpub']
d_data = {} d_data = {}
d_data['fingerprint'] = client._get_fingerprint_hex() d_data['fingerprint'] = get_xpub_fingerprint_hex(master_xpub)
d_data['type'] = 'coldcard' d_data['type'] = 'coldcard'
d_data['path'] = CC_SIMULATOR_SOCK d_data['path'] = CC_SIMULATOR_SOCK
d_data['needs_pin_sent'] = False
d_data['needs_passphrase_sent'] = False
results.append(d_data) results.append(d_data)
except RuntimeError as e: except RuntimeError as e:
if str(e) == 'Cannot connect to simulator. Is it running?': if str(e) == 'Cannot connect to simulator. Is it running?':
@ -262,5 +250,4 @@ def enumerate(password=''):
raise e raise e
if client: if client:
client.close() client.close()
return results return results

View File

@ -15,7 +15,7 @@ import sys
import time import time
from ..hwwclient import HardwareWalletClient from ..hwwclient import HardwareWalletClient
from ..errors import ActionCanceledError, BadArgumentError, DeviceFailureError, DeviceAlreadyInitError, DeviceNotReadyError, HWWError, NoPasswordError, UnavailableActionError, UNKNOWN_ERROR from ..errors import ActionCanceledError, BadArgumentError, DeviceFailureError, DeviceAlreadyInitError, DeviceNotReadyError, NoPasswordError, UnavailableActionError, DeviceFailureError
from ..serializations import CTransaction, PSBT, hash256, hash160, ser_sig_der, ser_sig_compact, ser_compact_size from ..serializations import CTransaction, PSBT, hash256, hash160, ser_sig_der, ser_sig_compact, ser_compact_size
from ..base58 import get_xpub_fingerprint, decode, to_address, xpub_main_2_test, get_xpub_fingerprint_hex from ..base58 import get_xpub_fingerprint, decode, to_address, xpub_main_2_test, get_xpub_fingerprint_hex
@ -603,14 +603,8 @@ def enumerate(password=''):
else: else:
master_xpub = client.get_pubkey_at_path('m/0h')['xpub'] master_xpub = client.get_pubkey_at_path('m/0h')['xpub']
d_data['fingerprint'] = get_xpub_fingerprint_hex(master_xpub) d_data['fingerprint'] = get_xpub_fingerprint_hex(master_xpub)
d_data['needs_pin_sent'] = False
d_data['needs_passphrase_sent'] = True
except HWWError as e:
d_data['error'] = "Could not open client or get fingerprint information: " + e.get_msg()
d_data['code'] = e.get_code()
except Exception as e: except Exception as e:
d_data['error'] = "Could not open client or get fingerprint information: " + str(e) d_data['error'] = "Could not open client or get fingerprint information: " + str(e)
d_data['code'] = UNKNOWN_ERROR
if client: if client:
client.close() client.close()

View File

@ -1,6 +1,5 @@
# KeepKey interaction script # KeepKey interaction script
from ..errors import HWWError, UNKNOWN_ERROR
from .trezorlib.transport import enumerate_devices from .trezorlib.transport import enumerate_devices
from .trezor import TrezorClient from .trezor import TrezorClient
from ..base58 import get_xpub_fingerprint_hex from ..base58 import get_xpub_fingerprint_hex
@ -26,24 +25,13 @@ def enumerate(password=''):
client.client.init_device() client.client.init_device()
if not 'keepkey' in client.client.features.vendor: if not 'keepkey' in client.client.features.vendor:
continue continue
d_data['needs_pin_sent'] = client.client.features.pin_protection and not client.client.features.pin_cached
d_data['needs_passphrase_sent'] = client.client.features.passphrase_protection and not client.client.features.passphrase_cached
if d_data['needs_pin_sent']:
raise DeviceNotReadyError('Keepkey is locked. Unlock by using \'promptpin\' and then \'sendpin\'.')
if d_data['needs_passphrase_sent'] and not password:
raise DeviceNotReadyError("Passphrase needs to be specified before the fingerprint information can be retrieved")
if client.client.features.initialized: if client.client.features.initialized:
master_xpub = client.get_pubkey_at_path('m/0h')['xpub'] master_xpub = client.get_pubkey_at_path('m/0h')['xpub']
d_data['fingerprint'] = get_xpub_fingerprint_hex(master_xpub) d_data['fingerprint'] = get_xpub_fingerprint_hex(master_xpub)
d_data['needs_passphrase_sent'] = False # Passphrase is always needed for the above to have worked, so it's already sent
else: else:
d_data['error'] = 'Not initialized' d_data['error'] = 'Not initialized'
except HWWError as e:
d_data['error'] = "Could not open client or get fingerprint information: " + e.get_msg()
d_data['code'] = e.get_code()
except Exception as e: except Exception as e:
d_data['error'] = "Could not open client or get fingerprint information: " + str(e) d_data['error'] = "Could not open client or get fingerprint information: " + str(e)
d_data['code'] = UNKNOWN_ERROR
if client: if client:
client.close() client.close()

View File

@ -1,7 +1,7 @@
# Ledger interaction script # Ledger interaction script
from ..hwwclient import HardwareWalletClient from ..hwwclient import HardwareWalletClient
from ..errors import ActionCanceledError, BadArgumentError, DeviceConnectionError, DeviceFailureError, HWWError, UnavailableActionError, UNKNOWN_ERROR from ..errors import ActionCanceledError, BadArgumentError, DeviceConnectionError, DeviceFailureError, UnavailableActionError
from .btchip.btchip import * from .btchip.btchip import *
from .btchip.btchipUtils import * from .btchip.btchipUtils import *
import base64 import base64
@ -350,11 +350,8 @@ def enumerate(password=''):
client = LedgerClient(path, password) client = LedgerClient(path, password)
master_xpub = client.get_pubkey_at_path('m/0h')['xpub'] master_xpub = client.get_pubkey_at_path('m/0h')['xpub']
d_data['fingerprint'] = get_xpub_fingerprint_hex(master_xpub) d_data['fingerprint'] = get_xpub_fingerprint_hex(master_xpub)
d_data['needs_pin_sent'] = False
d_data['needs_passphrase_sent'] = False
except Exception as e: except Exception as e:
d_data['error'] = "Could not open client or get fingerprint information: " + str(e) d_data['error'] = "Could not open client or get fingerprint information: " + str(e)
d_data['code'] = UNKNOWN_ERROR
if client: if client:
client.close() client.close()

View File

@ -1,7 +1,7 @@
# Trezor interaction script # Trezor interaction script
from ..hwwclient import HardwareWalletClient from ..hwwclient import HardwareWalletClient
from ..errors import ActionCanceledError, BadArgumentError, DeviceAlreadyInitError, DeviceAlreadyUnlockedError, DeviceConnectionError, DeviceNotReadyError, HWWError, UnavailableActionError, UNKNOWN_ERROR from ..errors import ActionCanceledError, BadArgumentError, DeviceAlreadyInitError, DeviceAlreadyUnlockedError, DeviceConnectionError, UnavailableActionError, DeviceNotReadyError
from .trezorlib.client import TrezorClient as Trezor from .trezorlib.client import TrezorClient as Trezor
from .trezorlib.debuglink import TrezorClientDebugLink, DebugUI from .trezorlib.debuglink import TrezorClientDebugLink, DebugUI
from .trezorlib.exceptions import Cancelled from .trezorlib.exceptions import Cancelled
@ -100,7 +100,6 @@ class TrezorClient(HardwareWalletClient):
transport = get_transport(path) transport = get_transport(path)
self.client = TrezorClientDebugLink(transport=transport) self.client = TrezorClientDebugLink(transport=transport)
self.simulator = True self.simulator = True
self.client.set_passphrase(password)
else: else:
self.client = Trezor(transport=get_transport(path), ui=PassphraseUI(password)) self.client = Trezor(transport=get_transport(path), ui=PassphraseUI(password))
@ -422,24 +421,13 @@ def enumerate(password=''):
client.client.init_device() client.client.init_device()
if not 'trezor' in client.client.features.vendor: if not 'trezor' in client.client.features.vendor:
continue continue
d_data['needs_pin_sent'] = client.client.features.pin_protection and not client.client.features.pin_cached
d_data['needs_passphrase_sent'] = client.client.features.passphrase_protection and not client.client.features.passphrase_cached
if d_data['needs_pin_sent']:
raise DeviceNotReadyError('Trezor is locked. Unlock by using \'promptpin\' and then \'sendpin\'.')
if d_data['needs_passphrase_sent'] and not password:
raise DeviceNotReadyError("Passphrase needs to be specified before the fingerprint information can be retrieved")
if client.client.features.initialized: if client.client.features.initialized:
master_xpub = client.get_pubkey_at_path('m/0h')['xpub'] master_xpub = client.get_pubkey_at_path('m/0h')['xpub']
d_data['fingerprint'] = get_xpub_fingerprint_hex(master_xpub) d_data['fingerprint'] = get_xpub_fingerprint_hex(master_xpub)
d_data['needs_passphrase_sent'] = False # Passphrase is always needed for the above to have worked, so it's already sent
else: else:
d_data['error'] = 'Not initialized' d_data['error'] = 'Not initialized'
except HWWError as e:
d_data['error'] = "Could not open client or get fingerprint information: " + e.get_msg()
d_data['code'] = e.get_code()
except Exception as e: except Exception as e:
d_data['error'] = "Could not open client or get fingerprint information: " + str(e) d_data['error'] = "Could not open client or get fingerprint information: " + str(e)
d_data['code'] = UNKNOWN_ERROR
if client: if client:
client.close() client.close()

View File

@ -178,10 +178,7 @@ class TrezorClient:
@tools.session @tools.session
def init_device(self): def init_device(self):
resp = self.call_raw(messages.GetFeatures()) resp = self.call_raw(messages.Initialize(state=self.state))
# If GetFeatures fails, try initializing and clearing inconsistent state on the device
if isinstance(resp, messages.Failure):
resp = self.call_raw(messages.Initialize())
if not isinstance(resp, messages.Features): if not isinstance(resp, messages.Features):
raise exceptions.TrezorException("Unexpected initial response") raise exceptions.TrezorException("Unexpected initial response")
else: else:

View File

@ -16,7 +16,6 @@ DEVICE_NOT_READY = -12
UNKNOWN_ERROR = -13 UNKNOWN_ERROR = -13
ACTION_CANCELED = -14 ACTION_CANCELED = -14
DEVICE_BUSY = -15 DEVICE_BUSY = -15
NEED_TO_BE_ROOT = -16
# Exceptions # Exceptions
class HWWError(Exception): class HWWError(Exception):

View File

@ -1,60 +0,0 @@
import sys
from subprocess import check_call, CalledProcessError, DEVNULL
from .errors import NEED_TO_BE_ROOT
from shutil import copy
from os import path, environ, listdir, getlogin, geteuid
class UDevInstaller(object):
@staticmethod
def install(source, location):
try:
udev_installer = UDevInstaller()
udev_installer.copy_udev_rule_files(source, location)
udev_installer.trigger()
udev_installer.reload_rules()
udev_installer.add_user_plugdev_group()
except CalledProcessError as e:
if geteuid() != 0:
return {'error':'Need to be root.','code':NEED_TO_BE_ROOT}
raise
return {"success": True}
def __init__(self):
self._udevadm = '/sbin/udevadm'
self._groupadd = '/usr/sbin/groupadd'
self._usermod = '/usr/sbin/usermod'
def _execute(self, command, *args):
command = [command] + list(args)
check_call(command, stderr=DEVNULL, stdout=DEVNULL)
def trigger(self):
self._execute(self._udevadm, 'trigger')
def reload_rules(self):
self._execute(self._udevadm, 'control', '--reload-rules')
def add_user_plugdev_group(self):
self._create_group('plugdev')
self._add_user_to_group(getlogin(), 'plugdev')
def _create_group(self, name):
try:
self._execute(self._groupadd, name)
except CalledProcessError as e:
if e.returncode != 9: # group already exists
raise
def _add_user_to_group(self, user, group):
self._execute(self._usermod, '-aG', group, user)
def copy_udev_rule_files(self, source, location):
src_dir_path = source
for rules_file_name in listdir(src_dir_path):
rules_file_path = _resource_path(path.join(src_dir_path, rules_file_name))
copy(rules_file_path, location)
def _resource_path(relative_path):
if hasattr(sys, '_MEIPASS'):
return path.join(sys._MEIPASS, relative_path)
return path.join(relative_path)

View File

@ -9,10 +9,6 @@ repository = "https://github.com/bitcoin-core/HWI"
homepage = "https://github.com/bitcoin-core/HWI" homepage = "https://github.com/bitcoin-core/HWI"
exclude = ["docs/", "test/"] exclude = ["docs/", "test/"]
include = ["hwilib/**/*.py"] include = ["hwilib/**/*.py"]
packages = [
{ include = "hwi.py" },
{ include = "hwilib" },
]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.5.6" python = ">=3.5.6"

View File

@ -1,18 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from distutils.core import setup from distutils.core import setup
packages = \
['hwilib',
'hwilib.devices',
'hwilib.devices.btchip',
'hwilib.devices.ckcc',
'hwilib.devices.trezorlib',
'hwilib.devices.trezorlib.messages',
'hwilib.devices.trezorlib.transport']
package_data = \
{'': ['*']}
modules = \ modules = \
['hwi'] ['hwi']
install_requires = \ install_requires = \
@ -33,12 +21,10 @@ setup_kwargs = {
'name': 'hwi', 'name': 'hwi',
'version': '1.0.0', 'version': '1.0.0',
'description': 'A library for working with Bitcoin hardware wallets', 'description': 'A library for working with Bitcoin hardware wallets',
'long_description': "# Bitcoin Hardware Wallet Interface\n\n[![Build Status](https://travis-ci.org/bitcoin-core/HWI.svg?branch=master)](https://travis-ci.org/bitcoin-core/HWI)\n\nThe Bitcoin Hardware Wallet Interface is a Python library and command line tool for interacting with hardware wallets.\nIt provides a standard way for software to work with hardware wallets without needing to implement device specific drivers.\nPython software can use the provided library (`hwilib`). Software in other languages can execute the `hwi` tool.\n\n## Prerequisites\n\nPython 3 is required. The libraries and udev rules 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\n```\n\nFor macOS:\n```\nbrew install libusb\n```\n\nThis project uses the [Poetry](https://github.com/sdispater/poetry) dependency manager.\nOnce HWI's source has been downloaded with git clone, it and its dependencies can be installed via poetry by execting the following in the root source directory:\n\n```\npoetry install\n```\n\nPip can also be used to install all of the dependencies (in virtualenv or system):\n\n```\npip3 install hidapi # HID API needed in general\npip3 install ecdsa\npip3 install pyaes\npip3 install typing_extensions\npip3 install mnemonic\npip3 install libusb1\n```\n## Install\n\n```\ngit clone https://github.com/bitcoin-core/HWI.git\ncd HWI\n```\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\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 S | Trezor One | Trezor Model T | Digital BitBox | KeepKey | Coldcard |\n|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n| Support Planned | Yes | Yes | Yes | Yes | Yes | Yes |\n| Implemented | Yes | Yes | Yes | Yes | Yes | Yes |\n| xpub retrieval | Yes | Yes | Yes | Yes | Yes | Yes |\n| Message Signing | Yes | Yes | ? | Yes | Yes | Yes |\n| Device Setup | N/A | Yes | ? | Yes | Yes | N/A |\n| Device Wipe | N/A | Yes | ? | Yes | Yes | N/A |\n| Device Recovery | N/A | Yes | ? | N/A | Yes | N/A |\n| Device Backup | N/A | N/A | ? | Yes | N/A | Yes |\n| P2PKH Inputs | Yes | Yes | ? | Yes | Yes | Yes |\n| P2SH-P2WPKH Inputs | Yes | Yes | ? | Yes | Yes | Yes |\n| P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes |\n| P2SH Multisig Inputs | Yes | Yes | ? | Yes | Yes | N/A |\n| P2SH-P2WSH Multisig Inputs | Yes | No | ? | Yes | No | N/A |\n| P2WSH Multisig Inputs | Yes | No | ? | Yes | Yes | N/A |\n| Bare Multisig Inputs | Yes | N/A | ? | Yes | N/A | N/A |\n| Aribtrary scriptPubKey Inputs | Yes | N/A | ? | Yes | N/A | N/A |\n| Aribtrary redeemScript Inputs | Yes | N/A | ? | Yes | N/A | N/A |\n| Arbitrary witnessScript Inputs | Yes | N/A | ? | Yes | N/A | N/A |\n| Non-wallet inputs | Yes | Yes | ? | Yes | Yes | Yes |\n| Mixed Segwit and Non-Segwit Inputs | N/A | Yes | ? | Yes | Yes | Yes |\n| Display on device screen | Yes | Yes | Yes | N/A | Yes | Yes |\n\n## Using with Bitcoin Core\n\nSee [Using Bitcoin Core with Hardware Wallets](docs/bitcoin-core-usage.md).\n\n## License\n\nThis project is available under the MIT License, Copyright Andrew Chow.\n", 'long_description': "# Bitcoin Hardware Wallet Interface\n\n[![Build Status](https://travis-ci.org/bitcoin-core/HWI.svg?branch=master)](https://travis-ci.org/bitcoin-core/HWI)\n\nThe Bitcoin Hardware Wallet Interface is a Python library and command line tool for interacting with hardware wallets.\nIt provides a standard way for software to work with hardware wallets without needing to implement device specific drivers.\nPython software can use the provided library (`hwilib`). Software in other languages can execute the `hwi` tool.\n\n## Prerequisites\n\nPython 3 is required. The libraries and udev rules 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\n```\n\nFor macOS:\n```\nbrew install libusb\n```\n\nThis project uses the [Poetry](https://github.com/sdispater/poetry) dependency manager.\nOnce HWI's source has been downloaded with git clone, it and its dependencies can be installed via poetry by execting the following in the root source directory:\n\n```\npoetry install\n```\n\nPip can also be used to install all of the dependencies (in virtualenv or system):\n\n```\npip3 install hidapi # HID API needed in general\npip3 install ecdsa\npip3 install pyaes\npip3 install typing_extensions\npip3 install mnemonic\npip3 install libusb1\n```\n## Install\n\n```\ngit clone https://github.com/bitcoin-core/HWI.git\ncd HWI\n```\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\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 S | Trezor One | Digital BitBox | KeepKey | Coldcard |\n|:---:|:---:|:---:|:---:|:---:|:---:|\n| Support Planned | Yes | Yes | Yes | Yes | Yes |\n| Implemented | Yes | Yes | Yes | Yes | Yes |\n| xpub retrieval | Yes | Yes | Yes | Yes | Yes |\n| Message Signing | Yes | Yes | Yes | Yes | Yes |\n| Device Setup | N/A | Yes | Yes | Yes | N/A |\n| Device Wipe | N/A | Yes | Yes | Yes | N/A |\n| Device Recovery | N/A | Yes | N/A | Yes | N/A |\n| Device Backup | N/A | N/A | Yes | N/A | Yes |\n| P2PKH Inputs | Yes | Yes | Yes | Yes | Yes |\n| P2SH-P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes |\n| P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes |\n| P2SH Multisig Inputs | Yes | Yes | Yes | Yes | N/A |\n| P2SH-P2WSH Multisig Inputs | Yes | No | Yes | No | N/A |\n| P2WSH Multisig Inputs | Yes | No | Yes | Yes | N/A |\n| Bare Multisig Inputs | Yes | N/A | Yes | N/A | N/A |\n| Aribtrary scriptPubKey Inputs | Yes | N/A | Yes | N/A | N/A |\n| Aribtrary redeemScript Inputs | Yes | N/A | Yes | N/A | N/A |\n| Arbitrary witnessScript Inputs | Yes | N/A | Yes | N/A | N/A |\n| Non-wallet inputs | Yes | Yes | Yes | Yes | Yes |\n| Mixed Segwit and Non-Segwit Inputs | N/A | Yes | Yes | Yes | Yes |\n| Display on device screen | 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': 'Andrew Chow',
'author_email': 'andrew@achow101.com', 'author_email': 'andrew@achow101.com',
'url': 'https://github.com/bitcoin-core/HWI', 'url': 'https://github.com/bitcoin-core/HWI',
'packages': packages,
'package_data': package_data,
'py_modules': modules, 'py_modules': modules,
'install_requires': install_requires, 'install_requires': install_requires,
'extras_require': extras_require, 'extras_require': extras_require,

View File

@ -14,15 +14,11 @@ from test_trezor import trezor_test_suite
from test_ledger import ledger_test_suite from test_ledger import ledger_test_suite
from test_digitalbitbox import digitalbitbox_test_suite from test_digitalbitbox import digitalbitbox_test_suite
from test_keepkey import keepkey_test_suite from test_keepkey import keepkey_test_suite
from test_udevrules import TestUdevRulesInstaller
parser = argparse.ArgumentParser(description='Setup the testing environment and run automated tests') parser = argparse.ArgumentParser(description='Setup the testing environment and run automated tests')
trezor_group = parser.add_mutually_exclusive_group() trezor_group = parser.add_mutually_exclusive_group()
trezor_group.add_argument('--no_trezor', help='Do not run Trezor test with emulator', action='store_true') trezor_group.add_argument('--no_trezor', help='Do not run Trezor test with emulator', action='store_true')
trezor_group.add_argument('--trezor', help='Path to Trezor emulator.', default='work/trezor-firmware/legacy/firmware/trezor.elf') trezor_group.add_argument('--trezor', help='Path to Trezor emulator.', default='work/trezor-mcu/firmware/trezor.elf')
trezor_t_group = parser.add_mutually_exclusive_group()
trezor_t_group.add_argument('--no_trezor_t', help='Do not run Trezor T test with emulator', action='store_true')
trezor_t_group.add_argument('--trezor_t', help='Path to Trezor T emulator.', default='work/trezor-firmware/core/emu.sh')
coldcard_group = parser.add_mutually_exclusive_group() coldcard_group = parser.add_mutually_exclusive_group()
coldcard_group.add_argument('--no_coldcard', help='Do not run Coldcard test with simulator', action='store_true') coldcard_group.add_argument('--no_coldcard', help='Do not run Coldcard test with simulator', action='store_true')
coldcard_group.add_argument('--coldcard', help='Path to Coldcard simulator.', default='work/firmware/unix/headless.py') coldcard_group.add_argument('--coldcard', help='Path to Coldcard simulator.', default='work/firmware/unix/headless.py')
@ -44,25 +40,20 @@ suite = unittest.TestSuite()
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestDescriptor)) suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestDescriptor))
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestSegwitAddress)) suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestSegwitAddress))
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestPSBT)) suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestPSBT))
if sys.platform.startswith("linux"):
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestUdevRulesInstaller))
if not args.no_trezor or not args.no_coldcard or args.ledger or not args.no_bitbox or not args.no_keepkey:
if not args.no_trezor or not args.no_coldcard or args.ledger or not args.no_bitbox or not args.no_keepkey or not args.no_trezor_t:
# Start bitcoind # Start bitcoind
rpc, userpass = start_bitcoind(args.bitcoind) rpc, userpass = start_bitcoind(args.bitcoind)
if not args.no_bitbox:
suite.addTest(digitalbitbox_test_suite(rpc, userpass, args.bitbox, args.interface))
if not args.no_coldcard:
suite.addTest(coldcard_test_suite(args.coldcard, rpc, userpass, args.interface))
if not args.no_trezor: if not args.no_trezor:
suite.addTest(trezor_test_suite(args.trezor, rpc, userpass, args.interface)) suite.addTest(trezor_test_suite(args.trezor, rpc, userpass, args.interface))
if not args.no_trezor_t: if not args.no_coldcard:
suite.addTest(trezor_test_suite(args.trezor_t, rpc, userpass, args.interface, True)) suite.addTest(coldcard_test_suite(args.coldcard, rpc, userpass, args.interface))
if not args.no_keepkey:
suite.addTest(keepkey_test_suite(args.keepkey, rpc, userpass, args.interface))
if args.ledger: if args.ledger:
suite.addTest(ledger_test_suite(rpc, userpass, args.interface)) suite.addTest(ledger_test_suite(rpc, userpass, args.interface))
if not args.no_bitbox:
suite.addTest(digitalbitbox_test_suite(rpc, userpass, args.bitbox, args.interface))
if not args.no_keepkey:
suite.addTest(keepkey_test_suite(args.keepkey, rpc, userpass, args.interface))
result = unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite) result = unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite)
sys.exit(not result.wasSuccessful()) sys.exit(not result.wasSuccessful())

View File

@ -9,12 +9,12 @@ cd work
# Clone trezor-mcu if it doesn't exist, or update it if it does # Clone trezor-mcu if it doesn't exist, or update it if it does
trezor_setup_needed=false trezor_setup_needed=false
if [ ! -d "trezor-firmware" ]; then if [ ! -d "trezor-mcu" ]; then
git clone --recursive https://github.com/trezor/trezor-firmware.git git clone --recursive https://github.com/trezor/trezor-mcu.git
cd trezor-firmware cd trezor-mcu
trezor_setup_needed=true trezor_setup_needed=true
else else
cd trezor-firmware cd trezor-mcu
git fetch git fetch
# Determine if we need to pull. From https://stackoverflow.com/a/3278427 # Determine if we need to pull. From https://stackoverflow.com/a/3278427
@ -31,30 +31,16 @@ else
fi fi
fi fi
# Build trezor one emulator. This is pretty fast, so rebuilding every time is ok # Build emulator. This is pretty fast, so rebuilding every time is ok
# But there should be some caching that makes this faster # But there should be some caching that makes this faster
cd legacy
export EMULATOR=1 TREZOR_TRANSPORT_V1=1 DEBUG_LINK=1 HEADLESS=1 export EMULATOR=1 TREZOR_TRANSPORT_V1=1 DEBUG_LINK=1 HEADLESS=1
if [ "$trezor_setup_needed" == true ] ; then if [ "$trezor_setup_needed" == true ] ; then
script/setup script/setup
pipenv install pipenv install
fi fi
pipenv run script/cibuild pipenv run script/cibuild
# Delete any emulator.img file
find . -name "emulator.img" -exec rm {} \;
cd .. cd ..
# Build trezor t emulator. This is pretty fast, so rebuilding every time is ok
# But there should be some caching that makes this faster
cd core
if [ "$trezor_setup_needed" == true ] ; then
make vendor
fi
make build_unix
# Delete any emulator.img file
rm /var/tmp/trezor.flash
cd ../..
# Clone coldcard firmware if it doesn't exist, or update it if it does # Clone coldcard firmware if it doesn't exist, or update it if it does
coldcard_setup_needed=false coldcard_setup_needed=false
if [ ! -d "firmware" ]; then if [ ! -d "firmware" ]; then
@ -158,8 +144,6 @@ cd ../../../
export PATH=$PATH:`pwd`/nanopb/generator export PATH=$PATH:`pwd`/nanopb/generator
pipenv run cmake -C cmake/caches/emulator.cmake . -DNANOPB_DIR=nanopb/ -DKK_HAVE_STRLCAT=OFF -DKK_HAVE_STRLCPY=OFF pipenv run cmake -C cmake/caches/emulator.cmake . -DNANOPB_DIR=nanopb/ -DKK_HAVE_STRLCAT=OFF -DKK_HAVE_STRLCPY=OFF
pipenv run make -j$(nproc) kkemu pipenv run make -j$(nproc) kkemu
# Delete any emulator.img file
find . -name "emulator.img" -exec rm {} \;
cd .. cd ..
# Clone bitcoind if it doesn't exist, or update it if it does # Clone bitcoind if it doesn't exist, or update it if it does

View File

@ -18,12 +18,7 @@ def coldcard_test_suite(simulator, rpc, userpass, interface):
# Wait for simulator to be up # Wait for simulator to be up
while True: while True:
enum_res = process_commands(['enumerate']) enum_res = process_commands(['enumerate'])
found = False if len(enum_res) > 0 and 'error' not in enum_res[0]:
for dev in enum_res:
if dev['type'] == 'coldcard' and 'error' not in dev:
found = True
break
if found:
break break
time.sleep(0.5) time.sleep(0.5)
# Cleanup # Cleanup
@ -75,12 +70,12 @@ def coldcard_test_suite(simulator, rpc, userpass, interface):
# Generic device tests # Generic device tests
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(DeviceTestCase.parameterize(TestColdcardManCommands, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', '', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestColdcardManCommands, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', '', interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, 'coldcard', 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', 'tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd', interface=interface))
return suite return suite
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -3,7 +3,6 @@
import atexit import atexit
import json import json
import os import os
import shlex
import shutil import shutil
import subprocess import subprocess
import tempfile import tempfile
@ -52,12 +51,11 @@ def start_bitcoind(bitcoind_path):
return (rpc, userpass) return (rpc, userpass)
class DeviceTestCase(unittest.TestCase): class DeviceTestCase(unittest.TestCase):
def __init__(self, rpc, rpc_userpass, type, full_type, path, fingerprint, master_xpub, password = '', emulator=None, interface='library', methodName='runTest'): def __init__(self, rpc, rpc_userpass, type, path, fingerprint, master_xpub, password = '', emulator=None, interface='library', methodName='runTest'):
super(DeviceTestCase, self).__init__(methodName) super(DeviceTestCase, self).__init__(methodName)
self.rpc = rpc self.rpc = rpc
self.rpc_userpass = rpc_userpass self.rpc_userpass = rpc_userpass
self.type = type self.type = type
self.full_type = full_type
self.path = path self.path = path
self.fingerprint = fingerprint self.fingerprint = fingerprint
self.master_xpub = master_xpub self.master_xpub = master_xpub
@ -72,24 +70,21 @@ class DeviceTestCase(unittest.TestCase):
self.interface = interface self.interface = interface
@staticmethod @staticmethod
def parameterize(testclass, rpc, rpc_userpass, type, full_type, path, fingerprint, master_xpub, password = '', interface='library', emulator=None): def parameterize(testclass, rpc, rpc_userpass, type, path, fingerprint, master_xpub, password = '', interface='library', emulator=None):
testloader = unittest.TestLoader() testloader = unittest.TestLoader()
testnames = testloader.getTestCaseNames(testclass) testnames = testloader.getTestCaseNames(testclass)
suite = unittest.TestSuite() suite = unittest.TestSuite()
for name in testnames: for name in testnames:
suite.addTest(testclass(rpc, rpc_userpass, type, full_type, path, fingerprint, master_xpub, password, emulator, interface, name)) suite.addTest(testclass(rpc, rpc_userpass, type, path, fingerprint, master_xpub, password, emulator, interface, name))
return suite return suite
def do_command(self, args): def do_command(self, args):
cli_args = []
for arg in args:
cli_args.append(shlex.quote(arg))
if self.interface == 'cli': if self.interface == 'cli':
proc = subprocess.Popen(['hwi ' + ' '.join(cli_args)], stdout=subprocess.PIPE, shell=True) proc = subprocess.Popen(['hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
result = proc.communicate() result = proc.communicate()
return json.loads(result[0].decode()) return json.loads(result[0].decode())
elif self.interface == 'bindist': elif self.interface == 'bindist':
proc = subprocess.Popen(['../dist/hwi ' + ' '.join(cli_args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True) proc = subprocess.Popen(['../dist/hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
result = proc.communicate() result = proc.communicate()
return json.loads(result[0].decode()) return json.loads(result[0].decode())
elif self.interface == 'stdin': elif self.interface == 'stdin':
@ -106,10 +101,10 @@ class DeviceTestCase(unittest.TestCase):
return [] return []
def __str__(self): def __str__(self):
return '{}: {}'.format(self.full_type, super().__str__()) return '{}: {}'.format(self.type, super().__str__())
def __repr__(self): def __repr__(self):
return '{}: {}'.format(self.full_type, super().__repr__()) return '{}: {}'.format(self.type, super().__repr__())
class TestDeviceConnect(DeviceTestCase): class TestDeviceConnect(DeviceTestCase):
def setUp(self): def setUp(self):
@ -159,9 +154,9 @@ class TestDeviceConnect(DeviceTestCase):
class TestGetKeypool(DeviceTestCase): class TestGetKeypool(DeviceTestCase):
def setUp(self): def setUp(self):
self.rpc = AuthServiceProxy('http://{}@127.0.0.1:18443'.format(self.rpc_userpass)) self.rpc = AuthServiceProxy('http://{}@127.0.0.1:18443'.format(self.rpc_userpass))
if '{}_test'.format(self.full_type) not in self.rpc.listwallets(): if '{}_test'.format(self.type) not in self.rpc.listwallets():
self.rpc.createwallet('{}_test'.format(self.full_type), True) self.rpc.createwallet('{}_test'.format(self.type), True)
self.wrpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/{}_test'.format(self.rpc_userpass, self.full_type)) self.wrpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/{}_test'.format(self.rpc_userpass, self.type))
self.wpk_rpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/'.format(self.rpc_userpass)) self.wpk_rpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/'.format(self.rpc_userpass))
if '--testnet' not in self.dev_args: if '--testnet' not in self.dev_args:
self.dev_args.append('--testnet') self.dev_args.append('--testnet')
@ -266,9 +261,9 @@ class TestGetKeypool(DeviceTestCase):
class TestSignTx(DeviceTestCase): class TestSignTx(DeviceTestCase):
def setUp(self): def setUp(self):
self.rpc = AuthServiceProxy('http://{}@127.0.0.1:18443'.format(self.rpc_userpass)) self.rpc = AuthServiceProxy('http://{}@127.0.0.1:18443'.format(self.rpc_userpass))
if '{}_test'.format(self.full_type) not in self.rpc.listwallets(): if '{}_test'.format(self.type) not in self.rpc.listwallets():
self.rpc.createwallet('{}_test'.format(self.full_type), True) self.rpc.createwallet('{}_test'.format(self.type), True)
self.wrpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/{}_test'.format(self.rpc_userpass, self.full_type)) self.wrpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/{}_test'.format(self.rpc_userpass, self.type))
self.wpk_rpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/'.format(self.rpc_userpass)) self.wpk_rpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/'.format(self.rpc_userpass))
if '--testnet' not in self.dev_args: if '--testnet' not in self.dev_args:
self.dev_args.append('--testnet') self.dev_args.append('--testnet')
@ -410,13 +405,13 @@ class TestSignTx(DeviceTestCase):
# Test wrapper to avoid mixed-inputs signing for Ledger # Test wrapper to avoid mixed-inputs signing for Ledger
def test_signtx(self): def test_signtx(self):
supports_mixed = {'coldcard', 'trezor_1', 'digitalbitbox', 'keepkey'} supports_mixed = {'coldcard', 'trezor', 'digitalbitbox', 'keepkey'}
supports_multisig = {'ledger', 'trezor_1', 'digitalbitbox', 'keepkey'} supports_multisig = {'ledger', 'trezor', 'digitalbitbox', 'keepkey'}
if self.full_type not in supports_mixed: if self.type not in supports_mixed:
self._test_signtx("legacy", self.full_type in supports_multisig) self._test_signtx("legacy", self.type in supports_multisig)
self._test_signtx("segwit", self.full_type in supports_multisig) self._test_signtx("segwit", self.type in supports_multisig)
else: else:
self._test_signtx("all", self.full_type in supports_multisig) self._test_signtx("all", self.type in supports_multisig)
# Make a huge transaction which might cause some problems with different interfaces # Make a huge transaction which might cause some problems with different interfaces
def test_big_tx(self): def test_big_tx(self):
@ -461,68 +456,45 @@ class TestDisplayAddress(DeviceTestCase):
self.assertEqual(result['code'], -7) self.assertEqual(result['code'], -7)
def test_display_address_path(self): def test_display_address_path(self):
result = self.do_command(self.dev_args + ['displayaddress', '--path', 'm/44h/1h/0h/0/0']) self.do_command(self.dev_args + ['displayaddress', '--path', 'm/44h/1h/0h/0/0'])
self.assertNotIn('error', result) self.do_command(self.dev_args + ['displayaddress', '--sh_wpkh', '--path', 'm/49h/1h/0h/0/0'])
self.assertNotIn('code', result) self.do_command(self.dev_args + ['displayaddress', '--wpkh', '--path', 'm/84h/1h/0h/0/0'])
self.assertIn('address', result)
result = self.do_command(self.dev_args + ['displayaddress', '--sh_wpkh', '--path', 'm/49h/1h/0h/0/0'])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
result = self.do_command(self.dev_args + ['displayaddress', '--wpkh', '--path', 'm/84h/1h/0h/0/0'])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
def test_display_address_bad_path(self): def test_display_address_bad_path(self):
result = self.do_command(self.dev_args + ['displayaddress', '--path', 'f']) result = self.do_command(self.dev_args + ['displayaddress', '--path', 'f'])
self.assertEquals(result['code'], -7) self.assertEquals(result['code'], -7)
def test_display_address_descriptor(self): def test_display_address_descriptor(self):
account_xpub = self.do_command(self.dev_args + ['getxpub', 'm/84h/1h/0h'])['xpub'] account_xpub = process_commands(self.dev_args + ['getxpub', 'm/84h/1h/0h'])['xpub']
p2sh_segwit_account_xpub = self.do_command(self.dev_args + ['getxpub', 'm/49h/1h/0h'])['xpub'] p2sh_segwit_account_xpub = process_commands(self.dev_args + ['getxpub', 'm/49h/1h/0h'])['xpub']
legacy_account_xpub = self.do_command(self.dev_args + ['getxpub', 'm/44h/1h/0h'])['xpub'] legacy_account_xpub = process_commands(self.dev_args + ['getxpub', 'm/44h/1h/0h'])['xpub']
# Native SegWit address using xpub: # Native SegWit address using xpub:
result = self.do_command(self.dev_args + ['displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h]' + account_xpub + '/0/0)']) process_commands(self.dev_args + ['displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h)]' + account_xpub + '/0/0)'])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
# Native SegWit address using hex encoded pubkey: # Native SegWit address using hex encoded pubkey:
result = self.do_command(self.dev_args + ['displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h]' + xpub_to_pub_hex(account_xpub) + '/0/0)']) process_commands(self.dev_args + ['displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h)]' + xpub_to_pub_hex(account_xpub) + '/0/0)'])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
# P2SH wrapped SegWit address using xpub: # P2SH wrapped SegWit address using xpub:
result = self.do_command(self.dev_args + ['displayaddress', '--desc', 'sh(wpkh([' + self.fingerprint + '/49h/1h/0h]' + p2sh_segwit_account_xpub + '/0/0))']) process_commands(self.dev_args + ['displayaddress', '--desc', 'sh(wpkh([' + self.fingerprint + '/49h/1h/0h)]' + p2sh_segwit_account_xpub + '/0/0))'])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
# Legacy address # Legacy address
result = self.do_command(self.dev_args + ['displayaddress', '--desc', 'pkh([' + self.fingerprint + '/44h/1h/0h]' + legacy_account_xpub + '/0/0)']) process_commands(self.dev_args + ['displayaddress', '--desc', 'pkh([' + self.fingerprint + '/44h/1h/0h)]' + legacy_account_xpub + '/0/0)'])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
# Should check xpub # Should check xpub
result = self.do_command(self.dev_args + ['displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h]' + "not_and_xpub" + '/0/0)']) result = process_commands(self.dev_args + ['displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h)]' + "not_and_xpub" + '/0/0)'])
self.assertIn('error', result) self.assertIn('error', result)
self.assertIn('code', result) self.assertIn('code', result)
self.assertEqual(result['code'], -7) self.assertEqual(result['code'], -7)
# Should check hex pub # Should check hex pub
result = self.do_command(self.dev_args + ['displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h]' + "not_and_xpub" + '/0/0)']) result = process_commands(self.dev_args + ['displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h)]' + "not_and_xpub" + '/0/0)'])
self.assertIn('error', result) self.assertIn('error', result)
self.assertIn('code', result) self.assertIn('code', result)
self.assertEqual(result['code'], -7) self.assertEqual(result['code'], -7)
# Should check fingerprint # Should check fingerprint
self.do_command(self.dev_args + ['displayaddress', '--desc', 'wpkh([00000000/84h/1h/0h]' + account_xpub + '/0/0)']) process_commands(self.dev_args + ['displayaddress', '--desc', 'wpkh([00000000/84h/1h/0h)]' + account_xpub + '/0/0)'])
self.assertIn('error', result) self.assertIn('error', result)
self.assertIn('code', result) self.assertIn('code', result)
self.assertEqual(result['code'], -7) self.assertEqual(result['code'], -7)

View File

@ -37,7 +37,6 @@ def digitalbitbox_test_suite(rpc, userpass, simulator, interface):
# params # params
type = 'digitalbitbox' type = 'digitalbitbox'
full_type = 'digitalbitbox'
path = 'udp:127.0.0.1:35345' path = 'udp:127.0.0.1:35345'
fingerprint = 'a31b978a' fingerprint = 'a31b978a'
master_xpub = 'xpub6BsWJiRvbzQJg3J6tgUKmHWYbHJSj41EjAAje6LuDwnYLqLiNSWK4N7rCXwiUmNJTBrKL8AEH3LBzhJdgdxoy4T9aMPLCWAa6eWKGCFjQhq' master_xpub = 'xpub6BsWJiRvbzQJg3J6tgUKmHWYbHJSj41EjAAje6LuDwnYLqLiNSWK4N7rCXwiUmNJTBrKL8AEH3LBzhJdgdxoy4T9aMPLCWAa6eWKGCFjQhq'
@ -94,7 +93,7 @@ def digitalbitbox_test_suite(rpc, userpass, simulator, interface):
self.assertTrue(result['success']) self.assertTrue(result['success'])
# Reset back to original # Reset back to original
result = self.do_command(self.dev_args + ['wipe']) result = process_commands(self.dev_args + ['wipe'])
self.assertTrue(result['success']) self.assertTrue(result['success'])
send_plain(b'{"password":"0000"}', dev) send_plain(b'{"password":"0000"}', dev)
send_encrypt(json.dumps({"seed":{"source":"backup","filename":"test_backup.pdf","key":"key"}}), '0000', dev) send_encrypt(json.dumps({"seed":{"source":"backup","filename":"test_backup.pdf","key":"key"}}), '0000', dev)
@ -127,11 +126,11 @@ def digitalbitbox_test_suite(rpc, userpass, simulator, interface):
# Generic Device tests # Generic Device tests
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(DeviceTestCase.parameterize(TestDBBManCommands, rpc, userpass, type, full_type, path, fingerprint, master_xpub, '0000', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDBBManCommands, rpc, userpass, type, path, fingerprint, master_xpub, '0000', interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, type, full_type, path, fingerprint, master_xpub, '0000', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, type, path, fingerprint, master_xpub, '0000', interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, type, full_type, path, fingerprint, master_xpub, '0000', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, type, path, fingerprint, master_xpub, '0000', interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, type, full_type, path, fingerprint, master_xpub, '0000', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, type, path, fingerprint, master_xpub, '0000', interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, type, full_type, path, fingerprint, master_xpub, '0000', interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, type, path, fingerprint, master_xpub, '0000', interface=interface))
return suite return suite
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -4,7 +4,6 @@ import argparse
import atexit import atexit
import json import json
import os import os
import shlex
import socket import socket
import subprocess import subprocess
import sys import sys
@ -83,15 +82,12 @@ class KeepkeyTestCase(unittest.TestCase):
return suite return suite
def do_command(self, args): def do_command(self, args):
cli_args = []
for arg in args:
cli_args.append(shlex.quote(arg))
if self.interface == 'cli': if self.interface == 'cli':
proc = subprocess.Popen(['hwi ' + ' '.join(cli_args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True) proc = subprocess.Popen(['hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
result = proc.communicate() result = proc.communicate()
return json.loads(result[0].decode()) return json.loads(result[0].decode())
elif self.interface == 'bindist': elif self.interface == 'bindist':
proc = subprocess.Popen(['../dist/hwi ' + ' '.join(cli_args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True) proc = subprocess.Popen(['../dist/hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
result = proc.communicate() result = proc.communicate()
return json.loads(result[0].decode()) return json.loads(result[0].decode())
elif self.interface == 'stdin': elif self.interface == 'stdin':
@ -180,19 +176,11 @@ class TestKeepkeyManCommands(KeepkeyTestCase):
result = self.do_command(self.dev_args + ['sendpin', '1234']) result = self.do_command(self.dev_args + ['sendpin', '1234'])
self.assertEqual(result['error'], 'This device does not need a PIN') self.assertEqual(result['error'], 'This device does not need a PIN')
self.assertEqual(result['code'], -11) self.assertEqual(result['code'], -11)
result = self.do_command(self.dev_args + ['enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_pin_sent'])
# Set a PIN # Set a PIN
device.wipe(self.client) device.wipe(self.client)
load_device_by_mnemonic(client=self.client, mnemonic='alcohol woman abuse must during monitor noble actual mixed trade anger aisle', pin='1234', passphrase_protection=False, label='test') load_device_by_mnemonic(client=self.client, mnemonic='alcohol woman abuse must during monitor noble actual mixed trade anger aisle', pin='1234', passphrase_protection=False, label='test')
self.client.call(messages.ClearSession()) self.client.call(messages.ClearSession())
result = self.do_command(self.dev_args + ['enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertTrue(dev['needs_pin_sent'])
result = self.do_command(self.dev_args + ['promptpin']) result = self.do_command(self.dev_args + ['promptpin'])
self.assertTrue(result['success']) self.assertTrue(result['success'])
@ -220,11 +208,6 @@ class TestKeepkeyManCommands(KeepkeyTestCase):
result = self.do_command(self.dev_args + ['sendpin', pin]) result = self.do_command(self.dev_args + ['sendpin', pin])
self.assertTrue(result['success']) self.assertTrue(result['success'])
result = self.do_command(self.dev_args + ['enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_pin_sent'])
# Sending PIN after unlock # Sending PIN after unlock
result = self.do_command(self.dev_args + ['promptpin']) result = self.do_command(self.dev_args + ['promptpin'])
self.assertEqual(result['error'], 'The PIN has already been sent to this device') self.assertEqual(result['error'], 'The PIN has already been sent to this device')
@ -233,58 +216,12 @@ class TestKeepkeyManCommands(KeepkeyTestCase):
self.assertEqual(result['error'], 'The PIN has already been sent to this device') self.assertEqual(result['error'], 'The PIN has already been sent to this device')
self.assertEqual(result['code'], -11) self.assertEqual(result['code'], -11)
def test_passphrase(self):
# There's no passphrase
result = self.do_command(self.dev_args + ['enumerate'])
for dev in result:
if dev['type'] == 'keepkey' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_passphrase_sent'])
self.assertEquals(dev['fingerprint'], '95d8f670')
# Setting a passphrase won't change the fingerprint
result = self.do_command(self.dev_args + ['-p', 'pass', 'enumerate'])
for dev in result:
if dev['type'] == 'keepkey' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_passphrase_sent'])
self.assertEquals(dev['fingerprint'], '95d8f670')
# Set a passphrase
device.wipe(self.client)
self.client.set_passphrase('pass')
load_device_by_mnemonic(client=self.client, mnemonic='alcohol woman abuse must during monitor noble actual mixed trade anger aisle', pin='', passphrase_protection=True, label='test')
self.client.call(messages.ClearSession())
# A passphrase will need to be sent
result = self.do_command(self.dev_args + ['enumerate'])
for dev in result:
if dev['type'] == 'keepkey' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertTrue(dev['needs_passphrase_sent'])
result = self.do_command(self.dev_args + ['-p', 'pass', 'enumerate'])
for dev in result:
if dev['type'] == 'keepkey' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_passphrase_sent'])
fpr = dev['fingerprint']
# A different passphrase would not change the fingerprint
result = self.do_command(self.dev_args + ['-p', 'pass2', 'enumerate'])
for dev in result:
if dev['type'] == 'keepkey' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_passphrase_sent'])
self.assertEqual(dev['fingerprint'], fpr)
# Clearing the session and starting a new one with a new passphrase should change the passphrase
self.client.call(messages.ClearSession())
result = self.do_command(self.dev_args + ['-p', 'pass3', 'enumerate'])
for dev in result:
if dev['type'] == 'keepkey' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_passphrase_sent'])
self.assertNotEqual(dev['fingerprint'], fpr)
def keepkey_test_suite(emulator, rpc, userpass, interface): def keepkey_test_suite(emulator, rpc, userpass, interface):
# Redirect stderr to /dev/null as it's super spammy # Redirect stderr to /dev/null as it's super spammy
sys.stderr = open(os.devnull, 'w') sys.stderr = open(os.devnull, 'w')
# Device info for tests # Device info for tests
type = 'keepkey' type = 'keepkey'
full_type = 'keepkey'
path = 'udp:127.0.0.1:21324' path = 'udp:127.0.0.1:21324'
fingerprint = '95d8f670' fingerprint = '95d8f670'
master_xpub = 'xpub6D1weXBcFAo8CqBbpP4TbH5sxQH8ZkqC5pDEvJ95rNNBZC9zrKmZP2fXMuve7ZRBe18pWQQsGg68jkq24mZchHwYENd8cCiSb71u3KD4AFH' master_xpub = 'xpub6D1weXBcFAo8CqBbpP4TbH5sxQH8ZkqC5pDEvJ95rNNBZC9zrKmZP2fXMuve7ZRBe18pWQQsGg68jkq24mZchHwYENd8cCiSb71u3KD4AFH'
@ -292,11 +229,11 @@ def keepkey_test_suite(emulator, rpc, userpass, interface):
# Generic Device tests # Generic Device tests
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, type, full_type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, type, full_type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, type, full_type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, type, full_type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, type, full_type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface))
suite.addTest(KeepkeyTestCase.parameterize(TestKeepkeyGetxpub, emulator=dev_emulator, interface=interface)) suite.addTest(KeepkeyTestCase.parameterize(TestKeepkeyGetxpub, emulator=dev_emulator, interface=interface))
suite.addTest(KeepkeyTestCase.parameterize(TestKeepkeyManCommands, emulator=dev_emulator, interface=interface)) suite.addTest(KeepkeyTestCase.parameterize(TestKeepkeyManCommands, emulator=dev_emulator, interface=interface))
return suite return suite

View File

@ -73,12 +73,12 @@ def ledger_test_suite(rpc, userpass, interface):
# Generic Device tests # Generic Device tests
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(DeviceTestCase.parameterize(TestLedgerDisabledCommands, rpc, userpass, 'ledger', 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestLedgerDisabledCommands, rpc, userpass, 'ledger', path, fingerprint, master_xpub, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, 'ledger', 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, 'ledger', path, fingerprint, master_xpub, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, 'ledger', 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, 'ledger', path, fingerprint, master_xpub, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, 'ledger', 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, 'ledger', path, fingerprint, master_xpub, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, 'ledger', 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, 'ledger', path, fingerprint, master_xpub, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, 'ledger', 'ledger', path, fingerprint, master_xpub, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, 'ledger', path, fingerprint, master_xpub, interface=interface))
return suite return suite
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -4,8 +4,6 @@ import argparse
import atexit import atexit
import json import json
import os import os
import shlex
import signal
import socket import socket
import subprocess import subprocess
import sys import sys
@ -37,7 +35,7 @@ class TrezorEmulator(DeviceEmulator):
def start(self): def start(self):
# Start the Trezor emulator # Start the Trezor emulator
self.emulator_proc = subprocess.Popen(['./' + os.path.basename(self.emulator_path)], cwd=os.path.dirname(self.emulator_path), stdout=subprocess.DEVNULL, env={'SDL_VIDEODRIVER': 'dummy', 'PYOPT': '0'}, shell=True, preexec_fn=os.setsid) self.emulator_proc = subprocess.Popen(['./' + os.path.basename(self.emulator_path)], cwd=os.path.dirname(self.emulator_path))
# Wait for emulator to be up # Wait for emulator to be up
# From https://github.com/trezor/trezor-mcu/blob/master/script/wait_for_emulator.py # From https://github.com/trezor/trezor-mcu/blob/master/script/wait_for_emulator.py
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@ -65,8 +63,8 @@ class TrezorEmulator(DeviceEmulator):
return client return client
def stop(self): def stop(self):
os.killpg(os.getpgid(self.emulator_proc.pid), signal.SIGINT) self.emulator_proc.kill()
os.waitpid(self.emulator_proc.pid, 0) self.emulator_proc.wait()
class TrezorTestCase(unittest.TestCase): class TrezorTestCase(unittest.TestCase):
def __init__(self, emulator, interface='library', methodName='runTest'): def __init__(self, emulator, interface='library', methodName='runTest'):
@ -84,15 +82,12 @@ class TrezorTestCase(unittest.TestCase):
return suite return suite
def do_command(self, args): def do_command(self, args):
cli_args = []
for arg in args:
cli_args.append(shlex.quote(arg))
if self.interface == 'cli': if self.interface == 'cli':
proc = subprocess.Popen(['hwi ' + ' '.join(cli_args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True) proc = subprocess.Popen(['hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
result = proc.communicate() result = proc.communicate()
return json.loads(result[0].decode()) return json.loads(result[0].decode())
elif self.interface == 'bindist': elif self.interface == 'bindist':
proc = subprocess.Popen(['../dist/hwi ' + ' '.join(cli_args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True) proc = subprocess.Popen(['../dist/hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
result = proc.communicate() result = proc.communicate()
return json.loads(result[0].decode()) return json.loads(result[0].decode())
elif self.interface == 'stdin': elif self.interface == 'stdin':
@ -104,10 +99,10 @@ class TrezorTestCase(unittest.TestCase):
return process_commands(args) return process_commands(args)
def __str__(self): def __str__(self):
return 'trezor 1: {}'.format(super().__str__()) return 'trezor: {}'.format(super().__str__())
def __repr__(self): def __repr__(self):
return 'trezor 1: {}'.format(super().__repr__()) return 'trezor: {}'.format(super().__repr__())
# Trezor specific getxpub test because this requires device specific thing to set xprvs # Trezor specific getxpub test because this requires device specific thing to set xprvs
class TestTrezorGetxpub(TrezorTestCase): class TestTrezorGetxpub(TrezorTestCase):
@ -181,19 +176,11 @@ class TestTrezorManCommands(TrezorTestCase):
result = self.do_command(self.dev_args + ['sendpin', '1234']) result = self.do_command(self.dev_args + ['sendpin', '1234'])
self.assertEqual(result['error'], 'This device does not need a PIN') self.assertEqual(result['error'], 'This device does not need a PIN')
self.assertEqual(result['code'], -11) self.assertEqual(result['code'], -11)
result = self.do_command(self.dev_args + ['enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_pin_sent'])
# Set a PIN # Set a PIN
device.wipe(self.client) device.wipe(self.client)
load_device_by_mnemonic(client=self.client, mnemonic='alcohol woman abuse must during monitor noble actual mixed trade anger aisle', pin='1234', passphrase_protection=False, label='test') load_device_by_mnemonic(client=self.client, mnemonic='alcohol woman abuse must during monitor noble actual mixed trade anger aisle', pin='1234', passphrase_protection=False, label='test')
self.client.call(messages.ClearSession()) self.client.call(messages.ClearSession())
result = self.do_command(self.dev_args + ['enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertTrue(dev['needs_pin_sent'])
result = self.do_command(self.dev_args + ['promptpin']) result = self.do_command(self.dev_args + ['promptpin'])
self.assertTrue(result['success']) self.assertTrue(result['success'])
@ -221,11 +208,6 @@ class TestTrezorManCommands(TrezorTestCase):
result = self.do_command(self.dev_args + ['sendpin', pin]) result = self.do_command(self.dev_args + ['sendpin', pin])
self.assertTrue(result['success']) self.assertTrue(result['success'])
result = self.do_command(self.dev_args + ['enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_pin_sent'])
# Sending PIN after unlock # Sending PIN after unlock
result = self.do_command(self.dev_args + ['promptpin']) result = self.do_command(self.dev_args + ['promptpin'])
self.assertEqual(result['error'], 'The PIN has already been sent to this device') self.assertEqual(result['error'], 'The PIN has already been sent to this device')
@ -234,51 +216,7 @@ class TestTrezorManCommands(TrezorTestCase):
self.assertEqual(result['error'], 'The PIN has already been sent to this device') self.assertEqual(result['error'], 'The PIN has already been sent to this device')
self.assertEqual(result['code'], -11) self.assertEqual(result['code'], -11)
def test_passphrase(self): def trezor_test_suite(emulator, rpc, userpass, interface):
# There's no passphrase
result = self.do_command(self.dev_args + ['enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_passphrase_sent'])
self.assertEquals(dev['fingerprint'], '95d8f670')
# Setting a passphrase won't change the fingerprint
result = self.do_command(self.dev_args + ['-p', 'pass', 'enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_passphrase_sent'])
self.assertEquals(dev['fingerprint'], '95d8f670')
# Set a passphrase
device.wipe(self.client)
load_device_by_mnemonic(client=self.client, mnemonic='alcohol woman abuse must during monitor noble actual mixed trade anger aisle', pin='', passphrase_protection=True, label='test')
self.client.call(messages.ClearSession())
# A passphrase will need to be sent
result = self.do_command(self.dev_args + ['enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertTrue(dev['needs_passphrase_sent'])
result = self.do_command(self.dev_args + ['-p', 'pass', 'enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_passphrase_sent'])
fpr = dev['fingerprint']
# A different passphrase would not change the fingerprint
result = self.do_command(self.dev_args + ['-p', 'pass2', 'enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_passphrase_sent'])
self.assertEqual(dev['fingerprint'], fpr)
# Clearing the session and starting a new one with a new passphrase should change the passphrase
self.client.call(messages.Initialize())
result = self.do_command(self.dev_args + ['-p', 'pass3', 'enumerate'])
for dev in result:
if dev['type'] == 'trezor' and dev['path'] == 'udp:127.0.0.1:21324':
self.assertFalse(dev['needs_passphrase_sent'])
self.assertNotEqual(dev['fingerprint'], fpr)
def trezor_test_suite(emulator, rpc, userpass, interface, model_t=False):
# Redirect stderr to /dev/null as it's super spammy # Redirect stderr to /dev/null as it's super spammy
sys.stderr = open(os.devnull, 'w') sys.stderr = open(os.devnull, 'w')
@ -289,21 +227,15 @@ def trezor_test_suite(emulator, rpc, userpass, interface, model_t=False):
master_xpub = 'xpub6D1weXBcFAo8CqBbpP4TbH5sxQH8ZkqC5pDEvJ95rNNBZC9zrKmZP2fXMuve7ZRBe18pWQQsGg68jkq24mZchHwYENd8cCiSb71u3KD4AFH' master_xpub = 'xpub6D1weXBcFAo8CqBbpP4TbH5sxQH8ZkqC5pDEvJ95rNNBZC9zrKmZP2fXMuve7ZRBe18pWQQsGg68jkq24mZchHwYENd8cCiSb71u3KD4AFH'
dev_emulator = TrezorEmulator(emulator) dev_emulator = TrezorEmulator(emulator)
if model_t:
full_type = 'trezor_t'
else:
full_type = 'trezor_1'
# Generic Device tests # Generic Device tests
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, type, full_type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDeviceConnect, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, type, full_type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestGetKeypool, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, type, full_type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignTx, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, type, full_type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestDisplayAddress, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, type, full_type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface)) suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, type, path, fingerprint, master_xpub, emulator=dev_emulator, interface=interface))
if not model_t: suite.addTest(TrezorTestCase.parameterize(TestTrezorGetxpub, emulator=dev_emulator, interface=interface))
suite.addTest(TrezorTestCase.parameterize(TestTrezorGetxpub, emulator=dev_emulator, interface=interface)) suite.addTest(TrezorTestCase.parameterize(TestTrezorManCommands, emulator=dev_emulator, interface=interface))
suite.addTest(TrezorTestCase.parameterize(TestTrezorManCommands, emulator=dev_emulator, interface=interface))
return suite return suite
if __name__ == '__main__': if __name__ == '__main__':
@ -311,11 +243,10 @@ if __name__ == '__main__':
parser.add_argument('emulator', help='Path to the Trezor emulator') parser.add_argument('emulator', help='Path to the Trezor emulator')
parser.add_argument('bitcoind', help='Path to bitcoind binary') parser.add_argument('bitcoind', help='Path to bitcoind binary')
parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli', 'bindist'], default='library') parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli', 'bindist'], default='library')
parser.add_argument('--model_t', help='The emulator is for the Trezor T', action='store_true')
args = parser.parse_args() args = parser.parse_args()
# Start bitcoind # Start bitcoind
rpc, userpass = start_bitcoind(args.bitcoind) rpc, userpass = start_bitcoind(args.bitcoind)
suite = trezor_test_suite(args.emulator, rpc, userpass, args.interface, args.model_t) suite = trezor_test_suite(args.emulator, rpc, userpass, args.interface)
unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite) unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite)

View File

@ -1,39 +0,0 @@
#! /usr/bin/env python3
import unittest
import json
import filecmp
from os import makedirs, remove, removedirs, walk, path
from hwilib.cli import process_commands
class TestUdevRulesInstaller(unittest.TestCase):
INSTALLATION_FOLDER = 'rules.d'
SOURCE_FOLDER = '../udev'
@classmethod
def setUpClass(cls):
# Create directory where copy the udev rules to.
makedirs(cls.INSTALLATION_FOLDER, exist_ok=True)
@classmethod
def tearDownClass(self):
for root, dirs, files in walk(self.INSTALLATION_FOLDER, topdown=False):
for name in files:
remove(path.join(root, name))
removedirs(self.INSTALLATION_FOLDER)
def test_rules_file_are_copied(self):
result = process_commands( ['installudevrules', '--source', self.SOURCE_FOLDER, '--location', self.INSTALLATION_FOLDER])
self.assertIn('error', result)
self.assertIn('code', result)
self.assertEqual(result['error'], 'Need to be root.')
self.assertEqual(result['code'], -16)
# Assert files wre copied
for root, dirs, files in walk(self.INSTALLATION_FOLDER, topdown=False):
for file_name in files:
src = path.join(self.SOURCE_FOLDER, file_name)
tgt = path.join(self.INSTALLATION_FOLDER, file_name)
self.assertTrue(filecmp.cmp(src, tgt))
if __name__ == "__main__":
unittest.main()