Compare commits

..

No commits in common. "master" and "fix-ranges" have entirely different histories.

53 changed files with 227 additions and 1395 deletions

View File

@ -1 +1 @@
3.6.8
3.5.6

View File

@ -2,7 +2,7 @@ language: python
os: linux
dist: xenial
python:
- '3.6.8'
- '3.5'
cache:
pip: true
ccache: true
@ -40,7 +40,7 @@ addons:
- cython3
- ccache
install:
- pip install pipenv pysdl2 python-bitcoinrpc protobuf poetry==0.12.12
- pip install pipenv pysdl2 python-bitcoinrpc protobuf
# 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"
- unzip "protoc-3.4.0-linux-x86_64.zip" -d protoc
@ -48,35 +48,10 @@ install:
# Build emulators/simulators and bitcoind
- cd test; ./setup_environment.sh; cd ..
- pip uninstall -y trezor # Hack to get rid of master branch version of trezor that is installed for trezor-mcu build
- poetry install
- python setup.py install
jobs:
include:
- name: With process_commands interface
script: cd test; poetry run ./run_tests.py --interface=library
script: cd test; ./run_tests.py --interface=library
- name: With command line interface
script: cd test; poetry run ./run_tests.py --interface=cli
- name: With stdin interface
script: cd test; poetry run ./run_tests.py --interface=stdin
- name: With linux binary distribution command line interface
services: docker
before_script:
- docker build -t hwi-builder -f contrib/build.Dockerfile .
script:
- docker run -it --name hwi-builder -v $PWD:/opt/hwi --rm --workdir /opt/hwi hwi-builder /bin/bash -c "contrib/build_bin.sh && contrib/build_wine.sh && contrib/build_dist.sh"
- sudo chown -R `whoami`:`whoami` dist/
- cd test; poetry run ./run_tests.py --interface=bindist
- cd ..; sha256sum dist/*
- name: macOS binary distribution (no tests)
os: osx
osx_image: xcode7.3
language: generic
addons:
artifacts:
working_dir: dist
install:
- brew update && brew upgrade pyenv
- brew install libusb
- cat contrib/reproducible-python.diff | PYTHON_CONFIGURE_OPTS="--enable-framework" BUILD_DATE="Jan 1 2019" BUILD_TIME="00:00:00" pyenv install -kp 3.6.8
script:
- contrib/build_bin.sh
- shasum -a 256 dist/*
script: cd test; ./run_tests.py --interface=cli

View File

@ -1,33 +1,14 @@
# Bitcoin Hardware Wallet Interface
# Bitcoin Hardware Wallet Interaction scripts
[![Build Status](https://travis-ci.org/bitcoin-core/HWI.svg?branch=master)](https://travis-ci.org/bitcoin-core/HWI)
The Bitcoin Hardware Wallet Interface is a Python library and command line tool for interacting with hardware wallets.
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.
This project contains several scripts for interacting with Bitcoin hardware wallets.
## Prerequisites
Python 3 is required. The libraries and udev rules for each device must also be installed. Some libraries will need to be installed
Python 3 is required. The libraries and udev rules for each device must also be installed.
For Ubuntu/Debian:
```
sudo apt install libusb-1.0-0-dev libudev-dev
```
For macOS:
```
brew install libusb
```
This project uses the [Poetry](https://github.com/sdispater/poetry) dependency manager.
Once 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:
```
poetry install
```
Pip can also be used to install all of the dependencies (in virtualenv or system):
Install all of the libraries using `pip` (in virtualenv or system):
```
pip3 install hidapi # HID API needed in general
@ -64,29 +45,29 @@ 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 S | Trezor One | Trezor Model T | Digital BitBox | KeepKey | Coldcard |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| Support Planned | Yes | Yes | Yes | Yes | Yes | Yes |
| Implemented | Yes | Yes | Yes | Yes | Yes | Yes |
| xpub retrieval | Yes | Yes | Yes | Yes | Yes | Yes |
| Message Signing | Yes | Yes | Yes | Yes | Yes | Yes |
| Device Setup | N/A | Yes | Yes | Yes | Yes | N/A |
| Device Wipe | N/A | Yes | Yes | Yes | Yes | N/A |
| Device Recovery | N/A | Yes | Yes | N/A | Yes | N/A |
| Device Backup | N/A | N/A | N/A | Yes | N/A | Yes |
| P2PKH Inputs | Yes | Yes | Yes | Yes | Yes | Yes |
| P2SH-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-P2WSH Multisig Inputs | Yes | Yes | Yes | Yes | No | N/A |
| P2WSH Multisig Inputs | Yes | Yes | Yes | Yes | Yes | N/A |
| Bare Multisig Inputs | Yes | N/A | N/A | Yes | N/A | N/A |
| Aribtrary scriptPubKey Inputs | Yes | N/A | N/A | Yes | N/A | N/A |
| Aribtrary redeemScript Inputs | Yes | N/A | N/A | Yes | N/A | N/A |
| Arbitrary witnessScript Inputs | Yes | N/A | N/A | Yes | N/A | N/A |
| Non-wallet inputs | Yes | Yes | Yes | Yes | Yes | Yes |
| Mixed Segwit and Non-Segwit Inputs | N/A | Yes | N/A | Yes | Yes | Yes |
| Display on device screen | Yes | Yes | Yes | N/A | Yes | Yes |
| Feature \ Device | Ledger Nano S | Trezor One | Digital BitBox | KeepKey | Coldcard |
|:---:|:---:|:---:|:---:|:---:|:---:|
| Support Planned | Yes | Yes | Yes | Yes | Yes |
| Implemented | Yes | Yes | Yes | Yes | Yes |
| xpub retrieval | Yes | Yes | Yes | Yes | Yes |
| Message Signing | Yes | Yes | Yes | Yes | Yes |
| Device Setup | N/A | Yes | Yes | Yes | N/A |
| Device Wipe | N/A | Yes | Yes | Yes | N/A |
| Device Recovery | N/A | Yes | N/A | Yes | N/A |
| Device Backup | N/A | N/A | Yes | N/A | Yes |
| P2PKH Inputs | Yes | Yes | Yes | Yes | Yes |
| P2SH-P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes |
| P2WPKH Inputs | Yes | Yes | Yes | Yes | Yes |
| P2SH Multisig Inputs | Yes | Yes | Yes | Yes | N/A |
| P2SH-P2WSH Multisig Inputs | Yes | No | Yes | No | N/A |
| P2WSH Multisig Inputs | Yes | No | Yes | Yes | N/A |
| Bare Multisig Inputs | Yes | N/A | Yes | N/A | N/A |
| Aribtrary scriptPubKey Inputs | Yes | N/A | Yes | N/A | N/A |
| Aribtrary redeemScript Inputs | Yes | 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 |
| Mixed Segwit and Non-Segwit Inputs | N/A | Yes | Yes | Yes | Yes |
| Display on device screen | Yes | Yes | N/A | Yes | Yes |
## Using with Bitcoin Core

View File

@ -1,35 +0,0 @@
# Assorted tools
## `build_bin.sh`
Creates a virtualenv with the locked dependencies using Poetry. Then uses pyinstaller to create a standalone binary for the OS type currently running.
## `build_dist.sh`
Creates a virtualenv with the locked dependencies using Poetry. Then uses Poetry to produce deterministic builds of the wheel and sdist for upload to PyPi
`faketime` needs to be installed
## `build_wine.sh`
Sets up Wine with Python and everything needed to build Windows binaries. Creates a virtualenv with the locked dependencies using Poetry. Then uses pyinstaller to create a standalone Windows binary.
`wine` needs to be installed
## `generate_setup.sh`
Builds the source distribution and extracts the setup.py from it.
## `build.Dockerfile`
A Dockerfile for setting up the deterministic build environment.
# Other files
## `reproducible-python.diff`
A path for python in order to do a deterministic build of Python for the deterministically built binaries.
## `pyinstaller-hooks/hook-hwilib.devices.py`
Pyinstaller hook so that the device drivers are actually included. Due to how the imports work, we need this hook.

View File

@ -1,50 +0,0 @@
FROM debian:stretch-slim
SHELL ["/bin/bash", "-c"]
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get install -y \
apt-transport-https \
git \
make \
build-essential \
libssl-dev \
zlib1g-dev \
libbz2-dev \
libreadline-dev \
libsqlite3-dev \
wget \
curl \
llvm \
libncurses5-dev \
xz-utils \
libxml2-dev \
libxmlsec1-dev \
libffi-dev \
liblzma-dev \
libusb-1.0-0-dev \
libudev-dev \
faketime \
zip \
dos2unix
RUN curl https://pyenv.run | bash
ENV PATH="/root/.pyenv/bin:$PATH"
COPY contrib/reproducible-python.diff /opt/reproducible-python.diff
ENV PYTHON_CONFIGURE_OPTS="--enable-shared"
ENV BUILD_DATE="Jan 1 2019"
ENV BUILD_TIME="00:00:00"
RUN eval "$(pyenv init -)" && eval "$(pyenv virtualenv-init -)" && cat /opt/reproducible-python.diff | pyenv install -kp 3.6.8
RUN dpkg --add-architecture i386
RUN wget -nc https://dl.winehq.org/wine-builds/winehq.key
RUN apt-key add winehq.key
RUN echo "deb https://dl.winehq.org/wine-builds/debian/ stretch main" >> /etc/apt/sources.list
RUN apt-get update
RUN apt-get install --install-recommends -y \
wine-stable-amd64 \
wine-stable-i386 \
wine-stable \
winehq-stable \
p7zip-full

View File

@ -1,36 +0,0 @@
#! /bin/bash
# Script for building standalone binary releases deterministically
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
pip install -U pip
pip install poetry==0.12.12
# Setup poetry and install the dependencies
poetry install
# We now need to remove debugging symbols and build id from the hidapi SO file
so_dir=`dirname $(dirname $(poetry run which python))`/lib/python3.6/site-packages
find ${so_dir} -name '*.so' -type f -execdir strip '{}' \;
if [[ $OSTYPE != *"darwin"* ]]; then
find ${so_dir} -name '*.so' -type f -execdir strip -R .note.gnu.build-id '{}' \;
fi
# We also need to change the timestamps of all of the base library files
lib_dir=`pyenv root`/versions/3.6.8/lib/python3.6
TZ=UTC find ${lib_dir} -name '*.py' -type f -execdir touch -t "201901010000.00" '{}' \;
# Make the standalone binary
export PYTHONHASHSEED=42
poetry run pyinstaller hwi.spec
unset PYTHONHASHSEED
# Make the final compressed package
pushd dist
VERSION=`poetry run hwi --version | cut -d " " -f 2`
OS=`uname | tr '[:upper:]' '[:lower:]'`
if [[ $OS == "darwin" ]]; then
OS="mac"
fi
tar -czf "hwi-${VERSION}-${OS}-amd64.tar.gz" hwi
popd

View File

@ -1,15 +0,0 @@
#! /bin/bash
# Script for building pypi distribution archives deterministically
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
pip install -U pip
pip install poetry==0.12.12
# Setup poetry and install the dependencies
poetry install
# Make the distribution archives for pypi
poetry build -f wheel
# faketime is needed to make sdist detereministic
TZ=UTC faketime -f "2019-01-01 00:00:00" poetry build -f sdist

View File

@ -1,76 +0,0 @@
#!/bin/bash
# Script which sets up Wine and builds the Windows standalone binary
set -e
PYTHON_VERSION=3.6.8
PYTHON_FOLDER="python3"
PYHOME="c:/$PYTHON_FOLDER"
PYTHON="wine $PYHOME/python.exe -OO -B"
LIBUSB_URL=https://github.com/libusb/libusb/releases/download/v1.0.22/libusb-1.0.22.7z
LIBUSB_HASH="671f1a420757b4480e7fadc8313d6fb3cbb75ca00934c417c1efa6e77fb8779b"
WINDOWS_SDK_URL=http://go.microsoft.com/fwlink/p/?LinkID=2033686
WINDOWS_SDK_HASH="016981259708e1afcab666c7c1ff44d1c4d63b5e778af8bc41b4f6db3d27961a"
WINDOWS_SDK_VERSION=10.0.17763.0
wine 'wineboot'
# Install Python
# Get the PGP keys
wget -N -c "https://www.python.org/static/files/pubkeys.txt"
gpg --import pubkeys.txt
rm pubkeys.txt
# Install python components
for msifile in core dev exe lib pip tools; do
wget -N -c "https://www.python.org/ftp/python/$PYTHON_VERSION/amd64/${msifile}.msi"
wget -N -c "https://www.python.org/ftp/python/$PYTHON_VERSION/amd64/${msifile}.msi.asc"
gpg --verify "${msifile}.msi.asc" "${msifile}.msi"
wine msiexec /i "${msifile}.msi" /qb TARGETDIR=$PYHOME
rm $msifile.msi*
done
# Get libusb
wget -N -c -O libusb.7z "$LIBUSB_URL"
echo "$LIBUSB_HASH libusb.7z" | sha256sum -c
7za x -olibusb libusb.7z -aoa
cp libusb/MS64/dll/libusb-1.0.dll ~/.wine/drive_c/python3/
rm -r libusb*
# Get the Windows SDK
pushd `mktemp -d`
wget -O sdk.iso "$WINDOWS_SDK_URL"
echo "$WINDOWS_SDK_HASH sdk.iso" | sha256sum -c
7z e sdk.iso
wine msiexec /i "Universal CRT Redistributable-x86_en-us.msi"
cp ~/.wine/drive_c/Program\ Files\ \(x86\)/Windows\ Kits/10/Redist/${WINDOWS_SDK_VERSION}/ucrt/DLLs/x64/*.dll ~/.wine/drive_c/windows/system32/
popd
# Update pip
$PYTHON -m pip install -U pip
# Install Poetry and things needed for pyinstaller
$PYTHON -m pip install poetry==0.12.12
# We also need to change the timestamps of all of the base library files
lib_dir=~/.wine/drive_c/python3/Lib
TZ=UTC find ${lib_dir} -name '*.py' -type f -execdir touch -t "201901010000.00" '{}' \;
# Install python dependencies
POETRY="wine $PYHOME/Scripts/poetry.exe"
sleep 5 # For some reason, pausing for a few seconds makes the next step work
$POETRY install -E windist
# Do the build
export PYTHONHASHSEED=42
$POETRY run pyinstaller hwi.spec
unset PYTHONHASHSEED
# Make the final compressed package
pushd dist
VERSION=`$POETRY run hwi --version | cut -d " " -f 2 | dos2unix`
zip "hwi-${VERSION}-windows-amd64.zip" hwi.exe
popd

View File

@ -1,32 +0,0 @@
#! /bin/bash
# Generates the setup.py file
set -e
# Setup poetry and install the dependencies
poetry install
# Build the source distribution
poetry build -f sdist
# Extract setup.py from the distribution
unset -v tarball
for file in dist/*
do
if [[ $file -nt $tarball && $file == *".tar.gz" ]]
then
tarball=$file
fi
done
unset -v toextract
for file in `tar -tf $tarball`
do
if [[ $file == *"setup.py" ]]
then
toextract=$file
fi
done
tar -xf $tarball $toextract
mv $toextract .
dir=`echo $toextract | cut -f1 -d"/"`
rm -r $dir

View File

@ -1,4 +0,0 @@
from hwilib.devices import __all__
hiddenimports = []
for d in __all__:
hiddenimports.append('hwilib.devices.' + d)

View File

@ -1,13 +0,0 @@
# DP: Build getbuildinfo.o with DATE/TIME values when defined
--- Makefile.pre.in
+++ Makefile.pre.in
@@ -741,6 +741,8 @@ Modules/getbuildinfo.o: $(PARSER_OBJS) \
-DGITVERSION="\"`LC_ALL=C $(GITVERSION)`\"" \
-DGITTAG="\"`LC_ALL=C $(GITTAG)`\"" \
-DGITBRANCH="\"`LC_ALL=C $(GITBRANCH)`\"" \
+ $(if $(BUILD_DATE),-DDATE='"$(BUILD_DATE)"') \
+ $(if $(BUILD_TIME),-DTIME='"$(BUILD_TIME)"') \
-o $@ $(srcdir)/Modules/getbuildinfo.c
Modules/getpath.o: $(srcdir)/Modules/getpath.c Makefile

View File

@ -1,6 +1,6 @@
# Using Bitcoin Core with Hardware Wallets
This approach is fairly manual, requires the command line, and Bitcoin Core >=0.18.0.
This approach is fairly manual, requires the command line, and requires a patched version of Bitcoin Core.
Note: For this guide, code lines prefixed with `$` means that the command is typed in the terminal. Lines without `$` are output of the commands.
@ -14,14 +14,14 @@ We are not liable for any coins that may be lost through this method. The softwa
This method of using hardware wallets uses Bitcoin Core as the wallet for monitoring the blockchain. It allows a user to use their own full node instead of relying on an SPV wallet or vendor provided software.
HWI works with Bitcoin Core as of commit [c576979b78b541bf3b4a7cbeee989b55d268e3e1](https://github.com/bitcoin/bitcoin/commit/c576979b78b541bf3b4a7cbeee989b55d268e3e1). It is usable with Bitcoin Core >=0.18.0.
HWI works with Bitcoin Core as of commit [c576979b78b541bf3b4a7cbeee989b55d268e3e1](https://github.com/bitcoin/bitcoin/commit/c576979b78b541bf3b4a7cbeee989b55d268e3e1).
## Setup
Clone Bitcoin Core and build it. Clone HWI.
Clone the modified Bitcoin Core and build it. Clone HWI.
```
$ git clone https://github.com/bitcoin/bitcoin.git
$ git clone https://github.com/achow101/bitcoin.git -b hww
$ cd bitcoin
$ ./autogen.sh
$ ./configure
@ -47,7 +47,7 @@ We will be fetching keys at the BIP 84 default.
```
$ ./hwi.py -f 8038ecd9 getkeypool --wpkh --keypool 0 1000
[{"desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/0/*)#36sal9a4", "internal": false, "range": [0, 1000], "timestamp": "now", "keypool": true, "watchonly": true}]
[{"desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/0/*)", "internal": false, "range": {"start": 0, "end": 1000}, "timestamp": "now", "keypool": true, "watchonly": true}]
```
We now create a new Bitcoin Core wallet and import the keys into Bitcoin Core. The output is formatted properly for Bitcoin Core so it can be copy and pasted.
@ -58,7 +58,7 @@ $ ../bitcoin/src/bitcoin-cli createwallet "coldcard" true
"name": "coldcard",
"warning": ""
}
$ ../bitcoin/src/bitcoin-cli -rpcwallet=coldcard importmulti '[{"desc": "wpkh([8038ecd9/84'/0'/0']xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/0/*)#36sal9a4", "internal": false, "range": [0, 1000], "timestamp": "now", "keypool": true, "watchonly": true}]'
$ ../bitcoin/src/bitcoin-cli -rpcwallet=coldcard importmulti '[{"desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/0/*)", "internal": false, "range": {"start": 0, "end": 1000}, "timestamp": "now", "keypool": true, "watchonly": true}]'
[
{
@ -71,8 +71,8 @@ Now we repeat the `getkeypool` and `importmulti` steps but set a `--internal` fl
```
$ ./hwi.py -f 8038ecd9 getkeypool --wpkh --keypool --internal 0 1000
[{"internal": true, "timestamp": "now", "desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/1/*)#qw4uzsdd", "keypool": true, "range": {"start": 0, "end": 1000}}]
$ ../bitcoin/src/bitcoin-cli -rpcwallet=coldcard importmulti '[{"internal": true, "timestamp": "now", "desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/1/*)", "keypool": true, "range": [0, 1000], "watchonly": true}]'
[{"internal": true, "timestamp": "now", "desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/1/*)", "keypool": true, "range": {"start": 0, "end": 1000}}]
$ ../bitcoin/src/bitcoin-cli -rpcwallet=coldcard importmulti '[{"internal": true, "timestamp": "now", "desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/1/*)", "keypool": true, "range": {"start": 0, "end": 1000}, "watchonly": true}]'
[
{
@ -90,8 +90,8 @@ Here are some examples (`<blockheight>` refers to a block height before the wall
$ ../bitcoin/src/bitcoin-cli rescanblockchain <blockheight>
$ ../bitcoin/src/bitcoin-cli rescanblockchain 500000 # Rescan from block 500000
$ ../bitcoin/src/bitcoin-cli -rpcwallet=coldcard importmulti '[{"internal": true, "timestamp": <blockheight>, "desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/1/*)#qw4uzsdd", "keypool": true, "range": [0, 1000], "watchonly": true}]'
$ ../bitcoin/src/bitcoin-cli -rpcwallet=coldcard importmulti '[{"internal": true, "timestamp": 500000, "desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/1/*)#qw4uzsdd", "keypool": true, "range": [0, 1000], "watchonly": true}]' # Imports and rescans from block 500000
$ ../bitcoin/src/bitcoin-cli -rpcwallet=coldcard importmulti '[{"internal": true, "timestamp": <blockheight>, "desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/1/*)", "keypool": true, "range": {"start": 0, "end": 1000}, "watchonly": true}]'
$ ../bitcoin/src/bitcoin-cli -rpcwallet=coldcard importmulti '[{"internal": true, "timestamp": 500000, "desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/1/*)", "keypool": true, "range": {"start": 0, "end": 1000}, "watchonly": true}]' # Imports and rescans from block 500000
```
## Usage
@ -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:
```
$ ./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
```
The output can be imported with `importmulti` as shown in the Setup steps.

View File

@ -12,7 +12,6 @@ Current implemented commands are:
- `restore`
- `backup`
- `displayaddress`
- `signmessage`
## Usage Notes

View File

@ -135,11 +135,3 @@ bitcoin-cli scantxoutset start '[{"desc":"wpkh(xpub6DP9afdc7qsz7s7mwAvciAR2dV6vP
"total_amount": 0.00000000
}
```
### Binary format handling
The input and output format supported by HWI is base64, which is prescribed by BIP174 as the string format. Note that the PSBT standard also allows for binary formatting when stored as a file. There is no direct support within HWI, but this can be easily accomplished using common utilities. A bash command-line example is detailed below, where the PSBT binary file is stored in `example.psbt` and only the common utilities `base64` and `jq` are required:
```
cat example.psbt | base64 --wrap=0 | ./hwi.py -t ledger --stdin signtx | jq .[] --raw-output | base64 -d > example_result.psbt
```

View File

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

View File

@ -1,51 +0,0 @@
# Release Process
1. Bump version number in `pyproject.toml` and `hwilib/__init__.py`, generate the setup.py file, and git tag release
2. Build distribution archives for PyPi with `contrib/build_dist.sh`
3. For MacOS and Linux, use `contrib/build_bin.sh`. This needs to be run on a MacOS machine for the MacOS binary and on a Linux machine for the linux one.
4. For Windows, use `contrib/build_wine.sh` to build the Windows binary using wine
5. Upload distribution archives to PyPi
6. Upload distribution archives and standalone binaries to Github
## Deterministic builds with Docker
Create the docker image:
```
docker build --no-cache -t hwi-builder -f contrib/build.Dockerfile .
```
Build everything
```
docker run -it --name hwi-builder -v $PWD:/opt/hwi --rm --workdir /opt/hwi hwi-builder /bin/bash -c "contrib/build_bin.sh && contrib/build_dist.sh && contrib/build_wine.sh"
```
## Building macOS binary
Note that the macOS build is non-deterministic.
First install [pyenv](https://github.com/pyenv/pyenv) using whichever method you prefer.
Then a deterministic build of Python 3.6.8 needs to be installed. This can be done with the patch in `contrib/reproducible-python.diff`. First `cd` into HWI's source tree. Then use:
```
cat contrib/reproducible-python.diff | PYTHON_CONFIGURE_OPTS="--enable-framework" BUILD_DATE="Jan 1 2019" BUILD_TIME="00:00:00" pyenv install -kp 3.6.8
```
Make sure that python 3.6.8 is active
```
$ python --version
Python 3.6.8
```
Now install [Poetry](https://github.com/sdispater/poetry) with `pip install poetry`
Additional dependencies can be installed with:
```
brew install libusb
```
Build the binaries by using `contrib/build_bin.sh`.

View File

@ -1,46 +0,0 @@
# -*- mode: python -*-
import platform
import subprocess
block_cipher = None
binaries = []
if platform.system() == 'Windows':
binaries = [("c:/python3/libusb-1.0.dll", ".")]
elif platform.system() == 'Linux':
binaries = [("/lib/x86_64-linux-gnu/libusb-1.0.so.0", ".")]
elif platform.system() == 'Darwin':
find_brew_libusb_proc = subprocess.Popen(['brew', '--prefix', 'libusb'], stdout=subprocess.PIPE)
libusb_path = find_brew_libusb_proc.communicate()[0]
binaries = [(libusb_path.rstrip().decode() + "/lib/libusb-1.0.dylib", ".")]
a = Analysis(['hwi.py'],
binaries=binaries,
datas=[],
hiddenimports=[],
hookspath=['contrib/pyinstaller-hooks/'],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
if platform.system() == 'Linux':
a.datas += Tree('udev', prefix='udev')
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='hwi',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
runtime_tmpdir=None,
console=True )

View File

@ -1 +0,0 @@
__version__ = '1.0.0'

View File

@ -2,17 +2,15 @@
from .commands import backup_device, displayaddress, enumerate, find_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 (
HWWError,
NO_DEVICE_PATH,
DEVICE_CONN_ERROR,
NO_PASSWORD,
UNKNWON_DEVICE_TYPE,
UNKNOWN_ERROR,
UNAVAILABLE_ACTION
UNKNOWN_ERROR
)
from . import __version__
import argparse
import getpass
@ -39,14 +37,10 @@ def getkeypool_handler(args, client):
return getkeypool(client, path=args.path, start=args.start, end=args.end, internal=args.internal, keypool=args.keypool, account=args.account, sh_wpkh=args.sh_wpkh, wpkh=args.wpkh)
def restore_device_handler(args, client):
if args.interactive:
return restore_device(client, label=args.label)
return {'error': 'restore requires interactive mode', 'code': UNAVAILABLE_ACTION}
return restore_device(client, label=args.label)
def setup_device_handler(args, client):
if args.interactive:
return setup_device(client, label=args.label, backup_passphrase=args.backup_passphrase)
return {'error': 'setup requires interactive mode', 'code': UNAVAILABLE_ACTION}
return setup_device(client, label=args.label, backup_passphrase=args.backup_passphrase)
def signmessage_handler(args, client):
return signmessage(client, message=args.message, path=args.path)
@ -63,11 +57,8 @@ def prompt_pin_handler(args, client):
def send_pin_handler(args, client):
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):
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)
def process_commands(args):
parser = argparse.ArgumentParser(description='Access and send commands to a hardware wallet device. Responses are in JSON format')
parser.add_argument('--device-path', '-d', help='Specify the device path of the device to connect to')
parser.add_argument('--device-type', '-t', help='Specify the type of device that will be connected. If `--device-path` not given, the first device of this type enumerated is used.')
parser.add_argument('--password', '-p', help='Device password if it has one (e.g. DigitalBitbox)', default='')
@ -75,9 +66,6 @@ def process_commands(cli_args):
parser.add_argument('--testnet', help='Use testnet prefixes', action='store_true')
parser.add_argument('--debug', help='Print debug statements', action='store_true')
parser.add_argument('--fingerprint', '-f', help='Specify the device to connect to using the first 4 bytes of the hash160 of the master public key. It will connect to the first device that matches this fingerprint.')
parser.add_argument('--version', action='version', version='%(prog)s {}'.format(__version__))
parser.add_argument('--stdin', help='Enter commands and arguments via stdin', action='store_true')
parser.add_argument('--interactive', '-i', help='Use some commands interactively. Currently required for all device configuration commands', action='store_true')
subparsers = parser.add_subparsers(description='Commands', dest='command')
# work-around to make subparser required
@ -121,7 +109,7 @@ def process_commands(cli_args):
displayaddr_parser.add_argument('--wpkh', action='store_true', help='Display the bech32 version of the address associated with this key path')
displayaddr_parser.set_defaults(func=displayaddress_handler)
setupdev_parser = subparsers.add_parser('setup', help='Setup a device. Passphrase protection uses the password given by -p. Requires interactive mode')
setupdev_parser = subparsers.add_parser('setup', help='Setup a device. Passphrase protection uses the password given by -p')
setupdev_parser.add_argument('--label', '-l', help='The name to give to the device', default='')
setupdev_parser.add_argument('--backup_passphrase', '-b', help='The passphrase to use for the backup, if applicable', default='')
setupdev_parser.set_defaults(func=setup_device_handler)
@ -129,7 +117,7 @@ def process_commands(cli_args):
wipedev_parser = subparsers.add_parser('wipe', help='Wipe a device')
wipedev_parser.set_defaults(func=wipe_device_handler)
restore_parser = subparsers.add_parser('restore', help='Initiate the device restoring process. Requires interactive mode')
restore_parser = subparsers.add_parser('restore', help='Initiate the device restoring process')
restore_parser.add_argument('--label', '-l', help='The name to give to the device', default='')
restore_parser.set_defaults(func=restore_device_handler)
@ -145,30 +133,7 @@ def process_commands(cli_args):
sendpin_parser.add_argument('pin', help='The numeric positions of the PIN')
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):
blank_count = 0
while True:
try:
line = input()
# Exit loop when we see 2 consecutive newlines (i.e. an empty line)
if line == '':
break
# Split the line and append it to the cli args
import shlex
cli_args.extend(shlex.split(line))
except EOFError:
# If we see EOF, stop taking input
break
# Parse arguments again for anything entered over stdin
args = parser.parse_args(cli_args)
args = parser.parse_args(args)
device_path = args.device_path
device_type = args.device_type
@ -187,17 +152,6 @@ def process_commands(cli_args):
if command == 'enumerate':
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
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)

View File

@ -2,15 +2,14 @@
# Hardware wallet interaction script
import glob
import importlib
from .serializations import PSBT, Base64ToHex, HexToBase64, hash160
from .base58 import get_xpub_fingerprint_as_id, get_xpub_fingerprint_hex, xpub_to_pub_hex
from os.path import dirname, basename, isfile
from .errors import NoPasswordError, UnavailableActionError, DeviceAlreadyInitError, DeviceAlreadyUnlockedError, UnknownDeviceError, BAD_ARGUMENT, NOT_IMPLEMENTED
from .descriptor import Descriptor
from .devices import __all__ as all_devs
from .udevinstaller import UDevInstaller
# Get the client for the device
def get_client(device_type, device_path, password=''):
@ -33,7 +32,11 @@ def get_client(device_type, device_path, password=''):
def enumerate(password=''):
result = []
for module in all_devs:
# Gets the module names of all the files in devices/
files = glob.glob(dirname(__file__)+"/devices/*.py")
modules = [ basename(f)[:-3] for f in files if isfile(f) and not f.endswith('__init__.py')]
for module in modules:
try:
imported_dev = importlib.import_module('.devices.' + module, __package__)
result.extend(imported_dev.enumerate(password))
@ -50,12 +53,8 @@ def find_device(device_path, password='', device_type=None, fingerprint=None):
client = None
try:
client = get_client(d['type'], d['path'], password)
master_fpr = d.get('fingerprint', None)
if master_fpr is None:
master_xpub = client.get_pubkey_at_path('m/0h')['xpub']
master_fpr = get_xpub_fingerprint_hex(master_xpub)
master_xpub = client.get_pubkey_at_path('m/0h')['xpub']
master_fpr = get_xpub_fingerprint_hex(master_xpub)
if fingerprint and master_fpr != fingerprint:
client.close()
continue
@ -157,10 +156,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 client.display_address(path, sh_wpkh, wpkh)
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:
return {'error':' `--wpkh` and `--sh_wpkh` can not be combined with --desc','code':BAD_ARGUMENT}
descriptor = Descriptor.parse(desc, client.is_testnet)
@ -192,6 +187,3 @@ def prompt_pin(client):
def send_pin(client, pin):
return client.send_pin(pin)
def install_udev_rules(source, location):
return UDevInstaller.install(source, location);

View File

@ -1,7 +0,0 @@
__all__ = [
'trezor',
'ledger',
'keepkey',
'digitalbitbox',
'coldcard'
]

View File

@ -8,7 +8,7 @@
#
# - ec_mult, ec_setup, aes_setup, mitm_verify
#
import hid, sys, os, platform
import hid, sys, os
from binascii import b2a_hex, a2b_hex
from hashlib import sha256
from .protocol import CCProtocolPacker, CCProtocolUnpacker, CCProtoError, MAX_MSG_LEN, MAX_BLK_LEN
@ -27,8 +27,6 @@ class ColdcardDevice:
self.is_simulator = False
if not dev and sn and '/' in sn:
if platform.system() == 'Windows':
raise RuntimeError("Cannot connect to simulator. Is it running?")
dev = UnixSimulatorPipe(sn)
found = 'simulator'
self.is_simulator = True

View File

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

View File

@ -15,7 +15,7 @@ import sys
import time
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 ..base58 import get_xpub_fingerprint, decode, to_address, xpub_main_2_test, get_xpub_fingerprint_hex
@ -603,14 +603,8 @@ def enumerate(password=''):
else:
master_xpub = client.get_pubkey_at_path('m/0h')['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:
d_data['error'] = "Could not open client or get fingerprint information: " + str(e)
d_data['code'] = UNKNOWN_ERROR
if client:
client.close()

View File

@ -1,6 +1,5 @@
# KeepKey interaction script
from ..errors import HWWError, UNKNOWN_ERROR
from .trezorlib.transport import enumerate_devices
from .trezor import TrezorClient
from ..base58 import get_xpub_fingerprint_hex
@ -26,24 +25,13 @@ def enumerate(password=''):
client.client.init_device()
if not 'keepkey' in client.client.features.vendor:
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:
master_xpub = client.get_pubkey_at_path('m/0h')['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:
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:
d_data['error'] = "Could not open client or get fingerprint information: " + str(e)
d_data['code'] = UNKNOWN_ERROR
if client:
client.close()

View File

@ -1,11 +1,10 @@
# Ledger interaction script
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.btchipUtils import *
import base64
import hid
import json
import struct
from .. import base58
@ -350,11 +349,8 @@ def enumerate(password=''):
client = LedgerClient(path, password)
master_xpub = client.get_pubkey_at_path('m/0h')['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:
d_data['error'] = "Could not open client or get fingerprint information: " + str(e)
d_data['code'] = UNKNOWN_ERROR
if client:
client.close()

View File

@ -1,19 +1,18 @@
# Trezor interaction script
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.debuglink import TrezorClientDebugLink, DebugUI
from .trezorlib.debuglink import TrezorClientDebugLink
from .trezorlib.exceptions import Cancelled
from .trezorlib.transport import enumerate_devices, get_transport
from .trezorlib.ui import echo, PassphraseUI, mnemonic_words, PIN_CURRENT, PIN_NEW, PIN_CONFIRM, PIN_MATRIX_DESCRIPTION, prompt
from .trezorlib.ui import PassphraseUI, mnemonic_words, PIN_MATRIX_DESCRIPTION
from .trezorlib import protobuf, tools, btc, device
from .trezorlib import messages as proto
from ..base58 import get_xpub_fingerprint, decode, to_address, xpub_main_2_test, get_xpub_fingerprint_hex
from ..serializations import ser_uint256, uint256_from_str
from .. import bech32
from usb1 import USBErrorNoDevice
from types import MethodType
import base64
import binascii
@ -70,37 +69,15 @@ def trezor_exception(f):
raise DeviceConnectionError('Device disconnected')
return func
def interactive_get_pin(self, code=None):
if code == PIN_CURRENT:
desc = "current PIN"
elif code == PIN_NEW:
desc = "new PIN"
elif code == PIN_CONFIRM:
desc = "new PIN again"
else:
desc = "PIN"
echo(PIN_MATRIX_DESCRIPTION)
while True:
pin = prompt("Please enter {}".format(desc), hide_input=True)
if not pin.isdigit():
echo("Non-numerical PIN provided, please try again")
else:
return pin
# This class extends the HardwareWalletClient for Trezor specific things
class TrezorClient(HardwareWalletClient):
def __init__(self, path, password=''):
super(TrezorClient, self).__init__(path, password)
self.simulator = False
if path.startswith('udp'):
logging.debug('Simulator found, using DebugLink')
transport = get_transport(path)
self.client = TrezorClientDebugLink(transport=transport)
self.simulator = True
self.client.set_passphrase(password)
else:
self.client = Trezor(transport=get_transport(path), ui=PassphraseUI(password))
@ -337,10 +314,6 @@ class TrezorClient(HardwareWalletClient):
@trezor_exception
def setup_device(self, label='', passphrase=''):
self.client.init_device()
if not self.simulator:
# Use interactive_get_pin
self.client.ui.get_pin = MethodType(interactive_get_pin, self.client.ui)
if self.client.features.initialized:
raise DeviceAlreadyInitError('Device is already initialized. Use wipe first and try again')
passphrase_enabled = False
@ -359,13 +332,8 @@ class TrezorClient(HardwareWalletClient):
# Restore device from mnemonic or xprv
@trezor_exception
def restore_device(self, label=''):
self.client.init_device()
if not self.simulator:
# Use interactive_get_pin
self.client.ui.get_pin = MethodType(interactive_get_pin, self.client.ui)
passphrase_enabled = False
device.recover(self.client, label=label, input_callback=mnemonic_words(), passphrase_protection=bool(self.password))
device.recover(self.client, label=label, input_callback=mnemonic_words, passphrase_protection=bool(self.password))
return {'success': True}
# Begin backup process
@ -422,24 +390,13 @@ def enumerate(password=''):
client.client.init_device()
if not 'trezor' in client.client.features.vendor:
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:
master_xpub = client.get_pubkey_at_path('m/0h')['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:
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:
d_data['error'] = "Could not open client or get fingerprint information: " + str(e)
d_data['code'] = UNKNOWN_ERROR
if client:
client.close()

View File

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

View File

@ -27,9 +27,8 @@ DEV_TREZOR1 = (0x534C, 0x0001)
DEV_TREZOR2 = (0x1209, 0x53C1)
DEV_TREZOR2_BL = (0x1209, 0x53C0)
DEV_KEEPKEY = (0x2B24, 0x0001)
DEV_KEEPKEY_WEBUSB = (0x2B24, 0x0002)
TREZORS = {DEV_TREZOR1, DEV_TREZOR2, DEV_TREZOR2_BL, DEV_KEEPKEY, DEV_KEEPKEY_WEBUSB}
TREZORS = {DEV_TREZOR1, DEV_TREZOR2, DEV_TREZOR2_BL, DEV_KEEPKEY}
UDEV_RULES_STR = """
Do you have udev rules installed?

View File

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

View File

@ -50,12 +50,8 @@ PIN_CONFIRM = PinMatrixRequestType.NewSecond
def echo(msg):
print(msg, file=sys.stderr)
def prompt(msg, hide_input=False):
if hide_input:
import getpass
return getpass.getpass(msg + ' :\n')
else:
return input(msg + ':\n')
def prompt(msg):
return input(msg)
class PassphraseUI:
def __init__(self, passphrase):

View File

@ -16,7 +16,6 @@ DEVICE_NOT_READY = -12
UNKNOWN_ERROR = -13
ACTION_CANCELED = -14
DEVICE_BUSY = -15
NEED_TO_BE_ROOT = -16
# Exceptions
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)

164
poetry.lock generated
View File

@ -1,164 +0,0 @@
[[package]]
category = "dev"
description = "Python graph (network) package"
name = "altgraph"
optional = false
python-versions = "*"
version = "0.16.1"
[[package]]
category = "main"
description = "ECDSA cryptographic signature library (pure python)"
name = "ecdsa"
optional = false
python-versions = "*"
version = "0.13"
[[package]]
category = "dev"
description = "Clean single-source support for Python 3 and 2"
name = "future"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "0.17.1"
[[package]]
category = "main"
description = "A Cython interface to the hidapi from https://github.com/signal11/hidapi"
name = "hidapi"
optional = false
python-versions = "*"
version = "0.7.99.post21"
[package.dependencies]
setuptools = ">=19.0"
[[package]]
category = "main"
description = "Pure-python wrapper for libusb-1.0"
name = "libusb1"
optional = false
python-versions = "*"
version = "1.7"
[[package]]
category = "dev"
description = "Mach-O header analysis and editing"
name = "macholib"
optional = false
python-versions = "*"
version = "1.11"
[package.dependencies]
altgraph = ">=0.15"
[[package]]
category = "main"
description = "Implementation of Bitcoin BIP-0039"
name = "mnemonic"
optional = false
python-versions = "*"
version = "0.18"
[package.dependencies]
pbkdf2 = "*"
[[package]]
category = "main"
description = "PKCS#5 v2.0 PBKDF2 Module"
name = "pbkdf2"
optional = false
python-versions = "*"
version = "1.3"
[[package]]
category = "dev"
description = "Python PE parsing module"
name = "pefile"
optional = false
python-versions = "*"
version = "2018.8.8"
[package.dependencies]
future = "*"
[[package]]
category = "main"
description = "Pure-Python Implementation of the AES block-cipher and common modes of operation"
name = "pyaes"
optional = false
python-versions = "*"
version = "1.6.1"
[[package]]
category = "dev"
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
name = "pyinstaller"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "3.4"
[package.dependencies]
altgraph = "*"
macholib = ">=1.8"
pefile = ">=2017.8.1"
setuptools = "*"
[[package]]
category = "dev"
description = "Enhanced version of python-jsonrpc for use with Bitcoin"
name = "python-bitcoinrpc"
optional = false
python-versions = "*"
version = "1.0"
[[package]]
category = "main"
description = ""
name = "pywin32-ctypes"
optional = true
python-versions = "*"
version = "0.2.0"
[[package]]
category = "main"
description = "Type Hints for Python"
name = "typing"
optional = false
python-versions = "*"
version = "3.6.6"
[[package]]
category = "main"
description = "Backported and Experimental Type Hints for Python 3.5+"
name = "typing-extensions"
optional = false
python-versions = "*"
version = "3.7.2"
[package.dependencies]
typing = ">=3.6.2"
[extras]
windist = ["pywin32-ctypes"]
[metadata]
content-hash = "f668b6352b31d2aa7cf5b0cf19b77d04b92821df3383c9105f75699bbe42aa2e"
python-versions = ">=3.5.6"
[metadata.hashes]
altgraph = ["d6814989f242b2b43025cba7161fc1b8fb487a62cd49c49245d6fd01c18ac997", "ddf5320017147ba7b810198e0b6619bd7b5563aa034da388cea8546b877f9b0c"]
ecdsa = ["40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c", "64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa"]
future = ["67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"]
hidapi = ["1ac170f4d601c340f2cd52fd06e85c5e77bad7ceac811a7bb54b529f7dc28c24", "6424ad75da0021ce8c1bcd78056a04adada303eff3c561f8d132b85d0a914cb3", "8d3be666f464347022e2b47caf9132287885d9eacc7895314fc8fefcb4e42946", "92878bad7324dee619b7832fbfc60b5360d378aa7c5addbfef0a410d8fd342c7", "b4b1f6aff0192e9be153fe07c1b7576cb7a1ff52e78e3f76d867be95301a8e87", "bf03f06f586ce7d8aeb697a94b7dba12dc9271aae92d7a8d4486360ff711a660", "c76de162937326fcd57aa399f94939ce726242323e65c15c67e183da1f6c26f7", "d4ad1e46aef98783a9e6274d523b8b1e766acfc3d72828cd44a337564d984cfa", "d4b5787a04613503357606bb10e59c3e2c1114fa00ee328b838dd257f41cbd7b", "e0be1aa6566979266a8fc845ab0e18613f4918cf2c977fe67050f5dc7e2a9a97", "edfb16b16a298717cf05b8c8a9ad1828b6ff3de5e93048ceccd74e6ae4ff0922"]
libusb1 = ["9d4f66d2ed699986b06bc3082cd262101cb26af7a76a34bd15b7eb56cba37e0f"]
macholib = ["ac02d29898cf66f27510d8f39e9112ae00590adb4a48ec57b25028d6962b1ae1", "c4180ffc6f909bf8db6cd81cff4b6f601d575568f4d5dee148c830e9851eb9db"]
mnemonic = ["02a7306a792370f4a0c106c2cf1ce5a0c84b9dbd7e71c6792fdb9ad88a727f1d"]
pbkdf2 = ["ac6397369f128212c43064a2b4878038dab78dab41875364554aaf2a684e6979"]
pefile = ["4c5b7e2de0c8cb6c504592167acf83115cbbde01fe4a507c16a1422850e86cd6"]
pyaes = ["02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"]
pyinstaller = ["a5a6e04a66abfcf8761e89a2ebad937919c6be33a7b8963e1a961b55cb35986b"]
python-bitcoinrpc = ["a6a6f35672635163bc491c25fe29520bdd063dedbeda3b37bf5be97aa038c6e7"]
pywin32-ctypes = ["24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"]
typing = ["4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d", "57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4", "a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a"]
typing-extensions = ["07b2c978670896022a43c4b915df8958bec4a6b84add7f2c87b2b728bda3ba64", "f3f0e67e1d42de47b5c67c32c9b26641642e9170fe7e292991793705cd5fef7c", "fb2cd053238d33a8ec939190f30cfd736c00653a85a2919415cecf7dc3d9da71"]

View File

@ -1,39 +0,0 @@
[tool.poetry]
name = "hwi"
version = "1.0.0"
description = "A library for working with Bitcoin hardware wallets"
authors = ["Andrew Chow <andrew@achow101.com>"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/bitcoin-core/HWI"
homepage = "https://github.com/bitcoin-core/HWI"
exclude = ["docs/", "test/"]
include = ["hwilib/**/*.py"]
packages = [
{ include = "hwi.py" },
{ include = "hwilib" },
]
[tool.poetry.dependencies]
python = ">=3.5.6"
hidapi = "^0.7.99"
ecdsa = "^0.13.0"
pyaes = "^1.6"
pywin32-ctypes = {version = "^0.2.0", optional = true}
mnemonic = "^0.18.0"
typing-extensions = "^3.7"
libusb1 = "^1.7"
[tool.poetry.dev-dependencies]
pyinstaller = "^3.4"
python-bitcoinrpc = "^1.0"
[tool.poetry.extras]
windist = ["pywin32-ctypes"]
[tool.poetry.scripts]
hwi = 'hwilib.cli:main'
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

View File

@ -1,50 +1,38 @@
# -*- coding: utf-8 -*-
from distutils.core import setup
import setuptools
packages = \
['hwilib',
'hwilib.devices',
'hwilib.devices.btchip',
'hwilib.devices.ckcc',
'hwilib.devices.trezorlib',
'hwilib.devices.trezorlib.messages',
'hwilib.devices.trezorlib.transport']
with open("README.md", "r") as fh:
long_description = fh.read()
package_data = \
{'': ['*']}
modules = \
['hwi']
install_requires = \
['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']
extras_require = \
{'windist': ['pywin32-ctypes>=0.2.0,<0.3.0']}
entry_points = \
{'console_scripts': ['hwi = hwilib.cli:main']}
setup_kwargs = {
'name': 'hwi',
'version': '1.0.0',
'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",
'author': 'Andrew Chow',
'author_email': 'andrew@achow101.com',
'url': 'https://github.com/bitcoin-core/HWI',
'packages': packages,
'package_data': package_data,
'py_modules': modules,
'install_requires': install_requires,
'extras_require': extras_require,
'entry_points': entry_points,
'python_requires': '>=3.5.6',
}
setup(**setup_kwargs)
setuptools.setup(
name="hwi",
version="0.0.5",
author="Andrew Chow",
author_email="andrew@achow101.com",
description="A library for working with Bitcoin hardware wallets",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/bitcoin-core/hwi",
packages=setuptools.find_packages(exclude=['docs', 'test']),
install_requires=[
'hidapi', # HID API needed in general
'pyaes',
'ecdsa', # Needed for Ledger but their library does not install it
'typing_extensions>=3.7',
'mnemonic>=0.18.0',
'libusb1'
],
python_requires='>=3',
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
extras_require={
'tests': ['python-bitcoinrpc']
},
entry_points={
'console_scripts': [
'hwi = hwilib.cli:main'
]
}
)

View File

@ -10,22 +10,19 @@ This is taken directly from the [python reference implementation](https://github
It implements all of the [BIP 174 serialization test vectors](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#Test_Vectors).
- `test_trezor.py` tests the command line interface and the Trezor implementation.
It uses the [Trezor One firmware emulator](https://github.com/trezor/trezor-mcu/#building-for-development).
It also tests usage with `bitcoind`.
- `test_keepkey.py` tests the command line interface and the Keepkey implementation.
It uses the [Keepkey firmware emulator](https://github.com/keepkey/keepkey-firmware/blob/master/docs/Build.md).
It also tests usage with `bitcoind`.
It also tests usage with `bitcoind`, so the [patched Bitcoin Core](../docs/bitcoin-core-usage.md#bitcoin-core) is required.
- `test_coldcard.py` tests the command line interface and Coldcard implementation.
It uses the [Coldcard simulator](https://github.com/Coldcard/firmware/tree/master/unix#coldcard-desktop-simulator).
It also tests usage with `bitcoind`.
It also tests usage with `bitcoind`, so the [patched Bitcoin Core](../docs/bitcoin-core-usage.md#bitcoin-core) is required.
`setup_environment.sh` will build the Trezor emulator, the Coldcard simulator, the Keepkey emulator, the Digital Bitbox simulator, and `bitcoind`.
if run in the `test/` directory, these will be built in `work/test/trezor-mcu`, `work/test/firmware`, `work/test/keepkey-firmware`, `work/test/mcu`, and `work/test/bitcoin` respectively.
`setup_environment.sh` will build the Trezor emulator, the Coldcard simulator, and the patched `bitcoind`.
if run in the `test/` directory, these will be built in `work/test/trezor-mcu`, `work/test/firmware`, and `work/test/bitcoin` respectively.
`run_tests.py` runs the tests. If run from the `test/` directory, it will be able to find the Trezor emulator, Coldcard simulator, Keepkey emulator, Digital Bitbox simulator, and bitcoind.
`run_tests.py` runs the tests. If run from the `test/` directory, it will be able to find the Trezor emulator, Coldcard simulator, and bitcoind.
Otherwise the paths to those will need to be specified on the command line.
test_trezor.py`, `test_coldcard.py`, `test_keepkey.py`, and `test/test_digitalbitbox.py` can be disabled.
test_trezor.py` and `test_coldcard.py` can be disabled.
If you are building the Trezor emulator, the Coldcard simulator, the Keepkey emulator, the Digital Bitbox simulator, and `bitcoind` without `setup_environment.sh`, then you will need to make `work/` inside of `test/`.
If you are building the Trezor emulator, the Coldcard simulator, and `bitcoind` without `setup_environment.sh`, then you will need to make `work/` inside of `test/`.
```
$ cd test

View File

@ -14,15 +14,11 @@ from test_trezor import trezor_test_suite
from test_ledger import ledger_test_suite
from test_digitalbitbox import digitalbitbox_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')
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('--trezor', help='Path to Trezor emulator.', default='work/trezor-firmware/legacy/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')
trezor_group.add_argument('--trezor', help='Path to Trezor emulator.', default='work/trezor-mcu/firmware/trezor.elf')
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('--coldcard', help='Path to Coldcard simulator.', default='work/firmware/unix/headless.py')
@ -36,7 +32,7 @@ dbb_group.add_argument('--no_bitbox', help='Do not run Digital Bitbox test with
dbb_group.add_argument('--bitbox', help='Path to Digital bitbox simulator.', default='work/mcu/build/bin/simulator')
parser.add_argument('--bitcoind', help='Path to bitcoind.', default='work/bitcoin/src/bitcoind')
parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli', 'bindist', 'stdin'], default='library')
parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli'], default='library')
args = parser.parse_args()
# Run tests
@ -44,25 +40,20 @@ suite = unittest.TestSuite()
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestDescriptor))
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestSegwitAddress))
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 or not args.no_trezor_t:
if not args.no_trezor or not args.no_coldcard or args.ledger or not args.no_bitbox or not args.no_keepkey:
# Start 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:
suite.addTest(trezor_test_suite(args.trezor, rpc, userpass, args.interface))
if not args.no_trezor_t:
suite.addTest(trezor_test_suite(args.trezor_t, rpc, userpass, args.interface, True))
if not args.no_keepkey:
suite.addTest(keepkey_test_suite(args.keepkey, rpc, userpass, args.interface))
if not args.no_coldcard:
suite.addTest(coldcard_test_suite(args.coldcard, rpc, userpass, args.interface))
if args.ledger:
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)
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
trezor_setup_needed=false
if [ ! -d "trezor-firmware" ]; then
git clone --recursive https://github.com/trezor/trezor-firmware.git
cd trezor-firmware
if [ ! -d "trezor-mcu" ]; then
git clone --recursive https://github.com/trezor/trezor-mcu.git
cd trezor-mcu
trezor_setup_needed=true
else
cd trezor-firmware
cd trezor-mcu
git fetch
# Determine if we need to pull. From https://stackoverflow.com/a/3278427
@ -31,30 +31,16 @@ else
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
cd legacy
export EMULATOR=1 TREZOR_TRANSPORT_V1=1 DEBUG_LINK=1 HEADLESS=1
if [ "$trezor_setup_needed" == true ] ; then
script/setup
pipenv install
fi
pipenv run script/cibuild
# Delete any emulator.img file
find . -name "emulator.img" -exec rm {} \;
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
coldcard_setup_needed=false
if [ ! -d "firmware" ]; then
@ -158,8 +144,6 @@ cd ../../../
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 make -j$(nproc) kkemu
# Delete any emulator.img file
find . -name "emulator.img" -exec rm {} \;
cd ..
# 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
while True:
enum_res = process_commands(['enumerate'])
found = False
for dev in enum_res:
if dev['type'] == 'coldcard' and 'error' not in dev:
found = True
break
if found:
if len(enum_res) > 0 and 'error' not in enum_res[0]:
break
time.sleep(0.5)
# Cleanup
@ -35,7 +30,7 @@ def coldcard_test_suite(simulator, rpc, userpass, interface):
# Coldcard specific management command tests
class TestColdcardManCommands(DeviceTestCase):
def test_setup(self):
result = self.do_command(self.dev_args + ['-i', 'setup'])
result = self.do_command(self.dev_args + ['setup'])
self.assertIn('error', result)
self.assertIn('code', result)
self.assertEqual(result['error'], 'The Coldcard does not support software setup')
@ -49,7 +44,7 @@ def coldcard_test_suite(simulator, rpc, userpass, interface):
self.assertEqual(result['code'], -9)
def test_restore(self):
result = self.do_command(self.dev_args + ['-i', 'restore'])
result = self.do_command(self.dev_args + ['restore'])
self.assertIn('error', result)
self.assertIn('code', result)
self.assertEqual(result['error'], 'The Coldcard does not support restoring via software')
@ -75,19 +70,19 @@ def coldcard_test_suite(simulator, rpc, userpass, interface):
# Generic device tests
suite = unittest.TestSuite()
suite.addTest(DeviceTestCase.parameterize(TestColdcardManCommands, rpc, userpass, 'coldcard', '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(TestGetKeypool, rpc, userpass, 'coldcard', '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(TestSignMessage, rpc, userpass, 'coldcard', '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(TestColdcardManCommands, rpc, userpass, 'coldcard', '/tmp/ckcc-simulator.sock', '0f056943', '', 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', '/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', '/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
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Test Coldcard implementation')
parser.add_argument('simulator', help='Path to the Coldcard simulator')
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'], default='library')
args = parser.parse_args()
# Start bitcoind

View File

@ -3,7 +3,6 @@
import atexit
import json
import os
import shlex
import shutil
import subprocess
import tempfile
@ -52,12 +51,11 @@ def start_bitcoind(bitcoind_path):
return (rpc, userpass)
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)
self.rpc = rpc
self.rpc_userpass = rpc_userpass
self.type = type
self.full_type = full_type
self.path = path
self.fingerprint = fingerprint
self.master_xpub = master_xpub
@ -72,31 +70,19 @@ class DeviceTestCase(unittest.TestCase):
self.interface = interface
@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()
testnames = testloader.getTestCaseNames(testclass)
suite = unittest.TestSuite()
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
def do_command(self, args):
cli_args = []
for arg in args:
cli_args.append(shlex.quote(arg))
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()
return json.loads(result[0].decode())
elif self.interface == 'bindist':
proc = subprocess.Popen(['../dist/hwi ' + ' '.join(cli_args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
result = proc.communicate()
return json.loads(result[0].decode())
elif self.interface == 'stdin':
input_str = '\n'.join(args) + '\n'
proc = subprocess.Popen(['hwi', '--stdin'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
result = proc.communicate(input_str.encode())
return json.loads(result[0].decode())
else:
return process_commands(args)
@ -106,10 +92,10 @@ class DeviceTestCase(unittest.TestCase):
return []
def __str__(self):
return '{}: {}'.format(self.full_type, super().__str__())
return '{}: {}'.format(self.type, super().__str__())
def __repr__(self):
return '{}: {}'.format(self.full_type, super().__repr__())
return '{}: {}'.format(self.type, super().__repr__())
class TestDeviceConnect(DeviceTestCase):
def setUp(self):
@ -159,9 +145,9 @@ class TestDeviceConnect(DeviceTestCase):
class TestGetKeypool(DeviceTestCase):
def setUp(self):
self.rpc = AuthServiceProxy('http://{}@127.0.0.1:18443'.format(self.rpc_userpass))
if '{}_test'.format(self.full_type) not in self.rpc.listwallets():
self.rpc.createwallet('{}_test'.format(self.full_type), True)
self.wrpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/{}_test'.format(self.rpc_userpass, self.full_type))
if '{}_test'.format(self.type) not in self.rpc.listwallets():
self.rpc.createwallet('{}_test'.format(self.type), True)
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))
if '--testnet' not in self.dev_args:
self.dev_args.append('--testnet')
@ -266,9 +252,9 @@ class TestGetKeypool(DeviceTestCase):
class TestSignTx(DeviceTestCase):
def setUp(self):
self.rpc = AuthServiceProxy('http://{}@127.0.0.1:18443'.format(self.rpc_userpass))
if '{}_test'.format(self.full_type) not in self.rpc.listwallets():
self.rpc.createwallet('{}_test'.format(self.full_type), True)
self.wrpc = AuthServiceProxy('http://{}@127.0.0.1:18443/wallet/{}_test'.format(self.rpc_userpass, self.full_type))
if '{}_test'.format(self.type) not in self.rpc.listwallets():
self.rpc.createwallet('{}_test'.format(self.type), True)
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))
if '--testnet' not in self.dev_args:
self.dev_args.append('--testnet')
@ -410,13 +396,13 @@ class TestSignTx(DeviceTestCase):
# Test wrapper to avoid mixed-inputs signing for Ledger
def test_signtx(self):
supports_mixed = {'coldcard', 'trezor_1', 'digitalbitbox', 'keepkey'}
supports_multisig = {'ledger', 'trezor_1', 'digitalbitbox', 'keepkey'}
if self.full_type not in supports_mixed:
self._test_signtx("legacy", self.full_type in supports_multisig)
self._test_signtx("segwit", self.full_type in supports_multisig)
supports_mixed = {'coldcard', 'trezor', 'digitalbitbox', 'keepkey'}
supports_multisig = {'ledger', 'trezor', 'digitalbitbox', 'keepkey'}
if self.type not in supports_mixed:
self._test_signtx("legacy", self.type in supports_multisig)
self._test_signtx("segwit", self.type in supports_multisig)
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
def test_big_tx(self):
@ -461,68 +447,45 @@ class TestDisplayAddress(DeviceTestCase):
self.assertEqual(result['code'], -7)
def test_display_address_path(self):
result = self.do_command(self.dev_args + ['displayaddress', '--path', 'm/44h/1h/0h/0/0'])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
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)
self.do_command(self.dev_args + ['displayaddress', '--path', 'm/44h/1h/0h/0/0'])
self.do_command(self.dev_args + ['displayaddress', '--sh_wpkh', '--path', 'm/49h/1h/0h/0/0'])
self.do_command(self.dev_args + ['displayaddress', '--wpkh', '--path', 'm/84h/1h/0h/0/0'])
def test_display_address_bad_path(self):
result = self.do_command(self.dev_args + ['displayaddress', '--path', 'f'])
self.assertEquals(result['code'], -7)
def test_display_address_descriptor(self):
account_xpub = self.do_command(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']
legacy_account_xpub = self.do_command(self.dev_args + ['getxpub', 'm/44h/1h/0h'])['xpub']
account_xpub = process_commands(self.dev_args + ['getxpub', 'm/84h/1h/0h'])['xpub']
p2sh_segwit_account_xpub = process_commands(self.dev_args + ['getxpub', 'm/49h/1h/0h'])['xpub']
legacy_account_xpub = process_commands(self.dev_args + ['getxpub', 'm/44h/1h/0h'])['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)'])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
process_commands(self.dev_args + ['displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h)]' + account_xpub + '/0/0)'])
# 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)'])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
process_commands(self.dev_args + ['displayaddress', '--desc', 'wpkh([' + self.fingerprint + '/84h/1h/0h)]' + xpub_to_pub_hex(account_xpub) + '/0/0)'])
# 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))'])
self.assertNotIn('error', result)
self.assertNotIn('code', result)
self.assertIn('address', result)
process_commands(self.dev_args + ['displayaddress', '--desc', 'sh(wpkh([' + self.fingerprint + '/49h/1h/0h)]' + p2sh_segwit_account_xpub + '/0/0))'])
# Legacy address
result = self.do_command(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)
process_commands(self.dev_args + ['displayaddress', '--desc', 'pkh([' + self.fingerprint + '/44h/1h/0h)]' + legacy_account_xpub + '/0/0)'])
# 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('code', result)
self.assertEqual(result['code'], -7)
# 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('code', result)
self.assertEqual(result['code'], -7)
# 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('code', result)
self.assertEqual(result['code'], -7)

View File

@ -37,7 +37,6 @@ def digitalbitbox_test_suite(rpc, userpass, simulator, interface):
# params
type = 'digitalbitbox'
full_type = 'digitalbitbox'
path = 'udp:127.0.0.1:35345'
fingerprint = 'a31b978a'
master_xpub = 'xpub6BsWJiRvbzQJg3J6tgUKmHWYbHJSj41EjAAje6LuDwnYLqLiNSWK4N7rCXwiUmNJTBrKL8AEH3LBzhJdgdxoy4T9aMPLCWAa6eWKGCFjQhq'
@ -45,7 +44,7 @@ def digitalbitbox_test_suite(rpc, userpass, simulator, interface):
# DigitalBitbox specific management command tests
class TestDBBManCommands(DeviceTestCase):
def test_restore(self):
result = self.do_command(self.dev_args + ['-i', 'restore'])
result = self.do_command(self.dev_args + ['restore'])
self.assertIn('error', result)
self.assertIn('code', result)
self.assertEqual(result['error'], 'The Digital Bitbox does not support restoring via software')
@ -73,7 +72,7 @@ def digitalbitbox_test_suite(rpc, userpass, simulator, interface):
def test_setup_wipe(self):
# Device is init, setup should fail
result = self.do_command(self.dev_args + ['-i', 'setup', '--label', 'setup_test', '--backup_passphrase', 'testpass'])
result = self.do_command(self.dev_args + ['setup', '--label', 'setup_test', '--backup_passphrase', 'testpass'])
self.assertEquals(result['code'], -10)
self.assertEquals(result['error'], 'Device is already initialized. Use wipe first and try again')
@ -82,25 +81,25 @@ def digitalbitbox_test_suite(rpc, userpass, simulator, interface):
self.assertTrue(result['success'])
# Check arguments
result = self.do_command(self.dev_args + ['-i', 'setup', '--label', 'setup_test'])
result = self.do_command(self.dev_args + ['setup', '--label', 'setup_test'])
self.assertEquals(result['code'], -7)
self.assertEquals(result['error'], 'The label and backup passphrase for a new Digital Bitbox wallet must be specified and cannot be empty')
result = self.do_command(self.dev_args + ['-i', 'setup', '--backup_passphrase', 'testpass'])
result = self.do_command(self.dev_args + ['setup', '--backup_passphrase', 'testpass'])
self.assertEquals(result['code'], -7)
self.assertEquals(result['error'], 'The label and backup passphrase for a new Digital Bitbox wallet must be specified and cannot be empty')
# Setup
result = self.do_command(self.dev_args + ['-i', 'setup', '--label', 'setup_test', '--backup_passphrase', 'testpass'])
result = self.do_command(self.dev_args + ['setup', '--label', 'setup_test', '--backup_passphrase', 'testpass'])
self.assertTrue(result['success'])
# Reset back to original
result = self.do_command(self.dev_args + ['wipe'])
result = process_commands(self.dev_args + ['wipe'])
self.assertTrue(result['success'])
send_plain(b'{"password":"0000"}', dev)
send_encrypt(json.dumps({"seed":{"source":"backup","filename":"test_backup.pdf","key":"key"}}), '0000', dev)
# Make sure device is init, setup should fail
result = self.do_command(self.dev_args + ['-i', 'setup', '--label', 'setup_test', '--backup_passphrase', 'testpass'])
result = self.do_command(self.dev_args + ['setup', '--label', 'setup_test', '--backup_passphrase', 'testpass'])
self.assertEquals(result['code'], -10)
self.assertEquals(result['error'], 'Device is already initialized. Use wipe first and try again')
@ -118,7 +117,7 @@ def digitalbitbox_test_suite(rpc, userpass, simulator, interface):
self.assertTrue(result['success'])
# Setup
result = self.do_command(self.dev_args + ['-i', 'setup', '--label', 'backup_test', '--backup_passphrase', 'testpass'])
result = self.do_command(self.dev_args + ['setup', '--label', 'backup_test', '--backup_passphrase', 'testpass'])
self.assertTrue(result['success'])
# make the backup
@ -127,18 +126,18 @@ def digitalbitbox_test_suite(rpc, userpass, simulator, interface):
# Generic Device tests
suite = unittest.TestSuite()
suite.addTest(DeviceTestCase.parameterize(TestDBBManCommands, rpc, userpass, type, full_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(TestGetKeypool, rpc, userpass, type, full_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(TestSignMessage, 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, 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, path, fingerprint, master_xpub, '0000', interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, type, path, fingerprint, master_xpub, '0000', interface=interface))
return suite
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Test Digital Bitbox implementation')
parser.add_argument('simulator', help='Path to simulator 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'], default='library')
args = parser.parse_args()
# Start bitcoind

View File

@ -4,7 +4,6 @@ import argparse
import atexit
import json
import os
import shlex
import socket
import subprocess
import sys
@ -83,22 +82,10 @@ class KeepkeyTestCase(unittest.TestCase):
return suite
def do_command(self, args):
cli_args = []
for arg in args:
cli_args.append(shlex.quote(arg))
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()
return json.loads(result[0].decode())
elif self.interface == 'bindist':
proc = subprocess.Popen(['../dist/hwi ' + ' '.join(cli_args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
result = proc.communicate()
return json.loads(result[0].decode())
elif self.interface == 'stdin':
input_str = '\n'.join(args) + '\n'
proc = subprocess.Popen(['hwi', '--stdin'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
result = proc.communicate(input_str.encode())
return json.loads(result[0].decode())
else:
return process_commands(args)
@ -145,7 +132,7 @@ class TestKeepkeyManCommands(KeepkeyTestCase):
def test_setup_wipe(self):
# Device is init, setup should fail
result = self.do_command(self.dev_args + ['-i', 'setup'])
result = self.do_command(self.dev_args + ['setup'])
self.assertEquals(result['code'], -10)
self.assertEquals(result['error'], 'Device is already initialized. Use wipe first and try again')
@ -161,7 +148,7 @@ class TestKeepkeyManCommands(KeepkeyTestCase):
self.assertTrue(result['success'])
# Make sure device is init, setup should fail
result = self.do_command(self.dev_args + ['-i', 'setup'])
result = self.do_command(self.dev_args + ['setup'])
self.assertEquals(result['code'], -10)
self.assertEquals(result['error'], 'Device is already initialized. Use wipe first and try again')
@ -180,19 +167,11 @@ class TestKeepkeyManCommands(KeepkeyTestCase):
result = self.do_command(self.dev_args + ['sendpin', '1234'])
self.assertEqual(result['error'], 'This device does not need a PIN')
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
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')
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'])
self.assertTrue(result['success'])
@ -220,11 +199,6 @@ class TestKeepkeyManCommands(KeepkeyTestCase):
result = self.do_command(self.dev_args + ['sendpin', pin])
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
result = self.do_command(self.dev_args + ['promptpin'])
self.assertEqual(result['error'], 'The PIN has already been sent to this device')
@ -233,58 +207,12 @@ class TestKeepkeyManCommands(KeepkeyTestCase):
self.assertEqual(result['error'], 'The PIN has already been sent to this device')
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):
# Redirect stderr to /dev/null as it's super spammy
sys.stderr = open(os.devnull, 'w')
# Device info for tests
type = 'keepkey'
full_type = 'keepkey'
path = 'udp:127.0.0.1:21324'
fingerprint = '95d8f670'
master_xpub = 'xpub6D1weXBcFAo8CqBbpP4TbH5sxQH8ZkqC5pDEvJ95rNNBZC9zrKmZP2fXMuve7ZRBe18pWQQsGg68jkq24mZchHwYENd8cCiSb71u3KD4AFH'
@ -292,11 +220,11 @@ def keepkey_test_suite(emulator, rpc, userpass, interface):
# Generic Device tests
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(TestGetKeypool, rpc, userpass, type, full_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(TestDisplayAddress, rpc, userpass, type, full_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(TestDeviceConnect, rpc, userpass, 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, 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, path, fingerprint, master_xpub, 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))
return suite
@ -305,7 +233,7 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Test Keepkey implementation')
parser.add_argument('emulator', help='Path to the Keepkey emulator')
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'], default='library')
args = parser.parse_args()
# Start bitcoind

View File

@ -44,7 +44,7 @@ def ledger_test_suite(rpc, userpass, interface):
self.assertEqual(result['code'], -9)
def test_setup(self):
result = self.do_command(self.dev_args + ['-i', 'setup'])
result = self.do_command(self.dev_args + ['setup'])
self.assertIn('error', result)
self.assertIn('code', result)
self.assertEqual(result['error'], 'The Ledger Nano S does not support software setup')
@ -58,7 +58,7 @@ def ledger_test_suite(rpc, userpass, interface):
self.assertEqual(result['code'], -9)
def test_restore(self):
result = self.do_command(self.dev_args + ['-i', 'restore'])
result = self.do_command(self.dev_args + ['restore'])
self.assertIn('error', result)
self.assertIn('code', result)
self.assertEqual(result['error'], 'The Ledger Nano S does not support restoring via software')
@ -73,18 +73,18 @@ def ledger_test_suite(rpc, userpass, interface):
# Generic Device tests
suite = unittest.TestSuite()
suite.addTest(DeviceTestCase.parameterize(TestLedgerDisabledCommands, rpc, userpass, 'ledger', '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(TestGetKeypool, rpc, userpass, 'ledger', '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(TestDisplayAddress, rpc, userpass, 'ledger', '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(TestLedgerDisabledCommands, rpc, userpass, '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', 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', path, fingerprint, master_xpub, interface=interface))
suite.addTest(DeviceTestCase.parameterize(TestSignMessage, rpc, userpass, 'ledger', path, fingerprint, master_xpub, interface=interface))
return suite
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Test Ledger implementation')
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'], default='library')
args = parser.parse_args()
# Start bitcoind

View File

@ -4,8 +4,6 @@ import argparse
import atexit
import json
import os
import shlex
import signal
import socket
import subprocess
import sys
@ -37,7 +35,7 @@ class TrezorEmulator(DeviceEmulator):
def start(self):
# 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
# From https://github.com/trezor/trezor-mcu/blob/master/script/wait_for_emulator.py
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@ -65,8 +63,8 @@ class TrezorEmulator(DeviceEmulator):
return client
def stop(self):
os.killpg(os.getpgid(self.emulator_proc.pid), signal.SIGINT)
os.waitpid(self.emulator_proc.pid, 0)
self.emulator_proc.kill()
self.emulator_proc.wait()
class TrezorTestCase(unittest.TestCase):
def __init__(self, emulator, interface='library', methodName='runTest'):
@ -84,30 +82,18 @@ class TrezorTestCase(unittest.TestCase):
return suite
def do_command(self, args):
cli_args = []
for arg in args:
cli_args.append(shlex.quote(arg))
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()
return json.loads(result[0].decode())
elif self.interface == 'bindist':
proc = subprocess.Popen(['../dist/hwi ' + ' '.join(cli_args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
result = proc.communicate()
return json.loads(result[0].decode())
elif self.interface == 'stdin':
input_str = '\n'.join(args) + '\n'
proc = subprocess.Popen(['hwi', '--stdin'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
result = proc.communicate(input_str.encode())
return json.loads(result[0].decode())
else:
return process_commands(args)
def __str__(self):
return 'trezor 1: {}'.format(super().__str__())
return 'trezor: {}'.format(super().__str__())
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
class TestTrezorGetxpub(TrezorTestCase):
@ -146,7 +132,7 @@ class TestTrezorManCommands(TrezorTestCase):
def test_setup_wipe(self):
# Device is init, setup should fail
result = self.do_command(self.dev_args + ['-i', 'setup'])
result = self.do_command(self.dev_args + ['setup'])
self.assertEquals(result['code'], -10)
self.assertEquals(result['error'], 'Device is already initialized. Use wipe first and try again')
@ -162,7 +148,7 @@ class TestTrezorManCommands(TrezorTestCase):
self.assertTrue(result['success'])
# Make sure device is init, setup should fail
result = self.do_command(self.dev_args + ['-i', 'setup'])
result = self.do_command(self.dev_args + ['setup'])
self.assertEquals(result['code'], -10)
self.assertEquals(result['error'], 'Device is already initialized. Use wipe first and try again')
@ -181,19 +167,11 @@ class TestTrezorManCommands(TrezorTestCase):
result = self.do_command(self.dev_args + ['sendpin', '1234'])
self.assertEqual(result['error'], 'This device does not need a PIN')
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
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')
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'])
self.assertTrue(result['success'])
@ -221,11 +199,6 @@ class TestTrezorManCommands(TrezorTestCase):
result = self.do_command(self.dev_args + ['sendpin', pin])
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
result = self.do_command(self.dev_args + ['promptpin'])
self.assertEqual(result['error'], 'The PIN has already been sent to this device')
@ -234,51 +207,7 @@ class TestTrezorManCommands(TrezorTestCase):
self.assertEqual(result['error'], 'The PIN has already been sent to this device')
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'] == '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):
def trezor_test_suite(emulator, rpc, userpass, interface):
# Redirect stderr to /dev/null as it's super spammy
sys.stderr = open(os.devnull, 'w')
@ -289,33 +218,26 @@ def trezor_test_suite(emulator, rpc, userpass, interface, model_t=False):
master_xpub = 'xpub6D1weXBcFAo8CqBbpP4TbH5sxQH8ZkqC5pDEvJ95rNNBZC9zrKmZP2fXMuve7ZRBe18pWQQsGg68jkq24mZchHwYENd8cCiSb71u3KD4AFH'
dev_emulator = TrezorEmulator(emulator)
if model_t:
full_type = 'trezor_t'
else:
full_type = 'trezor_1'
# Generic Device tests
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(TestGetKeypool, rpc, userpass, type, full_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(TestDisplayAddress, rpc, userpass, type, full_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))
if not model_t:
suite.addTest(TrezorTestCase.parameterize(TestTrezorGetxpub, emulator=dev_emulator, interface=interface))
suite.addTest(TrezorTestCase.parameterize(TestTrezorManCommands, 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, 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, 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(TrezorTestCase.parameterize(TestTrezorGetxpub, emulator=dev_emulator, interface=interface))
suite.addTest(TrezorTestCase.parameterize(TestTrezorManCommands, emulator=dev_emulator, interface=interface))
return suite
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Test Trezor implementation')
parser.add_argument('emulator', help='Path to the Trezor emulator')
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('--model_t', help='The emulator is for the Trezor T', action='store_true')
parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli'], default='library')
args = parser.parse_args()
# Start 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)

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()

View File

@ -1,9 +0,0 @@
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"

View File

@ -1,8 +0,0 @@
# probably not needed:
SUBSYSTEMS=="usb", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plugdev", MODE="0666"
# required:
# from <https://github.com/signal11/hidapi/blob/master/udev/99-hid.rules>
KERNEL=="hidraw*", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plugdev", MODE="0666"

View File

@ -1 +0,0 @@
SUBSYSTEM=="usb", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="dbb%n", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2402"

View File

@ -1,17 +0,0 @@
# TREZOR: The Original Hardware Wallet
# https://trezor.io/
#
# Put this file into /etc/udev/rules.d
#
# If you are creating a distribution package,
# put this into /usr/lib/udev/rules.d or /lib/udev/rules.d
# depending on your distribution
# TREZOR
SUBSYSTEM=="usb", ATTR{idVendor}=="534c", ATTR{idProduct}=="0001", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
# TREZOR v2
SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c0", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c1", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"

View File

@ -1,11 +0,0 @@
# KeepKey: Your Private Bitcoin Vault
# http://www.keepkey.com/
# Put this file into /usr/lib/udev/rules.d or /etc/udev/rules.d
# KeepKey HID Firmware/Bootloader
SUBSYSTEM=="usb", ATTR{idVendor}=="2b24", ATTR{idProduct}=="0001", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="keepkey%n"
KERNEL=="hidraw*", ATTRS{idVendor}=="2b24", ATTRS{idProduct}=="0001", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
# KeepKey WebUSB Firmware/Bootloader
SUBSYSTEM=="usb", ATTR{idVendor}=="2b24", ATTR{idProduct}=="0002", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="keepkey%n"
KERNEL=="hidraw*", ATTRS{idVendor}=="2b24", ATTRS{idProduct}=="0002", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"

View File

@ -1 +0,0 @@
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2402", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="dbbf%n"

View File

@ -1,23 +0,0 @@
# udev rules
This directory contains all of the udev rules for the supported devices as retrieved from vendor websites and repositories.
These are necessary for the devices to be reachable on linux environments.
`20-hw1.rules` (Ledger): https://github.com/LedgerHQ/udev-rules/blob/master/20-hw1.rules
`51-coinkite.rules` (Coldcard): https://github.com/Coldcard/ckcc-protocol/blob/master/51-coinkite.rules
`51-hid-digitalbitbox.rules`, `52-hid-digitalbitbox.rules` (Digital Bitbox): https://shiftcrypto.ch/start_linux
`51-trezor.rules` (Trezor): https://github.com/trezor/trezor-common/blob/master/udev/51-trezor.rules
`51-usb-keepkey.rules` (Keepkey): https://github.com/keepkey/udev-rules/blob/master/51-usb-keepkey.rules
# Usage
Apply these rules by copying them to `/etc/udev/rules.d/` and notifying `udevadm`.
Your user will need to be added to the `plugdev` group, which needs to be created if it does not already exist.
```
$ sudo cp udev/*.rules /etc/udev/rules.d/
$ sudo udevadm trigger
$ sudo udevadm control --reload-rules
$ sudo groupadd plugdev
$ sudo usermod -aG plugdev `whoami`
```