1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-21 23:18:13 +00:00

MONOREPO MERGE python-trezor

This commit is contained in:
matejcik 2019-04-15 19:15:12 +02:00
commit 37fe33fb4d
235 changed files with 26077 additions and 0 deletions

3
.gitmodules vendored
View File

@ -45,3 +45,6 @@
path = legacy/vendor/QR-Code-generator path = legacy/vendor/QR-Code-generator
url = https://github.com/nayuki/QR-Code-generator.git url = https://github.com/nayuki/QR-Code-generator.git
ignore = untracked ignore = untracked
[submodule "python/vendor/trezor-common"]
path = python/vendor/trezor-common
url = https://github.com/trezor/trezor-common.git

20
python/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
.project
.pydevproject
MANIFEST
/build
/dist
/trezor.egg-info
*.pyc
__pycache__
*.bin
*.py.cache
/.tox
/.cache
/.pytest_cache
/.mypy_cache
/.idea
/.vscode
/trezorlib/coins.json
/trezorlib/messages/*

57
python/.travis.yml Normal file
View File

@ -0,0 +1,57 @@
language: python
# Runs jobs on container based infrastructure
sudo: false
# Saves pip downloads/wheels between builds
cache:
directories:
- $HOME/.cache/pip
addons:
apt:
packages:
- libudev-dev
- libusb-1.0-0-dev
env:
global:
PROTOBUF_VERSION=3.4.0
python:
- "3.5"
- "3.6"
# workaround for https://github.com/travis-ci/travis-ci/issues/9815
matrix:
include:
- python: 3.7
dist: xenial
sudo: true
install:
# Optimisation: build requirements as wheels, which get cached by Travis
- pip install "pip>=9.0" wheel # pip 9.0 understands `python_requires` constraints
- pip install "setuptools>=38" # setuptools >= 38 are capable of using prebuilt wheels
- pip install tox-travis
- pip install -r requirements-dev.txt
# protobuf-related dependencies
- curl -LO "https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip"
- unzip "protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" -d protoc
- export PATH="$(pwd)/protoc/bin:$PATH"
before_script:
- ./trigger-travis.sh
script:
- python setup.py install
- if [ $TRAVIS_PYTHON_VERSION != 3.5 ]; then make style_check; fi
- tox
notifications:
webhooks:
urls:
- http://ci-bot.satoshilabs.com:5000/travis
on_success: always
on_failure: always
on_start: always

19
python/AUTHORS Normal file
View File

@ -0,0 +1,19 @@
python-trezor is free software, created in 2012 and maintained by SatoshiLabs
as part of the Trezor project.
Over the years, many people have contributed to the project. Here is an incomplete
list of credits:
alepop <https://github.com/alepop>
Jan 'matejcik' Matějek <jan.matejek@satoshilabs.com>
Jan Pochyla <jan.pochyla@satoshilabs.com>
Jochen Hoenicke <hoenicke@gmail.com>
Karel Bílek <karel.bilek@satoshilabs.com>
Marek Palatinus <slush@satoshilabs.com>
mruddy <https://github.com/mruddy>
Pavol Rusnak <stick@satoshilabs.com>
Peter van Mourik <https://github.com/tyrion70>
Roman Zeyde <https://github.com/romanz>
Saleem Rashid <trezor@saleemrashid.com>
Tomáš Sušánka <tomas.susanka@satoshilabs.com>
ZuluCrypto <https://github.com/zulucrypto>

267
python/CHANGELOG.md Normal file
View File

@ -0,0 +1,267 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
_At the moment, the project does **not** adhere to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). That is expected to change with version 1.0._
## [0.11.3] - Unreleased
[0.11.3]: https://github.com/trezor/python-trezor/compare/v0.11.2...master
### Added
- trezorctl can now send ERC20 tokens
- trezorctl usb-reset will perform USB reset on devices in inconsistent state
- set-display-rotation command added for TT firmware 2.1.1
### Changed
- Minimum firmware versions bumped to 1.8.0 and 2.1.0
### Fixed
- Ethereum commands in trezorctl now work
## [0.11.2] - 2019-02-27
[0.11.2]: https://github.com/trezor/python-trezor/compare/v0.11.1...v0.11.2
### Added
- full support for bootloader 1.8.0 and relevant firmware upgrade functionality
- trezorctl: support fully offline signing JSON-encoded transaction data
- trezorctl: dry-run for firmware upgrade command
- client: new convenience function `get_default_client` for simple script usage
- Dash: support DIP-2 special inputs [#351]
- Ethereum: add get_public_key methods
### Changed
- coins with BIP-143 fork id (BCH, BTG) won't require prev_tx [#352]
- device recovery will restore U2F counter
- Cardano: change `network` to `protocol_magic`
- tests can run interactively when `INTERACT=1` environment variable is set
- protobuf: improved `to_dict` function
### Deprecated
- trezorctl: interactive signing with `sign-tx` is considered deprecated
## [0.11.1] - 2018-12-28
[0.11.1]: https://github.com/trezor/python-trezor/compare/v0.11.0...v0.11.1
### Fixed
- crash when entering passphrase on device with Trezor T
- Qt widgets should only import QtCore [#349]
## [0.11.0] - 2018-12-06
[0.11.0]: https://github.com/trezor/python-trezor/compare/v0.10.2...v0.11.0
### Incompatible changes
- removed support for Python 3.3 and 3.4
- major refactor of `TrezorClient` and UI handling. Implementers must now provide a "UI" object instead of overriding callbacks [#307], [#314]
- protobuf classes now use a `get_fields()` method instead of `FIELDS` field [#312]
- all methods on `TrezorClient` class are now in separate modules and take a `TrezorClient` instance as argument [#276]
- mixin classes are also removed, you are not supposed to extend `TrezorClient` anymore
- `TrezorClientDebugLink` was moved to `debuglink` module
- changed signature of `trezorlib.btc.sign_tx`
- `@field` decorator was replaced by an argument to `@expect`
### Added
- trezorlib now has a hardcoded check preventing use of outdated firmware versions [#283]
- Ripple support [#286]
- Zencash support [#287]
- Cardano support [#300]
- Ontology support [#301]
- Tezos support [#302]
- Capricoin support [#325]
- limited Monero support (can only get address/watch key, monerowallet is required for signing)
- support for input flow in tests makes it easier to control complex UI workflows [#314]
- `protobuf.dict_to_proto` can create a protobuf instance from a plain dict
- support for smarter methods in trezord 2.0.25 and up
- support for seedless setup
- trezorctl: firmware handling is greatly improved [#304], [#308]
- trezorctl: Bitcoin-like signing flow is more user-friendly
- `tx_api` now supports Blockbook backend servers
### Changed
- better reporting for debuglink expected messages
- replaced Ed25519 module with a cleaner, optimized version
- further reorganization of transports makes them more robust when dependencies are missing
- codebase now follows [Black](https://github.com/ambv/black) code style
- in Qt modules, Qt5 is imported first [#315]
- `TxApiInsight` is just `TxApi`
- `device.reset` and `device.recover` now have reasonable defaults for all arguments
- protobuf classes are no longer part of the source distribution and must be compiled locally [#284]
- Stellar: addresses are always strings
### Removed
- `set_tx_api` method on `TrezorClient` is replaced by an argument for `sign_tx`
- caching functionality of `TxApi` was moved to a separate test-support class
- Stellar: public key methods removed
- `EncryptMessage` and `DecryptMessage` actions are gone
### Fixed:
- `TrezorClient` can now detect when a HID device is removed and a different one is plugged in on the same path
- trezorctl now works with Click 7.0 and considers "`_`" and "`-`" as same in command names [#314]
- bash completion fixed
- Stellar: several bugs in the XDR parser were fixed
## [0.10.2] - 2018-06-21
[0.10.2]: https://github.com/trezor/python-trezor/compare/v0.10.1...v0.10.2
### Added
- `stellar_get_address` and `_public_key` functions support `show_display` parameter
- trezorctl: `stellar_get_address` and `_public_key` commands for the respective functionality
### Removed
- trezorctl: `list_coins` is removed because we no longer parse the relevant protobuf field
(and newer Trezor firmwares don't send it) [#277]
### Fixed
- test support module was not included in the release, so code relying on the deprecated `ckd_public` module would fail [#280]
## [0.10.1] - 2018-06-11
[0.10.1]: https://github.com/trezor/python-trezor/compare/v0.10.0...v0.10.1
### Fixed
- previous release fails to build on Windows [#274]
## [0.10.0] - 2018-06-08
[0.10.0]: https://github.com/trezor/python-trezor/compare/v0.9.1...v0.10.0
### Added
- Lisk support [#197]
- Stellar support [#167], [#268]
- Wanchain support [#230]
- support for "auto lock delay" feature
- `TrezorClient` takes an additional argument `state` that allows reusing the previously entered passphrase [#241]
- USB transports mention udev rules in exception messages [#245]
- `log.enable_debug_output` function turns on wire logging, instead of having to use `TrezorClientVerbose`
- BIP32 paths now support `123h` in addition to `123'` to indicate hardening
- trezorctl: `-p` now supports prefix search for device path [#226]
- trezorctl: smarter handling of firmware updates [#242], [#269]
### Changed
- reorganized transports and moved into their own `transport` submodule
- protobuf messages and coins info is now regenerated at build time from the `trezor-common` repository [#248]
- renamed `ed25519raw` to `_ed25519` to indicate its privateness
- renamed `ed25519cosi` to `cosi` and expanded its API
- protobuf messages are now logged through Python's `logging` facility instead of custom printing through `VerboseWireMixin`
- `client.format_protobuf` is moved to `protobuf.format_message`
- `tools.Hash` is renamed to `tools.btc_hash`
- `coins` module `coins_txapi` is renamed to `tx_api`.
`coins_slip44` is renamed to `slip44`.
- build: stricter flake8 checks
- build: split requirements to separate files
- tests: unified finding test device, while respecting `TREZOR_PATH` env variable.
- tests: auto-skip appropriately marked tests based on Trezor device version
- tests: only show wire output when run with `-v`
- tests: allow running `xfail`ed tests selectively based on `pytest.ini`
- docs: updated README with clearer install instructions [#185]
- docs: switched changelog to Keep a Changelog format [#94]
### Deprecated
- `ckd_public` is only maintained in `tests.support` submodule and considered private
- `TrezorClient.expand_path` is moved to plain function `tools.parse_path`
- `TrezorDevice` is deprecated in favor of `transport.enumerate_devices` and `transport.get_transport`
- XPUB-related handling in `tools` is slated for removal
### Removed
- most Python 2 compatibility constructs are gone [#229]
- `TrezorClientVerbose` and `VerboseWireMixin` is removed
- specific `tx_api.TxApi*` classes removed in favor of `coins.tx_api`
- `client.PRIME_DERIVATION_FLAG` is removed in favor of `tools.HARDENED_FLAG` and `tools.H_()`
- hard dependency on Ethereum libraries and HIDAPI is changed into extras that need to be
specified explicitly. Require `trezor[hidapi]` or `trezor[ethereum]` to get them.
### Fixed
- WebUSB enumeration returning bad devices on Windows 10 [#223]
- `sign_tx` operation sending empty address string [#237]
- Wrongly formatted Ethereum signatures [#236]
- protobuf layer would wrongly encode signed integers [#249], [#250]
- protobuf pretty-printing broken on Python 3.4 [#256]
- trezorctl: Matrix recovery on Windows wouldn't allow backspace [#207]
- aes_encfs_getpass.py: fixed Python 3 bug [#169]
## [0.9.1] - 2018-03-05
[0.9.1]: https://github.com/trezor/python-trezor/compare/v0.9.0...v0.9.1
### Added
- proper support for Trezor model T
- support for Monacoin
- improvements to `trezorctl`:
- add pretty-printing of features and protobuf debug dumps (fixes [#199])
- support `TREZOR_PATH` environment variable to preselect a Trezor device.
### Removed
- gradually dropping Python 2 compatibility (pypi package will now be marked as Python 3 only)
[#94]: https://github.com/trezor/python-trezor/issues/94
[#167]: https://github.com/trezor/python-trezor/issues/167
[#169]: https://github.com/trezor/python-trezor/issues/169
[#185]: https://github.com/trezor/python-trezor/issues/185
[#197]: https://github.com/trezor/python-trezor/issues/197
[#199]: https://github.com/trezor/python-trezor/issues/199
[#207]: https://github.com/trezor/python-trezor/issues/207
[#223]: https://github.com/trezor/python-trezor/issues/223
[#226]: https://github.com/trezor/python-trezor/issues/226
[#229]: https://github.com/trezor/python-trezor/issues/229
[#230]: https://github.com/trezor/python-trezor/issues/230
[#236]: https://github.com/trezor/python-trezor/issues/236
[#237]: https://github.com/trezor/python-trezor/issues/237
[#241]: https://github.com/trezor/python-trezor/issues/241
[#242]: https://github.com/trezor/python-trezor/issues/242
[#245]: https://github.com/trezor/python-trezor/issues/245
[#248]: https://github.com/trezor/python-trezor/issues/248
[#249]: https://github.com/trezor/python-trezor/issues/249
[#250]: https://github.com/trezor/python-trezor/issues/250
[#256]: https://github.com/trezor/python-trezor/issues/256
[#268]: https://github.com/trezor/python-trezor/issues/268
[#269]: https://github.com/trezor/python-trezor/issues/269
[#274]: https://github.com/trezor/python-trezor/issues/274
[#276]: https://github.com/trezor/python-trezor/issues/276
[#277]: https://github.com/trezor/python-trezor/issues/277
[#280]: https://github.com/trezor/python-trezor/issues/280
[#283]: https://github.com/trezor/python-trezor/issues/283
[#284]: https://github.com/trezor/python-trezor/issues/284
[#286]: https://github.com/trezor/python-trezor/issues/286
[#287]: https://github.com/trezor/python-trezor/issues/287
[#300]: https://github.com/trezor/python-trezor/issues/300
[#301]: https://github.com/trezor/python-trezor/issues/301
[#302]: https://github.com/trezor/python-trezor/issues/302
[#304]: https://github.com/trezor/python-trezor/issues/304
[#307]: https://github.com/trezor/python-trezor/issues/307
[#308]: https://github.com/trezor/python-trezor/issues/308
[#312]: https://github.com/trezor/python-trezor/issues/312
[#314]: https://github.com/trezor/python-trezor/issues/314
[#315]: https://github.com/trezor/python-trezor/issues/315
[#325]: https://github.com/trezor/python-trezor/issues/325
[#349]: https://github.com/trezor/python-trezor/issues/349
[#351]: https://github.com/trezor/python-trezor/issues/351
[#352]: https://github.com/trezor/python-trezor/issues/352

165
python/COPYING Normal file
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

9
python/MANIFEST.in Normal file
View File

@ -0,0 +1,9 @@
recursive-include bash_completion.d *.sh
include tools/*
recursive-include trezorlib *
recursive-include vendor/trezor-common *
exclude vendor/trezor-common/.*
include AUTHORS README.md COPYING CHANGELOG.md
include requirements*.txt

58
python/Makefile Normal file
View File

@ -0,0 +1,58 @@
PYTHON=python3
SETUP=$(PYTHON) setup.py
EXCLUDES=.vscode
STYLE_TARGETS=trezorlib trezorctl setup.py
EXCLUDE_TARGETS=trezorlib/messages
all: build
build:
$(SETUP) build
install:
$(SETUP) install
dist: clean
$(SETUP) sdist
$(SETUP) bdist_wheel
clean: clean-generated clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
clean-generated: ## remove generated files
rm -f trezorlib/messages/*.py
rm -f trezorlib/coins.json
clean-build: ## remove build artifacts
rm -fr build/
rm -fr dist/
rm -fr .eggs/
find . -name '*.egg-info' -exec rm -fr {} +
find . -name '*.egg' -exec rm -f {} +
clean-pyc: ## remove Python file artifacts
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
find . -name '__pycache__' -exec rm -fr {} +
clean-test: ## remove test and coverage artifacts
rm -fr .tox/
rm -f .coverage
rm -fr htmlcov/
rm -fr .pytest_cache
git-clean:
git clean -dfx -e $(EXCLUDES)
style:
black $(STYLE_TARGETS)
isort --apply --recursive $(STYLE_TARGETS) --skip-glob "*/$(EXCLUDE_TARGETS)/*"
autoflake -i --remove-all-unused-imports -r $(STYLE_TARGETS) --exclude "$(EXCLUDE_TARGETS)"
style_check:
black --check $(STYLE_TARGETS)
isort --diff --check-only --recursive $(STYLE_TARGETS) --skip-glob "*/$(EXCLUDE_TARGETS)/*"
flake8
.PHONY: all build install clean style style_check git-clean clean-generated clean-build clean-pyc clean-test

172
python/README.md Normal file
View File

@ -0,0 +1,172 @@
# python-trezor
[![image](https://travis-ci.org/trezor/python-trezor.svg?branch=master)](https://travis-ci.org/trezor/python-trezor) [![repology](https://repology.org/badge/tiny-repos/python:trezor.svg)](https://repology.org/metapackage/python:trezor) [![image](https://badges.gitter.im/trezor/community.svg)](https://gitter.im/trezor/community)
Python library and commandline client for communicating with TREZOR
Hardware Wallet
See <https://trezor.io> for more information
## Install
Python-trezor requires Python 3.5 or higher, and libusb 1.0. The easiest
way to install it is with `pip`. The rest of this guide assumes you have
a working `pip`; if not, you can refer to [this
guide](https://packaging.python.org/tutorials/installing-packages/).
### Quick installation
On a typical Linux / Mac / BSD system, you already have all you need.
Install `trezor` with:
```sh
pip3 install --upgrade setuptools
pip3 install trezor
```
On Windows, you also need to install
[libusb](https://github.com/libusb/libusb/wiki/Windows) and the
appropriate [drivers](https://zadig.akeo.ie/). This is, unfortunately, a
topic bigger than this README.
### Older Trezor One support
If your Trezor One is on firmware **1.6.3** or older, you will need HIDAPI support
for it to be recognized. That requires additional packages.
#### Debian / Ubuntu
On a Debian or Ubuntu based system, you can install these:
```sh
sudo apt-get install python3-dev python3-pip cython3 libusb-1.0-0-dev libudev-dev
```
#### Windows
On a Windows based system, you can install these (for more info on choco, refer to [this](https://chocolatey.org/install)):
```sh
choco install vcbuildtools python3 protoc
refreshenv
pip3 install protobuf
```
When installing the trezor library, you need to specify that you want
`hidapi`:
```sh
pip3 install --upgrade setuptools
pip3 install trezor[hidapi]
```
### Ethereum support
Ethereum requires additional python packages. Instead of
`pip3 install trezor`, specify `pip3 install trezor[ethereum]`.
You can combine it with the above, to get both HIDAPI and Ethereum
support:
```sh
pip3 install trezor[ethereum,hidapi]
```
### FreeBSD
On FreeBSD you can install the packages:
```sh
pkg install security/py-trezor
```
or build via ports:
```sh
cd /usr/ports/security/py-trezor
make install clean
```
### Building from source
Sometimes you might need to install the latest-and-greatest unreleased version
straight from GitHub. You will need some prerequisites first:
```sh
sudo apt-get install protobuf-compiler protobuf-dev
pip3 install protobuf
```
If you just need to install the package, you can use pip again:
```sh
pip3 install git+https://github.com/trezor/python-trezor
```
If you want to work on the sources, make a local clone:
```sh
git clone https://github.com/trezor/python-trezor
cd python-trezor
python3 setup.py prebuild
python3 setup.py develop
```
## Command line client (trezorctl)
The included `trezorctl` python script can perform various tasks such as
changing setting in the Trezor, signing transactions, retrieving account
info and addresses. See the [docs/](docs/) sub folder for detailed
examples and options.
NOTE: An older version of the `trezorctl` command is [available for
Debian Stretch](https://packages.debian.org/en/stretch/python-trezor)
(and comes pre-installed on [Tails OS](https://tails.boum.org/)).
## Python Library
You can use this python library to interact with a Bitcoin Trezor and
use its capabilities in your application. See examples here in the
[tools/](tools/) sub folder.
## PIN Entering
When you are asked for PIN, you have to enter scrambled PIN. Follow the
numbers shown on TREZOR display and enter the their positions using the
numeric keyboard mapping:
| | | |
|---|---|---|
| 7 | 8 | 9 |
| 4 | 5 | 6 |
| 1 | 2 | 3 |
Example: your PIN is **1234** and TREZOR is displaying the following:
| | | |
|---|---|---|
| 2 | 8 | 3 |
| 5 | 4 | 6 |
| 7 | 9 | 1 |
You have to enter: **3795**
## Contributing
Python-trezor pulls coins info and protobuf messages from
[trezor-common](https://github.com/trezor/trezor-common) repository. If
you are developing new features for Trezor, you will want to start
there. Once your changes are accepted to `trezor-common`, you can make a
PR against this repository. Don't forget to update the submodule with:
```sh
git submodule update --init --remote
```
Then, rebuild the protobuf messages and get `coins.json` by running:
```sh
python3 setup.py prebuild
```
To get support for BTC-like coins, these steps are enough and no further
changes to the library are necessary.

View File

@ -0,0 +1,21 @@
_trezorctl()
{
export TREZORCTL_COMPLETION_CACHE
local cur prev cmds base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
if [ -z "$TREZORCTL_COMPLETION_CACHE" ]; then
help_output=$(trezorctl --help | grep '^ [a-z]' | awk '{ print $1 }')
export TREZORCTL_COMPLETION_CACHE="$help_output"
fi
cmds="$TREZORCTL_COMPLETION_CACHE"
COMPREPLY=($(compgen -W "${cmds}" -- ${cur}))
return 0
}
complete -F _trezorctl trezorctl

115
python/docs/EXAMPLES.rst Normal file
View File

@ -0,0 +1,115 @@
Examples demonstrating how to use trezorctl
===========================================
Show all available `options <OPTIONS.rst>`_:
.. code::
trezorctl --help
Retrieve features, settings and coin types supported by your device:
.. code::
trezorctl get-features
Bitcoin examples
----------------
Get first receiving address of first account for Bitcoin (Legacy / non-SegWit):
.. code::
trezorctl get-address --coin Bitcoin --script-type address --address "m/44'/0'/0'/0/0"
Get first receiving address of first account for Bitcoin (SegWit-in-P2SH):
.. code::
trezorctl get-address --coin Bitcoin --script-type p2shsegwit --address "m/49'/0'/0'/0/0"
Get first receiving address of first account for Bitcoin (Bech32 native SegWit P2WPKH):
.. code::
trezorctl get-address --coin Bitcoin --script-type segwit --address "m/84'/0'/0'/0/0"
Get Legacy Bitcoin ``xpub`` (can be used to create a watch-only wallet):
.. code::
trezorctl get-public-node --coin Bitcoin --address "m/44'/0'/0'"
Transaction signing
-------------------
You can use ``trezorctl`` to sign a transaction without it automatically being broadcast to the Bitcoin network.
You will need the following pieces of info:
1) Transaction ID containing the Output we want to spend (aka ``prevhash`` or ``a5ea715a...d201e64e`` in example below).
2) Index number of the Output being spent from the above tx (aka ``previndex`` or ``0`` in example below).
3) BIP32 path to the Node which can spend the above UTXO (eg ``Bitcoin/0'/0/0`` for the first).
4) Destination address where you want to send funds (eg ``3M8XGFBKwkf7miBzpkU3x2DoWwAVrD1mhk`` below).
5) Amount to send in satoshis - ``91305`` in the example below (multiply BTC amount 0.00091305 by 100,000,000).
6) Expected fee (``0.00019695`` BTC in example below). Note: the miner receives all satoshis left unspent from a transaction. If you want to receive some change, you need to send it to an address you own (otherwise it will go to miner). Fee is not needed below, we just want it as a sanity check.
There are many ways to retrieve the info above: from a watch-only wallet in Bitcoin Core, https://coinb.in (`screenshot <sign_tx-coinb.in.png>`_) etc. The easiest way is using the Trezor online wallet: https://beta-wallet.trezor.io
After authenticating, open the "Send" tab, fill-out all details, then open the "Show transaction details" menu to see the info needed above (`screenshot <sign_tx-trezor.io.png>`_). Once you have the required details, you can then perform the transaction signing using ``trezorctl`` as shown in the example below:
.. code::
trezorctl sign-tx -c Bitcoin
Input (prevhash:previndex, empty to move on): a5ea715aa99ca30516f3af6f622dfe7399d883d49ad74b1fe33fdf73d201e64e:0
Node path to sign with (e.g.- Bitcoin/0'/0/0): Bitcoin/0'/0/0
Input (prevhash:previndex, empty to move on):
Pay to address (empty to move on): 3M8XGFBKwkf7miBzpkU3x2DoWwAVrD1mhk
Amount (in satoshis): 91305
Pay to address (empty to move on):
Passphrase required:
Confirm your Passphrase:
RECEIVED PART OF SERIALIZED TX (152 BYTES)
RECEIVED PART OF SERIALIZED TX (37 BYTES)
SIGNED IN 52.538 SECONDS, CALLED 10 MESSAGES, 189 BYTES
Signed Transaction:
01000000014ee601d273df3fe31f4bd79ad483d89973fe2d626faff31605a39ca95a71eaa5000000006a47304402206386a0ad0f0b196d375a0805eee2aebe4644032c2998aaf00e43ce68a293986702202ad25964844657e10130f81201b7d87eb8047cf0c09dfdcbbe68a1a732e80ded012103b375a0dd50c8dbc4a6156a55e31274ee0537191e1bc824a09278a220fafba2dbffffffff01a96401000000000017a914d53d47ccd1579b93c284e9caf3c81f3f417871698700000000
Use the following form to broadcast it to the network:
https://btc-bitcore1.trezor.io/tx/send
The signed transaction text can then be inspected in Electrum (`screenshot <sign_tx-electrum2.png>`_), `coinb.in <https://coinb.in/?verify=01000000014ee601d273df3fe31f4bd79ad483d89973fe2d626faff31605a39ca95a71eaa5000000006a47304402206386a0ad0f0b196d375a0805eee2aebe4644032c2998aaf00e43ce68a293986702202ad25964844657e10130f81201b7d87eb8047cf0c09dfdcbbe68a1a732e80ded012103b375a0dd50c8dbc4a6156a55e31274ee0537191e1bc824a09278a220fafba2dbffffffff01a96401000000000017a914d53d47ccd1579b93c284e9caf3c81f3f417871698700000000#verify>`_ or another tool. If all info is correct, you can then broadcast the tx to the Bitcoin network via the URL provided by ``trezorctl`` or Electrum (Tools → Load transaction → From text. Here is a `screenshot <sign_tx-electrum1.png>`_). TIP: Electrum will only show the transaction fee if you previously imported the spending address (eg ``16ijWp48xn8hj6deD5ZHSJcgNjtYbpiki8`` from example tx above). Also, the final tx size (and therefore satoshis / byte) might be slightly different than the estimate shown on beta-wallet.trezor.io
The final broadcast and mined transaction can be seen here: https://blockchain.info/tx/270684c14be85efec9adafa50339fd120658381ed2300b9207d0a0df2a5f0bf9
Litecoin examples
-----------------
Get first receiving address of first account for Litecoin (SegWit-in-P2SH):
.. code::
trezorctl get-address --coin Litecoin --script-type p2shsegwit --address "m/49'/2'/0'/0/0"
Get first receiving address of first account for Litecoin (Bech32 native SegWit P2WPKH):
.. code::
trezorctl get-address --coin Litecoin --script-type segwit --address "m/84'/2'/0'/0/0"
Notes
-----
1. Bech32 native SegWit encoded addresses require `Trezor Firmware v1.6.0 <https://github.com/trezor/trezor-mcu/releases>`_ or later.

83
python/docs/OPTIONS.rst Normal file
View File

@ -0,0 +1,83 @@
Commandline options for trezorctl
=================================
See `EXAMPLES.rst <EXAMPLES.rst>`_ for examples on how to use.
Use the following command to see all options:
.. code::
trezorctl --help
.. code::
Usage: trezorctl [OPTIONS] COMMAND [ARGS]...
Options:
-p, --path TEXT Select device by specific path.
-v, --verbose Show communication messages.
-j, --json Print result as JSON object
--help Show this message and exit.
Commands:
backup-device Perform device seed backup.
cardano-get-address Get Cardano address.
cardano-get-public-key Get Cardano public key.
cardano-sign-tx Sign Cardano transaction.
change-pin Change new PIN or remove existing.
clear-session Clear session (remove cached PIN, passphrase, etc.).
cosi-commit Ask device to commit to CoSi signing.
cosi-sign Ask device to sign using CoSi.
decrypt-keyvalue Decrypt value by given key and path.
disable-passphrase Disable passphrase.
enable-passphrase Enable passphrase.
encrypt-keyvalue Encrypt value by given key and path.
ethereum-get-address Get Ethereum address in hex encoding.
ethereum-sign-message Sign message with Ethereum address.
ethereum-sign-tx Sign (and optionally publish) Ethereum transaction.
ethereum-verify-message Verify message signed with Ethereum address.
firmware-update Upload new firmware to device.
get-address Get address for specified path.
get-entropy Get example entropy.
get-features Retrieve device features and settings.
get-public-node Get public node of given path.
lisk-get-address Get Lisk address for specified path.
lisk-get-public-key Get Lisk public key for specified path.
lisk-sign-message Sign message with Lisk address.
lisk-sign-tx Sign Lisk transaction.
lisk-verify-message Verify message signed with Lisk address.
list List connected TREZOR devices.
load-device Load custom configuration to the device.
monero-get-address Get Monero address for specified path.
monero-get-watch-key Get Monero watch key for specified path.
nem-get-address Get NEM address for specified path.
nem-sign-tx Sign (and optionally broadcast) NEM transaction.
ontology-get-address Get Ontology address for specified path.
ontology-get-public-key Get Ontology public key for specified path.
ontology-sign-ont-id-add-attributes
Sign Ontology ONT ID Attributes adding.
ontology-sign-ont-id-register Sign Ontology ONT ID Registration.
ontology-sign-transfer Sign Ontology transfer.
ontology-sign-withdraw-ong Sign Ontology withdraw Ong.
ping Send ping message.
recovery-device Start safe recovery workflow.
reset-device Perform device setup and generate new seed.
ripple-get-address Get Ripple address
ripple-sign-tx Sign Ripple transaction
self-test Perform a self-test.
set-auto-lock-delay Set auto-lock delay (in seconds).
set-flags Set device flags.
set-homescreen Set new homescreen.
set-label Set new device label.
set-passphrase-source Set passphrase source.
set-u2f-counter Set U2F counter.
sign-message Sign message using address of given path.
sign-tx Sign transaction.
stellar-get-address Get Stellar public address
stellar-sign-transaction Sign a base64-encoded transaction envelope
tezos-get-address Get Tezos address for specified path.
tezos-get-public-key Get Tezos public key.
tezos-sign-tx Sign Tezos transaction.
verify-message Verify message.
version Show version of trezorctl/trezorlib.
wipe-device Reset device to factory defaults and remove all private data.

5
python/docs/README.rst Normal file
View File

@ -0,0 +1,5 @@
Documentation for trezorctl commandline client
==============================================
* `EXAMPLES.rst <EXAMPLES.rst>`_ - Examples demonstrating how to use trezorctl
* `OPTIONS.rst <OPTIONS.rst>`_ - Commandline options for trezorctl

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,4 @@
These scripts automate some tasks related to release process.
* __`relicence.py`__ rewrites licence headers in all non-empty Python files
* __`linkify-changelog.py`__ generates Markdown links to github issues/PRs in changelog

View File

@ -0,0 +1,57 @@
#!/usr/bin/env python3
import os
import requests
RELEASES_URL = "https://beta-wallet.trezor.io/data/firmware/{}/releases.json"
MODELS = ("1", "T")
FILENAME = os.path.join(os.path.dirname(__file__), "..", "trezorlib", "__init__.py")
START_LINE = "MINIMUM_FIRMWARE_VERSION = {\n"
END_LINE = "}\n"
def version_str(vtuple):
return ".".join(map(str, vtuple))
def fetch_releases(model):
version = model
if model == "T":
version = "2"
url = RELEASES_URL.format(version)
releases = requests.get(url).json()
releases.sort(key=lambda r: r["version"], reverse=True)
return releases
def find_latest_required(model):
releases = fetch_releases(model)
return next(r for r in releases if r["required"])
with open(FILENAME, "r+") as f:
output = []
line = None
# copy up to & incl START_LINE
while line != START_LINE:
line = next(f)
output.append(line)
# throw away until END_LINE
while line != END_LINE:
line = next(f)
# append models
for model in MODELS:
rel = find_latest_required(model)
version_tuple = tuple(rel["version"])
line = f' "{model}": {version_tuple!r},\n'
output.append(line)
output.append(END_LINE)
# finish reading file
for line in f:
output.append(line)
f.seek(0)
f.truncate(0)
for line in output:
f.write(line)

View File

@ -0,0 +1,27 @@
#!/usr/bin/env python3
import os
import re
LINK_RE = re.compile(r"\[#(\d+)\]")
ISSUE_URL = "https://github.com/trezor/python-trezor/issues/"
CHANGELOG = os.path.dirname(__file__) + "/../CHANGELOG.md"
changelog_entries = set()
result_lines = []
with open(CHANGELOG, "r+") as changelog:
for line in changelog:
if ISSUE_URL in line:
break
for n in LINK_RE.findall(line):
changelog_entries.add(int(n))
result_lines.append(line)
changelog.seek(0)
changelog.truncate(0)
for line in result_lines:
changelog.write(line)
for issue in sorted(changelog_entries):
changelog.write(f"[#{issue}]: {ISSUE_URL}{issue}\n")

View File

@ -0,0 +1,51 @@
#!/usr/bin/env python3
LICENSE_NOTICE = """\
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
"""
EXCLUDE_FILES = ["trezorlib/__init__.py", "trezorlib/_ed25519.py"]
def one_file(fp):
lines = list(fp)
new = lines[:]
while new and new[0][0] == "#":
new.pop(0)
while new and new[0].strip() == "":
new.pop(0)
data = "".join([LICENSE_NOTICE] + new)
fp.seek(0)
fp.write(data)
fp.truncate()
import glob
import os
for fn in glob.glob("trezorlib/**/*.py", recursive=True):
if fn in EXCLUDE_FILES:
continue
statinfo = os.stat(fn)
if statinfo.st_size == 0:
continue
with open(fn, "r+") as fp:
one_file(fp)

View File

@ -0,0 +1,8 @@
-r requirements.txt
-r requirements-optional.txt
pytest>=3.6
flake8
protobuf
isort==4.3.10
black; python_version >= "3.6"
autoflake>=1.2

View File

@ -0,0 +1,3 @@
hidapi >= 0.7.99.post20
rlp >= 1.1.0
web3 >= 4.8

8
python/requirements.txt Normal file
View File

@ -0,0 +1,8 @@
ecdsa>=0.9
mnemonic>=0.17
requests>=2.4.0
click>=7,<8
pyblake2>=0.9.3
libusb1>=1.6.4
construct>=2.9
typing_extensions>=3.6

36
python/setup.cfg Normal file
View File

@ -0,0 +1,36 @@
[flake8]
filename =
*.py,
./trezorctl
exclude =
.tox/,
build/,
dist/,
vendor/,
trezorlib/messages/__init__.py
ignore =
# E203 whitespace before ':'
E203,
# E221: multiple spaces before operator
E221,
# E241: multiple spaces after ':'
E241,
# E402: module level import not at top of file
E402,
# E501: line too long
E501,
# E741 ambiguous variable name
E741,
# W503: line break before binary operator
W503
[isort]
multi_line_output = 3
include_trailing_comma = True
force_grid_wrap = 0
combine_as_imports = True
line_length = 88
not_skip=__init__.py
known_first_party=trezorlib
known_third_party=hidapi, rlp, ethjsonrpc, ecdsa, mnemonic, requests, click, pyblake2, \
usb, construct, pytest

148
python/setup.py Executable file
View File

@ -0,0 +1,148 @@
#!/usr/bin/env python3
import glob
import json
import os.path
import re
import subprocess
import sys
from distutils.errors import DistutilsError
from setuptools import Command, find_packages, setup
from setuptools.command.build_py import build_py
from setuptools.command.develop import develop
install_requires = [
"setuptools>=19.0",
"ecdsa>=0.9",
"mnemonic>=0.17",
"requests>=2.4.0",
"click>=7,<8",
"pyblake2>=0.9.3",
"libusb1>=1.6.4",
"construct>=2.9",
"typing_extensions>=3.6",
]
CWD = os.path.dirname(os.path.realpath(__file__))
TREZOR_COMMON = os.path.join(CWD, "vendor", "trezor-common")
def read(*path):
filename = os.path.join(CWD, *path)
with open(filename, "r") as f:
return f.read()
def find_version():
version_file = read("trezorlib", "__init__.py")
version_match = re.search(r"^__version__ = \"(.*)\"$", version_file, re.M)
if version_match:
return version_match.group(1)
else:
raise RuntimeError("Version string not found")
def build_coins_json(dst):
TOOLS_PATH = os.path.join(TREZOR_COMMON, "tools")
sys.path.insert(0, TOOLS_PATH)
import coin_info
coins = coin_info.coin_info().bitcoin
support = coin_info.support_info(coins)
for coin in coins:
coin["support"] = support[coin["key"]]
with open(dst, "w") as f:
json.dump(coins, f, indent=2, sort_keys=True)
del sys.path[0]
class PrebuildCommand(Command):
description = "update vendored files (coins.json, protobuf messages)"
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
# check for existence of the submodule directory
common_defs = os.path.join(TREZOR_COMMON, "defs")
if not os.path.exists(common_defs):
raise DistutilsError(
"trezor-common submodule seems to be missing.\n"
+ "Use 'git submodule update --init' to retrieve it."
)
# generate and copy coins.json to the tree
coins_json = os.path.join(CWD, "trezorlib", "coins.json")
build_coins_json(coins_json)
# regenerate messages
try:
proto_srcs = glob.glob(os.path.join(TREZOR_COMMON, "protob", "*.proto"))
subprocess.check_call(
[
sys.executable,
os.path.join(TREZOR_COMMON, "protob", "pb2py"),
"-o",
os.path.join(CWD, "trezorlib", "messages"),
"-P",
"..protobuf",
]
+ proto_srcs
)
except Exception as e:
raise DistutilsError(
"Generating protobuf failed. Make sure you have 'protoc' in your PATH."
) from e
def _patch_prebuild(cls):
"""Patch a setuptools command to depend on `prebuild`"""
orig_run = cls.run
def new_run(self):
self.run_command("prebuild")
orig_run(self)
cls.run = new_run
_patch_prebuild(build_py)
_patch_prebuild(develop)
setup(
name="trezor",
version=find_version(),
author="TREZOR",
author_email="info@trezor.io",
license="LGPLv3",
description="Python library for communicating with TREZOR Hardware Wallet",
long_description="{}\n\n{}".format(read("README.md"), read("CHANGELOG.md")),
long_description_content_type="text/markdown",
url="https://github.com/trezor/python-trezor",
packages=find_packages(),
package_data={"trezorlib": ["coins.json"]},
scripts=["trezorctl"],
install_requires=install_requires,
extras_require={
"hidapi": ["hidapi>=0.7.99.post20"],
"ethereum": ["rlp>=1.1.0", "web3>=4.8"],
},
python_requires=">=3.5",
include_package_data=True,
zip_safe=False,
classifiers=[
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Operating System :: POSIX :: Linux",
"Operating System :: Microsoft :: Windows",
"Operating System :: MacOS :: MacOS X",
"Programming Language :: Python :: 3 :: Only",
],
cmdclass={"prebuild": PrebuildCommand},
)

9
python/shell.nix Normal file
View File

@ -0,0 +1,9 @@
with import <nixpkgs> {};
let
myPython = python3.withPackages(p: [p.pytest p.black p.isort p.flake8 p.requests p.mnemonic p.construct p.pyblake2 p.mock p.ecdsa p.click p.libusb1 p.protobuf p.typing-extensions]);
in
stdenv.mkDerivation {
name = "python-trezor-dev";
buildInputs = [ myPython autoflake protobuf ];
}

84
python/tools/deserialize_tx.py Executable file
View File

@ -0,0 +1,84 @@
#!/usr/bin/env python3
import os
import sys
try:
import construct as c
except ImportError:
sys.stderr.write("This tool requires Construct. Install it with 'pip install Construct'.\n")
sys.exit(1)
from construct import this, len_
if os.isatty(sys.stdin.fileno()):
tx_hex = input("Enter transaction in hex format: ")
else:
tx_hex = sys.stdin.read().strip()
tx_bin = bytes.fromhex(tx_hex)
CompactUintStruct = c.Struct(
"base" / c.Int8ul,
"ext" / c.Switch(this.base, {0xfd: c.Int16ul, 0xfe: c.Int32ul, 0xff: c.Int64ul}),
)
class CompactUintAdapter(c.Adapter):
def _encode(self, obj, context, path):
if obj < 0xfd:
return {"base": obj}
if obj < 2 ** 16:
return {"base": 0xfd, "ext": obj}
if obj < 2 ** 32:
return {"base": 0xfe, "ext": obj}
if obj < 2 ** 64:
return {"base": 0xff, "ext": obj}
raise ValueError("Value too big for compact uint")
def _decode(self, obj, context, path):
return obj["ext"] or obj["base"]
class ConstFlag(c.Adapter):
def __init__(self, const):
self.const = const
super().__init__(c.Optional(c.Const(const)))
def _encode(self, obj, context, path):
return self.const if obj else None
def _decode(self, obj, context, path):
return obj is not None
CompactUint = CompactUintAdapter(CompactUintStruct)
TxInput = c.Struct(
"tx" / c.Bytes(32),
"index" / c.Int32ul,
# TODO coinbase tx
"script" / c.Prefixed(CompactUint, c.GreedyBytes),
"sequence" / c.Int32ul,
)
TxOutput = c.Struct(
"value" / c.Int64ul,
"pk_script" / c.Prefixed(CompactUint, c.GreedyBytes),
)
StackItem = c.Prefixed(CompactUint, c.GreedyBytes)
TxInputWitness = c.PrefixedArray(CompactUint, StackItem)
Transaction = c.Struct(
"version" / c.Int32ul,
"segwit" / ConstFlag(b"\x00\x01"),
"inputs" / c.PrefixedArray(CompactUint, TxInput),
"outputs" / c.PrefixedArray(CompactUint, TxOutput),
"witness" / c.If(this.segwit, TxInputWitness[len_(this.inputs)]),
"lock_time" / c.Int32ul,
c.Terminated,
)
print(Transaction.parse(tx_bin))

138
python/tools/encfs_aes_getpass.py Executable file
View File

@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""
Use TREZOR as a hardware key for opening EncFS filesystem!
Usage:
encfs --standard --extpass=./encfs_aes_getpass.py ~/.crypt ~/crypt
"""
import os
import sys
import json
import hashlib
import trezorlib
version_tuple = tuple(map(int, trezorlib.__version__.split(".")))
if not (0, 11) <= version_tuple < (0, 12):
raise RuntimeError("trezorlib version mismatch (0.11.x is required)")
from trezorlib.client import TrezorClient
from trezorlib.transport import enumerate_devices
from trezorlib.ui import ClickUI
import trezorlib.misc
def wait_for_devices():
devices = enumerate_devices()
while not len(devices):
sys.stderr.write("Please connect TREZOR to computer and press Enter...")
input()
devices = enumerate_devices()
return devices
def choose_device(devices):
if not len(devices):
raise RuntimeError("No TREZOR connected!")
if len(devices) == 1:
try:
return devices[0]
except IOError:
raise RuntimeError("Device is currently in use")
i = 0
sys.stderr.write("----------------------------\n")
sys.stderr.write("Available devices:\n")
for d in devices:
try:
client = TrezorClient(d, ui=ClickUI())
except IOError:
sys.stderr.write("[-] <device is currently in use>\n")
continue
if client.features.label:
sys.stderr.write("[%d] %s\n" % (i, client.features.label))
else:
sys.stderr.write("[%d] <no label>\n" % i)
client.close()
i += 1
sys.stderr.write("----------------------------\n")
sys.stderr.write("Please choose device to use:")
try:
device_id = int(input())
return devices[device_id]
except Exception:
raise ValueError("Invalid choice, exiting...")
def main():
if "encfs_root" not in os.environ:
sys.stderr.write(
"\nThis is not a standalone script and is not meant to be run independently.\n"
)
sys.stderr.write(
"\nUsage: encfs --standard --extpass=./encfs_aes_getpass.py ~/.crypt ~/crypt\n"
)
sys.exit(1)
devices = wait_for_devices()
transport = choose_device(devices)
client = TrezorClient(transport, ui=ClickUI())
rootdir = os.environ["encfs_root"] # Read "man encfs" for more
passw_file = os.path.join(rootdir, "password.dat")
if not os.path.exists(passw_file):
# New encfs drive, let's generate password
sys.stderr.write("Please provide label for new drive: ")
label = input()
sys.stderr.write("Computer asked TREZOR for new strong password.\n")
# 32 bytes, good for AES
trezor_entropy = trezorlib.misc.get_entropy(client, 32)
urandom_entropy = os.urandom(32)
passw = hashlib.sha256(trezor_entropy + urandom_entropy).digest()
if len(passw) != 32:
raise ValueError("32 bytes password expected")
bip32_path = [10, 0]
passw_encrypted = trezorlib.misc.encrypt_keyvalue(
client, bip32_path, label, passw, False, True
)
data = {
"label": label,
"bip32_path": bip32_path,
"password_encrypted_hex": passw_encrypted.hex(),
}
json.dump(data, open(passw_file, "w"))
# Let's load password
data = json.load(open(passw_file, "r"))
passw = trezorlib.misc.decrypt_keyvalue(
client,
data["bip32_path"],
data["label"],
bytes.fromhex(data["password_encrypted_hex"]),
False,
True,
)
print(passw)
if __name__ == "__main__":
main()

22
python/tools/helloworld.py Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
from trezorlib.client import get_default_client
from trezorlib.tools import parse_path
from trezorlib import btc
def main():
# Use first connected device
client = get_default_client()
# Print out TREZOR's features and settings
print(client.features)
# Get the first address of first BIP44 account
# (should be the same address as shown in wallet.trezor.io)
bip32_path = parse_path("44'/0'/0'/0/0")
address = btc.get_address(client, "Bitcoin", bip32_path, True)
print("Bitcoin address:", address)
if __name__ == "__main__":
main()

49
python/tools/mem_flashblock.py Executable file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env python3
from trezorlib.debuglink import DebugLink
from trezorlib.transport import enumerate_devices
import sys
# fmt: off
sectoraddrs = [0x8000000, 0x8004000, 0x8008000, 0x800c000,
0x8010000, 0x8020000, 0x8040000, 0x8060000,
0x8080000, 0x80a0000, 0x80c0000, 0x80f0000]
sectorlens = [0x4000, 0x4000, 0x4000, 0x4000,
0x8000, 0x10000, 0x10000, 0x10000,
0x10000, 0x10000, 0x10000, 0x10000]
# fmt: on
def find_debug():
for device in enumerate_devices():
try:
debug_transport = device.find_debug()
debug = DebugLink(debug_transport, auto_interact=False)
debug.open()
return debug
except Exception:
continue
else:
print("No suitable Trezor device found")
sys.exit(1)
def main():
debug = find_debug()
sector = int(sys.argv[1])
f = open(sys.argv[2], "rb")
content = f.read(sectorlens[sector])
if len(content) != sectorlens[sector]:
print("Not enough bytes in file")
return
debug.flash_erase(sector)
step = 0x400
for offset in range(0, sectorlens[sector], step):
debug.memory_write(
sectoraddrs[sector] + offset, content[offset : offset + step], flash=True
)
if __name__ == "__main__":
main()

46
python/tools/mem_read.py Executable file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env python3
from trezorlib.debuglink import DebugLink
from trezorlib.transport import enumerate_devices
import sys
# usage examples
# read entire bootloader: ./mem_read.py 8000000 8000
# read initial stack pointer: ./mem_read.py 8000000 4
# an entire bootloader can be later disassembled with:
# arm-none-eabi-objdump -D -b binary -m arm -M force-thumb memory.dat
# note that in order for this to work, your trezor device must
# be running a firmware that was built with debug link enabled
def find_debug():
for device in enumerate_devices():
try:
debug_transport = device.find_debug()
debug = DebugLink(debug_transport, auto_interact=False)
debug.open()
return debug
except Exception:
continue
else:
print("No suitable Trezor device found")
sys.exit(1)
def main():
debug = find_debug()
arg1 = int(sys.argv[1], 16)
arg2 = int(sys.argv[2], 16)
step = 0x400 if arg2 >= 0x400 else arg2
f = open("memory.dat", "wb")
for addr in range(arg1, arg1 + arg2, step):
mem = debug.memory_read(addr, step)
f.write(mem)
f.close()
if __name__ == "__main__":
main()

27
python/tools/mem_write.py Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env python3
from trezorlib.debuglink import DebugLink
from trezorlib.transport import enumerate_devices
import sys
def find_debug():
for device in enumerate_devices():
try:
debug_transport = device.find_debug()
debug = DebugLink(debug_transport, auto_interact=False)
debug.open()
return debug
except Exception:
continue
else:
print("No suitable Trezor device found")
sys.exit(1)
def main():
debug = find_debug()
debug.memory_write(int(sys.argv[1], 16), bytes.fromhex(sys.argv[2]), flash=True)
if __name__ == "__main__":
main()

72
python/tools/mnemonic_check.py Executable file
View File

@ -0,0 +1,72 @@
#!/usr/bin/env python3
import hashlib
import mnemonic
__doc__ = '''
Use this script to cross-check that TREZOR generated valid
mnemonic sentence for given internal (TREZOR-generated)
and external (computer-generated) entropy.
Keep in mind that you're entering secret information to this script.
Leaking of these information may lead to stealing your bitcoins
from your wallet! We strongly recommend to run this script only on
highly secured computer (ideally live linux distribution
without an internet connection).
'''
def generate_entropy(strength, internal_entropy, external_entropy):
'''
strength - length of produced seed. One of 128, 192, 256
random - binary stream of random data from external HRNG
'''
if strength not in (128, 192, 256):
raise ValueError("Invalid strength")
if not internal_entropy:
raise ValueError("Internal entropy is not provided")
if len(internal_entropy) < 32:
raise ValueError("Internal entropy too short")
if not external_entropy:
raise ValueError("External entropy is not provided")
if len(external_entropy) < 32:
raise ValueError("External entropy too short")
entropy = hashlib.sha256(internal_entropy + external_entropy).digest()
entropy_stripped = entropy[:strength // 8]
if len(entropy_stripped) * 8 != strength:
raise ValueError("Entropy length mismatch")
return entropy_stripped
def main():
print(__doc__)
comp = bytes.fromhex(input("Please enter computer-generated entropy (in hex): ").strip())
trzr = bytes.fromhex(input("Please enter TREZOR-generated entropy (in hex): ").strip())
word_count = int(input("How many words your mnemonic has? "))
strength = word_count * 32 // 3
entropy = generate_entropy(strength, trzr, comp)
words = mnemonic.Mnemonic('english').to_mnemonic(entropy)
if not mnemonic.Mnemonic('english').check(words):
print("Mnemonic is invalid")
return
if len(words.split(' ')) != word_count:
print("Mnemonic length mismatch!")
return
print("Generated mnemonic is:", words)
if __name__ == '__main__':
main()

179
python/tools/pwd_reader.py Executable file
View File

@ -0,0 +1,179 @@
#!/usr/bin/env python3
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import hmac
import hashlib
import json
import os
from urllib.parse import urlparse
from trezorlib import misc, ui
from trezorlib.client import TrezorClient
from trezorlib.transport import get_transport
from trezorlib.tools import parse_path
# Return path by BIP-32
BIP32_PATH = parse_path("10016h/0")
# Deriving master key
def getMasterKey(client):
bip32_path = BIP32_PATH
ENC_KEY = 'Activate TREZOR Password Manager?'
ENC_VALUE = bytes.fromhex('2d650551248d792eabf628f451200d7f51cb63e46aadcbb1038aacb05e8c8aee2d650551248d792eabf628f451200d7f51cb63e46aadcbb1038aacb05e8c8aee')
key = misc.encrypt_keyvalue(
client,
bip32_path,
ENC_KEY,
ENC_VALUE,
True,
True
)
return key.hex()
# Deriving file name and encryption key
def getFileEncKey(key):
filekey, enckey = key[:len(key) // 2], key[len(key) // 2:]
FILENAME_MESS = b'5f91add3fa1c3c76e90c90a3bd0999e2bd7833d06a483fe884ee60397aca277a'
digest = hmac.new(str.encode(filekey), FILENAME_MESS, hashlib.sha256).hexdigest()
filename = digest + '.pswd'
return [filename, filekey, enckey]
# File level decryption and file reading
def decryptStorage(path, key):
cipherkey = bytes.fromhex(key)
with open(path, 'rb') as f:
iv = f.read(12)
tag = f.read(16)
cipher = Cipher(algorithms.AES(cipherkey), modes.GCM(iv, tag), backend=default_backend())
decryptor = cipher.decryptor()
data = ''
while True:
block = f.read(16)
# data are not authenticated yet
if block:
data = data + decryptor.update(block).decode()
else:
break
# throws exception when the tag is wrong
data = data + decryptor.finalize().decode()
return json.loads(data)
def decryptEntryValue(nonce, val):
cipherkey = bytes.fromhex(nonce)
iv = val[:12]
tag = val[12:28]
cipher = Cipher(algorithms.AES(cipherkey), modes.GCM(iv, tag), backend=default_backend())
decryptor = cipher.decryptor()
data = ''
inputData = val[28:]
while True:
block = inputData[:16]
inputData = inputData[16:]
if block:
data = data + decryptor.update(block).decode()
else:
break
# throws exception when the tag is wrong
data = data + decryptor.finalize().decode()
return json.loads(data)
# Decrypt give entry nonce
def getDecryptedNonce(client, entry):
print()
print('Waiting for TREZOR input ...')
print()
if 'item' in entry:
item = entry['item']
else:
item = entry['title']
pr = urlparse(item)
if pr.scheme and pr.netloc:
item = pr.netloc
ENC_KEY = 'Unlock %s for user %s?' % (item, entry['username'])
ENC_VALUE = entry['nonce']
decrypted_nonce = misc.decrypt_keyvalue(
client,
BIP32_PATH,
ENC_KEY,
bytes.fromhex(ENC_VALUE),
False,
True
)
return decrypted_nonce.hex()
# Pretty print of list
def printEntries(entries):
print('Password entries')
print('================')
print()
for k, v in entries.items():
print('Entry id: #%s' % k)
print('-------------')
for kk, vv in v.items():
if kk in ['nonce', 'safe_note', 'password']:
continue # skip these fields
print('*', kk, ': ', vv)
print()
return
def main():
try:
transport = get_transport()
except Exception as e:
print(e)
return
client = TrezorClient(transport=transport, ui=ui.ClickUI())
print()
print('Confirm operation on TREZOR')
print()
masterKey = getMasterKey(client)
# print('master key:', masterKey)
fileName = getFileEncKey(masterKey)[0]
# print('file name:', fileName)
home = os.path.expanduser('~')
path = os.path.join(home, 'Dropbox', 'Apps', 'TREZOR Password Manager')
# print('path to file:', path)
encKey = getFileEncKey(masterKey)[2]
# print('enckey:', encKey)
full_path = os.path.join(path, fileName)
parsed_json = decryptStorage(full_path, encKey)
# list entries
entries = parsed_json['entries']
printEntries(entries)
entry_id = input('Select entry number to decrypt: ')
entry_id = str(entry_id)
plain_nonce = getDecryptedNonce(client, entries[entry_id])
pwdArr = entries[entry_id]['password']['data']
pwdHex = ''.join([hex(x)[2:].zfill(2) for x in pwdArr])
print('password: ', decryptEntryValue(plain_nonce, bytes.fromhex(pwdHex)))
safeNoteArr = entries[entry_id]['safe_note']['data']
safeNoteHex = ''.join([hex(x)[2:].zfill(2) for x in safeNoteArr])
print('safe_note:', decryptEntryValue(plain_nonce, bytes.fromhex(safeNoteHex)))
return
if __name__ == '__main__':
main()

View File

@ -0,0 +1,34 @@
#!/usr/bin/env python3
# example usage: ./rng_entropy_collector.py stm32_rng_1.dat 1048576
# note: for reading large amounts of entropy, compile a firmware
# that has DEBUG_RNG == 1 as that will disable the user button
# push confirmation
import io
import sys
from trezorlib import misc, ui
from trezorlib.client import TrezorClient
from trezorlib.transport import get_transport
def main():
try:
client = TrezorClient(get_transport(), ui=ui.ClickUI())
except Exception as e:
print(e)
return
arg1 = sys.argv[1] # output file
arg2 = int(sys.argv[2], 10) # total number of how many bytes of entropy to read
step = 1024 if arg2 >= 1024 else arg2 # trezor will only return 1KB at a time
with io.open(arg1, 'wb') as f:
for i in range(0, arg2, step):
entropy = misc.get_entropy(client, step)
f.write(entropy)
client.close()
if __name__ == '__main__':
main()

101
python/tools/trezor-otp.py Executable file
View File

@ -0,0 +1,101 @@
#!/usr/bin/env python3
import configparser
import os
import re
import sys
import pyotp
from trezorlib.client import TrezorClient
from trezorlib.misc import decrypt_keyvalue, encrypt_keyvalue
from trezorlib.tools import parse_path
from trezorlib.transport import get_transport
from trezorlib.ui import ClickUI
BIP32_PATH = parse_path("10016h/0")
def encrypt(type, domain, secret):
transport = get_transport()
client = TrezorClient(transport, ClickUI())
dom = type.upper() + ": " + domain
enc = encrypt_keyvalue(client, BIP32_PATH, dom, secret.encode(), False, True)
client.close()
return enc.hex()
def decrypt(type, domain, secret):
transport = get_transport()
client = TrezorClient(transport, ClickUI())
dom = type.upper() + ": " + domain
dec = decrypt_keyvalue(client, BIP32_PATH, dom, secret, False, True)
client.close()
return dec
class Config:
def __init__(self):
XDG_CONFIG_HOME = os.getenv("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
os.makedirs(XDG_CONFIG_HOME, exist_ok=True)
self.filename = XDG_CONFIG_HOME + "/trezor-otp.ini"
self.config = configparser.ConfigParser()
self.config.read(self.filename)
def add(self, domain, secret, type="totp"):
self.config[domain] = {}
self.config[domain]["secret"] = encrypt(type, domain, secret)
self.config[domain]["type"] = type
if type == "hotp":
self.config[domain]["counter"] = "0"
with open(self.filename, "w") as f:
self.config.write(f)
def get(self, domain):
s = self.config[domain]
if s["type"] == "hotp":
s["counter"] = str(int(s["counter"]) + 1)
with open(self.filename, "w") as f:
self.config.write(f)
secret = decrypt(s["type"], domain, bytes.fromhex(s["secret"]))
if s["type"] == "totp":
return pyotp.TOTP(secret).now()
if s["type"] == "hotp":
c = int(s["counter"])
return pyotp.HOTP(secret).at(c)
return ValueError("unknown domain or type")
def add():
c = Config()
domain = input("domain: ")
while True:
secret = input("secret: ")
if re.match(r"^[A-Z2-7]{16}$", secret):
break
print("invalid secret")
while True:
type = input("type (t=totp h=hotp): ")
if type in ("t", "h"):
break
print("invalid type")
c.add(domain, secret, type + "otp")
print("Entry added")
def get(domain):
c = Config()
s = c.get(domain)
print(s)
def main():
if len(sys.argv) < 2:
print("Usage: trezor-otp.py [add|domain]")
sys.exit(1)
if sys.argv[1] == "add":
add()
else:
get(sys.argv[1])
if __name__ == "__main__":
main()

18
python/tox.ini Normal file
View File

@ -0,0 +1,18 @@
[tox]
envlist =
py35,
py36,
py37,
[testenv]
deps =
-rrequirements-dev.txt
commands =
# Generate local files
python setup.py build
# Working in the local directory, try to compile all bytecode
python -m compileall trezorlib/
# From installed version, smoke-test trezorctl
trezorctl --help
# Run non-device-dependent tests from installed version
python -E -m pytest --pyarg trezorlib.tests.unit_tests

1989
python/trezorctl Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
__version__ = "0.11.3"
# fmt: off
MINIMUM_FIRMWARE_VERSION = {
"1": (1, 8, 0),
"T": (2, 1, 0),
}
# fmt: on

View File

@ -0,0 +1,299 @@
# ed25519.py - Optimized version of the reference implementation of Ed25519
# downloaded from https://github.com/pyca/ed25519
#
# Written in 2011? by Daniel J. Bernstein <djb@cr.yp.to>
# 2013 by Donald Stufft <donald@stufft.io>
# 2013 by Alex Gaynor <alex.gaynor@gmail.com>
# 2013 by Greg Price <price@mit.edu>
#
# To the extent possible under law, the author(s) have dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. This software is distributed without any warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication along
# with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
"""
NB: This code is not safe for use with secret keys or secret data.
The only safe use of this code is for verifying signatures on public messages.
Functions for computing the public key of a secret key and for signing
a message are included, namely publickey_unsafe and signature_unsafe,
for testing purposes only.
The root of the problem is that Python's long-integer arithmetic is
not designed for use in cryptography. Specifically, it may take more
or less time to execute an operation depending on the values of the
inputs, and its memory access patterns may also depend on the inputs.
This opens it to timing and cache side-channel attacks which can
disclose data to an attacker. We rely on Python's long-integer
arithmetic, so we cannot handle secrets without risking their disclosure.
"""
import hashlib
from typing import List, NewType, Tuple
Point = NewType("Point", Tuple[int, int, int, int])
__version__ = "1.0.dev1"
b = 256
q = 2 ** 255 - 19
l = 2 ** 252 + 27742317777372353535851937790883648493
COORD_MASK = ~(1 + 2 + 4 + (1 << b - 1))
COORD_HIGH_BIT = 1 << b - 2
def H(m: bytes) -> bytes:
return hashlib.sha512(m).digest()
def pow2(x: int, p: int) -> int:
"""== pow(x, 2**p, q)"""
while p > 0:
x = x * x % q
p -= 1
return x
def inv(z: int) -> int:
"""$= z^{-1} mod q$, for z != 0"""
# Adapted from curve25519_athlon.c in djb's Curve25519.
z2 = z * z % q # 2
z9 = pow2(z2, 2) * z % q # 9
z11 = z9 * z2 % q # 11
z2_5_0 = (z11 * z11) % q * z9 % q # 31 == 2^5 - 2^0
z2_10_0 = pow2(z2_5_0, 5) * z2_5_0 % q # 2^10 - 2^0
z2_20_0 = pow2(z2_10_0, 10) * z2_10_0 % q # ...
z2_40_0 = pow2(z2_20_0, 20) * z2_20_0 % q
z2_50_0 = pow2(z2_40_0, 10) * z2_10_0 % q
z2_100_0 = pow2(z2_50_0, 50) * z2_50_0 % q
z2_200_0 = pow2(z2_100_0, 100) * z2_100_0 % q
z2_250_0 = pow2(z2_200_0, 50) * z2_50_0 % q # 2^250 - 2^0
return pow2(z2_250_0, 5) * z11 % q # 2^255 - 2^5 + 11 = q - 2
d = -121665 * inv(121666) % q
I = pow(2, (q - 1) // 4, q)
def xrecover(y: int) -> int:
xx = (y * y - 1) * inv(d * y * y + 1)
x = pow(xx, (q + 3) // 8, q)
if (x * x - xx) % q != 0:
x = (x * I) % q
if x % 2 != 0:
x = q - x
return x
By = 4 * inv(5)
Bx = xrecover(By)
B = Point((Bx % q, By % q, 1, (Bx * By) % q))
ident = Point((0, 1, 1, 0))
def edwards_add(P: Point, Q: Point) -> Point:
# This is formula sequence 'addition-add-2008-hwcd-3' from
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
(x1, y1, z1, t1) = P
(x2, y2, z2, t2) = Q
a = (y1 - x1) * (y2 - x2) % q
b = (y1 + x1) * (y2 + x2) % q
c = t1 * 2 * d * t2 % q
dd = z1 * 2 * z2 % q
e = b - a
f = dd - c
g = dd + c
h = b + a
x3 = e * f
y3 = g * h
t3 = e * h
z3 = f * g
return Point((x3 % q, y3 % q, z3 % q, t3 % q))
def edwards_double(P: Point) -> Point:
# This is formula sequence 'dbl-2008-hwcd' from
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
(x1, y1, z1, _) = P
a = x1 * x1 % q
b = y1 * y1 % q
c = 2 * z1 * z1 % q
# dd = -a
e = ((x1 + y1) * (x1 + y1) - a - b) % q
g = -a + b # dd + b
f = g - c
h = -a - b # dd - b
x3 = e * f
y3 = g * h
t3 = e * h
z3 = f * g
return Point((x3 % q, y3 % q, z3 % q, t3 % q))
def scalarmult(P: Point, e: int) -> Point:
if e == 0:
return ident
Q = scalarmult(P, e // 2)
Q = edwards_double(Q)
if e & 1:
Q = edwards_add(Q, P)
return Q
# Bpow[i] == scalarmult(B, 2**i)
Bpow = [] # type: List[Point]
def make_Bpow() -> None:
P = B
for _ in range(253):
Bpow.append(P)
P = edwards_double(P)
make_Bpow()
def scalarmult_B(e: int) -> Point:
"""
Implements scalarmult(B, e) more efficiently.
"""
# scalarmult(B, l) is the identity
e = e % l
P = ident
for i in range(253):
if e & 1:
P = edwards_add(P, Bpow[i])
e = e // 2
assert e == 0, e
return P
def encodeint(y: int) -> bytes:
return y.to_bytes(b // 8, "little")
def encodepoint(P: Point) -> bytes:
(x, y, z, _) = P
zi = inv(z)
x = (x * zi) % q
y = (y * zi) % q
xbit = (x & 1) << (b - 1)
y_result = y & ~xbit # clear x bit
y_result |= xbit # set corret x bit value
return encodeint(y_result)
def decodeint(s: bytes) -> int:
return int.from_bytes(s, "little")
def decodepoint(s: bytes) -> Point:
y = decodeint(s) & ~(1 << b - 1) # y without the highest bit
x = xrecover(y)
if x & 1 != bit(s, b - 1):
x = q - x
P = Point((x, y, 1, (x * y) % q))
if not isoncurve(P):
raise ValueError("decoding point that is not on curve")
return P
def decodecoord(s: bytes) -> int:
a = decodeint(s[: b // 8])
# clear mask bits
a &= COORD_MASK
# set high bit
a |= COORD_HIGH_BIT
return a
def bit(h: bytes, i: int) -> int:
return (h[i // 8] >> (i % 8)) & 1
def publickey_unsafe(sk: bytes) -> bytes:
"""
Not safe to use with secret keys or secret data.
See module docstring. This function should be used for testing only.
"""
h = H(sk)
a = decodecoord(h)
A = scalarmult_B(a)
return encodepoint(A)
def Hint(m: bytes) -> int:
return decodeint(H(m))
def signature_unsafe(m: bytes, sk: bytes, pk: bytes) -> bytes:
"""
Not safe to use with secret keys or secret data.
See module docstring. This function should be used for testing only.
"""
h = H(sk)
a = decodecoord(h)
r = Hint(h[b // 8 : b // 4] + m)
R = scalarmult_B(r)
S = (r + Hint(encodepoint(R) + pk + m) * a) % l
return encodepoint(R) + encodeint(S)
def isoncurve(P: Point) -> bool:
(x, y, z, t) = P
return (
z % q != 0
and x * y % q == z * t % q
and (y * y - x * x - z * z - d * t * t) % q == 0
)
class SignatureMismatch(Exception):
pass
def checkvalid(s: bytes, m: bytes, pk: bytes) -> None:
"""
Not safe to use when any argument is secret.
See module docstring. This function should be used only for
verifying public signatures of public messages.
"""
if len(s) != b // 4:
raise ValueError("signature length is wrong")
if len(pk) != b // 8:
raise ValueError("public-key length is wrong")
R = decodepoint(s[: b // 8])
A = decodepoint(pk)
S = decodeint(s[b // 8 : b // 4])
h = Hint(encodepoint(R) + pk + m)
(x1, y1, z1, _) = P = scalarmult_B(S)
(x2, y2, z2, _) = Q = edwards_add(R, scalarmult(A, h))
if (
not isoncurve(P)
or not isoncurve(Q)
or (x1 * z2 - x2 * z1) % q != 0
or (y1 * z2 - y2 * z1) % q != 0
):
raise SignatureMismatch("signature does not pass verification")

193
python/trezorlib/btc.py Normal file
View File

@ -0,0 +1,193 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from . import coins, messages
from .tools import CallException, expect, normalize_nfc, session
@expect(messages.PublicKey)
def get_public_node(
client,
n,
ecdsa_curve_name=None,
show_display=False,
coin_name=None,
script_type=messages.InputScriptType.SPENDADDRESS,
):
return client.call(
messages.GetPublicKey(
address_n=n,
ecdsa_curve_name=ecdsa_curve_name,
show_display=show_display,
coin_name=coin_name,
script_type=script_type,
)
)
@expect(messages.Address, field="address")
def get_address(
client,
coin_name,
n,
show_display=False,
multisig=None,
script_type=messages.InputScriptType.SPENDADDRESS,
):
return client.call(
messages.GetAddress(
address_n=n,
coin_name=coin_name,
show_display=show_display,
multisig=multisig,
script_type=script_type,
)
)
@expect(messages.MessageSignature)
def sign_message(
client, coin_name, n, message, script_type=messages.InputScriptType.SPENDADDRESS
):
message = normalize_nfc(message)
return client.call(
messages.SignMessage(
coin_name=coin_name, address_n=n, message=message, script_type=script_type
)
)
def verify_message(client, coin_name, address, signature, message):
message = normalize_nfc(message)
try:
resp = client.call(
messages.VerifyMessage(
address=address,
signature=signature,
message=message,
coin_name=coin_name,
)
)
except CallException as e:
resp = e
return isinstance(resp, messages.Success)
@session
def sign_tx(client, coin_name, inputs, outputs, details=None, prev_txes=None):
# set up a transactions dict
txes = {None: messages.TransactionType(inputs=inputs, outputs=outputs)}
# preload all relevant transactions ahead of time
if coin_name in coins.by_name:
load_prevtxes = not coins.by_name[coin_name]["force_bip143"]
else:
load_prevtxes = True
if load_prevtxes:
for inp in inputs:
if inp.script_type not in (
messages.InputScriptType.SPENDP2SHWITNESS,
messages.InputScriptType.SPENDWITNESS,
messages.InputScriptType.EXTERNAL,
):
try:
prev_tx = prev_txes[inp.prev_hash]
except Exception as e:
raise ValueError("Could not retrieve prev_tx") from e
if not isinstance(prev_tx, messages.TransactionType):
raise ValueError("Invalid value for prev_tx") from None
txes[inp.prev_hash] = prev_tx
if details is None:
signtx = messages.SignTx()
else:
signtx = details
signtx.coin_name = coin_name
signtx.inputs_count = len(inputs)
signtx.outputs_count = len(outputs)
res = client.call(signtx)
# Prepare structure for signatures
signatures = [None] * len(inputs)
serialized_tx = b""
def copy_tx_meta(tx):
tx_copy = messages.TransactionType(**tx)
# clear fields
tx_copy.inputs_cnt = len(tx.inputs)
tx_copy.inputs = []
tx_copy.outputs_cnt = len(tx.bin_outputs or tx.outputs)
tx_copy.outputs = []
tx_copy.bin_outputs = []
tx_copy.extra_data_len = len(tx.extra_data or b"")
tx_copy.extra_data = None
return tx_copy
R = messages.RequestType
while isinstance(res, messages.TxRequest):
# If there's some part of signed transaction, let's add it
if res.serialized:
if res.serialized.serialized_tx:
serialized_tx += res.serialized.serialized_tx
if res.serialized.signature_index is not None:
idx = res.serialized.signature_index
sig = res.serialized.signature
if signatures[idx] is not None:
raise ValueError("Signature for index %d already filled" % idx)
signatures[idx] = sig
if res.request_type == R.TXFINISHED:
break
# Device asked for one more information, let's process it.
current_tx = txes[res.details.tx_hash]
if res.request_type == R.TXMETA:
msg = copy_tx_meta(current_tx)
res = client.call(messages.TxAck(tx=msg))
elif res.request_type == R.TXINPUT:
msg = messages.TransactionType()
msg.inputs = [current_tx.inputs[res.details.request_index]]
res = client.call(messages.TxAck(tx=msg))
elif res.request_type == R.TXOUTPUT:
msg = messages.TransactionType()
if res.details.tx_hash:
msg.bin_outputs = [current_tx.bin_outputs[res.details.request_index]]
else:
msg.outputs = [current_tx.outputs[res.details.request_index]]
res = client.call(messages.TxAck(tx=msg))
elif res.request_type == R.TXEXTRADATA:
o, l = res.details.extra_data_offset, res.details.extra_data_len
msg = messages.TransactionType()
msg.extra_data = current_tx.extra_data[o : o + l]
res = client.call(messages.TxAck(tx=msg))
if isinstance(res, messages.Failure):
raise CallException("Signing failed")
if not isinstance(res, messages.TxRequest):
raise CallException("Unexpected message")
if None in signatures:
raise RuntimeError("Some signatures are missing!")
return signatures, serialized_tx

View File

@ -0,0 +1,92 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from typing import List
from . import messages, tools
from .tools import expect, session
REQUIRED_FIELDS_TRANSACTION = ("inputs", "outputs", "transactions")
REQUIRED_FIELDS_INPUT = ("path", "prev_hash", "prev_index", "type")
@expect(messages.CardanoAddress, field="address")
def get_address(client, address_n, show_display=False):
return client.call(
messages.CardanoGetAddress(address_n=address_n, show_display=show_display)
)
@expect(messages.CardanoPublicKey)
def get_public_key(client, address_n):
return client.call(messages.CardanoGetPublicKey(address_n=address_n))
@session
def sign_tx(
client,
inputs: List[messages.CardanoTxInputType],
outputs: List[messages.CardanoTxOutputType],
transactions: List[bytes],
protocol_magic,
):
response = client.call(
messages.CardanoSignTx(
inputs=inputs,
outputs=outputs,
transactions_count=len(transactions),
protocol_magic=protocol_magic,
)
)
while isinstance(response, messages.CardanoTxRequest):
tx_index = response.tx_index
transaction_data = bytes.fromhex(transactions[tx_index])
ack_message = messages.CardanoTxAck(transaction=transaction_data)
response = client.call(ack_message)
return response
def create_input(input) -> messages.CardanoTxInputType:
if not all(input.get(k) is not None for k in REQUIRED_FIELDS_INPUT):
raise ValueError("The input is missing some fields")
path = input["path"]
return messages.CardanoTxInputType(
address_n=tools.parse_path(path),
prev_hash=bytes.fromhex(input["prev_hash"]),
prev_index=input["prev_index"],
type=input["type"],
)
def create_output(output) -> messages.CardanoTxOutputType:
if not output.get("amount") or not (output.get("address") or output.get("path")):
raise ValueError("The output is missing some fields")
if output.get("path"):
path = output["path"]
return messages.CardanoTxOutputType(
address_n=tools.parse_path(path), amount=int(output["amount"])
)
return messages.CardanoTxOutputType(
address=output["address"], amount=int(output["amount"])
)

View File

@ -0,0 +1,21 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import warnings
from .tests.support.ckd_public import * # noqa
warnings.warn("ckd_public module is deprecated and will be removed", DeprecationWarning)

379
python/trezorlib/client.py Normal file
View File

@ -0,0 +1,379 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import logging
import sys
import warnings
from mnemonic import Mnemonic
from . import MINIMUM_FIRMWARE_VERSION, exceptions, messages, tools
if sys.version_info.major < 3:
raise Exception("Trezorlib does not support Python 2 anymore.")
LOG = logging.getLogger(__name__)
VENDORS = ("bitcointrezor.com", "trezor.io")
MAX_PASSPHRASE_LENGTH = 50
DEPRECATION_ERROR = """
Incompatible Trezor library detected.
(Original error: {})
""".strip()
OUTDATED_FIRMWARE_ERROR = """
Your Trezor firmware is out of date. Update it with the following command:
trezorctl firmware-update
Or visit https://wallet.trezor.io/
""".strip()
def get_buttonrequest_value(code):
# Converts integer code to its string representation of ButtonRequestType
return [
k
for k in dir(messages.ButtonRequestType)
if getattr(messages.ButtonRequestType, k) == code
][0]
def get_default_client(path=None, ui=None, **kwargs):
"""Get a client for a connected Trezor device.
Returns a TrezorClient instance with minimum fuss.
If no path is specified, finds first connected Trezor. Otherwise performs
a prefix-search for the specified device. If no UI is supplied, instantiates
the default CLI UI.
"""
from .transport import get_transport
from .ui import ClickUI
transport = get_transport(path, prefix_search=True)
if ui is None:
ui = ClickUI()
return TrezorClient(transport, ui, **kwargs)
class TrezorClient:
"""Trezor client, a connection to a Trezor device.
This class allows you to manage connection state, send and receive protobuf
messages, handle user interactions, and perform some generic tasks
(send a cancel message, initialize or clear a session, ping the device).
You have to provide a transport, i.e., a raw connection to the device. You can use
`trezorlib.transport.get_transport` to find one.
You have to provide an UI implementation for the three kinds of interaction:
- button request (notify the user that their interaction is needed)
- PIN request (on T1, ask the user to input numbers for a PIN matrix)
- passphrase request (ask the user to enter a passphrase)
See `trezorlib.ui` for details.
You can supply a `state` you saved in the previous session. If you do,
the user might not need to enter their passphrase again.
"""
def __init__(self, transport, ui=None, state=None):
LOG.info("creating client instance for device: {}".format(transport.get_path()))
self.transport = transport
self.ui = ui
self.state = state
if ui is None:
warnings.warn("UI class not supplied. This will probably crash soon.")
self.session_counter = 0
self.init_device()
def open(self):
if self.session_counter == 0:
self.transport.begin_session()
self.session_counter += 1
def close(self):
if self.session_counter == 1:
self.transport.end_session()
self.session_counter -= 1
def cancel(self):
self._raw_write(messages.Cancel())
def call_raw(self, msg):
__tracebackhide__ = True # for pytest # pylint: disable=W0612
self._raw_write(msg)
return self._raw_read()
def _raw_write(self, msg):
__tracebackhide__ = True # for pytest # pylint: disable=W0612
self.transport.write(msg)
def _raw_read(self):
__tracebackhide__ = True # for pytest # pylint: disable=W0612
return self.transport.read()
def _callback_pin(self, msg):
try:
pin = self.ui.get_pin(msg.type)
except exceptions.Cancelled:
self.call_raw(messages.Cancel())
raise
if not pin.isdigit():
self.call_raw(messages.Cancel())
raise ValueError("Non-numeric PIN provided")
resp = self.call_raw(messages.PinMatrixAck(pin=pin))
if isinstance(resp, messages.Failure) and resp.code in (
messages.FailureType.PinInvalid,
messages.FailureType.PinCancelled,
messages.FailureType.PinExpected,
):
raise exceptions.PinException(resp.code, resp.message)
else:
return resp
def _callback_passphrase(self, msg):
if msg.on_device:
passphrase = None
else:
try:
passphrase = self.ui.get_passphrase()
except exceptions.Cancelled:
self.call_raw(messages.Cancel())
raise
passphrase = Mnemonic.normalize_string(passphrase)
if len(passphrase) > MAX_PASSPHRASE_LENGTH:
self.call_raw(messages.Cancel())
raise ValueError("Passphrase too long")
resp = self.call_raw(
messages.PassphraseAck(passphrase=passphrase, state=self.state)
)
if isinstance(resp, messages.PassphraseStateRequest):
# TODO report to the user that the passphrase has changed?
self.state = resp.state
return self.call_raw(messages.PassphraseStateAck())
else:
return resp
def _callback_button(self, msg):
__tracebackhide__ = True # for pytest # pylint: disable=W0612
# do this raw - send ButtonAck first, notify UI later
self._raw_write(messages.ButtonAck())
self.ui.button_request(msg.code)
return self._raw_read()
@tools.session
def call(self, msg):
self.check_firmware_version()
resp = self.call_raw(msg)
while True:
if isinstance(resp, messages.PinMatrixRequest):
resp = self._callback_pin(resp)
elif isinstance(resp, messages.PassphraseRequest):
resp = self._callback_passphrase(resp)
elif isinstance(resp, messages.ButtonRequest):
resp = self._callback_button(resp)
elif isinstance(resp, messages.Failure):
if resp.code == messages.FailureType.ActionCancelled:
raise exceptions.Cancelled
raise exceptions.TrezorFailure(resp)
else:
return resp
@tools.session
def init_device(self):
resp = self.call_raw(messages.Initialize(state=self.state))
if not isinstance(resp, messages.Features):
raise exceptions.TrezorException("Unexpected initial response")
else:
self.features = resp
if self.features.vendor not in VENDORS:
raise RuntimeError("Unsupported device")
# A side-effect of this is a sanity check for broken protobuf definitions.
# If the `vendor` field doesn't exist, you probably have a mismatched
# checkout of trezor-common.
self.version = (
self.features.major_version,
self.features.minor_version,
self.features.patch_version,
)
self.check_firmware_version(warn_only=True)
def is_outdated(self):
if self.features.bootloader_mode:
return False
model = self.features.model or "1"
required_version = MINIMUM_FIRMWARE_VERSION[model]
return self.version < required_version
def check_firmware_version(self, warn_only=False):
if self.is_outdated():
if warn_only:
warnings.warn(OUTDATED_FIRMWARE_ERROR, stacklevel=2)
else:
raise exceptions.OutdatedFirmwareError(OUTDATED_FIRMWARE_ERROR)
@tools.expect(messages.Success, field="message")
def ping(
self,
msg,
button_protection=False,
pin_protection=False,
passphrase_protection=False,
):
# We would like ping to work on any valid TrezorClient instance, but
# due to the protection modes, we need to go through self.call, and that will
# raise an exception if the firmware is too old.
# So we short-circuit the simplest variant of ping with call_raw.
if not button_protection and not pin_protection and not passphrase_protection:
# XXX this should be: `with self:`
try:
self.open()
return self.call_raw(messages.Ping(message=msg))
finally:
self.close()
msg = messages.Ping(
message=msg,
button_protection=button_protection,
pin_protection=pin_protection,
passphrase_protection=passphrase_protection,
)
return self.call(msg)
def get_device_id(self):
return self.features.device_id
@tools.expect(messages.Success, field="message")
@tools.session
def clear_session(self):
return self.call_raw(messages.ClearSession())
def MovedTo(where):
def moved_to(*args, **kwargs):
msg = "Function has been moved to " + where
raise RuntimeError(DEPRECATION_ERROR.format(msg))
return moved_to
class ProtocolMixin(object):
"""Fake mixin for old-style software that constructed TrezorClient class
from separate mixins.
Now it only simulates existence of original attributes to prevent some early
crashes, and raises errors when any of the attributes are actually called.
"""
def __init__(self, *args, **kwargs):
warnings.warn("TrezorClient mixins are not supported anymore")
self.tx_api = None # Electrum checks that this attribute exists
super().__init__(*args, **kwargs)
def set_tx_api(self, tx_api):
warnings.warn("set_tx_api is deprecated, use new arguments to sign_tx")
@staticmethod
def expand_path(n):
warnings.warn(
"expand_path is deprecated, use tools.parse_path",
DeprecationWarning,
stacklevel=2,
)
return tools.parse_path(n)
# Device functionality
wipe_device = MovedTo("device.wipe")
recovery_device = MovedTo("device.recover")
reset_device = MovedTo("device.reset")
backup_device = MovedTo("device.backup")
set_u2f_counter = MovedTo("device.set_u2f_counter")
apply_settings = MovedTo("device.apply_settings")
apply_flags = MovedTo("device.apply_flags")
change_pin = MovedTo("device.change_pin")
# Firmware functionality
firmware_update = MovedTo("firmware.update")
# BTC-like functionality
get_public_node = MovedTo("btc.get_public_node")
get_address = MovedTo("btc.get_address")
sign_tx = MovedTo("btc.sign_tx")
sign_message = MovedTo("btc.sign_message")
verify_message = MovedTo("btc.verify_message")
# CoSi functionality
cosi_commit = MovedTo("cosi.commit")
cosi_sign = MovedTo("cosi.sign")
# Ethereum functionality
ethereum_get_address = MovedTo("ethereum.get_address")
ethereum_sign_tx = MovedTo("ethereum.sign_tx")
ethereum_sign_message = MovedTo("ethereum.sign_message")
ethereum_verify_message = MovedTo("ethereum.verify_message")
# Lisk functionality
lisk_get_address = MovedTo("lisk.get_address")
lisk_get_public_key = MovedTo("lisk.get_public_key")
lisk_sign_message = MovedTo("lisk.sign_message")
lisk_verify_message = MovedTo("lisk.verify_message")
lisk_sign_tx = MovedTo("lisk.sign_tx")
# NEM functionality
nem_get_address = MovedTo("nem.get_address")
nem_sign_tx = MovedTo("nem.sign_tx")
# Stellar functionality
stellar_get_address = MovedTo("stellar.get_address")
stellar_sign_transaction = MovedTo("stellar.sign_tx")
# Miscellaneous cryptographic functionality
get_entropy = MovedTo("misc.get_entropy")
sign_identity = MovedTo("misc.sign_identity")
get_ecdh_session_key = MovedTo("misc.get_ecdh_session_key")
encrypt_keyvalue = MovedTo("misc.encrypt_keyvalue")
decrypt_keyvalue = MovedTo("misc.decrypt_keyvalue")
# Debug device functionality
load_device_by_mnemonic = MovedTo("debuglink.load_device_by_mnemonic")
load_device_by_xprv = MovedTo("debuglink.load_device_by_xprv")
class BaseClient:
"""Compatibility proxy for original BaseClient class.
Prevents early crash in Electrum forks and possibly other software.
"""
def __init__(self, *args, **kwargs):
warnings.warn("TrezorClient mixins are not supported anymore")
self.trezor_client = TrezorClient(*args, **kwargs)
def __getattr__(self, key):
return getattr(self.trezor_client, key)
# further Electrum compatibility
proto = None

51
python/trezorlib/coins.py Normal file
View File

@ -0,0 +1,51 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import json
import os.path
from .tx_api import TxApi
COINS_JSON = os.path.join(os.path.dirname(__file__), "coins.json")
def _load_coins_json():
# Load coins.json to local variables
# NOTE: coins.json comes from 'vendor/trezor-common/coins.json',
# which is a git submodule. If you're trying to run trezorlib directly
# from the checkout (or tarball), initialize the submodule with:
# $ git submodule update --init
# and install coins.json with:
# $ python setup.py prebuild
with open(COINS_JSON) as coins_json:
return json.load(coins_json)
# exported variables
__all__ = ["by_name", "slip44", "tx_api"]
try:
coins_list = _load_coins_json()
by_name = {coin["coin_name"]: coin for coin in coins_list}
except Exception as e:
raise ImportError("Failed to load coins.json. Check your installation.") from e
slip44 = {name: coin["slip44"] for name, coin in by_name.items()}
tx_api = {
name: TxApi(coin)
for name, coin in by_name.items()
if coin["blockbook"] or coin["bitcore"]
}

138
python/trezorlib/cosi.py Normal file
View File

@ -0,0 +1,138 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from functools import reduce
from typing import Iterable, List, Tuple
from . import _ed25519, messages
from .tools import expect
# XXX, these could be NewType's, but that would infect users of the cosi module with these types as well.
# Unsure if we want that.
Ed25519PrivateKey = bytes
Ed25519PublicPoint = bytes
Ed25519Signature = bytes
def combine_keys(pks: Iterable[Ed25519PublicPoint]) -> Ed25519PublicPoint:
"""Combine a list of Ed25519 points into a "global" CoSi key."""
P = [_ed25519.decodepoint(pk) for pk in pks]
combine = reduce(_ed25519.edwards_add, P)
return Ed25519PublicPoint(_ed25519.encodepoint(combine))
def combine_sig(
global_R: Ed25519PublicPoint, sigs: Iterable[Ed25519Signature]
) -> Ed25519Signature:
"""Combine a list of signatures into a single CoSi signature."""
S = [_ed25519.decodeint(si) for si in sigs]
s = sum(S) % _ed25519.l
sig = global_R + _ed25519.encodeint(s)
return Ed25519Signature(sig)
def get_nonce(
sk: Ed25519PrivateKey, data: bytes, ctr: int = 0
) -> Tuple[int, Ed25519PublicPoint]:
"""Calculate CoSi nonces for given data.
These differ from Ed25519 deterministic nonces in that there is a counter appended at end.
Returns both the private point `r` and the partial signature `R`.
`r` is returned for performance reasons: :func:`sign_with_privkey`
takes it as its `nonce` argument so that it doesn't repeat the `get_nonce` call.
`R` should be combined with other partial signatures through :func:`combine_keys`
to obtain a "global commitment".
"""
# r = hash(hash(sk)[b .. 2b] + M + ctr)
# R = rB
h = _ed25519.H(sk)
bytesize = _ed25519.b // 8
assert len(h) == bytesize * 2
r = _ed25519.Hint(h[bytesize:] + data + ctr.to_bytes(4, "big"))
R = _ed25519.scalarmult(_ed25519.B, r)
return r, Ed25519PublicPoint(_ed25519.encodepoint(R))
def verify(
signature: Ed25519Signature, digest: bytes, pub_key: Ed25519PublicPoint
) -> None:
"""Verify Ed25519 signature. Raise exception if the signature is invalid."""
# XXX this *might* change to bool function
_ed25519.checkvalid(signature, digest, pub_key)
def verify_m_of_n(
signature: Ed25519Signature,
digest: bytes,
m: int,
n: int,
mask: int,
keys: List[Ed25519PublicPoint],
) -> None:
if m < 1:
raise ValueError("At least 1 signer must be specified")
selected_keys = [keys[i] for i in range(n) if mask & (1 << i)]
if len(selected_keys) < m:
raise ValueError(
"Not enough signers ({} required, {} found)".format(m, len(selected_keys))
)
global_pk = combine_keys(selected_keys)
return verify(signature, digest, global_pk)
def pubkey_from_privkey(privkey: Ed25519PrivateKey) -> Ed25519PublicPoint:
"""Interpret 32 bytes of data as an Ed25519 private key.
Calculate and return the corresponding public key.
"""
return Ed25519PublicPoint(_ed25519.publickey_unsafe(privkey))
def sign_with_privkey(
digest: bytes,
privkey: Ed25519PrivateKey,
global_pubkey: Ed25519PublicPoint,
nonce: int,
global_commit: Ed25519PublicPoint,
) -> Ed25519Signature:
"""Create a CoSi signature of `digest` with the supplied private key.
This function needs to know the global public key and global commitment.
"""
h = _ed25519.H(privkey)
a = _ed25519.decodecoord(h)
S = (nonce + _ed25519.Hint(global_commit + global_pubkey + digest) * a) % _ed25519.l
return Ed25519Signature(_ed25519.encodeint(S))
# ====== Client functions ====== #
@expect(messages.CosiCommitment)
def commit(client, n, data):
return client.call(messages.CosiCommit(address_n=n, data=data))
@expect(messages.CosiSignature)
def sign(client, n, data, global_commitment, global_pubkey):
return client.call(
messages.CosiSign(
address_n=n,
data=data,
global_commitment=global_commitment,
global_pubkey=global_pubkey,
)
)

View File

@ -0,0 +1,505 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from copy import deepcopy
from mnemonic import Mnemonic
from . import messages as proto, protobuf, tools
from .client import TrezorClient
from .tools import expect
EXPECTED_RESPONSES_CONTEXT_LINES = 3
class DebugLink:
def __init__(self, transport, auto_interact=True):
self.transport = transport
self.allow_interactions = auto_interact
def open(self):
self.transport.begin_session()
def close(self):
self.transport.end_session()
def _call(self, msg, nowait=False):
self.transport.write(msg)
if nowait:
return None
ret = self.transport.read()
return ret
def state(self):
return self._call(proto.DebugLinkGetState())
def read_pin(self):
state = self.state()
return state.pin, state.matrix
def read_pin_encoded(self):
return self.encode_pin(*self.read_pin())
def encode_pin(self, pin, matrix=None):
"""Transform correct PIN according to the displayed matrix."""
if matrix is None:
_, matrix = self.read_pin()
return "".join([str(matrix.index(p) + 1) for p in pin])
def read_layout(self):
obj = self._call(proto.DebugLinkGetState())
return obj.layout
def read_mnemonic_secret(self):
obj = self._call(proto.DebugLinkGetState())
return obj.mnemonic_secret
def read_recovery_word(self):
obj = self._call(proto.DebugLinkGetState())
return (obj.recovery_fake_word, obj.recovery_word_pos)
def read_reset_word(self):
obj = self._call(proto.DebugLinkGetState())
return obj.reset_word
def read_reset_word_pos(self):
obj = self._call(proto.DebugLinkGetState())
return obj.reset_word_pos
def read_reset_entropy(self):
obj = self._call(proto.DebugLinkGetState())
return obj.reset_entropy
def read_passphrase_protection(self):
obj = self._call(proto.DebugLinkGetState())
return obj.passphrase_protection
def input(self, word=None, button=None, swipe=None):
if not self.allow_interactions:
return
decision = proto.DebugLinkDecision()
if button is not None:
decision.yes_no = button
elif word is not None:
decision.input = word
elif swipe is not None:
decision.up_down = swipe
else:
raise ValueError("You need to provide input data.")
self._call(decision, nowait=True)
def press_button(self, yes_no):
self._call(proto.DebugLinkDecision(yes_no=yes_no), nowait=True)
def press_yes(self):
self.input(button=True)
def press_no(self):
self.input(button=False)
def swipe_up(self):
self.input(swipe=True)
def swipe_down(self):
self.input(swipe=False)
def stop(self):
self._call(proto.DebugLinkStop(), nowait=True)
@expect(proto.DebugLinkMemory, field="memory")
def memory_read(self, address, length):
return self._call(proto.DebugLinkMemoryRead(address=address, length=length))
def memory_write(self, address, memory, flash=False):
self._call(
proto.DebugLinkMemoryWrite(address=address, memory=memory, flash=flash),
nowait=True,
)
def flash_erase(self, sector):
self._call(proto.DebugLinkFlashErase(sector=sector), nowait=True)
class NullDebugLink(DebugLink):
def __init__(self):
super().__init__(None)
def open(self):
pass
def close(self):
pass
def _call(self, msg, nowait=False):
if not nowait:
if isinstance(msg, proto.DebugLinkGetState):
return proto.DebugLinkState()
else:
raise RuntimeError("unexpected call to a fake debuglink")
class DebugUI:
INPUT_FLOW_DONE = object()
def __init__(self, debuglink: DebugLink):
self.debuglink = debuglink
self.pin = None
self.passphrase = "sphinx of black quartz, judge my wov"
self.input_flow = None
def button_request(self, code):
if self.input_flow is None:
self.debuglink.press_yes()
elif self.input_flow is self.INPUT_FLOW_DONE:
raise AssertionError("input flow ended prematurely")
else:
try:
self.input_flow.send(code)
except StopIteration:
self.input_flow = self.INPUT_FLOW_DONE
def get_pin(self, code=None):
if self.pin:
return self.pin
else:
return self.debuglink.read_pin_encoded()
def get_passphrase(self):
return self.passphrase
class TrezorClientDebugLink(TrezorClient):
# This class implements automatic responses
# and other functionality for unit tests
# for various callbacks, created in order
# to automatically pass unit tests.
#
# This mixing should be used only for purposes
# of unit testing, because it will fail to work
# without special DebugLink interface provided
# by the device.
def __init__(self, transport, auto_interact=True):
try:
debug_transport = transport.find_debug()
self.debug = DebugLink(debug_transport, auto_interact)
except Exception:
if not auto_interact:
self.debug = NullDebugLink()
else:
raise
self.ui = DebugUI(self.debug)
self.in_with_statement = 0
self.screenshot_id = 0
self.filters = {}
# Always press Yes and provide correct pin
self.setup_debuglink(True, True)
# Do not expect any specific response from device
self.expected_responses = None
self.current_response = None
# Use blank passphrase
self.set_passphrase("")
super().__init__(transport, ui=self.ui)
def open(self):
super().open()
self.debug.open()
def close(self):
self.debug.close()
super().close()
def set_filter(self, message_type, callback):
self.filters[message_type] = callback
def _filter_message(self, msg):
message_type = msg.__class__
callback = self.filters.get(message_type)
if callable(callback):
return callback(deepcopy(msg))
else:
return msg
def set_input_flow(self, input_flow):
if input_flow is None:
self.ui.input_flow = None
return
if callable(input_flow):
input_flow = input_flow()
if not hasattr(input_flow, "send"):
raise RuntimeError("input_flow should be a generator function")
self.ui.input_flow = input_flow
next(input_flow) # can't send before first yield
def __enter__(self):
# For usage in with/expected_responses
self.in_with_statement += 1
return self
def __exit__(self, _type, value, traceback):
self.in_with_statement -= 1
if _type is not None:
# Another exception raised
return False
if self.expected_responses is None:
# no need to check anything else
return False
# return isinstance(value, TypeError)
# Evaluate missed responses in 'with' statement
if self.current_response < len(self.expected_responses):
self._raise_unexpected_response(None)
# Cleanup
self.expected_responses = None
self.current_response = None
return False
def set_expected_responses(self, expected):
if not self.in_with_statement:
raise RuntimeError("Must be called inside 'with' statement")
self.expected_responses = expected
self.current_response = 0
def setup_debuglink(self, button, pin_correct):
# self.button = button # True -> YES button, False -> NO button
if pin_correct:
self.ui.pin = None
else:
self.ui.pin = "444222"
def set_passphrase(self, passphrase):
self.ui.passphrase = Mnemonic.normalize_string(passphrase)
def set_mnemonic(self, mnemonic):
self.mnemonic = Mnemonic.normalize_string(mnemonic).split(" ")
def _raw_read(self):
__tracebackhide__ = True # for pytest # pylint: disable=W0612
# if SCREENSHOT and self.debug:
# from PIL import Image
# layout = self.debug.state().layout
# im = Image.new("RGB", (128, 64))
# pix = im.load()
# for x in range(128):
# for y in range(64):
# rx, ry = 127 - x, 63 - y
# if (ord(layout[rx + (ry / 8) * 128]) & (1 << (ry % 8))) > 0:
# pix[x, y] = (255, 255, 255)
# im.save("scr%05d.png" % self.screenshot_id)
# self.screenshot_id += 1
resp = super()._raw_read()
resp = self._filter_message(resp)
self._check_request(resp)
return resp
def _raw_write(self, msg):
return super()._raw_write(self._filter_message(msg))
def _raise_unexpected_response(self, msg):
__tracebackhide__ = True # for pytest # pylint: disable=W0612
start_at = max(self.current_response - EXPECTED_RESPONSES_CONTEXT_LINES, 0)
stop_at = min(
self.current_response + EXPECTED_RESPONSES_CONTEXT_LINES + 1,
len(self.expected_responses),
)
output = []
output.append("Expected responses:")
if start_at > 0:
output.append(" (...{} previous responses omitted)".format(start_at))
for i in range(start_at, stop_at):
exp = self.expected_responses[i]
prefix = " " if i != self.current_response else ">>> "
set_fields = {
key: value
for key, value in exp.__dict__.items()
if value is not None and value != []
}
oneline_str = ", ".join("{}={!r}".format(*i) for i in set_fields.items())
if len(oneline_str) < 60:
output.append(
"{}{}({})".format(prefix, exp.__class__.__name__, oneline_str)
)
else:
item = []
item.append("{}{}(".format(prefix, exp.__class__.__name__))
for key, value in set_fields.items():
item.append("{} {}={!r}".format(prefix, key, value))
item.append("{})".format(prefix))
output.append("\n".join(item))
if stop_at < len(self.expected_responses):
omitted = len(self.expected_responses) - stop_at
output.append(" (...{} following responses omitted)".format(omitted))
output.append("")
if msg is not None:
output.append("Actually received:")
output.append(protobuf.format_message(msg))
else:
output.append("This message was never received.")
raise AssertionError("\n".join(output))
def _check_request(self, msg):
__tracebackhide__ = True # for pytest # pylint: disable=W0612
if self.expected_responses is None:
return
if self.current_response >= len(self.expected_responses):
raise AssertionError(
"No more messages were expected, but we got:\n"
+ protobuf.format_message(msg)
)
expected = self.expected_responses[self.current_response]
if msg.__class__ != expected.__class__:
self._raise_unexpected_response(msg)
for field, value in expected.__dict__.items():
if value is None or value == []:
continue
if getattr(msg, field) != value:
self._raise_unexpected_response(msg)
self.current_response += 1
def mnemonic_callback(self, _):
word, pos = self.debug.read_recovery_word()
if word != "":
return word
if pos != 0:
return self.mnemonic[pos - 1]
raise RuntimeError("Unexpected call")
@expect(proto.Success, field="message")
def load_device_by_mnemonic(
client,
mnemonic,
pin,
passphrase_protection,
label,
language="english",
skip_checksum=False,
expand=False,
):
# Convert mnemonic to UTF8 NKFD
mnemonic = Mnemonic.normalize_string(mnemonic)
# Convert mnemonic to ASCII stream
mnemonic = mnemonic.encode()
m = Mnemonic("english")
if expand:
mnemonic = m.expand(mnemonic)
if not skip_checksum and not m.check(mnemonic):
raise ValueError("Invalid mnemonic checksum")
if client.features.initialized:
raise RuntimeError(
"Device is initialized already. Call device.wipe() and try again."
)
resp = client.call(
proto.LoadDevice(
mnemonic=mnemonic,
pin=pin,
passphrase_protection=passphrase_protection,
language=language,
label=label,
skip_checksum=skip_checksum,
)
)
client.init_device()
return resp
@expect(proto.Success, field="message")
def load_device_by_xprv(client, xprv, pin, passphrase_protection, label, language):
if client.features.initialized:
raise RuntimeError(
"Device is initialized already. Call wipe_device() and try again."
)
if xprv[0:4] not in ("xprv", "tprv"):
raise ValueError("Unknown type of xprv")
if not 100 < len(xprv) < 112: # yes this is correct in Python
raise ValueError("Invalid length of xprv")
node = proto.HDNodeType()
data = tools.b58decode(xprv, None).hex()
if data[90:92] != "00":
raise ValueError("Contain invalid private key")
checksum = (tools.btc_hash(bytes.fromhex(data[:156]))[:4]).hex()
if checksum != data[156:]:
raise ValueError("Checksum doesn't match")
# version 0488ade4
# depth 00
# fingerprint 00000000
# child_num 00000000
# chaincode 873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508
# privkey 00e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35
# checksum e77e9d71
node.depth = int(data[8:10], 16)
node.fingerprint = int(data[10:18], 16)
node.child_num = int(data[18:26], 16)
node.chain_code = bytes.fromhex(data[26:90])
node.private_key = bytes.fromhex(data[92:156]) # skip 0x00 indicating privkey
resp = client.call(
proto.LoadDevice(
node=node,
pin=pin,
passphrase_protection=passphrase_protection,
language=language,
label=label,
)
)
client.init_device()
return resp
@expect(proto.Success, field="message")
def self_test(client):
if client.features.bootloader_mode is not True:
raise RuntimeError("Device must be in bootloader mode")
return client.call(
proto.SelfTest(
payload=b"\x00\xFF\x55\xAA\x66\x99\x33\xCCABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!\x00\xFF\x55\xAA\x66\x99\x33\xCC"
)
)

210
python/trezorlib/device.py Normal file
View File

@ -0,0 +1,210 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import os
import time
import warnings
from . import messages as proto
from .exceptions import Cancelled
from .tools import expect, session
from .transport import enumerate_devices, get_transport
RECOVERY_BACK = "\x08" # backspace character, sent literally
class TrezorDevice:
"""
This class is deprecated. (There is no reason for it to exist in the first
place, it is nothing but a collection of two functions.)
Instead, please use functions from the ``trezorlib.transport`` module.
"""
@classmethod
def enumerate(cls):
warnings.warn("TrezorDevice is deprecated.", DeprecationWarning)
return enumerate_devices()
@classmethod
def find_by_path(cls, path):
warnings.warn("TrezorDevice is deprecated.", DeprecationWarning)
return get_transport(path, prefix_search=False)
@expect(proto.Success, field="message")
def apply_settings(
client,
label=None,
language=None,
use_passphrase=None,
homescreen=None,
passphrase_source=None,
auto_lock_delay_ms=None,
display_rotation=None,
):
settings = proto.ApplySettings()
if label is not None:
settings.label = label
if language:
settings.language = language
if use_passphrase is not None:
settings.use_passphrase = use_passphrase
if homescreen is not None:
settings.homescreen = homescreen
if passphrase_source is not None:
settings.passphrase_source = passphrase_source
if auto_lock_delay_ms is not None:
settings.auto_lock_delay_ms = auto_lock_delay_ms
if display_rotation is not None:
settings.display_rotation = display_rotation
out = client.call(settings)
client.init_device() # Reload Features
return out
@expect(proto.Success, field="message")
def apply_flags(client, flags):
out = client.call(proto.ApplyFlags(flags=flags))
client.init_device() # Reload Features
return out
@expect(proto.Success, field="message")
def change_pin(client, remove=False):
ret = client.call(proto.ChangePin(remove=remove))
client.init_device() # Re-read features
return ret
@expect(proto.Success, field="message")
def set_u2f_counter(client, u2f_counter):
ret = client.call(proto.SetU2FCounter(u2f_counter=u2f_counter))
return ret
@expect(proto.Success, field="message")
def wipe(client):
ret = client.call(proto.WipeDevice())
client.init_device()
return ret
@expect(proto.Success, field="message")
def recover(
client,
word_count=24,
passphrase_protection=False,
pin_protection=True,
label=None,
language="english",
input_callback=None,
type=proto.RecoveryDeviceType.ScrambledWords,
dry_run=False,
u2f_counter=None,
):
if client.features.model == "1" and input_callback is None:
raise RuntimeError("Input callback required for Trezor One")
if word_count not in (12, 18, 24):
raise ValueError("Invalid word count. Use 12/18/24")
if client.features.initialized and not dry_run:
raise RuntimeError(
"Device already initialized. Call device.wipe() and try again."
)
if u2f_counter is None:
u2f_counter = int(time.time())
res = client.call(
proto.RecoveryDevice(
word_count=word_count,
passphrase_protection=bool(passphrase_protection),
pin_protection=bool(pin_protection),
label=label,
language=language,
enforce_wordlist=True,
type=type,
dry_run=dry_run,
u2f_counter=u2f_counter,
)
)
while isinstance(res, proto.WordRequest):
try:
inp = input_callback(res.type)
res = client.call(proto.WordAck(word=inp))
except Cancelled:
res = client.call(proto.Cancel())
client.init_device()
return res
@expect(proto.Success, field="message")
@session
def reset(
client,
display_random=False,
strength=None,
passphrase_protection=False,
pin_protection=True,
label=None,
language="english",
u2f_counter=0,
skip_backup=False,
no_backup=False,
):
if client.features.initialized:
raise RuntimeError(
"Device is initialized already. Call wipe_device() and try again."
)
if strength is None:
if client.features.model == "1":
strength = 256
else:
strength = 128
# Begin with device reset workflow
msg = proto.ResetDevice(
display_random=bool(display_random),
strength=strength,
passphrase_protection=bool(passphrase_protection),
pin_protection=bool(pin_protection),
language=language,
label=label,
u2f_counter=u2f_counter,
skip_backup=bool(skip_backup),
no_backup=bool(no_backup),
)
resp = client.call(msg)
if not isinstance(resp, proto.EntropyRequest):
raise RuntimeError("Invalid response, expected EntropyRequest")
external_entropy = os.urandom(32)
# LOG.debug("Computer generated entropy: " + external_entropy.hex())
ret = client.call(proto.EntropyAck(entropy=external_entropy))
client.init_device()
return ret
@expect(proto.Success, field="message")
def backup(client):
ret = client.call(proto.BackupDevice())
return ret

View File

@ -0,0 +1,108 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from . import messages as proto
from .tools import CallException, expect, normalize_nfc, session
def int_to_big_endian(value):
return value.to_bytes((value.bit_length() + 7) // 8, "big")
# ====== Client functions ====== #
@expect(proto.EthereumAddress, field="address")
def get_address(client, n, show_display=False, multisig=None):
return client.call(proto.EthereumGetAddress(address_n=n, show_display=show_display))
@expect(proto.EthereumPublicKey)
def get_public_node(client, n, show_display=False):
return client.call(
proto.EthereumGetPublicKey(address_n=n, show_display=show_display)
)
@session
def sign_tx(
client,
n,
nonce,
gas_price,
gas_limit,
to,
value,
data=None,
chain_id=None,
tx_type=None,
):
msg = proto.EthereumSignTx(
address_n=n,
nonce=int_to_big_endian(nonce),
gas_price=int_to_big_endian(gas_price),
gas_limit=int_to_big_endian(gas_limit),
value=int_to_big_endian(value),
)
if to:
msg.to = to
if data:
msg.data_length = len(data)
data, chunk = data[1024:], data[:1024]
msg.data_initial_chunk = chunk
if chain_id:
msg.chain_id = chain_id
if tx_type is not None:
msg.tx_type = tx_type
response = client.call(msg)
while response.data_length is not None:
data_length = response.data_length
data, chunk = data[data_length:], data[:data_length]
response = client.call(proto.EthereumTxAck(data_chunk=chunk))
# https://github.com/trezor/trezor-core/pull/311
# only signature bit returned. recalculate signature_v
if response.signature_v <= 1:
response.signature_v += 2 * chain_id + 35
return response.signature_v, response.signature_r, response.signature_s
@expect(proto.EthereumMessageSignature)
def sign_message(client, n, message):
message = normalize_nfc(message)
return client.call(proto.EthereumSignMessage(address_n=n, message=message))
def verify_message(client, address, signature, message):
message = normalize_nfc(message)
try:
resp = client.call(
proto.EthereumVerifyMessage(
address=address, signature=signature, message=message
)
)
except CallException as e:
resp = e
if isinstance(resp, proto.Success):
return True
return False

View File

@ -0,0 +1,51 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
class TrezorException(Exception):
pass
class TrezorFailure(TrezorException):
def __init__(self, failure):
self.failure = failure
# TODO: this is backwards compatibility with tests. it should be changed
super().__init__(self.failure.code, self.failure.message)
def __str__(self):
from .messages import FailureType
types = {
getattr(FailureType, name): name
for name in dir(FailureType)
if not name.startswith("_")
}
if self.failure.message is not None:
return "{}: {}".format(types[self.failure.code], self.failure.message)
else:
return types[self.failure.code]
class PinException(TrezorException):
pass
class Cancelled(TrezorException):
pass
class OutdatedFirmwareError(TrezorException):
pass

View File

@ -0,0 +1,435 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import hashlib
from enum import Enum
from typing import Callable, List, NewType, Tuple
import construct as c
import ecdsa
import pyblake2
from . import cosi, messages, tools
V1_SIGNATURE_SLOTS = 3
V1_BOOTLOADER_KEYS = {
1: "04d571b7f148c5e4232c3814f777d8faeaf1a84216c78d569b71041ffc768a5b2d810fc3bb134dd026b57e65005275aedef43e155f48fc11a32ec790a93312bd58",
2: "0463279c0c0866e50c05c799d32bd6bab0188b6de06536d1109d2ed9ce76cb335c490e55aee10cc901215132e853097d5432eda06b792073bd7740c94ce4516cb1",
3: "0443aedbb6f7e71c563f8ed2ef64ec9981482519e7ef4f4aa98b27854e8c49126d4956d300ab45fdc34cd26bc8710de0a31dbdf6de7435fd0b492be70ac75fde58",
4: "04877c39fd7c62237e038235e9c075dab261630f78eeb8edb92487159fffedfdf6046c6f8b881fa407c4a4ce6c28de0b19c1f4e29f1fcbc5a58ffd1432a3e0938a",
5: "047384c51ae81add0a523adbb186c91b906ffb64c2c765802bf26dbd13bdf12c319e80c2213a136c8ee03d7874fd22b70d68e7dee469decfbbb510ee9a460cda45",
}
V2_BOOTLOADER_KEYS = [
bytes.fromhex("c2c87a49c5a3460977fbb2ec9dfe60f06bd694db8244bd4981fe3b7a26307f3f"),
bytes.fromhex("80d036b08739b846f4cb77593078deb25dc9487aedcf52e30b4fb7cd7024178a"),
bytes.fromhex("b8307a71f552c60a4cbb317ff48b82cdbf6b6bb5f04c920fec7badf017883751"),
]
V2_BOOTLOADER_M = 2
V2_BOOTLOADER_N = 3
ONEV2_CHUNK_SIZE = 1024 * 64
V2_CHUNK_SIZE = 1024 * 128
def _transform_vendor_trust(data: bytes) -> bytes:
"""Byte-swap and bit-invert the VendorTrust field.
Vendor trust is interpreted as a bitmask in a 16-bit little-endian integer,
with the added twist that 0 means set and 1 means unset.
We feed it to a `BitStruct` that expects a big-endian sequence where bits have
the traditional meaning. We must therefore do a bitwise negation of each byte,
and return them in reverse order. This is the same transformation both ways,
fortunately, so we don't need two separate functions.
"""
return bytes(~b & 0xFF for b in data)[::-1]
class FirmwareIntegrityError(Exception):
pass
class InvalidSignatureError(FirmwareIntegrityError):
pass
class Unsigned(FirmwareIntegrityError):
pass
# fmt: off
Toif = c.Struct(
"magic" / c.Const(b"TOI"),
"format" / c.Enum(c.Byte, full_color=b"f", grayscale=b"g"),
"width" / c.Int16ul,
"height" / c.Int16ul,
"data" / c.Prefixed(c.Int32ul, c.GreedyBytes),
)
VendorTrust = c.Transformed(c.BitStruct(
"reserved" / c.Default(c.BitsInteger(9), 0),
"show_vendor_string" / c.Flag,
"require_user_click" / c.Flag,
"red_background" / c.Flag,
"delay" / c.BitsInteger(4),
), _transform_vendor_trust, 2, _transform_vendor_trust, 2)
VendorHeader = c.Struct(
"_start_offset" / c.Tell,
"magic" / c.Const(b"TRZV"),
"_header_len" / c.Padding(4),
"expiry" / c.Int32ul,
"version" / c.Struct(
"major" / c.Int8ul,
"minor" / c.Int8ul,
),
"vendor_sigs_required" / c.Int8ul,
"vendor_sigs_n" / c.Rebuild(c.Int8ul, c.len_(c.this.pubkeys)),
"vendor_trust" / VendorTrust,
"reserved" / c.Padding(14),
"pubkeys" / c.Bytes(32)[c.this.vendor_sigs_n],
"vendor_string" / c.Aligned(4, c.PascalString(c.Int8ul, "utf-8")),
"vendor_image" / Toif,
"_data_end_offset" / c.Tell,
c.Padding(-(c.this._data_end_offset + 65) % 512),
"sigmask" / c.Byte,
"signature" / c.Bytes(64),
"_end_offset" / c.Tell,
"header_len" / c.Pointer(
c.this._start_offset + 4,
c.Rebuild(c.Int32ul, c.this._end_offset - c.this._start_offset)
),
)
VersionLong = c.Struct(
"major" / c.Int8ul,
"minor" / c.Int8ul,
"patch" / c.Int8ul,
"build" / c.Int8ul,
)
FirmwareHeader = c.Struct(
"_start_offset" / c.Tell,
"magic" / c.Const(b"TRZF"),
"header_len" / c.Int32ul,
"expiry" / c.Int32ul,
"code_length" / c.Rebuild(
c.Int32ul,
lambda this:
len(this._.code) if "code" in this._
else (this.code_length or 0)
),
"version" / VersionLong,
"fix_version" / VersionLong,
"reserved" / c.Padding(8),
"hashes" / c.Bytes(32)[16],
"v1_signatures" / c.Bytes(64)[V1_SIGNATURE_SLOTS],
"v1_key_indexes" / c.Int8ul[V1_SIGNATURE_SLOTS], # pylint: disable=E1136
"reserved" / c.Padding(220),
"sigmask" / c.Byte,
"signature" / c.Bytes(64),
"_end_offset" / c.Tell,
"_rebuild_header_len" / c.If(
c.this.version.major > 1,
c.Pointer(
c.this._start_offset + 4,
c.Rebuild(c.Int32ul, c.this._end_offset - c.this._start_offset)
),
),
)
Firmware = c.Struct(
"vendor_header" / VendorHeader,
"firmware_header" / FirmwareHeader,
"_code_offset" / c.Tell,
"code" / c.Bytes(c.this.firmware_header.code_length),
c.Terminated,
)
FirmwareOneV2 = c.Struct(
"firmware_header" / FirmwareHeader,
"_code_offset" / c.Tell,
"code" / c.Bytes(c.this.firmware_header.code_length),
c.Terminated,
)
FirmwareOne = c.Struct(
"magic" / c.Const(b"TRZR"),
"code_length" / c.Rebuild(c.Int32ul, c.len_(c.this.code)),
"key_indexes" / c.Int8ul[V1_SIGNATURE_SLOTS], # pylint: disable=E1136
"flags" / c.BitStruct(
c.Padding(7),
"restore_storage" / c.Flag,
),
"reserved" / c.Padding(52),
"signatures" / c.Bytes(64)[V1_SIGNATURE_SLOTS],
"code" / c.Bytes(c.this.code_length),
c.Terminated,
"embedded_onev2" / c.RestreamData(c.this.code, c.Optional(FirmwareOneV2)),
)
# fmt: on
class FirmwareFormat(Enum):
TREZOR_ONE = 1
TREZOR_T = 2
TREZOR_ONE_V2 = 3
FirmwareType = NewType("FirmwareType", c.Container)
ParsedFirmware = Tuple[FirmwareFormat, FirmwareType]
def parse(data: bytes) -> ParsedFirmware:
if data[:4] == b"TRZR":
version = FirmwareFormat.TREZOR_ONE
cls = FirmwareOne
elif data[:4] == b"TRZV":
version = FirmwareFormat.TREZOR_T
cls = Firmware
elif data[:4] == b"TRZF":
version = FirmwareFormat.TREZOR_ONE_V2
cls = FirmwareOneV2
else:
raise ValueError("Unrecognized firmware image type")
try:
fw = cls.parse(data)
except Exception as e:
raise FirmwareIntegrityError("Invalid firmware image") from e
return version, FirmwareType(fw)
def digest_onev1(fw: FirmwareType) -> bytes:
return hashlib.sha256(fw.code).digest()
def check_sig_v1(
digest: bytes, key_indexes: List[int], signatures: List[bytes]
) -> None:
distinct_key_indexes = set(i for i in key_indexes if i != 0)
if not distinct_key_indexes:
raise Unsigned
if len(distinct_key_indexes) < len(key_indexes):
raise InvalidSignatureError(
"Not enough distinct signatures (found {}, need {})".format(
len(distinct_key_indexes), len(key_indexes)
)
)
for i in range(len(key_indexes)):
key_idx = key_indexes[i]
signature = signatures[i]
if key_idx not in V1_BOOTLOADER_KEYS:
# unknown pubkey
raise InvalidSignatureError("Unknown key in slot {}".format(i))
pubkey = bytes.fromhex(V1_BOOTLOADER_KEYS[key_idx])[1:]
verify = ecdsa.VerifyingKey.from_string(pubkey, curve=ecdsa.curves.SECP256k1)
try:
verify.verify_digest(signature, digest)
except ecdsa.BadSignatureError as e:
raise InvalidSignatureError("Invalid signature in slot {}".format(i)) from e
def _header_digest(
header: c.Container,
header_type: c.Construct,
hash_function: Callable = pyblake2.blake2s,
) -> bytes:
stripped_header = header.copy()
stripped_header.sigmask = 0
stripped_header.signature = b"\0" * 64
stripped_header.v1_key_indexes = [0, 0, 0]
stripped_header.v1_signatures = [b"\0" * 64] * 3
header_bytes = header_type.build(stripped_header)
return hash_function(header_bytes).digest()
def digest_v2(fw: FirmwareType) -> bytes:
return _header_digest(fw.firmware_header, FirmwareHeader, pyblake2.blake2s)
def digest_onev2(fw: FirmwareType) -> bytes:
return _header_digest(fw.firmware_header, FirmwareHeader, hashlib.sha256)
def validate_code_hashes(
fw: FirmwareType,
hash_function: Callable = pyblake2.blake2s,
chunk_size: int = V2_CHUNK_SIZE,
padding_byte: bytes = None,
) -> None:
for i, expected_hash in enumerate(fw.firmware_header.hashes):
if i == 0:
# Because first chunk is sent along with headers, there is less code in it.
chunk = fw.code[: chunk_size - fw._code_offset]
else:
# Subsequent chunks are shifted by the "missing header" size.
ptr = i * chunk_size - fw._code_offset
chunk = fw.code[ptr : ptr + chunk_size]
# padding for last chunk
if padding_byte is not None and i > 1 and chunk and len(chunk) < chunk_size:
chunk += padding_byte[0:1] * (chunk_size - len(chunk))
if not chunk and expected_hash == b"\0" * 32:
continue
chunk_hash = hash_function(chunk).digest()
if chunk_hash != expected_hash:
raise FirmwareIntegrityError("Invalid firmware data.")
def validate_onev2(fw: FirmwareType, allow_unsigned: bool = False) -> None:
try:
check_sig_v1(
digest_onev2(fw),
fw.firmware_header.v1_key_indexes,
fw.firmware_header.v1_signatures,
)
except Unsigned:
if not allow_unsigned:
raise
validate_code_hashes(
fw,
hash_function=hashlib.sha256,
chunk_size=ONEV2_CHUNK_SIZE,
padding_byte=b"\xFF",
)
def validate_onev1(fw: FirmwareType, allow_unsigned: bool = False) -> None:
try:
check_sig_v1(digest_onev1(fw), fw.key_indexes, fw.signatures)
except Unsigned:
if not allow_unsigned:
raise
if fw.embedded_onev2:
validate_onev2(fw.embedded_onev2, allow_unsigned)
def validate_v2(fw: FirmwareType, skip_vendor_header: bool = False) -> None:
vendor_fingerprint = _header_digest(fw.vendor_header, VendorHeader)
fingerprint = digest_v2(fw)
if not skip_vendor_header:
try:
# if you want to validate a custom vendor header, you can modify
# the global variables to match your keys and m-of-n scheme
cosi.verify_m_of_n(
fw.vendor_header.signature,
vendor_fingerprint,
V2_BOOTLOADER_M,
V2_BOOTLOADER_N,
fw.vendor_header.sigmask,
V2_BOOTLOADER_KEYS,
)
except Exception:
raise InvalidSignatureError("Invalid vendor header signature.")
# XXX expiry is not used now
# now = time.gmtime()
# if time.gmtime(fw.vendor_header.expiry) < now:
# raise ValueError("Vendor header expired.")
try:
cosi.verify_m_of_n(
fw.firmware_header.signature,
fingerprint,
fw.vendor_header.vendor_sigs_required,
fw.vendor_header.vendor_sigs_n,
fw.firmware_header.sigmask,
fw.vendor_header.pubkeys,
)
except Exception:
raise InvalidSignatureError("Invalid firmware signature.")
# XXX expiry is not used now
# if time.gmtime(fw.firmware_header.expiry) < now:
# raise ValueError("Firmware header expired.")
validate_code_hashes(fw)
def digest(version: FirmwareFormat, fw: FirmwareType) -> bytes:
if version == FirmwareFormat.TREZOR_ONE:
return digest_onev1(fw)
elif version == FirmwareFormat.TREZOR_ONE_V2:
return digest_onev2(fw)
elif version == FirmwareFormat.TREZOR_T:
return digest_v2(fw)
else:
raise ValueError("Unrecognized firmware version")
def validate(
version: FirmwareFormat, fw: FirmwareType, allow_unsigned: bool = False
) -> None:
if version == FirmwareFormat.TREZOR_ONE:
return validate_onev1(fw, allow_unsigned)
elif version == FirmwareFormat.TREZOR_ONE_V2:
return validate_onev2(fw, allow_unsigned)
elif version == FirmwareFormat.TREZOR_T:
return validate_v2(fw)
else:
raise ValueError("Unrecognized firmware version")
# ====== Client functions ====== #
@tools.session
def update(client, data):
if client.features.bootloader_mode is False:
raise RuntimeError("Device must be in bootloader mode")
resp = client.call(messages.FirmwareErase(length=len(data)))
# TREZORv1 method
if isinstance(resp, messages.Success):
resp = client.call(messages.FirmwareUpload(payload=data))
if isinstance(resp, messages.Success):
return
else:
raise RuntimeError("Unexpected result %s" % resp)
# TREZORv2 method
while isinstance(resp, messages.FirmwareRequest):
payload = data[resp.offset : resp.offset + resp.length]
digest = pyblake2.blake2s(payload).digest()
resp = client.call(messages.FirmwareUpload(payload=payload, hash=digest))
if isinstance(resp, messages.Success):
return
else:
raise RuntimeError("Unexpected message %s" % resp)

58
python/trezorlib/lisk.py Normal file
View File

@ -0,0 +1,58 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from . import messages as proto
from .protobuf import dict_to_proto
from .tools import CallException, dict_from_camelcase, expect, normalize_nfc
@expect(proto.LiskAddress, field="address")
def get_address(client, n, show_display=False):
return client.call(proto.LiskGetAddress(address_n=n, show_display=show_display))
@expect(proto.LiskPublicKey)
def get_public_key(client, n, show_display=False):
return client.call(proto.LiskGetPublicKey(address_n=n, show_display=show_display))
@expect(proto.LiskMessageSignature)
def sign_message(client, n, message):
message = normalize_nfc(message)
return client.call(proto.LiskSignMessage(address_n=n, message=message))
def verify_message(client, pubkey, signature, message):
message = normalize_nfc(message)
try:
resp = client.call(
proto.LiskVerifyMessage(
signature=signature, public_key=pubkey, message=message
)
)
except CallException as e:
resp = e
return isinstance(resp, proto.Success)
RENAMES = {"lifetime": "life_time", "keysgroup": "keys_group"}
@expect(proto.LiskSignedTx)
def sign_tx(client, n, transaction):
transaction = dict_from_camelcase(transaction, renames=RENAMES)
msg = dict_to_proto(proto.LiskTransactionCommon, transaction)
return client.call(proto.LiskSignTx(address_n=n, transaction=msg))

51
python/trezorlib/log.py Normal file
View File

@ -0,0 +1,51 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import logging
from typing import Optional, Set, Type
from . import protobuf
OMITTED_MESSAGES = set() # type: Set[Type[protobuf.MessageType]]
class PrettyProtobufFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
time = self.formatTime(record)
message = "[{time}] {source} {level}: {msg}".format(
time=time,
level=record.levelname.upper(),
source=record.name,
msg=super().format(record),
)
if hasattr(record, "protobuf"):
if type(record.protobuf) in OMITTED_MESSAGES:
message += " ({} bytes)".format(record.protobuf.ByteSize())
else:
message += "\n" + protobuf.format_message(record.protobuf)
return message
def enable_debug_output(handler: Optional[logging.Handler] = None):
if handler is None:
handler = logging.StreamHandler()
formatter = PrettyProtobufFormatter()
handler.setFormatter(formatter)
logger = logging.getLogger("trezorlib")
logger.setLevel(logging.DEBUG)
logger.addHandler(handler)

View File

@ -0,0 +1,62 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from . import messages
map_type_to_class = {}
map_class_to_type = {}
def build_map():
for msg_name in dir(messages.MessageType):
if msg_name.startswith("__"):
continue
try:
msg_class = getattr(messages, msg_name)
except AttributeError:
raise ValueError(
"Implementation of protobuf message '%s' is missing" % msg_name
)
if msg_class.MESSAGE_WIRE_TYPE != getattr(messages.MessageType, msg_name):
raise ValueError(
"Inconsistent wire type and MessageType record for '%s'" % msg_class
)
register_message(msg_class)
def register_message(msg_class):
if msg_class.MESSAGE_WIRE_TYPE in map_type_to_class:
raise Exception(
"Message for wire type %s is already registered by %s"
% (msg_class.MESSAGE_WIRE_TYPE, get_class(msg_class.MESSAGE_WIRE_TYPE))
)
map_class_to_type[msg_class] = msg_class.MESSAGE_WIRE_TYPE
map_type_to_class[msg_class.MESSAGE_WIRE_TYPE] = msg_class
def get_type(msg):
return map_class_to_type[msg.__class__]
def get_class(t):
return map_type_to_class[t]
build_map()

View File

82
python/trezorlib/misc.py Normal file
View File

@ -0,0 +1,82 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from . import messages as proto
from .tools import expect
@expect(proto.Entropy, field="entropy")
def get_entropy(client, size):
return client.call(proto.GetEntropy(size=size))
@expect(proto.SignedIdentity)
def sign_identity(
client, identity, challenge_hidden, challenge_visual, ecdsa_curve_name=None
):
return client.call(
proto.SignIdentity(
identity=identity,
challenge_hidden=challenge_hidden,
challenge_visual=challenge_visual,
ecdsa_curve_name=ecdsa_curve_name,
)
)
@expect(proto.ECDHSessionKey)
def get_ecdh_session_key(client, identity, peer_public_key, ecdsa_curve_name=None):
return client.call(
proto.GetECDHSessionKey(
identity=identity,
peer_public_key=peer_public_key,
ecdsa_curve_name=ecdsa_curve_name,
)
)
@expect(proto.CipheredKeyValue, field="value")
def encrypt_keyvalue(
client, n, key, value, ask_on_encrypt=True, ask_on_decrypt=True, iv=b""
):
return client.call(
proto.CipherKeyValue(
address_n=n,
key=key,
value=value,
encrypt=True,
ask_on_encrypt=ask_on_encrypt,
ask_on_decrypt=ask_on_decrypt,
iv=iv,
)
)
@expect(proto.CipheredKeyValue, field="value")
def decrypt_keyvalue(
client, n, key, value, ask_on_encrypt=True, ask_on_decrypt=True, iv=b""
):
return client.call(
proto.CipherKeyValue(
address_n=n,
key=key,
value=value,
encrypt=False,
ask_on_encrypt=ask_on_encrypt,
ask_on_decrypt=ask_on_decrypt,
iv=iv,
)
)

View File

@ -0,0 +1,37 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from . import messages as proto
from .tools import expect
# MAINNET = 0
# TESTNET = 1
# STAGENET = 2
# FAKECHAIN = 3
@expect(proto.MoneroAddress, field="address")
def get_address(client, n, show_display=False, network_type=0):
return client.call(
proto.MoneroGetAddress(
address_n=n, show_display=show_display, network_type=network_type
)
)
@expect(proto.MoneroWatchKey)
def get_watch_key(client, n, network_type=0):
return client.call(proto.MoneroGetWatchKey(address_n=n, network_type=network_type))

200
python/trezorlib/nem.py Normal file
View File

@ -0,0 +1,200 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import json
from . import messages as proto
from .tools import CallException, expect
TYPE_TRANSACTION_TRANSFER = 0x0101
TYPE_IMPORTANCE_TRANSFER = 0x0801
TYPE_AGGREGATE_MODIFICATION = 0x1001
TYPE_MULTISIG_SIGNATURE = 0x1002
TYPE_MULTISIG = 0x1004
TYPE_PROVISION_NAMESPACE = 0x2001
TYPE_MOSAIC_CREATION = 0x4001
TYPE_MOSAIC_SUPPLY_CHANGE = 0x4002
def create_transaction_common(transaction):
msg = proto.NEMTransactionCommon()
msg.network = (transaction["version"] >> 24) & 0xFF
msg.timestamp = transaction["timeStamp"]
msg.fee = transaction["fee"]
msg.deadline = transaction["deadline"]
if "signer" in transaction:
msg.signer = bytes.fromhex(transaction["signer"])
return msg
def create_transfer(transaction):
msg = proto.NEMTransfer()
msg.recipient = transaction["recipient"]
msg.amount = transaction["amount"]
if "payload" in transaction["message"]:
msg.payload = bytes.fromhex(transaction["message"]["payload"])
if transaction["message"]["type"] == 0x02:
msg.public_key = bytes.fromhex(transaction["message"]["publicKey"])
if "mosaics" in transaction:
msg.mosaics = [
proto.NEMMosaic(
namespace=mosaic["mosaicId"]["namespaceId"],
mosaic=mosaic["mosaicId"]["name"],
quantity=mosaic["quantity"],
)
for mosaic in transaction["mosaics"]
]
return msg
def create_aggregate_modification(transactions):
msg = proto.NEMAggregateModification()
msg.modifications = [
proto.NEMCosignatoryModification(
type=modification["modificationType"],
public_key=bytes.fromhex(modification["cosignatoryAccount"]),
)
for modification in transactions["modifications"]
]
if "minCosignatories" in transactions:
msg.relative_change = transactions["minCosignatories"]["relativeChange"]
return msg
def create_provision_namespace(transaction):
msg = proto.NEMProvisionNamespace()
msg.namespace = transaction["newPart"]
if transaction["parent"]:
msg.parent = transaction["parent"]
msg.sink = transaction["rentalFeeSink"]
msg.fee = transaction["rentalFee"]
return msg
def create_mosaic_creation(transaction):
definition = transaction["mosaicDefinition"]
msg = proto.NEMMosaicCreation()
msg.definition = proto.NEMMosaicDefinition()
msg.definition.namespace = definition["id"]["namespaceId"]
msg.definition.mosaic = definition["id"]["name"]
if definition["levy"]:
msg.definition.levy = definition["levy"]["type"]
msg.definition.fee = definition["levy"]["fee"]
msg.definition.levy_address = definition["levy"]["recipient"]
msg.definition.levy_namespace = definition["levy"]["mosaicId"]["namespaceId"]
msg.definition.levy_mosaic = definition["levy"]["mosaicId"]["name"]
msg.definition.description = definition["description"]
for property in definition["properties"]:
name = property["name"]
value = json.loads(property["value"])
if name == "divisibility":
msg.definition.divisibility = value
elif name == "initialSupply":
msg.definition.supply = value
elif name == "supplyMutable":
msg.definition.mutable_supply = value
elif name == "transferable":
msg.definition.transferable = value
msg.sink = transaction["creationFeeSink"]
msg.fee = transaction["creationFee"]
return msg
def create_supply_change(transaction):
msg = proto.NEMMosaicSupplyChange()
msg.namespace = transaction["mosaicId"]["namespaceId"]
msg.mosaic = transaction["mosaicId"]["name"]
msg.type = transaction["supplyType"]
msg.delta = transaction["delta"]
return msg
def create_importance_transfer(transaction):
msg = proto.NEMImportanceTransfer()
msg.mode = transaction["importanceTransfer"]["mode"]
msg.public_key = bytes.fromhex(transaction["importanceTransfer"]["publicKey"])
return msg
def fill_transaction_by_type(msg, transaction):
if transaction["type"] == TYPE_TRANSACTION_TRANSFER:
msg.transfer = create_transfer(transaction)
elif transaction["type"] == TYPE_AGGREGATE_MODIFICATION:
msg.aggregate_modification = create_aggregate_modification(transaction)
elif transaction["type"] == TYPE_PROVISION_NAMESPACE:
msg.provision_namespace = create_provision_namespace(transaction)
elif transaction["type"] == TYPE_MOSAIC_CREATION:
msg.mosaic_creation = create_mosaic_creation(transaction)
elif transaction["type"] == TYPE_MOSAIC_SUPPLY_CHANGE:
msg.supply_change = create_supply_change(transaction)
elif transaction["type"] == TYPE_IMPORTANCE_TRANSFER:
msg.importance_transfer = create_importance_transfer(transaction)
else:
raise ValueError("Unknown transaction type")
def create_sign_tx(transaction):
msg = proto.NEMSignTx()
msg.transaction = create_transaction_common(transaction)
msg.cosigning = transaction["type"] == TYPE_MULTISIG_SIGNATURE
if transaction["type"] in (TYPE_MULTISIG_SIGNATURE, TYPE_MULTISIG):
other_trans = transaction["otherTrans"]
msg.multisig = create_transaction_common(other_trans)
fill_transaction_by_type(msg, other_trans)
elif "otherTrans" in transaction:
raise ValueError("Transaction does not support inner transaction")
else:
fill_transaction_by_type(msg, transaction)
return msg
# ====== Client functions ====== #
@expect(proto.NEMAddress, field="address")
def get_address(client, n, network, show_display=False):
return client.call(
proto.NEMGetAddress(address_n=n, network=network, show_display=show_display)
)
@expect(proto.NEMSignedTx)
def sign_tx(client, n, transaction):
try:
msg = create_sign_tx(transaction)
except ValueError as e:
raise CallException(e.args)
assert msg.transaction is not None
msg.transaction.address_n = n
return client.call(msg)

View File

@ -0,0 +1,70 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from . import messages
from .tools import expect
#
# Ontology functions
#
@expect(messages.OntologyAddress, field="address")
def get_address(client, address_n, show_display=False):
return client.call(
messages.OntologyGetAddress(address_n=address_n, show_display=show_display)
)
@expect(messages.OntologyPublicKey)
def get_public_key(client, address_n, show_display=False):
return client.call(
messages.OntologyGetPublicKey(address_n=address_n, show_display=show_display)
)
@expect(messages.OntologySignedTransfer)
def sign_transfer(client, address_n, t, tr):
return client.call(
messages.OntologySignTransfer(address_n=address_n, transaction=t, transfer=tr)
)
@expect(messages.OntologySignedWithdrawOng)
def sign_withdrawal(client, address_n, t, w):
return client.call(
messages.OntologySignWithdrawOng(
address_n=address_n, transaction=t, withdraw_ong=w
)
)
@expect(messages.OntologySignedOntIdRegister)
def sign_register(client, address_n, t, r):
return client.call(
messages.OntologySignOntIdRegister(
address_n=address_n, transaction=t, ont_id_register=r
)
)
@expect(messages.OntologySignedOntIdAddAttributes)
def sign_add_attr(client, address_n, t, a):
return client.call(
messages.OntologySignOntIdAddAttributes(
address_n=address_n, transaction=t, ont_id_add_attributes=a
)
)

View File

@ -0,0 +1,431 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
'''
Extremely minimal streaming codec for a subset of protobuf. Supports uint32,
bytes, string, embedded message and repeated fields.
For de-sererializing (loading) protobuf types, object with `Reader`
interface is required:
>>> class Reader:
>>> def readinto(self, buffer):
>>> """
>>> Reads `len(buffer)` bytes into `buffer`, or raises `EOFError`.
>>> """
For serializing (dumping) protobuf types, object with `Writer` interface is
required:
>>> class Writer:
>>> def write(self, buffer):
>>> """
>>> Writes all bytes from `buffer`, or raises `EOFError`.
>>> """
'''
from io import BytesIO
from typing import Any, Optional
_UVARINT_BUFFER = bytearray(1)
def load_uvarint(reader):
buffer = _UVARINT_BUFFER
result = 0
shift = 0
byte = 0x80
while byte & 0x80:
if reader.readinto(buffer) == 0:
raise EOFError
byte = buffer[0]
result += (byte & 0x7F) << shift
shift += 7
return result
def dump_uvarint(writer, n):
if n < 0:
raise ValueError("Cannot dump signed value, convert it to unsigned first.")
buffer = _UVARINT_BUFFER
shifted = True
while shifted:
shifted = n >> 7
buffer[0] = (n & 0x7F) | (0x80 if shifted else 0x00)
writer.write(buffer)
n = shifted
# protobuf interleaved signed encoding:
# https://developers.google.com/protocol-buffers/docs/encoding#structure
# the idea is to save the sign in LSbit instead of twos-complement.
# so counting up, you go: 0, -1, 1, -2, 2, ... (as the first bit changes, sign flips)
#
# To achieve this with a twos-complement number:
# 1. shift left by 1, leaving LSbit free
# 2. if the number is negative, do bitwise negation.
# This keeps positive number the same, and converts negative from twos-complement
# to the appropriate value, while setting the sign bit.
#
# The original algorithm makes use of the fact that arithmetic (signed) shift
# keeps the sign bits, so for a n-bit number, (x >> n) gets us "all sign bits".
# Then you can take "number XOR all-sign-bits", which is XOR 0 (identity) for positive
# and XOR 1 (bitwise negation) for negative. Cute and efficient.
#
# But this is harder in Python because we don't natively know the bit size of the number.
# So we have to branch on whether the number is negative.
def sint_to_uint(sint):
res = sint << 1
if sint < 0:
res = ~res
return res
def uint_to_sint(uint):
sign = uint & 1
res = uint >> 1
if sign:
res = ~res
return res
class UVarintType:
WIRE_TYPE = 0
class SVarintType:
WIRE_TYPE = 0
class BoolType:
WIRE_TYPE = 0
class BytesType:
WIRE_TYPE = 2
class UnicodeType:
WIRE_TYPE = 2
class MessageType:
WIRE_TYPE = 2
@classmethod
def get_fields(cls):
return {}
def __init__(self, **kwargs):
for kw in kwargs:
setattr(self, kw, kwargs[kw])
self._fill_missing()
def __eq__(self, rhs):
return self.__class__ is rhs.__class__ and self.__dict__ == rhs.__dict__
def __repr__(self):
d = {}
for key, value in self.__dict__.items():
if value is None or value == []:
continue
d[key] = value
return "<%s: %s>" % (self.__class__.__name__, d)
def __iter__(self):
return iter(self.keys())
def keys(self):
return (name for name, _, _ in self.get_fields().values())
def __getitem__(self, key):
return getattr(self, key)
def _fill_missing(self):
# fill missing fields
for fname, ftype, fflags in self.get_fields().values():
if not hasattr(self, fname):
if fflags & FLAG_REPEATED:
setattr(self, fname, [])
else:
setattr(self, fname, None)
def ByteSize(self):
data = BytesIO()
dump_message(data, self)
return len(data.getvalue())
class LimitedReader:
def __init__(self, reader, limit):
self.reader = reader
self.limit = limit
def readinto(self, buf):
if self.limit < len(buf):
raise EOFError
else:
nread = self.reader.readinto(buf)
self.limit -= nread
return nread
class CountingWriter:
def __init__(self):
self.size = 0
def write(self, buf):
nwritten = len(buf)
self.size += nwritten
return nwritten
FLAG_REPEATED = 1
def load_message(reader, msg_type):
fields = msg_type.get_fields()
msg = msg_type()
while True:
try:
fkey = load_uvarint(reader)
except EOFError:
break # no more fields to load
ftag = fkey >> 3
wtype = fkey & 7
field = fields.get(ftag, None)
if field is None: # unknown field, skip it
if wtype == 0:
load_uvarint(reader)
elif wtype == 2:
ivalue = load_uvarint(reader)
reader.readinto(bytearray(ivalue))
else:
raise ValueError
continue
fname, ftype, fflags = field
if wtype != ftype.WIRE_TYPE:
raise TypeError # parsed wire type differs from the schema
ivalue = load_uvarint(reader)
if ftype is UVarintType:
fvalue = ivalue
elif ftype is SVarintType:
fvalue = uint_to_sint(ivalue)
elif ftype is BoolType:
fvalue = bool(ivalue)
elif ftype is BytesType:
buf = bytearray(ivalue)
reader.readinto(buf)
fvalue = bytes(buf)
elif ftype is UnicodeType:
buf = bytearray(ivalue)
reader.readinto(buf)
fvalue = buf.decode()
elif issubclass(ftype, MessageType):
fvalue = load_message(LimitedReader(reader, ivalue), ftype)
else:
raise TypeError # field type is unknown
if fflags & FLAG_REPEATED:
pvalue = getattr(msg, fname)
pvalue.append(fvalue)
fvalue = pvalue
setattr(msg, fname, fvalue)
return msg
def dump_message(writer, msg):
repvalue = [0]
mtype = msg.__class__
fields = mtype.get_fields()
for ftag in fields:
fname, ftype, fflags = fields[ftag]
fvalue = getattr(msg, fname, None)
if fvalue is None:
continue
fkey = (ftag << 3) | ftype.WIRE_TYPE
if not fflags & FLAG_REPEATED:
repvalue[0] = fvalue
fvalue = repvalue
for svalue in fvalue:
dump_uvarint(writer, fkey)
if ftype is UVarintType:
dump_uvarint(writer, svalue)
elif ftype is SVarintType:
dump_uvarint(writer, sint_to_uint(svalue))
elif ftype is BoolType:
dump_uvarint(writer, int(svalue))
elif ftype is BytesType:
dump_uvarint(writer, len(svalue))
writer.write(svalue)
elif ftype is UnicodeType:
if not isinstance(svalue, bytes):
svalue = svalue.encode()
dump_uvarint(writer, len(svalue))
writer.write(svalue)
elif issubclass(ftype, MessageType):
counter = CountingWriter()
dump_message(counter, svalue)
dump_uvarint(writer, counter.size)
dump_message(writer, svalue)
else:
raise TypeError
def format_message(
pb: MessageType,
indent: int = 0,
sep: str = " " * 4,
truncate_after: Optional[int] = 256,
truncate_to: Optional[int] = 64,
) -> str:
def mostly_printable(bytes):
if not bytes:
return True
printable = sum(1 for byte in bytes if 0x20 <= byte <= 0x7E)
return printable / len(bytes) > 0.8
def pformat_value(value: Any, indent: int) -> str:
level = sep * indent
leadin = sep * (indent + 1)
if isinstance(value, MessageType):
return format_message(value, indent, sep)
if isinstance(value, list):
# short list of simple values
if not value or not isinstance(value[0], MessageType):
return repr(value)
# long list, one line per entry
lines = ["[", level + "]"]
lines[1:1] = [leadin + pformat_value(x, indent + 1) + "," for x in value]
return "\n".join(lines)
if isinstance(value, dict):
lines = ["{"]
for key, val in sorted(value.items()):
if val is None or val == []:
continue
lines.append(leadin + key + ": " + pformat_value(val, indent + 1) + ",")
lines.append(level + "}")
return "\n".join(lines)
if isinstance(value, (bytes, bytearray)):
length = len(value)
suffix = ""
if truncate_after and length > truncate_after:
suffix = "..."
value = value[: truncate_to or 0]
if mostly_printable(value):
output = repr(value)
else:
output = "0x" + value.hex()
return "{} bytes {}{}".format(length, output, suffix)
return repr(value)
return "{name} ({size} bytes) {content}".format(
name=pb.__class__.__name__,
size=pb.ByteSize(),
content=pformat_value(pb.__dict__, indent),
)
def value_to_proto(ftype, value):
if issubclass(ftype, MessageType):
raise TypeError("value_to_proto only converts simple values")
if ftype in (UVarintType, SVarintType):
return int(value)
if ftype is BoolType:
return bool(value)
if ftype is UnicodeType:
return str(value)
if ftype is BytesType:
if isinstance(value, str):
return bytes.fromhex(value)
elif isinstance(value, bytes):
return value
else:
raise TypeError("can't convert {} value to bytes".format(type(value)))
def dict_to_proto(message_type, d):
params = {}
for fname, ftype, fflags in message_type.get_fields().values():
repeated = fflags & FLAG_REPEATED
value = d.get(fname)
if value is None:
continue
if not repeated:
value = [value]
if issubclass(ftype, MessageType):
function = dict_to_proto
else:
function = value_to_proto
newvalue = [function(ftype, v) for v in value]
if not repeated:
newvalue = newvalue[0]
params[fname] = newvalue
return message_type(**params)
def to_dict(msg, hexlify_bytes=True):
def convert_value(value):
if hexlify_bytes and isinstance(value, bytes):
return value.hex()
elif isinstance(value, MessageType):
return to_dict(value, hexlify_bytes)
elif isinstance(value, list):
return [convert_value(v) for v in value]
else:
return value
res = {}
for key, value in msg.__dict__.items():
if value is None or value == []:
continue
res[key] = convert_value(value)
return res

View File

View File

@ -0,0 +1,173 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import math
import sys
try:
from PyQt5.QtWidgets import (
QPushButton,
QLineEdit,
QSizePolicy,
QLabel,
QApplication,
QWidget,
QGridLayout,
QVBoxLayout,
QHBoxLayout,
)
from PyQt5.QtGui import QRegExpValidator
from PyQt5.QtCore import QRegExp, Qt, QT_VERSION_STR
except Exception:
from PyQt4.QtGui import (
QPushButton,
QLineEdit,
QSizePolicy,
QRegExpValidator,
QLabel,
QApplication,
QWidget,
QGridLayout,
QVBoxLayout,
QHBoxLayout,
)
from PyQt4.QtCore import QObject, SIGNAL, QRegExp, Qt, QT_VERSION_STR
class PinButton(QPushButton):
def __init__(self, password, encoded_value):
super(PinButton, self).__init__("?")
self.password = password
self.encoded_value = encoded_value
if QT_VERSION_STR >= "5":
self.clicked.connect(self._pressed)
elif QT_VERSION_STR >= "4":
QObject.connect(self, SIGNAL("clicked()"), self._pressed)
else:
raise RuntimeError("Unsupported Qt version")
def _pressed(self):
self.password.setText(self.password.text() + str(self.encoded_value))
self.password.setFocus()
class PinMatrixWidget(QWidget):
"""
Displays widget with nine blank buttons and password box.
Encodes button clicks into sequence of numbers for passing
into PinAck messages of TREZOR.
show_strength=True may be useful for entering new PIN
"""
def __init__(self, show_strength=True, parent=None):
super(PinMatrixWidget, self).__init__(parent)
self.password = QLineEdit()
self.password.setValidator(QRegExpValidator(QRegExp("[1-9]+"), None))
self.password.setEchoMode(QLineEdit.Password)
if QT_VERSION_STR >= "5":
self.password.textChanged.connect(self._password_changed)
elif QT_VERSION_STR >= "4":
QObject.connect(
self.password, SIGNAL("textChanged(QString)"), self._password_changed
)
else:
raise RuntimeError("Unsupported Qt version")
self.strength = QLabel()
self.strength.setMinimumWidth(75)
self.strength.setAlignment(Qt.AlignCenter)
self._set_strength(0)
grid = QGridLayout()
grid.setSpacing(0)
for y in range(3)[::-1]:
for x in range(3):
button = PinButton(self.password, x + y * 3 + 1)
button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
button.setFocusPolicy(Qt.NoFocus)
grid.addWidget(button, 3 - y, x)
hbox = QHBoxLayout()
hbox.addWidget(self.password)
if show_strength:
hbox.addWidget(self.strength)
vbox = QVBoxLayout()
vbox.addLayout(grid)
vbox.addLayout(hbox)
self.setLayout(vbox)
def _set_strength(self, strength):
if strength < 3000:
self.strength.setText("weak")
self.strength.setStyleSheet("QLabel { color : #d00; }")
elif strength < 60000:
self.strength.setText("fine")
self.strength.setStyleSheet("QLabel { color : #db0; }")
elif strength < 360000:
self.strength.setText("strong")
self.strength.setStyleSheet("QLabel { color : #0a0; }")
else:
self.strength.setText("ULTIMATE")
self.strength.setStyleSheet("QLabel { color : #000; font-weight: bold;}")
def _password_changed(self, password):
self._set_strength(self.get_strength())
def get_strength(self):
digits = len(set(str(self.password.text())))
strength = math.factorial(9) / math.factorial(9 - digits)
return strength
def get_value(self):
return self.password.text()
if __name__ == "__main__":
"""
Demo application showing PinMatrix widget in action
"""
app = QApplication(sys.argv)
matrix = PinMatrixWidget()
def clicked():
print("PinMatrix value is", matrix.get_value())
print("Possible button combinations:", matrix.get_strength())
sys.exit()
ok = QPushButton("OK")
if QT_VERSION_STR >= "5":
ok.clicked.connect(clicked)
elif QT_VERSION_STR >= "4":
QObject.connect(ok, SIGNAL("clicked()"), clicked)
else:
raise RuntimeError("Unsupported Qt version")
vbox = QVBoxLayout()
vbox.addWidget(matrix)
vbox.addWidget(ok)
w = QWidget()
w.setLayout(vbox)
w.move(100, 100)
w.show()
app.exec_()

View File

@ -0,0 +1,47 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from . import messages
from .protobuf import dict_to_proto
from .tools import dict_from_camelcase, expect
REQUIRED_FIELDS = ("Fee", "Sequence", "TransactionType", "Payment")
REQUIRED_PAYMENT_FIELDS = ("Amount", "Destination")
@expect(messages.RippleAddress, field="address")
def get_address(client, address_n, show_display=False):
return client.call(
messages.RippleGetAddress(address_n=address_n, show_display=show_display)
)
@expect(messages.RippleSignedTx)
def sign_tx(client, address_n, msg: messages.RippleSignTx):
msg.address_n = address_n
return client.call(msg)
def create_sign_tx_msg(transaction) -> messages.RippleSignTx:
if not all(transaction.get(k) for k in REQUIRED_FIELDS):
raise ValueError("Some of the required fields missing")
if not all(transaction["Payment"].get(k) for k in REQUIRED_PAYMENT_FIELDS):
raise ValueError("Some of the required payment fields missing")
if transaction["TransactionType"] != "Payment":
raise ValueError("Only Payment transaction type is supported")
converted = dict_from_camelcase(transaction)
return dict_to_proto(messages.RippleSignTx, converted)

385
python/trezorlib/stellar.py Normal file
View File

@ -0,0 +1,385 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import base64
import struct
import xdrlib
from . import messages
from .tools import CallException, expect
# Memo types
MEMO_TYPE_NONE = 0
MEMO_TYPE_TEXT = 1
MEMO_TYPE_ID = 2
MEMO_TYPE_HASH = 3
MEMO_TYPE_RETURN = 4
# Asset types
ASSET_TYPE_NATIVE = 0
ASSET_TYPE_ALPHA4 = 1
ASSET_TYPE_ALPHA12 = 2
# Operations
OP_CREATE_ACCOUNT = 0
OP_PAYMENT = 1
OP_PATH_PAYMENT = 2
OP_MANAGE_OFFER = 3
OP_CREATE_PASSIVE_OFFER = 4
OP_SET_OPTIONS = 5
OP_CHANGE_TRUST = 6
OP_ALLOW_TRUST = 7
OP_ACCOUNT_MERGE = 8
OP_INFLATION = 9 # Included for documentation purposes, not supported by Trezor
OP_MANAGE_DATA = 10
OP_BUMP_SEQUENCE = 11
DEFAULT_BIP32_PATH = "m/44h/148h/0h"
# Stellar's BIP32 differs to Bitcoin's see https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md
DEFAULT_NETWORK_PASSPHRASE = "Public Global Stellar Network ; September 2015"
def address_from_public_key(pk_bytes):
"""Returns the base32-encoded version of pk_bytes (G...)
"""
final_bytes = bytearray()
# version
final_bytes.append(6 << 3)
# public key
final_bytes.extend(pk_bytes)
# checksum
final_bytes.extend(struct.pack("<H", _crc16_checksum(final_bytes)))
return base64.b32encode(final_bytes).decode()
def address_to_public_key(address_str):
"""Returns the raw 32 bytes representing a public key by extracting
it from the G... string
"""
decoded = base64.b32decode(address_str)
# skip 0th byte (version) and last two bytes (checksum)
return decoded[1:-2]
def parse_transaction_bytes(tx_bytes):
"""Parses base64data into a map with the following keys:
tx - a StellarSignTx describing the transaction header
operations - an array of protobuf message objects for each operation
"""
tx = messages.StellarSignTx()
unpacker = xdrlib.Unpacker(tx_bytes)
tx.source_account = _xdr_read_address(unpacker)
tx.fee = unpacker.unpack_uint()
tx.sequence_number = unpacker.unpack_uhyper()
# Timebounds is an optional field
if unpacker.unpack_bool():
max_timebound = 2 ** 32 - 1 # max unsigned 32-bit int
# (trezor does not support the full 64-bit time value)
tx.timebounds_start = unpacker.unpack_uhyper()
tx.timebounds_end = unpacker.unpack_uhyper()
if tx.timebounds_start > max_timebound or tx.timebounds_start < 0:
raise ValueError(
"Starting timebound out of range (must be between 0 and "
+ max_timebound
)
if tx.timebounds_end > max_timebound or tx.timebounds_end < 0:
raise ValueError(
"Ending timebound out of range (must be between 0 and " + max_timebound
)
# memo type determines what optional fields are set
tx.memo_type = unpacker.unpack_uint()
# text
if tx.memo_type == MEMO_TYPE_TEXT:
tx.memo_text = unpacker.unpack_string()
# id (64-bit uint)
if tx.memo_type == MEMO_TYPE_ID:
tx.memo_id = unpacker.unpack_uhyper()
# hash / return are the same structure (32 bytes representing a hash)
if tx.memo_type == MEMO_TYPE_HASH or tx.memo_type == MEMO_TYPE_RETURN:
tx.memo_hash = unpacker.unpack_fopaque(32)
tx.num_operations = unpacker.unpack_uint()
operations = []
for _ in range(tx.num_operations):
operations.append(_parse_operation_bytes(unpacker))
return tx, operations
def _parse_operation_bytes(unpacker):
"""Returns a protobuf message representing the next operation as read from
the byte stream in unpacker
"""
# Check for and parse optional source account field
source_account = None
if unpacker.unpack_bool():
source_account = unpacker.unpack_fopaque(32)
# Operation type (See OP_ constants)
type = unpacker.unpack_uint()
if type == OP_CREATE_ACCOUNT:
return messages.StellarCreateAccountOp(
source_account=source_account,
new_account=_xdr_read_address(unpacker),
starting_balance=unpacker.unpack_hyper(),
)
if type == OP_PAYMENT:
return messages.StellarPaymentOp(
source_account=source_account,
destination_account=_xdr_read_address(unpacker),
asset=_xdr_read_asset(unpacker),
amount=unpacker.unpack_hyper(),
)
if type == OP_PATH_PAYMENT:
op = messages.StellarPathPaymentOp(
source_account=source_account,
send_asset=_xdr_read_asset(unpacker),
send_max=unpacker.unpack_hyper(),
destination_account=_xdr_read_address(unpacker),
destination_asset=_xdr_read_asset(unpacker),
destination_amount=unpacker.unpack_hyper(),
paths=[],
)
num_paths = unpacker.unpack_uint()
for _ in range(num_paths):
op.paths.append(_xdr_read_asset(unpacker))
return op
if type == OP_MANAGE_OFFER:
return messages.StellarManageOfferOp(
source_account=source_account,
selling_asset=_xdr_read_asset(unpacker),
buying_asset=_xdr_read_asset(unpacker),
amount=unpacker.unpack_hyper(),
price_n=unpacker.unpack_uint(),
price_d=unpacker.unpack_uint(),
offer_id=unpacker.unpack_uhyper(),
)
if type == OP_CREATE_PASSIVE_OFFER:
return messages.StellarCreatePassiveOfferOp(
source_account=source_account,
selling_asset=_xdr_read_asset(unpacker),
buying_asset=_xdr_read_asset(unpacker),
amount=unpacker.unpack_hyper(),
price_n=unpacker.unpack_uint(),
price_d=unpacker.unpack_uint(),
)
if type == OP_SET_OPTIONS:
op = messages.StellarSetOptionsOp(source_account=source_account)
# Inflation destination
if unpacker.unpack_bool():
op.inflation_destination_account = _xdr_read_address(unpacker)
# clear flags
if unpacker.unpack_bool():
op.clear_flags = unpacker.unpack_uint()
# set flags
if unpacker.unpack_bool():
op.set_flags = unpacker.unpack_uint()
# master weight
if unpacker.unpack_bool():
op.master_weight = unpacker.unpack_uint()
# low threshold
if unpacker.unpack_bool():
op.low_threshold = unpacker.unpack_uint()
# medium threshold
if unpacker.unpack_bool():
op.medium_threshold = unpacker.unpack_uint()
# high threshold
if unpacker.unpack_bool():
op.high_threshold = unpacker.unpack_uint()
# home domain
if unpacker.unpack_bool():
op.home_domain = unpacker.unpack_string()
# signer
if unpacker.unpack_bool():
op.signer_type = unpacker.unpack_uint()
op.signer_key = unpacker.unpack_fopaque(32)
op.signer_weight = unpacker.unpack_uint()
return op
if type == OP_CHANGE_TRUST:
return messages.StellarChangeTrustOp(
source_account=source_account,
asset=_xdr_read_asset(unpacker),
limit=unpacker.unpack_uhyper(),
)
if type == OP_ALLOW_TRUST:
op = messages.StellarAllowTrustOp(
source_account=source_account,
trusted_account=_xdr_read_address(unpacker),
asset_type=unpacker.unpack_uint(),
)
if op.asset_type == ASSET_TYPE_ALPHA4:
op.asset_code = unpacker.unpack_fstring(4)
if op.asset_type == ASSET_TYPE_ALPHA12:
op.asset_code = unpacker.unpack_fstring(12)
op.is_authorized = unpacker.unpack_bool()
return op
if type == OP_ACCOUNT_MERGE:
return messages.StellarAccountMergeOp(
source_account=source_account,
destination_account=_xdr_read_address(unpacker),
)
# Inflation is not implemented since anyone can submit this operation to the network
if type == OP_MANAGE_DATA:
op = messages.StellarManageDataOp(
source_account=source_account, key=unpacker.unpack_string()
)
# Only set value if the field is present
if unpacker.unpack_bool():
op.value = unpacker.unpack_opaque()
return op
# Bump Sequence
# see: https://github.com/stellar/stellar-core/blob/master/src/xdr/Stellar-transaction.x#L269
if type == OP_BUMP_SEQUENCE:
return messages.StellarBumpSequenceOp(
source_account=source_account, bump_to=unpacker.unpack_uhyper()
)
raise ValueError("Unknown operation type: " + str(type))
def _xdr_read_asset(unpacker):
"""Reads a stellar Asset from unpacker"""
asset = messages.StellarAssetType(type=unpacker.unpack_uint())
if asset.type == ASSET_TYPE_ALPHA4:
asset.code = unpacker.unpack_fstring(4)
asset.issuer = _xdr_read_address(unpacker)
if asset.type == ASSET_TYPE_ALPHA12:
asset.code = unpacker.unpack_fstring(12)
asset.issuer = _xdr_read_address(unpacker)
return asset
def _xdr_read_address(unpacker):
"""Reads a stellar address and returns the string representing the address
This method assumes the encoded address is a public address (starting with G)
"""
# First 4 bytes are the address type
address_type = unpacker.unpack_uint()
if address_type != 0:
raise ValueError("Unsupported address type")
return address_from_public_key(unpacker.unpack_fopaque(32))
def _crc16_checksum(bytes):
"""Returns the CRC-16 checksum of bytearray bytes
Ported from Java implementation at: http://introcs.cs.princeton.edu/java/61data/CRC16CCITT.java.html
Initial value changed to 0x0000 to match Stellar configuration.
"""
crc = 0x0000
polynomial = 0x1021
for byte in bytes:
for i in range(8):
bit = (byte >> (7 - i) & 1) == 1
c15 = (crc >> 15 & 1) == 1
crc <<= 1
if c15 ^ bit:
crc ^= polynomial
return crc & 0xFFFF
# ====== Client functions ====== #
@expect(messages.StellarAddress, field="address")
def get_address(client, address_n, show_display=False):
return client.call(
messages.StellarGetAddress(address_n=address_n, show_display=show_display)
)
def sign_tx(
client, tx, operations, address_n, network_passphrase=DEFAULT_NETWORK_PASSPHRASE
):
tx.network_passphrase = network_passphrase
tx.address_n = address_n
tx.num_operations = len(operations)
# Signing loop works as follows:
#
# 1. Start with tx (header information for the transaction) and operations (an array of operation protobuf messagess)
# 2. Send the tx header to the device
# 3. Receive a StellarTxOpRequest message
# 4. Send operations one by one until all operations have been sent. If there are more operations to sign, the device will send a StellarTxOpRequest message
# 5. The final message received will be StellarSignedTx which is returned from this method
resp = client.call(tx)
try:
while isinstance(resp, messages.StellarTxOpRequest):
resp = client.call(operations.pop(0))
except IndexError:
# pop from empty list
raise CallException(
"Stellar.UnexpectedEndOfOperations",
"Reached end of operations without a signature.",
) from None
if not isinstance(resp, messages.StellarSignedTx):
raise CallException(messages.FailureType.UnexpectedMessage, resp)
if operations:
raise CallException(
"Stellar.UnprocessedOperations",
"Received a signature before processing all operations.",
)
return resp

View File

View File

@ -0,0 +1,75 @@
#!/usr/bin/env python3
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import os
import random
import string
from trezorlib import device
from trezorlib.debuglink import TrezorClientDebugLink
from trezorlib.transport import enumerate_devices, get_transport
def get_device():
path = os.environ.get("TREZOR_PATH")
if path:
return get_transport(path)
else:
devices = enumerate_devices()
for d in devices:
if hasattr(d, "find_debug"):
return d
raise RuntimeError("No debuggable device found")
def pin_input_flow(client, old_pin, new_pin):
# do you want to change pin?
yield
client.debug.press_yes()
if old_pin is not None:
# enter old pin
yield
client.debug.input(old_pin)
# enter new pin
yield
client.debug.input(new_pin)
# repeat new pin
yield
client.debug.input(new_pin)
if __name__ == "__main__":
wirelink = get_device()
client = TrezorClientDebugLink(wirelink)
client.open()
i = 0
last_pin = None
while True:
# set private field
device.apply_settings(client, auto_lock_delay_ms=(i % 10 + 10) * 1000)
# set public field
label = "".join(random.choices(string.ascii_uppercase + string.digits, k=17))
device.apply_settings(client, label=label)
assert client.features.label == label
print("iteration %d" % i)
i = i + 1

View File

@ -0,0 +1,85 @@
#!/usr/bin/env python3
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import os
import random
import string
from trezorlib import device
from trezorlib.debuglink import TrezorClientDebugLink
from trezorlib.transport import enumerate_devices, get_transport
def get_device():
path = os.environ.get("TREZOR_PATH")
if path:
return get_transport(path)
else:
devices = enumerate_devices()
for d in devices:
if hasattr(d, "find_debug"):
return d
raise RuntimeError("No debuggable device found")
def pin_input_flow(client, old_pin, new_pin):
# do you want to change pin?
yield
client.debug.press_yes()
if old_pin is not None:
# enter old pin
yield
client.debug.input(old_pin)
# enter new pin
yield
client.debug.input(new_pin)
# repeat new pin
yield
client.debug.input(new_pin)
if __name__ == "__main__":
wirelink = get_device()
client = TrezorClientDebugLink(wirelink)
client.open()
i = 0
last_pin = None
while True:
# set private field
device.apply_settings(client, use_passphrase=True)
assert client.features.passphrase_protection is True
device.apply_settings(client, use_passphrase=False)
assert client.features.passphrase_protection is False
# set public field
label = "".join(random.choices(string.ascii_uppercase + string.digits, k=17))
device.apply_settings(client, label=label)
assert client.features.label == label
# change PIN
new_pin = "".join(random.choices(string.digits, k=random.randint(6, 10)))
client.set_input_flow(pin_input_flow(client, last_pin, new_pin))
device.change_pin(client)
client.set_input_flow(None)
last_pin = new_pin
print("iteration %d" % i)
i = i + 1

View File

@ -0,0 +1,2 @@
*.out
*.err

View File

@ -0,0 +1,107 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from trezorlib import debuglink, device
from trezorlib.messages.PassphraseSourceType import HOST as PASSPHRASE_ON_HOST
from . import conftest
class TrezorTest:
# fmt: off
# 1 2 3 4 5 6 7 8 9 10 11 12
mnemonic12 = "alcohol woman abuse must during monitor noble actual mixed trade anger aisle"
mnemonic18 = "owner little vague addict embark decide pink prosper true fork panda embody mixture exchange choose canoe electric jewel"
mnemonic24 = "dignity pass list indicate nasty swamp pool script soccer toe leaf photo multiply desk host tomato cradle drill spread actor shine dismiss champion exotic"
mnemonic_all = " ".join(["all"] * 12)
# fmt: on
pin4 = "1234"
pin6 = "789456"
pin8 = "45678978"
def setup_method(self, method):
self.client = conftest.get_device()
# self.client.set_buttonwait(3)
device.wipe(self.client)
self.client.open()
def teardown_method(self, method):
self.client.close()
def _setup_mnemonic(self, mnemonic=None, pin="", passphrase=False, lock=True):
if mnemonic is None:
mnemonic = TrezorTest.mnemonic12
debuglink.load_device_by_mnemonic(
self.client,
mnemonic=mnemonic,
pin=pin,
passphrase_protection=passphrase,
label="test",
language="english",
)
if conftest.TREZOR_VERSION == 1 and lock:
# remove cached PIN (introduced via load_device)
self.client.clear_session()
if conftest.TREZOR_VERSION > 1 and passphrase:
device.apply_settings(self.client, passphrase_source=PASSPHRASE_ON_HOST)
def setup_mnemonic_allallall(self, lock=True):
self._setup_mnemonic(mnemonic=TrezorTest.mnemonic_all, lock=lock)
def setup_mnemonic_nopin_nopassphrase(self, lock=True):
self._setup_mnemonic(lock=lock)
def setup_mnemonic_nopin_passphrase(self, lock=True):
self._setup_mnemonic(passphrase=True, lock=lock)
def setup_mnemonic_pin_nopassphrase(self, lock=True):
self._setup_mnemonic(pin=TrezorTest.pin4, lock=lock)
def setup_mnemonic_pin_passphrase(self, lock=True):
self._setup_mnemonic(pin=TrezorTest.pin4, passphrase=True, lock=lock)
def generate_entropy(strength, internal_entropy, external_entropy):
"""
strength - length of produced seed. One of 128, 192, 256
random - binary stream of random data from external HRNG
"""
import hashlib
if strength not in (128, 192, 256):
raise ValueError("Invalid strength")
if not internal_entropy:
raise ValueError("Internal entropy is not provided")
if len(internal_entropy) < 32:
raise ValueError("Internal entropy too short")
if not external_entropy:
raise ValueError("External entropy is not provided")
if len(external_entropy) < 32:
raise ValueError("External entropy too short")
entropy = hashlib.sha256(internal_entropy + external_entropy).digest()
entropy_stripped = entropy[: strength // 8]
if len(entropy_stripped) * 8 != strength:
raise ValueError("Entropy length mismatch")
return entropy_stripped

View File

@ -0,0 +1,140 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import functools
import os
import pytest
from trezorlib import debuglink, log
from trezorlib.debuglink import TrezorClientDebugLink
from trezorlib.device import wipe as wipe_device
from trezorlib.transport import enumerate_devices, get_transport
TREZOR_VERSION = None
def get_device():
path = os.environ.get("TREZOR_PATH")
if path:
transport = get_transport(path)
else:
devices = enumerate_devices()
for device in devices:
if hasattr(device, "find_debug"):
transport = device
break
else:
raise RuntimeError("No debuggable device found")
env_interactive = int(os.environ.get("INTERACT", 0))
try:
return TrezorClientDebugLink(transport, auto_interact=not env_interactive)
except Exception as e:
raise RuntimeError(
"Failed to open debuglink for {}".format(transport.get_path())
) from e
def device_version():
client = get_device()
if client.features.model == "T":
return 2
else:
return 1
@pytest.fixture(scope="function")
def client():
client = get_device()
wipe_device(client)
client.open()
yield client
client.close()
def setup_client(mnemonic=None, pin="", passphrase=False):
if mnemonic is None:
mnemonic = " ".join(["all"] * 12)
if pin is True:
pin = "1234"
def client_decorator(function):
@functools.wraps(function)
def wrapper(client, *args, **kwargs):
debuglink.load_device_by_mnemonic(
client,
mnemonic=mnemonic,
pin=pin,
passphrase_protection=passphrase,
label="test",
language="english",
)
return function(client, *args, **kwargs)
return wrapper
return client_decorator
def pytest_configure(config):
global TREZOR_VERSION
TREZOR_VERSION = device_version()
if config.getoption("verbose"):
log.enable_debug_output()
def pytest_addoption(parser):
parser.addini(
"run_xfail",
"List of markers that will run even tests that are marked as xfail",
"args",
[],
)
parser.addoption(
"--interactive",
action="store_true",
help="Wait for user to do interaction manually",
)
def pytest_runtest_setup(item):
"""
Called for each test item (class, individual tests).
Performs custom processing, mainly useful for trezor CI testing:
* 'skip_t2' tests are skipped on T2 and 'skip_t1' tests are skipped on T1.
* no test should have both skips at the same time
* allows to 'runxfail' tests specified by 'run_xfail' in pytest.ini
"""
if item.get_closest_marker("skip_t1") and item.get_closest_marker("skip_t2"):
pytest.fail("Don't skip tests for both trezors!")
if item.get_closest_marker("skip_t2") and TREZOR_VERSION == 2:
pytest.skip("Test excluded on Trezor T")
if item.get_closest_marker("skip_t1") and TREZOR_VERSION == 1:
pytest.skip("Test excluded on Trezor 1")
xfail = item.get_closest_marker("xfail")
runxfail_markers = item.config.getini("run_xfail")
run_xfail = any(item.get_closest_marker(marker) for marker in runxfail_markers)
if xfail and run_xfail:
# Deep hack: pytest's private _evalxfail helper determines whether the test should xfail or not.
# The helper caches its result even before this hook runs.
# Here we force-set the result to False, meaning "test does NOT xfail, run as normal"
# IOW, this is basically per-item "--runxfail"
item._evalxfail.result = False

View File

@ -0,0 +1,49 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from trezorlib import device, messages
from .common import TrezorTest
class TestBasic(TrezorTest):
def test_features(self):
f0 = self.client.features
f1 = self.client.call(messages.Initialize())
assert f0 == f1
def test_ping(self):
ping = self.client.call(messages.Ping(message="ahoj!"))
assert ping == messages.Success(message="ahoj!")
def test_device_id_same(self):
id1 = self.client.get_device_id()
self.client.init_device()
id2 = self.client.get_device_id()
# ID must be at least 12 characters
assert len(id1) >= 12
# Every resulf of UUID must be the same
assert id1 == id2
def test_device_id_different(self):
id1 = self.client.get_device_id()
device.wipe(self.client)
id2 = self.client.get_device_id()
# Device ID must be fresh after every reset
assert id1 != id2

View File

@ -0,0 +1,73 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import time
import pytest
from trezorlib import btc
from trezorlib.tools import H_
from .common import TrezorTest
class TestBip32Speed(TrezorTest):
def test_public_ckd(self):
self.setup_mnemonic_nopin_nopassphrase()
btc.get_address(self.client, "Bitcoin", []) # to compute root node via BIP39
for depth in range(8):
start = time.time()
btc.get_address(self.client, "Bitcoin", range(depth))
delay = time.time() - start
expected = (depth + 1) * 0.26
print("DEPTH", depth, "EXPECTED DELAY", expected, "REAL DELAY", delay)
assert delay <= expected
def test_private_ckd(self):
self.setup_mnemonic_nopin_nopassphrase()
btc.get_address(self.client, "Bitcoin", []) # to compute root node via BIP39
for depth in range(8):
start = time.time()
address_n = [H_(-i) for i in range(-depth, 0)]
btc.get_address(self.client, "Bitcoin", address_n)
delay = time.time() - start
expected = (depth + 1) * 0.26
print("DEPTH", depth, "EXPECTED DELAY", expected, "REAL DELAY", delay)
assert delay <= expected
@pytest.mark.skip_t2
def test_cache(self):
self.setup_mnemonic_nopin_nopassphrase()
start = time.time()
for x in range(10):
btc.get_address(self.client, "Bitcoin", [x, 2, 3, 4, 5, 6, 7, 8])
nocache_time = time.time() - start
start = time.time()
for x in range(10):
btc.get_address(self.client, "Bitcoin", [1, 2, 3, 4, 5, 6, 7, x])
cache_time = time.time() - start
print("NOCACHE TIME", nocache_time)
print("CACHED TIME", cache_time)
# Cached time expected to be at least 2x faster
assert cache_time <= nocache_time / 2.0

View File

@ -0,0 +1,72 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
import trezorlib.messages as m
from .conftest import setup_client
@setup_client()
@pytest.mark.parametrize(
"message",
[
m.Ping(message="hello", button_protection=True),
m.GetAddress(
address_n=[0],
coin_name="Bitcoin",
script_type=m.InputScriptType.SPENDADDRESS,
show_display=True,
),
],
)
def test_cancel_message_via_cancel(client, message):
resp = client.call_raw(message)
assert isinstance(resp, m.ButtonRequest)
client.transport.write(m.ButtonAck())
client.transport.write(m.Cancel())
resp = client.transport.read()
assert isinstance(resp, m.Failure)
assert resp.code == m.FailureType.ActionCancelled
@setup_client()
@pytest.mark.parametrize(
"message",
[
m.Ping(message="hello", button_protection=True),
m.GetAddress(
address_n=[0],
coin_name="Bitcoin",
script_type=m.InputScriptType.SPENDADDRESS,
show_display=True,
),
],
)
def test_cancel_message_via_initialize(client, message):
resp = client.call_raw(message)
assert isinstance(resp, m.ButtonRequest)
client.transport.write(m.ButtonAck())
client.transport.write(m.Initialize())
resp = client.transport.read()
assert isinstance(resp, m.Features)

View File

@ -0,0 +1,105 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from hashlib import sha256
import pytest
from trezorlib import cosi
from trezorlib.tools import parse_path
from .common import TrezorTest
@pytest.mark.skip_t2
class TestCosi(TrezorTest):
def test_cosi_commit(self):
self.setup_mnemonic_pin_passphrase()
digest = sha256(b"this is a message").digest()
c0 = cosi.commit(self.client, parse_path("10018'/0'"), digest)
c1 = cosi.commit(self.client, parse_path("10018'/1'"), digest)
c2 = cosi.commit(self.client, parse_path("10018'/2'"), digest)
assert c0.pubkey != c1.pubkey
assert c0.pubkey != c2.pubkey
assert c1.pubkey != c2.pubkey
assert c0.commitment != c1.commitment
assert c0.commitment != c2.commitment
assert c1.commitment != c2.commitment
digestb = sha256(b"this is a different message").digest()
c0b = cosi.commit(self.client, parse_path("10018'/0'"), digestb)
c1b = cosi.commit(self.client, parse_path("10018'/1'"), digestb)
c2b = cosi.commit(self.client, parse_path("10018'/2'"), digestb)
assert c0.pubkey == c0b.pubkey
assert c1.pubkey == c1b.pubkey
assert c2.pubkey == c2b.pubkey
assert c0.commitment != c0b.commitment
assert c1.commitment != c1b.commitment
assert c2.commitment != c2b.commitment
def test_cosi_sign(self):
self.setup_mnemonic_pin_passphrase()
digest = sha256(b"this is a message").digest()
c0 = cosi.commit(self.client, parse_path("10018'/0'"), digest)
c1 = cosi.commit(self.client, parse_path("10018'/1'"), digest)
c2 = cosi.commit(self.client, parse_path("10018'/2'"), digest)
global_pk = cosi.combine_keys([c0.pubkey, c1.pubkey, c2.pubkey])
global_R = cosi.combine_keys([c0.commitment, c1.commitment, c2.commitment])
# fmt: off
sig0 = cosi.sign(self.client, parse_path("10018'/0'"), digest, global_R, global_pk)
sig1 = cosi.sign(self.client, parse_path("10018'/1'"), digest, global_R, global_pk)
sig2 = cosi.sign(self.client, parse_path("10018'/2'"), digest, global_R, global_pk)
# fmt: on
sig = cosi.combine_sig(
global_R, [sig0.signature, sig1.signature, sig2.signature]
)
cosi.verify(sig, digest, global_pk)
def test_cosi_compat(self):
self.setup_mnemonic_pin_passphrase()
digest = sha256(b"this is not a pipe").digest()
remote_commit = cosi.commit(self.client, parse_path("10018'/0'"), digest)
local_privkey = sha256(b"private key").digest()[:32]
local_pubkey = cosi.pubkey_from_privkey(local_privkey)
local_nonce, local_commitment = cosi.get_nonce(local_privkey, digest, 42)
global_pk = cosi.combine_keys([remote_commit.pubkey, local_pubkey])
global_R = cosi.combine_keys([remote_commit.commitment, local_commitment])
remote_sig = cosi.sign(
self.client, parse_path("10018'/0'"), digest, global_R, global_pk
)
local_sig = cosi.sign_with_privkey(
digest, local_privkey, global_pk, local_nonce, global_R
)
sig = cosi.combine_sig(global_R, [remote_sig.signature, local_sig])
cosi.verify(sig, digest, global_pk)

View File

@ -0,0 +1,48 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import messages as proto
from .common import TrezorTest
@pytest.mark.skip_t2
class TestDebuglink(TrezorTest):
def test_layout(self):
layout = self.client.debug.state().layout
assert len(layout) == 1024
def test_mnemonic(self):
self.setup_mnemonic_nopin_nopassphrase(lock=False)
mnemonic = self.client.debug.state().mnemonic_secret
assert mnemonic == self.mnemonic12.encode()
def test_pin(self):
self.setup_mnemonic_pin_passphrase()
# Manually trigger PinMatrixRequest
resp = self.client.call_raw(proto.Ping(message="test", pin_protection=True))
assert isinstance(resp, proto.PinMatrixRequest)
pin, matrix = self.client.debug.read_pin()
assert pin == "1234"
assert matrix != ""
pin_encoded = self.client.debug.read_pin_encoded()
resp = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
assert isinstance(resp, proto.Success)

View File

@ -0,0 +1,141 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import time
import pytest
from trezorlib import device, messages as proto
from .common import TrezorTest
from .conftest import TREZOR_VERSION
EXPECTED_RESPONSES_NOPIN = [proto.ButtonRequest(), proto.Success(), proto.Features()]
EXPECTED_RESPONSES_PIN = [proto.PinMatrixRequest()] + EXPECTED_RESPONSES_NOPIN
if TREZOR_VERSION >= 2:
EXPECTED_RESPONSES = EXPECTED_RESPONSES_NOPIN
else:
EXPECTED_RESPONSES = EXPECTED_RESPONSES_PIN
class TestMsgApplysettings(TrezorTest):
def test_apply_settings(self):
self.setup_mnemonic_pin_passphrase()
assert self.client.features.label == "test"
with self.client:
self.client.set_expected_responses(EXPECTED_RESPONSES)
device.apply_settings(self.client, label="new label")
assert self.client.features.label == "new label"
@pytest.mark.skip_t2
def test_invalid_language(self):
self.setup_mnemonic_pin_passphrase()
assert self.client.features.language == "english"
with self.client:
self.client.set_expected_responses(EXPECTED_RESPONSES)
device.apply_settings(self.client, language="nonexistent")
assert self.client.features.language == "english"
def test_apply_settings_passphrase(self):
self.setup_mnemonic_pin_nopassphrase()
assert self.client.features.passphrase_protection is False
with self.client:
self.client.set_expected_responses(EXPECTED_RESPONSES)
device.apply_settings(self.client, use_passphrase=True)
assert self.client.features.passphrase_protection is True
with self.client:
self.client.set_expected_responses(EXPECTED_RESPONSES_NOPIN)
device.apply_settings(self.client, use_passphrase=False)
assert self.client.features.passphrase_protection is False
with self.client:
self.client.set_expected_responses(EXPECTED_RESPONSES_NOPIN)
device.apply_settings(self.client, use_passphrase=True)
assert self.client.features.passphrase_protection is True
@pytest.mark.skip_t2
def test_apply_homescreen(self):
self.setup_mnemonic_pin_passphrase()
img = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"\x00\x00\x00\x00\x04\x80\x00\x00\x00\x00\x00\x00\x00\x00\x04\x88\x02\x00\x00\x00\x02\x91\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x90@\x00\x11@\x00\x00\x00\x00\x00\x00\x08\x00\x10\x92\x12\x04\x00\x00\x05\x12D\x00\x00\x00\x00\x00 \x00\x00\x08\x00Q\x00\x00\x02\xc0\x00\x00\x00\x00\x00\x00\x00\x10\x02 \x01\x04J\x00)$\x00\x00\x00\x00\x80\x00\x00\x00\x00\x08\x10\xa1\x00\x00\x02\x81 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\tP\x00\x00\x00\x00\x00\x00 \x00\x00\xa0\x00\xa0R \x12\x84\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x08\x00\tP\x00\x00\x00\x00 \x00\x04 \x00\x80\x02\x00@\x02T\xc2 \x00\x00\x00\x00\x00\x00\x00\x10@\x00)\t@\n\xa0\x80\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x80@\x14\xa9H\x04\x00\x00\x88@\x00\x00\x00\x00\x00\x02\x02$\x00\x15B@\x00\nP\x00\x00\x00\x00\x00\x80\x00\x00\x91\x01UP\x00\x00 \x02\x00\x00\x00\x00\x00\x00\x02\x08@ Z\xa5 \x00\x00\x80\x00\x00\x00\x00\x00\x00\x08\xa1%\x14*\xa0\x00\x00\x02\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00@\xaa\x91 \x00\x05E\x80\x00\x00\x00\x00\x00\x02*T\x05-D\x00\x00\x05 @\x00\x00\x00\x00\x00%@\x80\x11V\xa0\x88\x00\x05@\xb0\x00\x00\x00\x00\x00\x818$\x04\xabD \x00\x06\xa1T\x00\x00\x00\x00\x02\x03\xb8\x01R\xd5\x01\x00\x00\x05AP\x00\x00\x00\x00\x08\xadT\x00\x05j\xa4@\x00\x87ah\x00\x00\x00\x00\x02\x8d\xb8\x08\x00.\x01\x00\x00\x02\xa5\xa8\x10\x00\x00\x00*\xc1\xec \n\xaa\x88 \x02@\xf6\xd0\x02\x00\x00\x00\x0bB\xb6\x14@U"\x80\x00\x01{`\x00\x00\x00\x00M\xa3\xf8 \x15*\x00\x00\x00\x10n\xc0\x04\x00\x00\x02\x06\xc2\xa8)\x00\x96\x84\x80\x00\x00\x1b\x00\x00\x80@\x10\x87\xa7\xf0\x84\x10\xaa\x10\x00\x00D\x00\x00\x02 \x00\x8a\x06\xfa\xe0P\n-\x02@\x00\x12\x00\x00\x00\x00\x10@\x83\xdf\xa0\x00\x08\xaa@\x00\x00\x01H\x00\x05H\x04\x12\x01\xf7\x81P\x02T\t\x00\x00\x00 \x00\x00\x84\x10\x00\x00z\x00@)* \x00\x00\x01\n\xa0\x02 \x05\n\x00\x00\x05\x10\x84\xa8\x84\x80\x00\x00@\x14\x00\x92\x10\x80\x00\x04\x11@\tT\x00\x00\x00\x00\n@\x00\x08\x84@$\x00H\x00\x12Q\x02\x00\x00\x00\x00\x90\x02A\x12\xa8\n\xaa\x92\x10\x04\xa8\x10@\x00\x00\x04\x04\x00\x04I\x00\x04\x14H\x80"R\x01\x00\x00\x00!@\x00\x00$\xa0EB\x80\x08\x95hH\x00\x00\x00\x84\x10 \x05Z\x00\x00(\x00\x02\x00\xa1\x01\x00\x00\x04\x00@\x82\x00\xadH*\x92P\x00\xaaP\x00\x00\x00\x00\x11\x02\x01*\xad\x01\x00\x01\x01"\x11D\x08\x00\x00\x10\x80 \x00\x81W\x80J\x94\x04\x08\xa5 !\x00\x00\x00\x02\x00B*\xae\xa1\x00\x80\x10\x01\x08\xa4\x00\x00\x00\x00\x00\x84\x00\t[@"HA\x04E\x00\x84\x00\x00\x00\x10\x00\x01J\xd5\x82\x90\x02\x00!\x02\xa2\x00\x00\x00\x00\x00\x00\x00\x05~\xa0\x00 \x10\n)\x00\x11\x00\x00\x00\x00\x00\x00!U\x80\xa8\x88\x82\x80\x01\x00\x00\x00\x00\x00\x00H@\x11\xaa\xc0\x82\x00 *\n\x00\x00\x00\x00\x00\x00\x00\x00\n\xabb@ \x04\x00! \x84\x00\x00\x00\x00\x02@\xa5\x15A$\x04\x81(\n\x00\x00\x00\x00\x00\x00 \x01\x10\x02\xe0\x91\x02\x00\x00\x04\x00\x00\x00\x00\x00\x00\x01 \xa9\tQH@\x91 P\x00\x00\x00\x00\x00\x00\x08\x00\x00\xa0T\xa5\x00@\x80\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"\x00\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00 T\xa0\t\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00@\x02\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00*\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x10\x00\x00\x10\x02\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00@\x04\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x08@\x10\x00\x00\x00\x00'
with self.client:
self.client.set_expected_responses(EXPECTED_RESPONSES)
device.apply_settings(self.client, homescreen=img)
@pytest.mark.skip_t2
def test_apply_auto_lock_delay(self):
self.setup_mnemonic_pin_passphrase()
with self.client:
self.client.set_expected_responses(EXPECTED_RESPONSES_PIN)
device.apply_settings(self.client, auto_lock_delay_ms=int(10e3)) # 10 secs
time.sleep(0.1) # sleep less than auto-lock delay
with self.client:
# No PIN protection is required.
self.client.set_expected_responses([proto.Success()])
self.client.ping(msg="", pin_protection=True)
time.sleep(10.1) # sleep more than auto-lock delay
with self.client:
self.client.set_expected_responses(
[proto.PinMatrixRequest(), proto.Success()]
)
self.client.ping(msg="", pin_protection=True)
@pytest.mark.skip_t2
def test_apply_minimal_auto_lock_delay(self):
"""
Verify that the delay is not below the minimal auto-lock delay (10 secs)
otherwise the device may auto-lock before any user interaction.
"""
self.setup_mnemonic_pin_passphrase()
with self.client:
self.client.set_expected_responses(EXPECTED_RESPONSES_PIN)
# Note: the actual delay will be 10 secs (see above).
device.apply_settings(self.client, auto_lock_delay_ms=int(1e3))
time.sleep(0.1) # sleep less than auto-lock delay
with self.client:
# No PIN protection is required.
self.client.set_expected_responses([proto.Success()])
self.client.ping(msg="", pin_protection=True)
time.sleep(2) # sleep less than the minimal auto-lock delay
with self.client:
# No PIN protection is required.
self.client.set_expected_responses([proto.Success()])
self.client.ping(msg="", pin_protection=True)
time.sleep(10.1) # sleep more than the minimal auto-lock delay
with self.client:
self.client.set_expected_responses(
[proto.PinMatrixRequest(), proto.Success()]
)
self.client.ping(msg="", pin_protection=True)

View File

@ -0,0 +1,50 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib.cardano import get_address
from trezorlib.tools import parse_path
from .common import TrezorTest
@pytest.mark.cardano
@pytest.mark.skip_t1 # T1 support is not planned
class TestMsgCardanoGetAddress(TrezorTest):
@pytest.mark.parametrize(
"path,expected_address",
[
(
"m/44'/1815'/0'/0/0",
"Ae2tdPwUPEZLCq3sFv4wVYxwqjMH2nUzBVt1HFr4v87snYrtYq3d3bq2PUQ",
),
(
"m/44'/1815'/0'/0/1",
"Ae2tdPwUPEZEY6pVJoyuNNdLp7VbMB7U7qfebeJ7XGunk5Z2eHarkcN1bHK",
),
(
"m/44'/1815'/0'/0/2",
"Ae2tdPwUPEZ3gZD1QeUHvAqadAV59Zid6NP9VCR9BG5LLAja9YtBUgr6ttK",
),
],
)
def test_cardano_get_address(self, path, expected_address):
# data from https://iancoleman.io/bip39/#english
self.setup_mnemonic_nopin_nopassphrase()
address = get_address(self.client, parse_path(path))
assert address == expected_address

View File

@ -0,0 +1,60 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib.cardano import get_public_key
from trezorlib.tools import parse_path
from .common import TrezorTest
@pytest.mark.cardano
@pytest.mark.skip_t1 # T1 support is not planned
class TestMsgCardanoGetPublicKey(TrezorTest):
@pytest.mark.parametrize(
"path,public_key,chain_code",
[
(
"m/44'/1815'/0'",
"c0fce1839f1a84c4e770293ac2f5e0875141b29017b7f56ab135352d00ad6966",
"07faa161c9f5464315d2855f70fdf1431d5fa39eb838767bf17b69772137452f",
),
(
"m/44'/1815'/1'",
"ea5dde31b9f551e08a5b6b2f98b8c42c726f726c9ce0a7072102ead53bd8f21e",
"70f131bb799fd659c997221ad8cae7dcce4e8da701f8101cf15307fd3a3712a1",
),
(
"m/44'/1815'/2'",
"076338cee5ab3dae19f06ccaa80e3d4428cf0e1bdc04243e41bba7be63a90da7",
"5dcdf129f6f2d108292e615c4b67a1fc41a64e6a96130f5c981e5e8e046a6cd7",
),
(
"m/44'/1815'/3'",
"5f769380dc6fd17a4e0f2d23aa359442a712e5e96d7838ebb91eb020003cccc3",
"1197ea234f528987cbac9817ebc31344395b837a3bb7c2332f87e095e70550a5",
),
],
)
def test_cardano_get_public_key(self, path, public_key, chain_code):
self.setup_mnemonic_allallall()
key = get_public_key(self.client, parse_path(path))
assert key.node.public_key.hex() == public_key
assert key.node.chain_code.hex() == chain_code
assert key.xpub == public_key + chain_code

View File

@ -0,0 +1,235 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import cardano, messages
from trezorlib.exceptions import TrezorFailure
from .conftest import setup_client
PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 1097911063}
SAMPLE_INPUTS = [
{
"input": {
"path": "m/44'/1815'/0'/0/1",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0,
"type": 0,
},
"prev_tx": "839f8200d818582482582008abb575fac4c39d5bf80683f7f0c37e48f4e3d96e37d1f6611919a7241b456600ff9f8282d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a00305becffa0",
}
]
VALID_VECTORS = [
# Mainnet transaction without change
(
# protocol magic
PROTOCOL_MAGICS["mainnet"],
# inputs
[SAMPLE_INPUTS[0]["input"]],
# outputs
[
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
}
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
# tx hash
"799c65e8a2c0b1dc4232611728c09d3f3eb0d811c077f8e9798f84605ef1b23d",
# tx body
"82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e8ffa0818200d818588582584089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea26308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a6355840312c01c27317415b0b8acc86aa789da877fe7e15c65b7ea4c4565d8739117f5f6d9d38bf5d058f7be809b2b9b06c1d79fc6b20f9a4d76d8c89bae333edf5680c",
),
# Mainnet transaction with change
(
# protocol magic (mainnet)
764824073,
# inputs
[
{
"path": "m/44'/1815'/0'/0/1",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0,
"type": 0,
}
],
# outputs
[
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
},
{"path": "m/44'/1815'/0'/0/1", "amount": "1000000"},
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
# tx hash
"40bf94518f31aba7779dd99aa71fe867887bcb3e0bac2c6dc33d3f20ec74a6b1",
# tx body
"82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e88282d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a000f4240ffa0818200d818588582584089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea26308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63558400b47193163462023bdb72f03b2f6afc8e3645dbc9252cb70f7516da402ce3b8468e4a60929674de5862d6253315008e07b60aa189f5c455dd272ff1c84c89d0c",
),
# Testnet transaction
(
# protocol magic
PROTOCOL_MAGICS["testnet"],
# inputs
[SAMPLE_INPUTS[0]["input"]],
# outputs
[
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
}
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
# tx hash
"799c65e8a2c0b1dc4232611728c09d3f3eb0d811c077f8e9798f84605ef1b23d",
# tx body
"82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e8ffa0818200d818588582584089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea26308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63558403594ee7e2bfe4c84f886a8336cecb7c42983ce9a057345ebb6294a436087d8db93ca78cf514c7c48edff4c8435f690a5817951e2b55d2db729875ee7cc0f7d08",
),
]
INVALID_VECTORS = [
# Output address is a valid CBOR but invalid Cardano address
(
# protocol magic
PROTOCOL_MAGICS["mainnet"],
# inputs
[SAMPLE_INPUTS[0]["input"]],
# outputs
[
{
"address": "jsK75PTH2esX8k4Wvxenyz83LJJWToBbVmGrWUer2CHFHanLseh7r3sW5X5q",
"amount": "3003112",
}
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
"Invalid output address!",
),
# Output address is an invalid CBOR
(
# protocol magic
PROTOCOL_MAGICS["mainnet"],
# inputs
[SAMPLE_INPUTS[0]["input"]],
# outputs
[
{
"address": "jsK75PTH2esX8k4Wvxenyz83LJJWToBbVmGrWUer2CHFHanLseh7r3sW5X5q",
"amount": "3003112",
}
],
# transactions
[
"839f8200d818582482582008abb575fac4c39d5bf80683f7f0c37e48f4e3d96e37d1f6611919a7241b456600ff9f8282d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a00305becffa0"
],
"Invalid output address!",
),
# Output address is invalid CBOR
(
# protocol magic (mainnet)
764824073,
# inputs
[
{
"path": "m/44'/1815'/0'/0/1",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0,
"type": 0,
}
],
# outputs
[
{
"address": "5dnY6xgRcNUSLGa4gfqef2jGAMHb7koQs9EXErXLNC1LiMPUnhn8joXhvEJpWQtN3F4ysATcBvCn5tABgL3e4hPWapPHmcK5GJMSEaET5JafgAGwSrznzL1Mqa",
"amount": "3003112",
}
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
"Invalid output address!",
),
]
@pytest.mark.cardano
@pytest.mark.skip_t1 # T1 support is not planned
@setup_client()
@pytest.mark.parametrize(
"protocol_magic,inputs,outputs,transactions,tx_hash,tx_body", VALID_VECTORS
)
def test_cardano_sign_tx(
client, protocol_magic, inputs, outputs, transactions, tx_hash, tx_body
):
inputs = [cardano.create_input(i) for i in inputs]
outputs = [cardano.create_output(o) for o in outputs]
expected_responses = [
messages.CardanoTxRequest(tx_index=i) for i in range(len(transactions))
]
expected_responses += [
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
messages.CardanoSignedTx(),
]
def input_flow():
yield
client.debug.swipe_down()
client.debug.press_yes()
yield
client.debug.swipe_down()
client.debug.press_yes()
with client:
client.set_expected_responses(expected_responses)
client.set_input_flow(input_flow)
response = cardano.sign_tx(
client, inputs, outputs, transactions, protocol_magic
)
assert response.tx_hash.hex() == tx_hash
assert response.tx_body.hex() == tx_body
@pytest.mark.cardano
@pytest.mark.skip_t1 # T1 support is not planned
@setup_client()
@pytest.mark.parametrize(
"protocol_magic,inputs,outputs,transactions,expected_error_message", INVALID_VECTORS
)
def test_cardano_sign_tx_validation(
client, protocol_magic, inputs, outputs, transactions, expected_error_message
):
inputs = [cardano.create_input(i) for i in inputs]
outputs = [cardano.create_output(o) for o in outputs]
expected_responses = [
messages.CardanoTxRequest(tx_index=i) for i in range(len(transactions))
]
expected_responses += [messages.Failure()]
with client:
client.set_expected_responses(expected_responses)
with pytest.raises(TrezorFailure) as exc:
cardano.sign_tx(client, inputs, outputs, transactions, protocol_magic)
assert exc.value.args[1] == expected_error_message

View File

@ -0,0 +1,219 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import messages as proto
from .common import TrezorTest
@pytest.mark.skip_t2
class TestMsgChangepin(TrezorTest):
def test_set_pin(self):
self.setup_mnemonic_nopin_nopassphrase()
features = self.client.call_raw(proto.Initialize())
assert features.pin_protection is False
# Check that there's no PIN protection
ret = self.client.call_raw(proto.Ping(pin_protection=True))
assert isinstance(ret, proto.Success)
# Let's set new PIN
ret = self.client.call_raw(proto.ChangePin())
assert isinstance(ret, proto.ButtonRequest)
# Press button
self.client.debug.press_yes()
ret = self.client.call_raw(proto.ButtonAck())
# Send the PIN for first time
assert isinstance(ret, proto.PinMatrixRequest)
pin_encoded = self.client.debug.encode_pin(self.pin6)
ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
# Send the PIN for second time
assert isinstance(ret, proto.PinMatrixRequest)
pin_encoded = self.client.debug.encode_pin(self.pin6)
ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
# Now we're done
assert isinstance(ret, proto.Success)
# Check that there's PIN protection now
features = self.client.call_raw(proto.Initialize())
assert features.pin_protection is True
# Check that the PIN is correct
self.check_pin(self.pin6)
def test_change_pin(self):
self.setup_mnemonic_pin_passphrase()
features = self.client.call_raw(proto.Initialize())
assert features.pin_protection is True
# Check that there's PIN protection
ret = self.client.call_raw(proto.Ping(pin_protection=True))
assert isinstance(ret, proto.PinMatrixRequest)
self.client.call_raw(proto.Cancel())
# Check current PIN value
self.check_pin(self.pin4)
# Let's change PIN
ret = self.client.call_raw(proto.ChangePin())
assert isinstance(ret, proto.ButtonRequest)
# Press button
self.client.debug.press_yes()
ret = self.client.call_raw(proto.ButtonAck())
# Send current PIN
assert isinstance(ret, proto.PinMatrixRequest)
pin_encoded = self.client.debug.read_pin_encoded()
ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
# Send new PIN for first time
assert isinstance(ret, proto.PinMatrixRequest)
pin_encoded = self.client.debug.encode_pin(self.pin6)
ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
# Send the PIN for second time
assert isinstance(ret, proto.PinMatrixRequest)
pin_encoded = self.client.debug.encode_pin(self.pin6)
ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
# Now we're done
assert isinstance(ret, proto.Success)
# Check that there's still PIN protection now
features = self.client.call_raw(proto.Initialize())
assert features.pin_protection is True
# Check that the PIN is correct
self.check_pin(self.pin6)
def test_remove_pin(self):
self.setup_mnemonic_pin_passphrase()
features = self.client.call_raw(proto.Initialize())
assert features.pin_protection is True
# Check that there's PIN protection
ret = self.client.call_raw(proto.Ping(pin_protection=True))
assert isinstance(ret, proto.PinMatrixRequest)
self.client.call_raw(proto.Cancel())
# Let's remove PIN
ret = self.client.call_raw(proto.ChangePin(remove=True))
assert isinstance(ret, proto.ButtonRequest)
# Press button
self.client.debug.press_yes()
ret = self.client.call_raw(proto.ButtonAck())
# Send current PIN
assert isinstance(ret, proto.PinMatrixRequest)
pin_encoded = self.client.debug.read_pin_encoded()
ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
# Now we're done
assert isinstance(ret, proto.Success)
# Check that there's no PIN protection now
features = self.client.call_raw(proto.Initialize())
assert features.pin_protection is False
ret = self.client.call_raw(proto.Ping(pin_protection=True))
assert isinstance(ret, proto.Success)
def test_set_failed(self):
self.setup_mnemonic_nopin_nopassphrase()
features = self.client.call_raw(proto.Initialize())
assert features.pin_protection is False
# Check that there's no PIN protection
ret = self.client.call_raw(proto.Ping(pin_protection=True))
assert isinstance(ret, proto.Success)
# Let's set new PIN
ret = self.client.call_raw(proto.ChangePin())
assert isinstance(ret, proto.ButtonRequest)
# Press button
self.client.debug.press_yes()
ret = self.client.call_raw(proto.ButtonAck())
# Send the PIN for first time
assert isinstance(ret, proto.PinMatrixRequest)
pin_encoded = self.client.debug.encode_pin(self.pin6)
ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
# Send the PIN for second time, but with typo
assert isinstance(ret, proto.PinMatrixRequest)
pin_encoded = self.client.debug.encode_pin(self.pin4)
ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
# Now it should fail, because pins are different
assert isinstance(ret, proto.Failure)
# Check that there's still no PIN protection now
features = self.client.call_raw(proto.Initialize())
assert features.pin_protection is False
ret = self.client.call_raw(proto.Ping(pin_protection=True))
assert isinstance(ret, proto.Success)
def test_set_failed_2(self):
self.setup_mnemonic_pin_passphrase()
features = self.client.call_raw(proto.Initialize())
assert features.pin_protection is True
# Let's set new PIN
ret = self.client.call_raw(proto.ChangePin())
assert isinstance(ret, proto.ButtonRequest)
# Press button
self.client.debug.press_yes()
ret = self.client.call_raw(proto.ButtonAck())
# Send current PIN
assert isinstance(ret, proto.PinMatrixRequest)
pin_encoded = self.client.debug.read_pin_encoded()
ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
# Send the PIN for first time
assert isinstance(ret, proto.PinMatrixRequest)
pin_encoded = self.client.debug.encode_pin(self.pin6)
ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
# Send the PIN for second time, but with typo
assert isinstance(ret, proto.PinMatrixRequest)
pin_encoded = self.client.debug.encode_pin(self.pin6 + "3")
ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
# Now it should fail, because pins are different
assert isinstance(ret, proto.Failure)
# Check that there's still old PIN protection
features = self.client.call_raw(proto.Initialize())
assert features.pin_protection is True
self.check_pin(self.pin4)
def check_pin(self, pin):
self.client.clear_session()
ret = self.client.call_raw(proto.Ping(pin_protection=True))
assert isinstance(ret, proto.PinMatrixRequest)
pin_encoded = self.client.debug.encode_pin(pin)
ret = self.client.call_raw(proto.PinMatrixAck(pin=pin_encoded))
assert isinstance(ret, proto.Success)

View File

@ -0,0 +1,192 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import misc
from .common import TrezorTest
class TestMsgCipherkeyvalue(TrezorTest):
def test_encrypt(self):
self.setup_mnemonic_nopin_nopassphrase()
# different ask values
res = misc.encrypt_keyvalue(
self.client,
[0, 1, 2],
b"test",
b"testing message!",
ask_on_encrypt=True,
ask_on_decrypt=True,
)
assert res.hex() == "676faf8f13272af601776bc31bc14e8f"
res = misc.encrypt_keyvalue(
self.client,
[0, 1, 2],
b"test",
b"testing message!",
ask_on_encrypt=True,
ask_on_decrypt=False,
)
assert res.hex() == "5aa0fbcb9d7fa669880745479d80c622"
res = misc.encrypt_keyvalue(
self.client,
[0, 1, 2],
b"test",
b"testing message!",
ask_on_encrypt=False,
ask_on_decrypt=True,
)
assert res.hex() == "958d4f63269b61044aaedc900c8d6208"
res = misc.encrypt_keyvalue(
self.client,
[0, 1, 2],
b"test",
b"testing message!",
ask_on_encrypt=False,
ask_on_decrypt=False,
)
assert res.hex() == "e0cf0eb0425947000eb546cc3994bc6c"
# different key
res = misc.encrypt_keyvalue(
self.client,
[0, 1, 2],
b"test2",
b"testing message!",
ask_on_encrypt=True,
ask_on_decrypt=True,
)
assert res.hex() == "de247a6aa6be77a134bb3f3f925f13af"
# different message
res = misc.encrypt_keyvalue(
self.client,
[0, 1, 2],
b"test",
b"testing message! it is different",
ask_on_encrypt=True,
ask_on_decrypt=True,
)
assert (
res.hex()
== "676faf8f13272af601776bc31bc14e8f3ae1c88536bf18f1b44f1e4c2c4a613d"
)
# different path
res = misc.encrypt_keyvalue(
self.client,
[0, 1, 3],
b"test",
b"testing message!",
ask_on_encrypt=True,
ask_on_decrypt=True,
)
assert res.hex() == "b4811a9d492f5355a5186ddbfccaae7b"
def test_decrypt(self):
self.setup_mnemonic_nopin_nopassphrase()
# different ask values
res = misc.decrypt_keyvalue(
self.client,
[0, 1, 2],
b"test",
bytes.fromhex("676faf8f13272af601776bc31bc14e8f"),
ask_on_encrypt=True,
ask_on_decrypt=True,
)
assert res == b"testing message!"
res = misc.decrypt_keyvalue(
self.client,
[0, 1, 2],
b"test",
bytes.fromhex("5aa0fbcb9d7fa669880745479d80c622"),
ask_on_encrypt=True,
ask_on_decrypt=False,
)
assert res == b"testing message!"
res = misc.decrypt_keyvalue(
self.client,
[0, 1, 2],
b"test",
bytes.fromhex("958d4f63269b61044aaedc900c8d6208"),
ask_on_encrypt=False,
ask_on_decrypt=True,
)
assert res == b"testing message!"
res = misc.decrypt_keyvalue(
self.client,
[0, 1, 2],
b"test",
bytes.fromhex("e0cf0eb0425947000eb546cc3994bc6c"),
ask_on_encrypt=False,
ask_on_decrypt=False,
)
assert res == b"testing message!"
# different key
res = misc.decrypt_keyvalue(
self.client,
[0, 1, 2],
b"test2",
bytes.fromhex("de247a6aa6be77a134bb3f3f925f13af"),
ask_on_encrypt=True,
ask_on_decrypt=True,
)
assert res == b"testing message!"
# different message
res = misc.decrypt_keyvalue(
self.client,
[0, 1, 2],
b"test",
bytes.fromhex(
"676faf8f13272af601776bc31bc14e8f3ae1c88536bf18f1b44f1e4c2c4a613d"
),
ask_on_encrypt=True,
ask_on_decrypt=True,
)
assert res == b"testing message! it is different"
# different path
res = misc.decrypt_keyvalue(
self.client,
[0, 1, 3],
b"test",
bytes.fromhex("b4811a9d492f5355a5186ddbfccaae7b"),
ask_on_encrypt=True,
ask_on_decrypt=True,
)
assert res == b"testing message!"
def test_encrypt_badlen(self):
self.setup_mnemonic_nopin_nopassphrase()
with pytest.raises(Exception):
misc.encrypt_keyvalue(self.client, [0, 1, 2], b"test", b"testing")
def test_decrypt_badlen(self):
self.setup_mnemonic_nopin_nopassphrase()
with pytest.raises(Exception):
misc.decrypt_keyvalue(self.client, [0, 1, 2], b"test", b"testing")

View File

@ -0,0 +1,96 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import messages as proto
from .common import TrezorTest
@pytest.mark.skip_t2
class TestMsgClearsession(TrezorTest):
def test_clearsession(self):
self.setup_mnemonic_pin_passphrase()
with self.client:
self.client.set_expected_responses(
[
proto.ButtonRequest(code=proto.ButtonRequestType.ProtectCall),
proto.PinMatrixRequest(),
proto.PassphraseRequest(),
proto.Success(),
]
)
res = self.client.ping(
"random data",
button_protection=True,
pin_protection=True,
passphrase_protection=True,
)
assert res == "random data"
with self.client:
# pin and passphrase are cached
self.client.set_expected_responses(
[
proto.ButtonRequest(code=proto.ButtonRequestType.ProtectCall),
proto.Success(),
]
)
res = self.client.ping(
"random data",
button_protection=True,
pin_protection=True,
passphrase_protection=True,
)
assert res == "random data"
self.client.clear_session()
# session cache is cleared
with self.client:
self.client.set_expected_responses(
[
proto.ButtonRequest(code=proto.ButtonRequestType.ProtectCall),
proto.PinMatrixRequest(),
proto.PassphraseRequest(),
proto.Success(),
]
)
res = self.client.ping(
"random data",
button_protection=True,
pin_protection=True,
passphrase_protection=True,
)
assert res == "random data"
with self.client:
# pin and passphrase are cached
self.client.set_expected_responses(
[
proto.ButtonRequest(code=proto.ButtonRequestType.ProtectCall),
proto.Success(),
]
)
res = self.client.ping(
"random data",
button_protection=True,
pin_protection=True,
passphrase_protection=True,
)
assert res == "random data"

View File

@ -0,0 +1,52 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import ethereum
from trezorlib.tools import H_
from .common import TrezorTest
@pytest.mark.ethereum
class TestMsgEthereumGetaddress(TrezorTest):
def test_ethereum_getaddress(self):
self.setup_mnemonic_nopin_nopassphrase()
assert (
ethereum.get_address(self.client, [H_(44), H_(60)])
== "0xE025dfbE2C53638E547C6487DED34Add7b8Aafc1"
)
assert (
ethereum.get_address(self.client, [H_(44), H_(60), 1])
== "0xeD46C856D0c79661cF7d40FFE0C0C5077c00E898"
)
assert (
ethereum.get_address(self.client, [H_(44), H_(60), 0, H_(1)])
== "0x6682Fa7F3eC58581b1e576268b5463B4b5c93839"
)
assert (
ethereum.get_address(self.client, [H_(44), H_(60), H_(9), 0])
== "0xFb3BE0F9717fF5fCF3C58EB49a9Ed67F1BD89D4E"
)
assert (
ethereum.get_address(self.client, [H_(44), H_(60), 0, 9999999])
== "0x6b909b50d88c9A8E02453A87b3662E3e7a5E0CF1"
)
assert (
ethereum.get_address(self.client, [H_(44), H_(6060), 0, 9999999])
== "0x98b8e926bd224764De2A0E4f4CBe1521474050AF"
)

View File

@ -0,0 +1,44 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import ethereum
from trezorlib.tools import H_
from .common import TrezorTest
@pytest.mark.ethereum
class TestMsgEthereumGetPublicKey(TrezorTest):
def test_ethereum_getpublickey(self):
self.setup_mnemonic_nopin_nopassphrase()
res = ethereum.get_public_node(self.client, [H_(44), H_(60), H_(0)])
assert res.node.depth == 3
assert res.node.fingerprint == 0xC10CFFDA
assert res.node.child_num == 0x80000000
assert (
res.node.chain_code.hex()
== "813d9feda6421f97a6472ff36679aa9e211ff88f6bdee51093af313ce628087e"
)
assert (
res.node.public_key.hex()
== "0318c22dedce01caca32354f98428e3af06a452f3fa84e6af8f1b6aa362affa641"
)
assert (
res.xpub
== "xpub6D54vV8eUYHMVBZCnz4SLjuiQngXURVCGKKGoJrWUDRegdMByLTJKfRs64q3UKiQCsSHJPtCQehTvERczdghS7gb8oedWSyNDtBU1zYDJtb"
)

View File

@ -0,0 +1,46 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import ethereum
from trezorlib.tools import H_
from .common import TrezorTest
@pytest.mark.ethereum
class TestMsgEthereumSignmessage(TrezorTest):
PATH = [H_(44), H_(60), H_(0), 0]
ADDRESS = "0xEa53AF85525B1779eE99ece1a5560C0b78537C3b"
VECTORS = [
(
"This is an example of a signed message.",
"9bacd833b51fde010bab53bafd9d832eadd3b175d2af2e629bb2944fcc987dce7ff68bb3571ed25a720c220f2f9538bc8d04f582bee002c9af086590a49805901c",
),
(
"VeryLongMessage!" * 64,
"752d283b3aea1eb44fd09203f4d5c430a6544e399b8500b02722b54325f6d8d457fd83460a31045cb0d6e8356240954ba072fdfe5cdb3f16d416e2acf1a180a51c",
),
]
def test_sign(self):
self.setup_mnemonic_nopin_nopassphrase()
for msg, sig in self.VECTORS:
res = ethereum.sign_message(self.client, self.PATH, msg)
assert res.address == self.ADDRESS
assert res.signature.hex() == sig

View File

@ -0,0 +1,407 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import ethereum, messages as proto
from trezorlib.tools import parse_path
from .common import TrezorTest
TO_ADDR = "0x1d1c328764a41bda0492b66baa30c4a339ff85ef"
@pytest.mark.ethereum
class TestMsgEthereumSigntx(TrezorTest):
def test_ethereum_signtx_known_erc20_token(self):
self.setup_mnemonic_nopin_nopassphrase()
with self.client:
self.client.set_expected_responses(
[
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.EthereumTxRequest(data_length=None),
]
)
data = bytearray()
# method id signalizing `transfer(address _to, uint256 _value)` function
data.extend(bytes.fromhex("a9059cbb"))
# 1st function argument (to - the receiver)
data.extend(
bytes.fromhex(
"000000000000000000000000574bbb36871ba6b78e27f4b4dcfb76ea0091880b"
)
)
# 2nd function argument (value - amount to be transferred)
data.extend(
bytes.fromhex(
"000000000000000000000000000000000000000000000000000000000bebc200"
)
)
# 200 000 000 in dec, divisibility of ADT = 9, trezor1 displays 0.2 ADT, Trezor T 200 000 000 Wei ADT
sig_v, sig_r, sig_s = ethereum.sign_tx(
self.client,
n=parse_path("44'/60'/0'/0/0"),
nonce=0,
gas_price=20,
gas_limit=20,
# ADT token address
to="0xd0d6d6c5fe4a677d343cc433536bb717bae167dd",
chain_id=1,
# value needs to be 0, token value is set in the contract (data)
value=0,
data=data,
)
# taken from T1 might not be 100% correct but still better than nothing
assert (
sig_r.hex()
== "ec1df922115d256745410fbc2070296756583c8786e4d402a88d4e29ec513fa9"
)
assert (
sig_s.hex()
== "7001bfe3ba357e4a9f9e0d3a3f8a8962257615a4cf215db93e48b98999fc51b7"
)
def test_ethereum_signtx_unknown_erc20_token(self):
self.setup_mnemonic_nopin_nopassphrase()
with self.client:
self.client.set_expected_responses(
[
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.EthereumTxRequest(data_length=None),
]
)
data = bytearray()
# method id signalizing `transfer(address _to, uint256 _value)` function
data.extend(bytes.fromhex("a9059cbb"))
# 1st function argument (to - the receiver)
data.extend(
bytes.fromhex(
"000000000000000000000000574bbb36871ba6b78e27f4b4dcfb76ea0091880b"
)
)
# 2nd function argument (value - amount to be transferred)
data.extend(
bytes.fromhex(
"0000000000000000000000000000000000000000000000000000000000000123"
)
)
# since this token is unknown trezor should display "unknown token value"
sig_v, sig_r, sig_s = ethereum.sign_tx(
self.client,
n=parse_path("44'/60'/0'/0/1"),
nonce=0,
gas_price=20,
gas_limit=20,
# unknown token address (Grzegorz Brzęczyszczykiewicz Token)
to="0xfc6b5d6af8a13258f7cbd0d39e11b35e01a32f93",
chain_id=1,
# value needs to be 0, token value is set in the contract (data)
value=0,
data=data,
)
# taken from T1 might not be 100% correct but still better than nothing
assert (
sig_r.hex()
== "2559bbf1bcb80992b6eaa96f0074b19606d8ea7bf4219e1c9ac64a12855c0cce"
)
assert (
sig_s.hex()
== "633a74429eb6d3aeec4ed797542236a85daab3cab15e37736b87a45697541d7a"
)
def test_ethereum_signtx_nodata(self):
self.setup_mnemonic_nopin_nopassphrase()
with self.client:
self.client.set_expected_responses(
[
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.EthereumTxRequest(data_length=None), # v,r,s checked later
]
)
sig_v, sig_r, sig_s = ethereum.sign_tx(
self.client,
n=parse_path("44'/60'/0'/0/100"),
nonce=0,
gas_price=20,
gas_limit=20,
to=TO_ADDR,
value=10,
)
assert sig_v == 27
assert (
sig_r.hex()
== "2f548f63ddb4cf19b6b9f922da58ff71833b967d590f3b4dcc2a70810338a982"
)
assert (
sig_s.hex()
== "428d35f0dca963b5196b63e7aa5e0405d8bff77d6aee1202183f1f68dacb4483"
)
with self.client:
self.client.set_expected_responses(
[
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.EthereumTxRequest(data_length=None),
]
)
sig_v, sig_r, sig_s = ethereum.sign_tx(
self.client,
n=parse_path("44'/60'/0'/0/100"),
nonce=123456,
gas_price=20000,
gas_limit=20000,
to=TO_ADDR,
value=12345678901234567890,
)
assert sig_v == 27
assert (
sig_r.hex()
== "3bf0470cd7f5ad8d82613199f73deadc55c3c9f32f91b1a21b5ef644144ebd58"
)
assert (
sig_s.hex()
== "48b3ef1b2502febdf35e9ff4df0ba1fda62f042fad639eb4852a297fc9872ebd"
)
def test_ethereum_signtx_data(self):
self.setup_mnemonic_nopin_nopassphrase()
with self.client:
self.client.set_expected_responses(
[
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.EthereumTxRequest(data_length=None),
]
)
sig_v, sig_r, sig_s = ethereum.sign_tx(
self.client,
n=parse_path("44'/60'/0'/0/0"),
nonce=0,
gas_price=20,
gas_limit=20,
to=TO_ADDR,
value=10,
data=b"abcdefghijklmnop" * 16,
)
assert sig_v == 27
assert (
sig_r.hex()
== "e90f9e3dbfb34861d40d67570cb369049e675c6eebfdda6b08413a2283421b85"
)
assert (
sig_s.hex()
== "763912b8801f76cbea7792d98123a245514beeab2f3afebb4bab637888e8393a"
)
with self.client:
self.client.set_expected_responses(
[
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.EthereumTxRequest(
data_length=1024,
signature_r=None,
signature_s=None,
signature_v=None,
),
proto.EthereumTxRequest(data_length=1024),
proto.EthereumTxRequest(data_length=1024),
proto.EthereumTxRequest(data_length=3),
proto.EthereumTxRequest(),
]
)
sig_v, sig_r, sig_s = ethereum.sign_tx(
self.client,
n=parse_path("44'/60'/0'/0/0"),
nonce=123456,
gas_price=20000,
gas_limit=20000,
to=TO_ADDR,
value=12345678901234567890,
data=b"ABCDEFGHIJKLMNOP" * 256 + b"!!!",
)
assert sig_v == 27
assert (
sig_r.hex()
== "dd96d82d791118a55601dfcede237760d2e9734b76c373ede5362a447c42ac48"
)
assert (
sig_s.hex()
== "60a77558f28d483d476f9507cd8a6a4bb47b86611aaff95fd5499b9ee9ebe7ee"
)
def test_ethereum_signtx_message(self):
self.setup_mnemonic_nopin_nopassphrase()
with self.client:
self.client.set_expected_responses(
[
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.EthereumTxRequest(
data_length=1024,
signature_r=None,
signature_s=None,
signature_v=None,
),
proto.EthereumTxRequest(data_length=1024),
proto.EthereumTxRequest(data_length=1024),
proto.EthereumTxRequest(data_length=3),
proto.EthereumTxRequest(),
]
)
sig_v, sig_r, sig_s = ethereum.sign_tx(
self.client,
n=parse_path("44'/60'/0'/0/0"),
nonce=0,
gas_price=20000,
gas_limit=20000,
to=TO_ADDR,
value=0,
data=b"ABCDEFGHIJKLMNOP" * 256 + b"!!!",
)
assert sig_v == 27
assert (
sig_r.hex()
== "81af16020d3c6ad820cab2e2b0834fa37f4a9b0c2443f151a4e2f12fe1081b09"
)
assert (
sig_s.hex()
== "7b34b5d8a43771d493cd9fa0c7b27a9563e2a31799fb9f0c2809539a848b9f47"
)
def test_ethereum_signtx_newcontract(self):
self.setup_mnemonic_allallall()
# contract creation without data should fail.
with pytest.raises(Exception):
ethereum.sign_tx(
self.client,
n=parse_path("44'/60'/0'/0/0"),
nonce=123456,
gas_price=20000,
gas_limit=20000,
to="",
value=12345678901234567890,
)
with self.client:
self.client.set_expected_responses(
[
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
proto.EthereumTxRequest(
data_length=1024,
signature_r=None,
signature_s=None,
signature_v=None,
),
proto.EthereumTxRequest(data_length=1024),
proto.EthereumTxRequest(data_length=1024),
proto.EthereumTxRequest(data_length=3),
proto.EthereumTxRequest(),
]
)
sig_v, sig_r, sig_s = ethereum.sign_tx(
self.client,
n=parse_path("44'/60'/0'/0/0"),
nonce=0,
gas_price=20000,
gas_limit=20000,
to="",
value=12345678901234567890,
data=b"ABCDEFGHIJKLMNOP" * 256 + b"!!!",
)
assert sig_v == 28
assert (
sig_r.hex()
== "c86bda9de238b1c602648996561e7270a3be208da96bbf23474cb8e4014b9f93"
)
assert (
sig_s.hex()
== "18742403f75a05e7fa9868c30b36f1e55628de02d01c03084c1ff6775a13137c"
)
def test_ethereum_sanity_checks(self):
# gas overflow
with pytest.raises(Exception):
ethereum.sign_tx(
self.client,
n=parse_path("44'/60'/0'/0/0"),
nonce=123456,
gas_price=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
gas_limit=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
to=TO_ADDR,
value=12345678901234567890,
)
# no gas price
with pytest.raises(Exception):
ethereum.sign_tx(
self.client,
n=[0, 0],
nonce=123456,
gas_limit=10000,
to=TO_ADDR,
value=12345678901234567890,
)
# no gas limit
with pytest.raises(Exception):
ethereum.sign_tx(
self.client,
n=[0, 0],
nonce=123456,
gas_price=10000,
to=TO_ADDR,
value=12345678901234567890,
)
# no nonce
with pytest.raises(Exception):
ethereum.sign_tx(
self.client,
n=[0, 0],
gas_price=10000,
gas_limit=123456,
to=TO_ADDR,
value=12345678901234567890,
)

View File

@ -0,0 +1,219 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import ethereum
from trezorlib.tools import H_
from .common import TrezorTest
@pytest.mark.ethereum
class TestMsgEthereumSigntxChainId(TrezorTest):
def test_ethereum_signtx_eip155(self):
# chain_id, nonce, sig_v, sig_r, sig_s, value, gas_limit, data
VECTORS = [
(
3,
0,
42,
"cde31d8ab07d423d5e52aeb148180528ea54974cdb4c5578499c0137ec24d892",
"41fc58955b3b3e3f3b2aced65e11e8a3cb6339027f943bec3d504d6398b69dd2",
100000000000000000,
21000,
None,
),
(
3,
1,
41,
"57951fed170f3765dea164d65acd31373799db32ec572e213b1d9a1209956b98",
"0971f8830c0e2e89919309f217ed2eadb0c63d647e016d220729ce79d27c24a0",
100000000000000000,
21000,
None,
),
(
3,
2,
42,
"73744f66231690edd9eed2ab3c2b56ec4f6c4b9aabc633ae7f3f4ea94223d52c",
"7f500afbe2b2b4e4e57f22511e3a42b3596b85cad7fe1eca700cdae1905d3555",
100000000000000000,
21004,
b"\0",
),
(
3,
3,
42,
"1a4fc1ec5f98bf874d5336aaf1fa9069ce68dc36c3f77e93465c9ac2c8b4b741",
"13007c9b1df6a0d2f2ffa9d0ebcdec189122a5e781eb64967eb0d6a6def95b7a",
100000000000000000,
299732,
b"ABCDEFGHIJKLMNOP" * 256 + b"!!!",
),
(
3,
4,
42,
"8da0358d780df542f767d977f99ad034b6d9fa808fe50997141be2a1b93542c0",
"2dafe1ead8aae1051e6662c5d553b34067bda9c8fa7314ae8693ec61ddfc96d4",
0,
21004,
b"\0",
),
(
1,
1,
38,
"b72707f0f5a38339c9dd0359720312c739a8ac6554659c7af48456f06ba33374",
"75a431c046046942f9c1f3305cd08f34302164811c675ac0a0ac0b73cb30a90e",
0,
21004,
b"\0",
),
(
255,
1,
545,
"529172fb644a6d29b7218fb783f3d666021fc29cc4bf9bffbcfb3b84ab8d6181",
"30980c6102a12872ef9cd888f2bf90c81bbbdc8878ff7d1d1382f8983b0d0c49",
0,
21004,
b"\0",
),
(
256,
1,
548,
"db53c05c679bdfdf3ded787ce9607d3f109ae46c87b1dcc9ab34053e5ed0eace",
"39645dd48118d369b588dbf279f1a8c01051fabf65bf8eaa633c6433ff120cce",
0,
21004,
b"\0",
),
(
65535,
1,
131105,
"b520fa77767cdf07b6014d4a8fb35eebe5ed7c0edab97132b0dc74e3e1f13ed9",
"78735b2db4cf95fb651c5c1f5529e60542019e456c6cb7a9f4bd9bbb83418d99",
0,
21004,
b"\0",
),
(
65536,
1,
131107,
"4b6122ba875b57ce084bd5f08e9ae1944e998726a4056c9b7746432d8f46ba99",
"6812c2668ac9c9927b69ef7cf9baec54436f7319ccc14f0f664e1e94e6109e06",
0,
21004,
b"\0",
),
(
16777215,
1,
33554465,
"68a8c6f2336a8e3296f17a307d84a1e6d3ab1383fdcc62611c2e8426f2e2777e",
"2d4ce900077ab40aac26064945998dbac5a014baadae2d3cb629cdeb9452db61",
0,
21004,
b"\0",
),
(
16777216,
1,
33554468,
"b6c42c584ef69621a2e5f3e1ab9dad890dbff3c92a599230dd0e394cd29d1c68",
"497eec05ea52773d0f05e7fdf4f7993b3a06ef958804b39af699ef09ed0f5d7e",
0,
21004,
b"\0",
),
(
2147483629,
1,
4294967294,
"1a31f886c0bba527e622a731270dc29e62a607ff63558fca38745e5b9a672686",
"0f3fce8a70598bbb54387cde7e8f957a27e4a816cbc9408717b27d8666222bd9",
0,
21004,
b"\0",
),
(
2147483630,
1,
4294967296,
"ba6cb6e2ebbac3726db9a3e4a939454009108f6515330e567aeada14ecebe074",
"2bbfba1154cae32e3e6c6bbf3ce41cba6cc8c6b764245ba6026605506838e690",
0,
21004,
None,
),
(
2147483631,
1,
4294967298,
"3c743528e9ce315db02e487de93f2b2cfc93421e43f1d519f77a2f05bd2ce190",
"16c1fec1495fe5da89d1a026f1a575ff354e18ff0fb9d04a6cfb0413267ab2bc",
100000000000000000,
21000,
None,
),
(
3125659152,
1,
6251318340,
"82cde0c9e1a94c1305791b09e1bcd021a49b036a16d9733acbc1a08bb30f3410",
"472c8897519ba410b86f80993236d992e18e94d1f59c3d8760d2d7c90914dfc6",
1,
21005,
None,
),
(
4294967295,
1,
8589934625,
"67788e892fb372bba16823e16d3186f67494d7b1128555248f3661ad87e9d7ef",
"2faf9f06dfdf23ceca2796cf0d55c88187f199e98a94dfb15722824b244d81a1",
100000000000000000,
21000,
None,
),
]
self.setup_mnemonic_allallall()
for ci, n, sv, sr, ss, v, gl, d in VECTORS:
sig_v, sig_r, sig_s = ethereum.sign_tx(
self.client,
n=[H_(44), H_(60), H_(0), 0, 0],
nonce=n,
gas_price=20000000000,
gas_limit=gl,
to="0x8eA7a3fccC211ED48b763b4164884DDbcF3b0A98",
value=v,
chain_id=ci,
data=d,
)
assert sig_v == sv
assert sig_r.hex() == sr
assert sig_s.hex() == ss

View File

@ -0,0 +1,53 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import ethereum
from .common import TrezorTest
@pytest.mark.ethereum
class TestMsgEthereumVerifymessage(TrezorTest):
ADDRESS = "0xCb3864960e8DB1A751212c580AF27Ee8867d688F"
VECTORS = [
(
"This is an example of a signed message.",
"b7837058907192dbc9427bf57d93a0acca3816c92927a08be573b785f2d72dab65dad9c92fbe03a358acdb455eab2107b869945d11f4e353d9cc6ea957d08a871b",
),
(
"VeryLongMessage!" * 64,
"da2b73b0170479c2bfba3dd4839bf0d67732a44df8c873f3f3a2aca8a57d7bdc0b5d534f54c649e2d44135717001998b176d3cd1212366464db51f5838430fb31c",
),
]
def test_verify(self):
self.setup_mnemonic_nopin_nopassphrase()
for msg, sig in self.VECTORS:
res = ethereum.verify_message(
self.client, self.ADDRESS, bytes.fromhex(sig), msg
)
assert res is True
def test_verify_invalid(self):
self.setup_mnemonic_nopin_nopassphrase()
signature = bytes.fromhex(self.VECTORS[0][1])
res = ethereum.verify_message(
self.client, self.ADDRESS, signature, "another message"
)
assert res is False

View File

@ -0,0 +1,218 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import btc, messages as proto
from trezorlib.tools import H_, CallException, parse_path
from ..support import ckd_public as bip32
from .common import TrezorTest
def getmultisig(chain, nr, xpubs, signatures=[b"", b"", b""]):
return proto.MultisigRedeemScriptType(
nodes=[bip32.deserialize(xpub) for xpub in xpubs],
address_n=[chain, nr],
signatures=signatures,
m=2,
)
class TestMsgGetaddress(TrezorTest):
def test_btc(self):
self.setup_mnemonic_nopin_nopassphrase()
assert (
btc.get_address(self.client, "Bitcoin", [])
== "1EfKbQupktEMXf4gujJ9kCFo83k1iMqwqK"
)
assert (
btc.get_address(self.client, "Bitcoin", [1])
== "1CK7SJdcb8z9HuvVft3D91HLpLC6KSsGb"
)
assert (
btc.get_address(self.client, "Bitcoin", [0, H_(1)])
== "1JVq66pzRBvqaBRFeU9SPVvg3er4ZDgoMs"
)
assert (
btc.get_address(self.client, "Bitcoin", [H_(9), 0])
== "1F4YdQdL9ZQwvcNTuy5mjyQxXkyCfMcP2P"
)
assert (
btc.get_address(self.client, "Bitcoin", [0, 9999999])
== "1GS8X3yc7ntzwGw9vXwj9wqmBWZkTFewBV"
)
def test_ltc(self):
self.setup_mnemonic_nopin_nopassphrase()
assert (
btc.get_address(self.client, "Litecoin", [])
== "LYtGrdDeqYUQnTkr5sHT2DKZLG7Hqg7HTK"
)
assert (
btc.get_address(self.client, "Litecoin", [1])
== "LKRGNecThFP3Q6c5fosLVA53Z2hUDb1qnE"
)
assert (
btc.get_address(self.client, "Litecoin", [0, H_(1)])
== "LcinMK8pVrAtpz7Qpc8jfWzSFsDLgLYfG6"
)
assert (
btc.get_address(self.client, "Litecoin", [H_(9), 0])
== "LZHVtcwAEDf1BR4d67551zUijyLUpDF9EX"
)
assert (
btc.get_address(self.client, "Litecoin", [0, 9999999])
== "Laf5nGHSCT94C5dK6fw2RxuXPiw2ZuRR9S"
)
def test_tbtc(self):
self.setup_mnemonic_nopin_nopassphrase()
assert (
btc.get_address(self.client, "Testnet", [111, 42])
== "moN6aN6NP1KWgnPSqzrrRPvx2x1UtZJssa"
)
def test_bch(self):
self.setup_mnemonic_allallall()
assert (
btc.get_address(self.client, "Bcash", parse_path("44'/145'/0'/0/0"))
== "bitcoincash:qr08q88p9etk89wgv05nwlrkm4l0urz4cyl36hh9sv"
)
assert (
btc.get_address(self.client, "Bcash", parse_path("44'/145'/0'/0/1"))
== "bitcoincash:qr23ajjfd9wd73l87j642puf8cad20lfmqdgwvpat4"
)
assert (
btc.get_address(self.client, "Bcash", parse_path("44'/145'/0'/1/0"))
== "bitcoincash:qzc5q87w069lzg7g3gzx0c8dz83mn7l02scej5aluw"
)
def test_grs(self):
self.setup_mnemonic_allallall()
assert (
btc.get_address(self.client, "Groestlcoin", parse_path("44'/17'/0'/0/0"))
== "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"
)
assert (
btc.get_address(self.client, "Groestlcoin", parse_path("44'/17'/0'/1/0"))
== "FmRaqvVBRrAp2Umfqx9V1ectZy8gw54QDN"
)
assert (
btc.get_address(self.client, "Groestlcoin", parse_path("44'/17'/0'/1/1"))
== "Fmhtxeh7YdCBkyQF7AQG4QnY8y3rJg89di"
)
def test_multisig(self):
self.setup_mnemonic_allallall()
xpubs = []
for n in range(1, 4):
node = btc.get_public_node(self.client, parse_path("44'/0'/%d'" % n))
xpubs.append(node.xpub)
for nr in range(1, 4):
assert (
btc.get_address(
self.client,
"Bitcoin",
parse_path("44'/0'/%d'/0/0" % nr),
show_display=(nr == 1),
multisig=getmultisig(0, 0, xpubs=xpubs),
)
== "3Pdz86KtfJBuHLcSv4DysJo4aQfanTqCzG"
)
assert (
btc.get_address(
self.client,
"Bitcoin",
parse_path("44'/0'/%d'/1/0" % nr),
show_display=(nr == 1),
multisig=getmultisig(1, 0, xpubs=xpubs),
)
== "36gP3KVx1ooStZ9quZDXbAF3GCr42b2zzd"
)
def test_multisig_missing(self):
self.setup_mnemonic_allallall()
xpubs = []
for n in range(1, 4):
# shift account numbers by 10 to create valid multisig,
# but not containing the keys used below
n = n + 10
node = btc.get_public_node(self.client, parse_path("44'/0'/%d'" % n))
xpubs.append(node.xpub)
for nr in range(1, 4):
with pytest.raises(CallException):
btc.get_address(
self.client,
"Bitcoin",
parse_path("44'/0'/%d'/0/0" % nr),
show_display=(nr == 1),
multisig=getmultisig(0, 0, xpubs=xpubs),
)
with pytest.raises(CallException):
btc.get_address(
self.client,
"Bitcoin",
parse_path("44'/0'/%d'/1/0" % nr),
show_display=(nr == 1),
multisig=getmultisig(1, 0, xpubs=xpubs),
)
def test_bch_multisig(self):
self.setup_mnemonic_allallall()
xpubs = []
for n in range(1, 4):
node = btc.get_public_node(self.client, parse_path("44'/145'/%d'" % n))
xpubs.append(node.xpub)
for nr in range(1, 4):
assert (
btc.get_address(
self.client,
"Bcash",
parse_path("44'/145'/%d'/0/0" % nr),
show_display=(nr == 1),
multisig=getmultisig(0, 0, xpubs=xpubs),
)
== "bitcoincash:pqguz4nqq64jhr5v3kvpq4dsjrkda75hwy86gq0qzw"
)
assert (
btc.get_address(
self.client,
"Bcash",
parse_path("44'/145'/%d'/1/0" % nr),
show_display=(nr == 1),
multisig=getmultisig(1, 0, xpubs=xpubs),
)
== "bitcoincash:pp6kcpkhua7789g2vyj0qfkcux3yvje7euhyhltn0a"
)
def test_public_ckd(self):
self.setup_mnemonic_nopin_nopassphrase()
node = btc.get_public_node(self.client, []).node
node_sub1 = btc.get_public_node(self.client, [1]).node
node_sub2 = bip32.public_ckd(node, [1])
assert node_sub1.chain_code == node_sub2.chain_code
assert node_sub1.public_key == node_sub2.public_key
address1 = btc.get_address(self.client, "Bitcoin", [1])
address2 = bip32.get_address(node_sub2, 0)
assert address2 == "1CK7SJdcb8z9HuvVft3D91HLpLC6KSsGb"
assert address1 == address2

View File

@ -0,0 +1,113 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from trezorlib import btc, messages as proto
from trezorlib.tools import parse_path
from ..support import ckd_public as bip32
from .common import TrezorTest
class TestMsgGetaddressSegwit(TrezorTest):
def test_show_segwit(self):
self.setup_mnemonic_allallall()
assert (
btc.get_address(
self.client,
"Testnet",
parse_path("49'/1'/0'/1/0"),
True,
None,
script_type=proto.InputScriptType.SPENDP2SHWITNESS,
)
== "2N1LGaGg836mqSQqiuUBLfcyGBhyZbremDX"
)
assert (
btc.get_address(
self.client,
"Testnet",
parse_path("49'/1'/0'/0/0"),
False,
None,
script_type=proto.InputScriptType.SPENDP2SHWITNESS,
)
== "2N4Q5FhU2497BryFfUgbqkAJE87aKHUhXMp"
)
assert (
btc.get_address(
self.client,
"Testnet",
parse_path("44'/1'/0'/0/0"),
False,
None,
script_type=proto.InputScriptType.SPENDP2SHWITNESS,
)
== "2N6UeBoqYEEnybg4cReFYDammpsyDw8R2Mc"
)
assert (
btc.get_address(
self.client,
"Testnet",
parse_path("44'/1'/0'/0/0"),
False,
None,
script_type=proto.InputScriptType.SPENDADDRESS,
)
== "mvbu1Gdy8SUjTenqerxUaZyYjmveZvt33q"
)
assert (
btc.get_address(
self.client,
"Groestlcoin Testnet",
parse_path("49'/1'/0'/0/0"),
False,
None,
script_type=proto.InputScriptType.SPENDP2SHWITNESS,
)
== "2N4Q5FhU2497BryFfUgbqkAJE87aKDv3V3e"
)
def test_show_multisig_3(self):
self.setup_mnemonic_allallall()
nodes = map(
lambda index: btc.get_public_node(
self.client, parse_path("999'/1'/%d'" % index)
),
range(1, 4),
)
multisig1 = proto.MultisigRedeemScriptType(
nodes=[bip32.deserialize(n.xpub) for n in nodes],
address_n=[2, 0],
signatures=[b"", b"", b""],
m=2,
)
# multisig2 = proto.MultisigRedeemScriptType(
# pubkeys=map(lambda n: proto.HDNodePathType(node=bip32.deserialize(n.xpub), address_n=[2, 1]), nodes),
# signatures=[b'', b'', b''],
# m=2,
# )
for i in [1, 2, 3]:
assert (
btc.get_address(
self.client,
"Testnet",
parse_path("999'/1'/%d'/2/0" % i),
False,
multisig1,
script_type=proto.InputScriptType.SPENDP2SHWITNESS,
)
== "2N2MxyAfifVhb3AMagisxaj3uij8bfXqf4Y"
)

View File

@ -0,0 +1,123 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from trezorlib import btc, messages as proto
from trezorlib.tools import parse_path
from ..support import ckd_public as bip32
from .common import TrezorTest
class TestMsgGetaddressSegwitNative(TrezorTest):
def test_show_segwit(self):
self.setup_mnemonic_allallall()
assert (
btc.get_address(
self.client,
"Testnet",
parse_path("49'/1'/0'/0/0"),
True,
None,
script_type=proto.InputScriptType.SPENDWITNESS,
)
== "tb1qqzv60m9ajw8drqulta4ld4gfx0rdh82un5s65s"
)
assert (
btc.get_address(
self.client,
"Testnet",
parse_path("49'/1'/0'/1/0"),
False,
None,
script_type=proto.InputScriptType.SPENDWITNESS,
)
== "tb1q694ccp5qcc0udmfwgp692u2s2hjpq5h407urtu"
)
assert (
btc.get_address(
self.client,
"Testnet",
parse_path("44'/1'/0'/0/0"),
False,
None,
script_type=proto.InputScriptType.SPENDWITNESS,
)
== "tb1q54un3q39sf7e7tlfq99d6ezys7qgc62a6rxllc"
)
assert (
btc.get_address(
self.client,
"Testnet",
parse_path("44'/1'/0'/0/0"),
False,
None,
script_type=proto.InputScriptType.SPENDADDRESS,
)
== "mvbu1Gdy8SUjTenqerxUaZyYjmveZvt33q"
)
assert (
btc.get_address(
self.client,
"Groestlcoin",
parse_path("84'/17'/0'/0/0"),
False,
None,
script_type=proto.InputScriptType.SPENDWITNESS,
)
== "grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"
)
def test_show_multisig_3(self):
self.setup_mnemonic_allallall()
nodes = [
btc.get_public_node(self.client, parse_path("999'/1'/%d'" % index))
for index in range(1, 4)
]
multisig1 = proto.MultisigRedeemScriptType(
nodes=[bip32.deserialize(n.xpub) for n in nodes],
address_n=[2, 0],
signatures=[b"", b"", b""],
m=2,
)
multisig2 = proto.MultisigRedeemScriptType(
nodes=[bip32.deserialize(n.xpub) for n in nodes],
address_n=[2, 1],
signatures=[b"", b"", b""],
m=2,
)
for i in [1, 2, 3]:
assert (
btc.get_address(
self.client,
"Testnet",
parse_path("999'/1'/%d'/2/1" % i),
False,
multisig2,
script_type=proto.InputScriptType.SPENDWITNESS,
)
== "tb1qch62pf820spe9mlq49ns5uexfnl6jzcezp7d328fw58lj0rhlhasge9hzy"
)
assert (
btc.get_address(
self.client,
"Testnet",
parse_path("999'/1'/%d'/2/0" % i),
False,
multisig1,
script_type=proto.InputScriptType.SPENDWITNESS,
)
== "tb1qr6xa5v60zyt3ry9nmfew2fk5g9y3gerkjeu6xxdz7qga5kknz2ssld9z2z"
)

View File

@ -0,0 +1,84 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from trezorlib import btc, messages as proto
from ..support import ckd_public as bip32
from .common import TrezorTest
class TestMsgGetaddressShow(TrezorTest):
def test_show(self):
self.setup_mnemonic_nopin_nopassphrase()
assert (
btc.get_address(self.client, "Bitcoin", [1], show_display=True)
== "1CK7SJdcb8z9HuvVft3D91HLpLC6KSsGb"
)
assert (
btc.get_address(self.client, "Bitcoin", [2], show_display=True)
== "15AeAhtNJNKyowK8qPHwgpXkhsokzLtUpG"
)
assert (
btc.get_address(self.client, "Bitcoin", [3], show_display=True)
== "1CmzyJp9w3NafXMSEFH4SLYUPAVCSUrrJ5"
)
def test_show_multisig_3(self):
self.setup_mnemonic_nopin_nopassphrase()
node = bip32.deserialize(
"xpub661MyMwAqRbcF1zGijBb2K6x9YiJPh58xpcCeLvTxMX6spkY3PcpJ4ABcCyWfskq5DDxM3e6Ez5ePCqG5bnPUXR4wL8TZWyoDaUdiWW7bKy"
)
multisig = proto.MultisigRedeemScriptType(
pubkeys=[
proto.HDNodePathType(node=node, address_n=[1]),
proto.HDNodePathType(node=node, address_n=[2]),
proto.HDNodePathType(node=node, address_n=[3]),
],
signatures=[b"", b"", b""],
m=2,
)
for i in [1, 2, 3]:
assert (
btc.get_address(
self.client, "Bitcoin", [i], show_display=True, multisig=multisig
)
== "3E7GDtuHqnqPmDgwH59pVC7AvySiSkbibz"
)
def test_show_multisig_15(self):
self.setup_mnemonic_nopin_nopassphrase()
node = bip32.deserialize(
"xpub661MyMwAqRbcF1zGijBb2K6x9YiJPh58xpcCeLvTxMX6spkY3PcpJ4ABcCyWfskq5DDxM3e6Ez5ePCqG5bnPUXR4wL8TZWyoDaUdiWW7bKy"
)
pubs = []
for x in range(15):
pubs.append(proto.HDNodePathType(node=node, address_n=[x]))
multisig = proto.MultisigRedeemScriptType(
pubkeys=pubs, signatures=[b""] * 15, m=15
)
for i in range(15):
assert (
btc.get_address(
self.client, "Bitcoin", [i], show_display=True, multisig=multisig
)
== "3QaKF8zobqcqY8aS6nxCD5ZYdiRfL3RCmU"
)

View File

@ -0,0 +1,76 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from trezorlib import messages as proto, misc
from .common import TrezorTest
class TestMsgGetECDHSessionKey(TrezorTest):
def test_ecdh(self):
self.setup_mnemonic_nopin_nopassphrase()
# URI : gpg://Satoshi Nakamoto <satoshi@bitcoin.org>
identity = proto.IdentityType(
proto="gpg",
user="",
host="Satoshi Nakamoto <satoshi@bitcoin.org>",
port="",
path="",
index=0,
)
peer_public_key = bytes.fromhex(
"0407f2c6e5becf3213c1d07df0cfbe8e39f70a8c643df7575e5c56859ec52c45ca950499c019719dae0fda04248d851e52cf9d66eeb211d89a77be40de22b6c89d"
)
result = misc.get_ecdh_session_key(
self.client,
identity=identity,
peer_public_key=peer_public_key,
ecdsa_curve_name="secp256k1",
)
assert (
result.session_key.hex()
== "0495e5d8c9e5cc09e7cf4908774f52decb381ce97f2fc9ba56e959c13f03f9f47a03dd151cbc908bc1db84d46e2c33e7bbb9daddc800f985244c924fd64adf6647"
)
peer_public_key = bytes.fromhex(
"04811a6c2bd2a547d0dd84747297fec47719e7c3f9b0024f027c2b237be99aac39a9230acbd163d0cb1524a0f5ea4bfed6058cec6f18368f72a12aa0c4d083ff64"
)
result = misc.get_ecdh_session_key(
self.client,
identity=identity,
peer_public_key=peer_public_key,
ecdsa_curve_name="nist256p1",
)
assert (
result.session_key.hex()
== "046d1f5c48af2cf2c57076ac2c9d7808db2086f614cb7b8107119ff2c6270cd209749809efe0196f01a0cc633788cef1f4a2bd650c99570d06962f923fca6d8fdf"
)
peer_public_key = bytes.fromhex(
"40a8cf4b6a64c4314e80f15a8ea55812bd735fbb365936a48b2d78807b575fa17a"
)
result = misc.get_ecdh_session_key(
self.client,
identity=identity,
peer_public_key=peer_public_key,
ecdsa_curve_name="curve25519",
)
assert (
result.session_key.hex()
== "04e24516669e0b7d3d72e5129fddd07b6644c30915f5c8b7f1f62324afb3624311"
)

View File

@ -0,0 +1,48 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import math
import pytest
from trezorlib import messages as m, misc
ENTROPY_LENGTHS_POW2 = [2 ** l for l in range(10)]
ENTROPY_LENGTHS_POW2_1 = [2 ** l + 1 for l in range(10)]
ENTROPY_LENGTHS = ENTROPY_LENGTHS_POW2 + ENTROPY_LENGTHS_POW2_1
def entropy(data):
counts = {}
for c in data:
counts[c] = counts.get(c, 0) + 1
e = 0
for v in counts.values():
p = v / len(data)
e -= p * math.log(p, 256)
return e
@pytest.mark.parametrize("entropy_length", ENTROPY_LENGTHS)
def test_entropy(client, entropy_length):
with client:
client.set_expected_responses(
[m.ButtonRequest(code=m.ButtonRequestType.ProtectCall), m.Entropy()]
)
ent = misc.get_entropy(client, entropy_length)
assert len(ent) == entropy_length
print("{} bytes: entropy = {}".format(entropy_length, entropy(ent)))

View File

@ -0,0 +1,168 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from trezorlib import btc, messages as proto
from trezorlib.tools import H_
from ..support import ckd_public as bip32
from .common import TrezorTest
class TestMsgGetpublickey(TrezorTest):
def test_btc(self):
self.setup_mnemonic_nopin_nopassphrase()
assert (
bip32.serialize(btc.get_public_node(self.client, []).node, 0x0488B21E)
== "xpub661MyMwAqRbcF1zGijBb2K6x9YiJPh58xpcCeLvTxMX6spkY3PcpJ4ABcCyWfskq5DDxM3e6Ez5ePCqG5bnPUXR4wL8TZWyoDaUdiWW7bKy"
)
assert (
btc.get_public_node(self.client, [], coin_name="Bitcoin").xpub
== "xpub661MyMwAqRbcF1zGijBb2K6x9YiJPh58xpcCeLvTxMX6spkY3PcpJ4ABcCyWfskq5DDxM3e6Ez5ePCqG5bnPUXR4wL8TZWyoDaUdiWW7bKy"
)
assert (
bip32.serialize(btc.get_public_node(self.client, [1]).node, 0x0488B21E)
== "xpub68zNxjsTrV8y9AadThLW7dTAqEpZ7xBLFSyJ3X9pjTv6Njg6kxgjXJkzxq8u3ttnjBw1jupQHMP3gpGZzZqd1eh5S4GjkaMhPR18vMyUi8N"
)
assert (
btc.get_public_node(self.client, [1], coin_name="Bitcoin").xpub
== "xpub68zNxjsTrV8y9AadThLW7dTAqEpZ7xBLFSyJ3X9pjTv6Njg6kxgjXJkzxq8u3ttnjBw1jupQHMP3gpGZzZqd1eh5S4GjkaMhPR18vMyUi8N"
)
assert (
bip32.serialize(
btc.get_public_node(self.client, [0, H_(1)]).node, 0x0488B21E
)
== "xpub6A3FoZqYXj1AbW4thRwBh26YwZWbmoyjTaZwwxJjY1oKUpefLepL3RFS9DHKQrjAfxDrzDepYMDZPqXN6upQm3bHQ9xaXD5a3mqni3goF4v"
)
assert (
btc.get_public_node(self.client, [0, H_(1)], coin_name="Bitcoin").xpub
== "xpub6A3FoZqYXj1AbW4thRwBh26YwZWbmoyjTaZwwxJjY1oKUpefLepL3RFS9DHKQrjAfxDrzDepYMDZPqXN6upQm3bHQ9xaXD5a3mqni3goF4v"
)
assert (
bip32.serialize(
btc.get_public_node(self.client, [H_(9), 0]).node, 0x0488B21E
)
== "xpub6A2h5mzLDfYginoD7q7wCWbq18wTbN9gducRr2w5NRTwdLeoT3cJSwefFqW7uXTpVFGtpUyDMBNYs3DNvvXx6NPjF9YEbUQrtxFSWnPtVrv"
)
assert (
btc.get_public_node(self.client, [H_(9), 0], coin_name="Bitcoin").xpub
== "xpub6A2h5mzLDfYginoD7q7wCWbq18wTbN9gducRr2w5NRTwdLeoT3cJSwefFqW7uXTpVFGtpUyDMBNYs3DNvvXx6NPjF9YEbUQrtxFSWnPtVrv"
)
assert (
bip32.serialize(
btc.get_public_node(self.client, [0, 9999999]).node, 0x0488B21E
)
== "xpub6A3FoZqQEK6iwLZ4HFkqSo5fb35BH4bpjC4SPZ63prfLdGYPwYxEuC6o91bUvFFdMzKWe5rs3axHRUjxJaSvBnKKFtnfLwDACRxPxabsv2r"
)
assert (
btc.get_public_node(self.client, [0, 9999999], coin_name="Bitcoin").xpub
== "xpub6A3FoZqQEK6iwLZ4HFkqSo5fb35BH4bpjC4SPZ63prfLdGYPwYxEuC6o91bUvFFdMzKWe5rs3axHRUjxJaSvBnKKFtnfLwDACRxPxabsv2r"
)
def test_ltc(self):
self.setup_mnemonic_nopin_nopassphrase()
assert (
bip32.serialize(btc.get_public_node(self.client, []).node, 0x019DA462)
== "Ltub2SSUS19CirucVPGDKDBatBDBEM2s9UbH66pBURfaKrMocCPLhQ7Z7hecy5VYLHA5fRdXwB2e61j2VJCNzVsqKTCVEU1vECjqi5EyczFX9xp"
)
assert (
btc.get_public_node(self.client, [], coin_name="Litecoin").xpub
== "Ltub2SSUS19CirucVPGDKDBatBDBEM2s9UbH66pBURfaKrMocCPLhQ7Z7hecy5VYLHA5fRdXwB2e61j2VJCNzVsqKTCVEU1vECjqi5EyczFX9xp"
)
assert (
bip32.serialize(btc.get_public_node(self.client, [1]).node, 0x019DA462)
== "Ltub2VRVRP5VjvSyPXra4BLVyVZPv397sjhUNjBGsbtw6xko77JuQyBULxFSKheviJJ3KQLbL3Cx8P2RnudguTw4raUVjCACRG7jsumUptYx55C"
)
assert (
btc.get_public_node(self.client, [1], coin_name="Litecoin").xpub
== "Ltub2VRVRP5VjvSyPXra4BLVyVZPv397sjhUNjBGsbtw6xko77JuQyBULxFSKheviJJ3KQLbL3Cx8P2RnudguTw4raUVjCACRG7jsumUptYx55C"
)
assert (
bip32.serialize(
btc.get_public_node(self.client, [0, H_(1)]).node, 0x019DA462
)
== "Ltub2WUNGD3aRAKAqsLqHuwBYtCn2MqAXbVsarmvn33quWe2DCHTzfK4s4jsW5oM5G8RGAdSaM3NPNrwVvtV1ourbyNhhHr3BtqcYGc8caf5GoT"
)
assert (
btc.get_public_node(self.client, [0, H_(1)], coin_name="Litecoin").xpub
== "Ltub2WUNGD3aRAKAqsLqHuwBYtCn2MqAXbVsarmvn33quWe2DCHTzfK4s4jsW5oM5G8RGAdSaM3NPNrwVvtV1ourbyNhhHr3BtqcYGc8caf5GoT"
)
assert (
bip32.serialize(
btc.get_public_node(self.client, [H_(9), 0]).node, 0x019DA462
)
== "Ltub2WToYRCN76rgyA59iK7w4Ni45wG2M9fpmBpQg7gBjvJeMiHc7473Gb96ci29Zvs55TgUQcMmCD1vy8aVqpdPwJB9YHRhGAAuPT1nRLLXmFu"
)
assert (
btc.get_public_node(self.client, [H_(9), 0], coin_name="Litecoin").xpub
== "Ltub2WToYRCN76rgyA59iK7w4Ni45wG2M9fpmBpQg7gBjvJeMiHc7473Gb96ci29Zvs55TgUQcMmCD1vy8aVqpdPwJB9YHRhGAAuPT1nRLLXmFu"
)
assert (
bip32.serialize(
btc.get_public_node(self.client, [0, 9999999]).node, 0x019DA462
)
== "Ltub2WUNGD3S7kQjBhpzsjkqJfBtfqPk2r7xrUGRDdqACMW3MeBCbZSyiqbEVt7WaeesxCj6EDFQtcbfXa75DUYN2i6jZ2g81cyCgvijs9J2u2n"
)
assert (
btc.get_public_node(self.client, [0, 9999999], coin_name="Litecoin").xpub
== "Ltub2WUNGD3S7kQjBhpzsjkqJfBtfqPk2r7xrUGRDdqACMW3MeBCbZSyiqbEVt7WaeesxCj6EDFQtcbfXa75DUYN2i6jZ2g81cyCgvijs9J2u2n"
)
def test_tbtc(self):
self.setup_mnemonic_nopin_nopassphrase()
assert (
bip32.serialize(
btc.get_public_node(self.client, [111, 42]).node, 0x043587CF
)
== "tpubDAgixSyai5PWbc8N1mBkHDR5nLgAnHFtY7r4y5EzxqAxrt9YUDpZL3kaRoHVvCfrcwNo31c2isBP2uTHcZxEosuKbyJhCAbrvGoPuLUZ7Mz"
)
assert (
btc.get_public_node(self.client, [111, 42], coin_name="Testnet").xpub
== "tpubDAgixSyai5PWbc8N1mBkHDR5nLgAnHFtY7r4y5EzxqAxrt9YUDpZL3kaRoHVvCfrcwNo31c2isBP2uTHcZxEosuKbyJhCAbrvGoPuLUZ7Mz"
)
def test_script_type(self):
self.setup_mnemonic_nopin_nopassphrase()
assert (
btc.get_public_node(self.client, [], coin_name="Bitcoin").xpub
== "xpub661MyMwAqRbcF1zGijBb2K6x9YiJPh58xpcCeLvTxMX6spkY3PcpJ4ABcCyWfskq5DDxM3e6Ez5ePCqG5bnPUXR4wL8TZWyoDaUdiWW7bKy"
)
assert (
btc.get_public_node(
self.client,
[],
coin_name="Bitcoin",
script_type=proto.InputScriptType.SPENDADDRESS,
).xpub
== "xpub661MyMwAqRbcF1zGijBb2K6x9YiJPh58xpcCeLvTxMX6spkY3PcpJ4ABcCyWfskq5DDxM3e6Ez5ePCqG5bnPUXR4wL8TZWyoDaUdiWW7bKy"
)
assert (
btc.get_public_node(
self.client,
[],
coin_name="Bitcoin",
script_type=proto.InputScriptType.SPENDP2SHWITNESS,
).xpub
== "ypub6QqdH2c5z7966KBPZ5yDEQCTKWrkLK4dsw8RRjpMLMtyvvZmJ3nNv7pKdQw6fnQkUrLm6XEeheSCGVSpoJCQGm6fofpt9RoHVJYH72ecmVm"
)
assert (
btc.get_public_node(
self.client,
[],
coin_name="Bitcoin",
script_type=proto.InputScriptType.SPENDWITNESS,
).xpub
== "zpub6jftahH18ngZwcNWPSkqSVHxVV1CGw48o3eeD8iEiNGrz2NzYhwwYBUTectgfh4ftVTZqzqDAJnk9n4PWzcR4znGg1XJjLcmm2bvVc3Honv"
)

View File

@ -0,0 +1,84 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import btc
from trezorlib.tools import H_, CallException
from .common import TrezorTest
class TestMsgGetpublickeyCurve(TrezorTest):
def test_default_curve(self):
self.setup_mnemonic_nopin_nopassphrase()
assert (
btc.get_public_node(self.client, [H_(111), 42]).node.public_key.hex()
== "02e7fcec053f0df94d88c86447970743e8a1979d242d09338dcf8687a9966f7fbc"
)
assert (
btc.get_public_node(self.client, [H_(111), H_(42)]).node.public_key.hex()
== "03ce7b690969d773ba9ed212464eb2b534b87b9b8a9383300bddabe1f093f79220"
)
def test_secp256k1_curve(self):
self.setup_mnemonic_nopin_nopassphrase()
assert (
btc.get_public_node(
self.client, [H_(111), 42], ecdsa_curve_name="secp256k1"
).node.public_key.hex()
== "02e7fcec053f0df94d88c86447970743e8a1979d242d09338dcf8687a9966f7fbc"
)
assert (
btc.get_public_node(
self.client, [H_(111), H_(42)], ecdsa_curve_name="secp256k1"
).node.public_key.hex()
== "03ce7b690969d773ba9ed212464eb2b534b87b9b8a9383300bddabe1f093f79220"
)
def test_nist256p1_curve(self):
self.setup_mnemonic_nopin_nopassphrase()
assert (
btc.get_public_node(
self.client, [H_(111), 42], ecdsa_curve_name="nist256p1"
).node.public_key.hex()
== "02a9ce59b32bd64a70bc52aca96e5d09af65c6b9593ba2a60af8fccfe1437f2129"
)
assert (
btc.get_public_node(
self.client, [H_(111), H_(42)], ecdsa_curve_name="nist256p1"
).node.public_key.hex()
== "026fe35d8afed67dbf0561a1d32922e8ad0cd0d86effbc82be970cbed7d9bab2c2"
)
def test_ed25519_curve(self):
self.setup_mnemonic_nopin_nopassphrase()
# ed25519 curve does not support public derivation, so test only private derivation paths
assert (
btc.get_public_node(
self.client, [H_(111), H_(42)], ecdsa_curve_name="ed25519"
).node.public_key.hex()
== "0069a14b478e508eab6e93303f4e6f5c50b8136627830f2ed5c3a835fc6c0ea2b7"
)
assert (
btc.get_public_node(
self.client, [H_(111), H_(65535)], ecdsa_curve_name="ed25519"
).node.public_key.hex()
== "00514f73a05184458611b14c348fee4fd988d36cf3aee7207737861bac611de991"
)
# test failure when using public derivation
with pytest.raises(CallException):
btc.get_public_node(self.client, [H_(111), 42], ecdsa_curve_name="ed25519")

View File

@ -0,0 +1,37 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import lisk
from trezorlib.tools import parse_path
from .common import TrezorTest
LISK_PATH = parse_path("m/44h/134h/0h/1h")
@pytest.mark.lisk
class TestMsgLiskGetaddress(TrezorTest):
def test_lisk_getaddress(self):
self.setup_mnemonic_nopin_nopassphrase()
assert lisk.get_address(self.client, LISK_PATH[:2]) == "1431530009238518937L"
assert lisk.get_address(self.client, LISK_PATH[:3]) == "17563781916205589679L"
assert lisk.get_address(self.client, LISK_PATH) == "1874186517773691964L"
assert (
lisk.get_address(self.client, parse_path("m/44h/134h/999h/999h"))
== "16295203558710684671L"
)

View File

@ -0,0 +1,35 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib import lisk
from trezorlib.tools import parse_path
from .common import TrezorTest
LISK_PATH = parse_path("m/44h/134h/0h/0h")
@pytest.mark.lisk
class TestMsgLiskGetPublicKey(TrezorTest):
def test_lisk_get_public_key(self):
self.setup_mnemonic_nopin_nopassphrase()
sig = lisk.get_public_key(self.client, LISK_PATH)
assert (
sig.public_key.hex()
== "eb56d7bbb5e8ea9269405f7a8527fe126023d1db2c973cfac6f760b60ae27294"
)

Some files were not shown because too many files have changed in this diff Show More