././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1719315826.138107 trezor-0.13.9/0000775000175000017500000000000014636526562013352 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/AUTHORS0000664000175000017500000000151514636513242014413 0ustar00matejcikmatejcikpython-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 Jan 'matejcik' Matějek Jan Pochyla Jiří Musil Jochen Hoenicke Jonathan Cross Karel Bílek Marek Palatinus mruddy Pavol Rusnak Peter van Mourik Roman Zeyde Saleem Rashid Tomáš Sušánka ZuluCrypto ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315793.0 trezor-0.13.9/CHANGELOG.md0000664000175000017500000011310214636526521015154 0ustar00matejcikmatejcik# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.13.9] (2024-06-19) [0.13.9]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.8...python/v0.13.9 ### Added - trezorctl: Automatically go to bootloader when upgrading firmware. [#2919] - Support interaction-less upgrade. [#2919] - Added user adjustable brightness setting. [#3208] - Added Solana support. [#3359] - trezorctl: support for human-friendly Trezor Safe device authenticity check (requires separate installation of `cryptography` library). [#3364] - Added support for T3T1. [#3422] - Stellar: add support for StellarClaimClaimableBalanceOp. [#3434] - Cardano: Added support for tagged sets in CBOR (tag 258). [#3496] - Cardano: Added support for Conway certificates. [#3496] - Added ability to request Shamir backups with any number of groups/shares. [#3636] - Added flag for setting up device using SLIP39 "single". [#3868] - Added `--quality` argument to `trezorctl set homescreen`. [#3893] ### Changed - Renamed `trezorctl device self-test` command to `trezorctl device prodtest-t1`. [#3504] - Increased default JPEG quality for uploaded homescreen. [#3893] ### Incompatible changes - Renamed flag used for setting up device using BIP39 to `bip39`. [#3868] - Minimum required Python version is now 3.8. ## [0.13.8] (2023-10-19) [0.13.8]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.7...python/v0.13.8 ### Added - Added full support for Trezor Safe 3 (T2B1). - Added support for STM32F429I-DISC1 board [#2989] - Add support for address chunkification in Receive and Sign flow. [#3237] - Implement device authenticate command. [#3255] - trezorctl: support unlocking bootloader via `trezorctl device unlock-bootloader`. ### Changed - Use 'h' character for hardened BIP-32 components in help texts. [#3037] - trezorctl: Use 'h' character in generated descriptors. [#3037] - ClickUI: notify user in terminal that they should enter PIN or passphrase on Trezor. [#3203] - Internal names are used consistently in constants and names. Original model-based names are kept as aliases for backwards compatibility. - Trezor model detection will try to use the `internal_name` field. ### Fixed - Drop simple-rlp dependency and use internal copy [#3045] - Fixed printing Trezor model when validating firmware image [#3227] - Corrected vendor header signing keys for Safe 3 (T2B1). ## [0.13.7] (2023-06-02) [0.13.7]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.6...python/v0.13.7 ### Added - Recognize signing keys for T2B1. - Add possibility to call tutorial flow [#2795] - Add ability to change homescreen for Model R [#2967] - Recognize hw model field in vendor headers. [#3048] ## [0.13.6] (2023-04-24) [0.13.6]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.5...python/v0.13.6 ### Added - Signed Ethereum network and token definitions from host [#15] - Support SLIP-25 in get-descriptor. [#2541] - trezorctl: Support prompt configuration for `encrypt-keyvalue` / `decrypt-keyvalue`. [#2608] - Support for external reward addresses in Cardano CIP-36 registrations [#2692] - Auto-convert image to Trezor's homescreen format. [#2880] ### Changed - `trezorctl firmware verify` changed order of checks - fingerprint is validated before signatures. [#2745] ### Fixed - Removed attempt to initialize the device after wipe in bootloader mode [#2221] - Limit memory exhaustion in protobuf parser. [#2439] - `trezorctl ethereum sign-tx`: renamed `--gas-limit` shortcut to `-G` to avoid collision with `-t/--token` [#2535] - Fixed behavior of UDP transport search by path when full path is provided and prefix_search is True [#2786] - Fixed behavior of `trezorctl fw` with unsigned Trezor One firmwares. [#2801] - Improve typing information when `TrezorClient` has a more intelligent UI object. [#2832] - When enabling passphrase force-on-device, do not also prompt to enable passphrase if it is already enabled. [#2833] ## [0.13.5] (2022-12-28) [0.13.5]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.4...python/v0.13.5 ### Added - Add support for model field in firmware image. [#2701] - Add support for v3-style Trezor One signatures. [#2701] ### Changed - More structured information about signing keys for different models. [#2701] ### Incompatible changes - Instead of accepting a list of public keys, `FirmwareType.verify()` accepts a parameter configuring whether to use production or development keys. [#2701] ## [0.13.4] (2022-11-04) [0.13.4]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.3...python/v0.13.4 ### Added - Add UnlockPath message. [#2289] - Added new TOI formats - little endian full-color and even-high grayscale [#2414] - Add device set-busy command to trezorctl. [#2445] - Support SLIP-25 accounts in get-public-node and get-address. [#2517] - Add possibility to save emulator screenshots. [#2547] - Support for Cardano CIP-36 governance registration format [#2561] ### Removed - Remove DATA parameter from trezorctl cosi commit. - Remove firmware dumping capability. [#2433] ### Fixed - Fixed issue where type declarations were not visible to consumer packages. [#2542] ### Incompatible changes - Refactored firmware parsing and validation to a more object oriented approach. [#2576] ## [0.13.3] (2022-07-13) [0.13.3]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.2...python/v0.13.3 ### Added - Support for Cardano Babbage era transaction items [#2354] ### Fixed - Fix Click 7.x compatibility. [#2364] ## [0.13.2] (2022-06-30) [0.13.2]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.1...python/v0.13.2 ### Fixed - Fixed dependency error when running trezorctl without PIL. - Fixed dependency error when running trezorctl on Python 3.6 without rlp. - Fix `trezorctl --version` crash. [#1702] ## [0.13.1] (2022-06-29) [0.13.1]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.0...python/v0.13.1 ### Added - New exception type `DeviceIsBusy` indicates that the device is in use by another process. [#1026] - Support payment requests and GetNonce command. [#1430] - Add press_info() to DebugLink. [#1430] - Add support for blind EIP-712 signing for Trezor One [#1970] - Add ScriptUI for trezorctl, spawned by --script option [#2023] - Support T1 screenshot saving in Debuglink [#2093] - Support generating Electrum-compatible message signatures in CLI. [#2100] - Support Cardano Alonzo-era transaction items and --include-network-id flag [#2114] - trezorctl: Bitcoin commands can detect script type from derivation path. [#2159] - Add support for model R [#2230] - Add firmware get-hash command. [#2239] - Jump and stay in bootloader from firmware through SVC call reverse trampoline. [#2284] ### Changed - Unify boolean arguments/options in trezorlib commands to on/off [#2123] - Rename `normalize_nfc` to `prepare_message_bytes` in tools.py [#2126] - `trezorctl monero` network type arguments now accept symbolic names instead of numbers. [#2219] ### Fixed - trezorctl will correctly report that device is in use. [#1026] - Allow passing empty `message_hash` for domain-only EIP-712 hashes for Trezor T1 (i.e. when `primaryType`=`EIP712Domain`) [#2036] - Fixed error when printing protobuf message with a missing required field. [#2135] - Add compatibility with Click 8.1 [#2199] ## [0.13.0] - 2021-12-09 [0.13.0]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.4...python/v0.13.0 ### Added - `trezorctl firmware update` shows progress bar (Model T only) - Enabled session management via `EndSession` [#1227] - Added parameters to enable Cardano derivation when calling `init_device()`. [#1231] - two new trezorctl commands - `trezorctl firmware download` and `trezorctl firmware verify` [#1258] - Support no_script_type option in SignMessage. [#1586] - Support for Ethereum EIP1559 transactions [#1604] - Debuglink can automatically scroll through paginated views. [#1671] - Support for Taproot descriptors [#1710] - `trezorlib.stellar.from_envelope` was added, it includes support for the Stellar [TransactionV1](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0015.md#xdr) format transaction. [#1745] - Ethereum: support 64-bit chain IDs [#1771] - Support for Cardano multi-sig transactions, token minting, script addresses, multi-sig keys, minting keys and native script verification [#1772] - Added parameters to specify kind of Cardano derivation to all functions and `trezorctl` commands. [#1783] - Support for EIP-712 in library and `trezorctl ethereum sign-typed-data` [#1835] - Add script_pubkey field to TxInput message. [#1857] - Full type hinting checkable with pyright [#1893] ### Changed - protobuf is aware of `required` fields and default values [#379] - `trezorctl firmware-update` command changed to `trezorctl firmware update` [#1258] - `btc.sign_tx()` accepts keyword arguments for transaction metadata [#1266] - Raise `ValueError` when the txid for an input is not present in `prev_txes` during `btc.sign_tx` [#1442] - `trezorlib.mappings` was refactored for easier customization [#1449] - Refactor protobuf codec for better clarity [#1541] - `UdpTransport.wait_until_ready` no longer sets socket to nonblocking [#1668] - Cardano transaction parameters are now streamed into the device one by one instead of being sent as one large object [#1683] - `trezorlib.stellar` will refuse to process transactions containing MuxedAccount [#1838] - Use unified descriptors format. [#1885] - Introduce Trezor models as an abstraction over USB IDs, vendor strings, and possibly protobuf mappings. [#1967] ### Deprecated - instantiating protobuf objects with positional arguments is deprecated [#379] - `details` argument to `btc.sign_tx()` is deprecated. Use keyword arguments instead. [#379] - values of required fields must be supplied at instantiation time. Omitting them is deprecated. [#379] ### Removed - dropped Python 3.5 support [#810] - dropped debug-only `trezorctl debug show-text` functionality [#1531] - Removed support for Lisk [#1765] ### Fixed - fix operator precedence issue for ethereum sign-tx command [#1867] - Updated `tools/build_tx.py` to work with Blockbook's API protections. [#1896] - Fix PIN and passphrase entry in certain terminals on Windows [#1959] ### Incompatible changes - `client.init_device(derive_cardano=True)` must be used before calling Cardano functions. [#1231] - The type of argument to `ui.button_request(x)` is changed from int to ButtonRequest. The original int value can be accessed as `x.code` [#1671] - Due to transaction streaming in Cardano, it isn't possible to return the whole serialized transaction anymore. Instead the transaction hash, transaction witnesses and auxiliary data supplement are returned and the serialized transaction needs to be assembled by the client. [#1683] - `trezorlib.stellar` was reworked to use stellar-sdk instead of providing local implementations [#1745] - Cardano derivation now defaults to Icarus method. This will result in different keys for users with 24-word seed. [#1783] ## [0.12.4] - 2021-09-07 [0.12.4]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.3...python/v0.12.4 ### Fixed - trezorctl: fixed "Invalid value for " when using Click 8 and param is not specified [#1798] ## [0.12.3] - 2021-07-29 [0.12.3]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.2...python/v0.12.3 ### Added - `trezorctl btc get-descriptor` support [#1363] - `trezorctl btc reboot-to-bootloader` support [#1738] - distinguishing between temporary and permanent safety checks - trezorctl accepts PIN entered by letters (useful on laptops) - support for 50-digit PIN for T1 ### Changed - allowed Click 8.x as a requirement - replaced all references to Trezor Wallet with Trezor Suite, and modified all mentions of Beta Wallet ### Fixed - added missing requirement `attrs` - properly parse big numbers in `tools/build_tx.py` [#1257], [#1296] - it is now possible to set safety checks for T1 ## [0.12.2] - 2020-08-27 [0.12.2]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.1...python/v0.12.2 ### Added - `trezorlib.toif` module (moved from internal) can encode and decode TOIF image format - `trezorctl set homescreen` was improved and extended to support PNG images for Trezor T ### Changed - trezorctl will correctly notify the user if the image decoding library is missing ### Fixed - fix exception in `trezorctl btc get-address` [#1179] - fix exception in `trezorctl lisk sign-message` - fix exception in trezorctl commands that accept filenames [#1196] - fix "Invalid homescreen" error when un-setting homescreen ### Removed - removed option `--skip-vendor-header` from `trezorctl firmware-update` which did nothing [#1210] ## [0.12.1] - 2020-08-05 [0.12.1]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.0...python/v0.12.1 ### Added - `trezorctl set safety-checks` controls the new "safety checks" feature. [#1126] - `trezorctl btc get-address` can create multisig addresses. - the following commands are now equivalent in trezorctl: `firmware-update`, `firmware-upgrade`, `update-firmware`, `upgrade-firmware` - support for EXTERNAL input type [#38], [#1052] - support for ownership proofs - support for pre-authorized CoinJoin transactions [#37] - support for Cardano Shelley [#948] ### Changed - do not allow setting auto-lock delay unless PIN is configured ### Fixed - correctly calculate hashes for very small firmwares [f#1082] - unified file arguments in trezorctl - `TrezorClient.ping()` does not crash when device is PIN-locked ## [0.12.0] - 2020-04-01 [0.12.0]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.6...python/v0.12.0 ### Incompatible changes - `trezorlib.coins`, `trezorlib.tx_api`, and the file `coins.json`, were removed - `TrezorClient` argument `ui` is now mandatory. `state` argument was renamed to `session_id`. - UI callback `get_passphrase()` has a new argument `available_on_device`. - API for `cosi` module was changed - other changes may also introduce incompatibilities, please review the full list below ### Added - support for firmwares 1.9.0 and 2.3.0 - Model T now defaults to entering passphrase on device. New trezorctl option `-P` enforces entering passphrase on host. - support for "passphrase always on device" mode on model T - new trezorctl command `get-session` and option `-s` allows entering passphrase once for multiple subsequent trezorctl operations - built-in functionality of UdpTransport to wait until an emulator comes up, and the related command `trezorctl wait-for-emulator` - `trezorctl debug send-bytes` can send raw messages to the device [f#116] - when updating firmware, user is warned that the requested version does not match their device [f#823] - `trezorctl list` can now show name, model and id of device ### Changed - `trezorlib.tx_api.json_to_tx` was reduced to only support Bitcoin fields, and moved to `trezorlib.btc.from_json`. - API for `cosi` module was streamlined: `verify_m_of_n` is now `verify`, the old `verify` is `verify_combined` - internals of firmware parsing were reworked to support signing firmware headers - `get_default_client` respects `TREZOR_PATH` environment variable - UI callback `get_passphrase` has an additional argument `available_on_device`, indicating that the connected Trezor is capable of on-device entry - `Transport.write` and `read` method signatures changed to accept bytes instead of protobuf messages - trezorctl subcommands have a common `@with_client` decorator that manages exception handling and connecting to device ### Fixed - trezorctl does not print empty line when there is no output - trezorctl cleanly reports wire exceptions [f#226] ### Removed - `trezorlib.tx_api` was removed - `trezorlib.coins` and coin data was removed - `trezorlib.ckd_public`, which was deprecated in 0.10, was now removed. - `btc.sign_tx` will not preload transaction data from `prev_txes`, as usage with TxApi is being removed - PIN protection and passphrase protection for `ping()` command was removed - compatibility no-op code from trezorlib 0.9 was removed from `trezorlib.client` - `trezorlib.tools.CallException` was dropped, use `trezorlib.exceptions.TrezorFailure` instead ## [0.11.6] - 2019-12-30 [0.11.6]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.5...python/v0.11.6 ### Added - support for get-and-increase FIDO counter operation - support for setting wipe code - `trezorctl device recover` supports `--u2f-counter` option to set the FIDO counter to a custom value ### Changed - `trezorctl` command was reworked for ease of use and maintenance. See `trezorctl --help` and `OPTIONS.rst` for details. [f#510] - updated EOS transaction parser to match `cleos` in `delegatebw` and `undelegatebw` actions [f#680] [f#681] - `RecoveryDevice` does not set fields when doing dry-run recovery [f#666] ### Fixed - fixed "expand words" functionality in `trezorctl device recover` [f#778] ### Removed - trezorctl no longer interactively signs Bitcoin-like transactions, the only allowed input format is JSON. See [`docs/transaction-format.md`](docs/transaction-format.md) for details. - support for "load device by xprv" was removed from firmware and trezorlib ## [0.11.5] - 2019-09-26 [0.11.5]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.4...python/v0.11.5 ### Added - trezorctl can dump raw protobuf bytes in debug output [f#117] - trezorctl shows a warning when activating Shamir Backup if the device does not support it [f#445] - warnings are emitted when encountering unknown value for a protobuf enum [f#363] - debug messages show enum value names instead of raw numbers - support for packed repeated encoding in the protobuf decoder - in `trezorctl firmware-update`, the new `--beta` switch enables downloading beta firmwares. By default, only stable firmware is used. [f#411], [f#420] - in `trezorctl firmware-update`, the new `--bitcoin-only` switch enables downloading Bitcoin-only firmware - support for FIDO2 resident credential management - support for SD-protect features ### Changed - package directory structure was changed: `src` subdirectory contains sources and `tests` subdirectory contains tests, so that cwd is not cluttered - `trezorctl` script was moved into a module `trezorlib.cli.trezorctl` and is launched through the `entry_points` mechanism. This makes it usable on Windows - `pyblake2` is no longer required on Python 3.6 and up - input flows can only be used in with-block (only relevant for unit tests) - if not specified, trezorctl will set label to "SLIP-0014" in SLIP-0014 mode - in `clear_session` the client also forgets the passphrase state for TT [f#525] ### Fixed - trezorctl will properly check if a firmware is present on a new T1 [f#224] ### Removed - device test suite was moved out of trezor package ## [0.11.4] - 2019-07-31 [0.11.4]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.3...python/v0.11.4 ### Added - trezorctl support for SLIP-39 Shamir Backup - support for Binance Chain ## [0.11.3] - 2019-05-29 [0.11.3]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.2...python/v0.11.3 ### 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 - EOS support [f#87] - Tezos: add voting support [f#41] - `dict_to_proto` now allows enum values as strings ### Changed - Minimum firmware versions bumped to 1.8.0 and 2.1.0 - Cleaner errors when UI object is not supplied - Generated files are now part of the source tarball again. That means that `protoc` is no longer required. ### Fixed - Ethereum commands in trezorctl now work - Memory debugging tools now work again ### Removed - Tron and Ontology support removed until implementations exist in Trezor firmware ## [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) [f#41]: https://github.com/trezor/trezor-firmware/pull/41 [f#87]: https://github.com/trezor/trezor-firmware/pull/87 [f#116]: https://github.com/trezor/trezor-firmware/pull/116 [f#117]: https://github.com/trezor/trezor-firmware/pull/117 [f#224]: https://github.com/trezor/trezor-firmware/pull/224 [f#226]: https://github.com/trezor/trezor-firmware/pull/226 [f#363]: https://github.com/trezor/trezor-firmware/pull/363 [f#411]: https://github.com/trezor/trezor-firmware/pull/411 [f#420]: https://github.com/trezor/trezor-firmware/pull/420 [f#445]: https://github.com/trezor/trezor-firmware/pull/445 [f#510]: https://github.com/trezor/trezor-firmware/pull/510 [f#525]: https://github.com/trezor/trezor-firmware/pull/525 [f#666]: https://github.com/trezor/trezor-firmware/pull/666 [f#680]: https://github.com/trezor/trezor-firmware/pull/680 [f#681]: https://github.com/trezor/trezor-firmware/pull/681 [f#778]: https://github.com/trezor/trezor-firmware/pull/778 [f#823]: https://github.com/trezor/trezor-firmware/pull/823 [f#1082]: https://github.com/trezor/trezor-firmware/pull/1082 [#15]: https://github.com/trezor/trezor-firmware/pull/15 [#37]: https://github.com/trezor/trezor-firmware/pull/37 [#38]: https://github.com/trezor/trezor-firmware/pull/38 [#94]: https://github.com/trezor/python-trezor/pull/94 [#167]: https://github.com/trezor/python-trezor/pull/167 [#169]: https://github.com/trezor/python-trezor/pull/169 [#185]: https://github.com/trezor/python-trezor/pull/185 [#197]: https://github.com/trezor/python-trezor/pull/197 [#199]: https://github.com/trezor/python-trezor/pull/199 [#207]: https://github.com/trezor/python-trezor/pull/207 [#223]: https://github.com/trezor/python-trezor/pull/223 [#226]: https://github.com/trezor/python-trezor/pull/226 [#229]: https://github.com/trezor/python-trezor/pull/229 [#230]: https://github.com/trezor/python-trezor/pull/230 [#236]: https://github.com/trezor/python-trezor/pull/236 [#237]: https://github.com/trezor/python-trezor/pull/237 [#241]: https://github.com/trezor/python-trezor/pull/241 [#242]: https://github.com/trezor/python-trezor/pull/242 [#245]: https://github.com/trezor/python-trezor/pull/245 [#248]: https://github.com/trezor/python-trezor/pull/248 [#249]: https://github.com/trezor/python-trezor/pull/249 [#250]: https://github.com/trezor/python-trezor/pull/250 [#256]: https://github.com/trezor/python-trezor/pull/256 [#268]: https://github.com/trezor/python-trezor/pull/268 [#269]: https://github.com/trezor/python-trezor/pull/269 [#274]: https://github.com/trezor/python-trezor/pull/274 [#276]: https://github.com/trezor/python-trezor/pull/276 [#277]: https://github.com/trezor/python-trezor/pull/277 [#280]: https://github.com/trezor/python-trezor/pull/280 [#283]: https://github.com/trezor/python-trezor/pull/283 [#284]: https://github.com/trezor/python-trezor/pull/284 [#286]: https://github.com/trezor/python-trezor/pull/286 [#287]: https://github.com/trezor/python-trezor/pull/287 [#300]: https://github.com/trezor/python-trezor/pull/300 [#301]: https://github.com/trezor/python-trezor/pull/301 [#302]: https://github.com/trezor/python-trezor/pull/302 [#304]: https://github.com/trezor/python-trezor/pull/304 [#307]: https://github.com/trezor/python-trezor/pull/307 [#308]: https://github.com/trezor/python-trezor/pull/308 [#312]: https://github.com/trezor/python-trezor/pull/312 [#314]: https://github.com/trezor/python-trezor/pull/314 [#315]: https://github.com/trezor/python-trezor/pull/315 [#325]: https://github.com/trezor/python-trezor/pull/325 [#349]: https://github.com/trezor/python-trezor/pull/349 [#351]: https://github.com/trezor/python-trezor/pull/351 [#352]: https://github.com/trezor/python-trezor/pull/352 [#379]: https://github.com/trezor/trezor-firmware/pull/379 [#810]: https://github.com/trezor/trezor-firmware/pull/810 [#948]: https://github.com/trezor/trezor-firmware/pull/948 [#1026]: https://github.com/trezor/trezor-firmware/pull/1026 [#1052]: https://github.com/trezor/trezor-firmware/pull/1052 [#1126]: https://github.com/trezor/trezor-firmware/pull/1126 [#1179]: https://github.com/trezor/trezor-firmware/pull/1179 [#1196]: https://github.com/trezor/trezor-firmware/pull/1196 [#1210]: https://github.com/trezor/trezor-firmware/pull/1210 [#1227]: https://github.com/trezor/trezor-firmware/pull/1227 [#1231]: https://github.com/trezor/trezor-firmware/pull/1231 [#1257]: https://github.com/trezor/trezor-firmware/pull/1257 [#1258]: https://github.com/trezor/trezor-firmware/pull/1258 [#1266]: https://github.com/trezor/trezor-firmware/pull/1266 [#1296]: https://github.com/trezor/trezor-firmware/pull/1296 [#1363]: https://github.com/trezor/trezor-firmware/pull/1363 [#1430]: https://github.com/trezor/trezor-firmware/pull/1430 [#1442]: https://github.com/trezor/trezor-firmware/pull/1442 [#1449]: https://github.com/trezor/trezor-firmware/pull/1449 [#1531]: https://github.com/trezor/trezor-firmware/pull/1531 [#1541]: https://github.com/trezor/trezor-firmware/pull/1541 [#1586]: https://github.com/trezor/trezor-firmware/pull/1586 [#1604]: https://github.com/trezor/trezor-firmware/pull/1604 [#1668]: https://github.com/trezor/trezor-firmware/pull/1668 [#1671]: https://github.com/trezor/trezor-firmware/pull/1671 [#1683]: https://github.com/trezor/trezor-firmware/pull/1683 [#1702]: https://github.com/trezor/trezor-firmware/pull/1702 [#1710]: https://github.com/trezor/trezor-firmware/pull/1710 [#1738]: https://github.com/trezor/trezor-firmware/pull/1738 [#1745]: https://github.com/trezor/trezor-firmware/pull/1745 [#1765]: https://github.com/trezor/trezor-firmware/pull/1765 [#1771]: https://github.com/trezor/trezor-firmware/pull/1771 [#1772]: https://github.com/trezor/trezor-firmware/pull/1772 [#1783]: https://github.com/trezor/trezor-firmware/pull/1783 [#1798]: https://github.com/trezor/trezor-firmware/pull/1798 [#1835]: https://github.com/trezor/trezor-firmware/pull/1835 [#1838]: https://github.com/trezor/trezor-firmware/pull/1838 [#1857]: https://github.com/trezor/trezor-firmware/pull/1857 [#1867]: https://github.com/trezor/trezor-firmware/pull/1867 [#1885]: https://github.com/trezor/trezor-firmware/pull/1885 [#1893]: https://github.com/trezor/trezor-firmware/pull/1893 [#1896]: https://github.com/trezor/trezor-firmware/pull/1896 [#1959]: https://github.com/trezor/trezor-firmware/pull/1959 [#1967]: https://github.com/trezor/trezor-firmware/pull/1967 [#1970]: https://github.com/trezor/trezor-firmware/pull/1970 [#2023]: https://github.com/trezor/trezor-firmware/pull/2023 [#2036]: https://github.com/trezor/trezor-firmware/pull/2036 [#2093]: https://github.com/trezor/trezor-firmware/pull/2093 [#2100]: https://github.com/trezor/trezor-firmware/pull/2100 [#2114]: https://github.com/trezor/trezor-firmware/pull/2114 [#2123]: https://github.com/trezor/trezor-firmware/pull/2123 [#2126]: https://github.com/trezor/trezor-firmware/pull/2126 [#2135]: https://github.com/trezor/trezor-firmware/pull/2135 [#2159]: https://github.com/trezor/trezor-firmware/pull/2159 [#2199]: https://github.com/trezor/trezor-firmware/pull/2199 [#2219]: https://github.com/trezor/trezor-firmware/pull/2219 [#2221]: https://github.com/trezor/trezor-firmware/pull/2221 [#2230]: https://github.com/trezor/trezor-firmware/pull/2230 [#2239]: https://github.com/trezor/trezor-firmware/pull/2239 [#2284]: https://github.com/trezor/trezor-firmware/pull/2284 [#2289]: https://github.com/trezor/trezor-firmware/pull/2289 [#2354]: https://github.com/trezor/trezor-firmware/pull/2354 [#2364]: https://github.com/trezor/trezor-firmware/pull/2364 [#2414]: https://github.com/trezor/trezor-firmware/pull/2414 [#2433]: https://github.com/trezor/trezor-firmware/pull/2433 [#2439]: https://github.com/trezor/trezor-firmware/pull/2439 [#2445]: https://github.com/trezor/trezor-firmware/pull/2445 [#2517]: https://github.com/trezor/trezor-firmware/pull/2517 [#2535]: https://github.com/trezor/trezor-firmware/pull/2535 [#2541]: https://github.com/trezor/trezor-firmware/pull/2541 [#2542]: https://github.com/trezor/trezor-firmware/pull/2542 [#2547]: https://github.com/trezor/trezor-firmware/pull/2547 [#2561]: https://github.com/trezor/trezor-firmware/pull/2561 [#2576]: https://github.com/trezor/trezor-firmware/pull/2576 [#2608]: https://github.com/trezor/trezor-firmware/pull/2608 [#2692]: https://github.com/trezor/trezor-firmware/pull/2692 [#2701]: https://github.com/trezor/trezor-firmware/pull/2701 [#2745]: https://github.com/trezor/trezor-firmware/pull/2745 [#2786]: https://github.com/trezor/trezor-firmware/pull/2786 [#2795]: https://github.com/trezor/trezor-firmware/pull/2795 [#2801]: https://github.com/trezor/trezor-firmware/pull/2801 [#2832]: https://github.com/trezor/trezor-firmware/pull/2832 [#2833]: https://github.com/trezor/trezor-firmware/pull/2833 [#2880]: https://github.com/trezor/trezor-firmware/pull/2880 [#2919]: https://github.com/trezor/trezor-firmware/pull/2919 [#2967]: https://github.com/trezor/trezor-firmware/pull/2967 [#2989]: https://github.com/trezor/trezor-firmware/pull/2989 [#3037]: https://github.com/trezor/trezor-firmware/pull/3037 [#3045]: https://github.com/trezor/trezor-firmware/pull/3045 [#3048]: https://github.com/trezor/trezor-firmware/pull/3048 [#3203]: https://github.com/trezor/trezor-firmware/pull/3203 [#3208]: https://github.com/trezor/trezor-firmware/pull/3208 [#3227]: https://github.com/trezor/trezor-firmware/pull/3227 [#3237]: https://github.com/trezor/trezor-firmware/pull/3237 [#3255]: https://github.com/trezor/trezor-firmware/pull/3255 [#3359]: https://github.com/trezor/trezor-firmware/pull/3359 [#3364]: https://github.com/trezor/trezor-firmware/pull/3364 [#3422]: https://github.com/trezor/trezor-firmware/pull/3422 [#3434]: https://github.com/trezor/trezor-firmware/pull/3434 [#3496]: https://github.com/trezor/trezor-firmware/pull/3496 [#3504]: https://github.com/trezor/trezor-firmware/pull/3504 [#3636]: https://github.com/trezor/trezor-firmware/pull/3636 [#3868]: https://github.com/trezor/trezor-firmware/pull/3868 [#3893]: https://github.com/trezor/trezor-firmware/pull/3893 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/COPYING0000664000175000017500000001674314636513242014407 0ustar00matejcikmatejcik GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315793.0 trezor-0.13.9/MANIFEST.in0000664000175000017500000000047314636526521015107 0ustar00matejcikmatejcikrecursive-include bash_completion.d *.sh include tools/* graft src graft tests graft stubs include AUTHORS README.md COPYING CHANGELOG.md include requirements*.txt include tox.ini pyrightconfig.json exclude src/trezorlib/_proto_messages.mako exclude tests/*.bin global-exclude *.pyc global-exclude */__pycache__/* ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1719315826.138107 trezor-0.13.9/PKG-INFO0000664000175000017500000012606614636526562014462 0ustar00matejcikmatejcikMetadata-Version: 2.1 Name: trezor Version: 0.13.9 Summary: Python library for communicating with Trezor Hardware Wallet Home-page: https://github.com/trezor/trezor-firmware/tree/master/python Author: Trezor Author-email: info@trezor.io License: LGPLv3 Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Programming Language :: Python :: 3 :: Only Requires-Python: >=3.8 Description-Content-Type: text/markdown Provides-Extra: hidapi Provides-Extra: ethereum Provides-Extra: qt-widgets Provides-Extra: extra Provides-Extra: stellar Provides-Extra: authentication Provides-Extra: full License-File: COPYING License-File: AUTHORS # trezorlib [![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 command-line client for communicating with Trezor Hardware Wallet. See for more information. ## Install Python Trezor tools require Python 3.8 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/). On a typical system, you already have all you need. Install `trezor` with: ```sh pip3 install trezor ``` On Windows, you also need to either install [Trezor Bridge](https://suite.trezor.io/web/bridge/), or [libusb](https://github.com/libusb/libusb/wiki/Windows) and the appropriate [drivers](https://zadig.akeo.ie/). ### Firmware version requirements Current trezorlib version supports Trezor One version 1.8.0 and up, and Trezor T version 2.1.0 and up. For firmware versions below 1.8.0 and 2.1.0 respectively, the only supported operation is "upgrade firmware". Trezor One with firmware _older than 1.7.0_ and bootloader _older than 1.6.0_ (including pre-2021 fresh-out-of-the-box units) will not be recognized, unless you install HIDAPI support (see below). ### Installation options * **Ethereum**: To support Ethereum signing from command line, additional packages are needed. Install with: ```sh pip3 install trezor[ethereum] ``` * **Stellar**: To support Stellar signing from command line, additional packages are needed. Install with: ```sh pip3 install trezor[stellar] ``` * **Extended device authentication**: For user-friendly device authentication for Trezor Safe 3 and newer models (`trezorctl device authenticate` command), additional packages are needed. Install with: ```sh pip3 install trezor[authentication] ``` * **Firmware-less Trezor One**: If you are setting up a brand new Trezor One manufactured before 2021 (with pre-installed bootloader older than 1.6.0), you will need HIDAPI support. On Linux, you will need the following packages (or their equivalents) as prerequisites: `python3-dev`, `cython3`, `libusb-1.0-0-dev`, `libudev-dev`. Install with: ```sh pip3 install trezor[hidapi] ``` To install all four, use `pip3 install trezor[hidapi,ethereum,stellar,authentication]`. ### Distro packages Check out [Repology](https://repology.org/metapackage/python:trezor) to see if your operating system has an up-to-date python-trezor package. ### Installing latest version from GitHub ```sh pip3 install "git+https://github.com/trezor/trezor-firmware#egg=trezor&subdirectory=python" ``` ### Running from source Install the [Poetry](https://python-poetry.org/) tool, checkout `trezor-firmware` from git, and enter the poetry shell: ```sh pip3 install poetry git clone https://github.com/trezor/trezor-firmware cd trezor-firmware poetry install poetry shell ``` In this environment, trezorlib and the `trezorctl` tool is running from the live sources, so your changes are immediately effective. ## 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 [python/docs/](https://github.com/trezor/trezor-firmware/tree/master/python/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 Trezor and use its capabilities in your application. See examples here in the [tools/](https://github.com/trezor/trezor-firmware/tree/master/python/docs/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 If you want to change protobuf definitions, you will need to regenerate definitions in the `python/` subdirectory. First, make sure your submodules are up-to-date with: ```sh git submodule update --init --recursive ``` Then, rebuild the protobuf messages by running, from the `trezor-firmware` top-level directory: ```sh make gen ``` # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.13.9] (2024-06-19) [0.13.9]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.8...python/v0.13.9 ### Added - trezorctl: Automatically go to bootloader when upgrading firmware. [#2919] - Support interaction-less upgrade. [#2919] - Added user adjustable brightness setting. [#3208] - Added Solana support. [#3359] - trezorctl: support for human-friendly Trezor Safe device authenticity check (requires separate installation of `cryptography` library). [#3364] - Added support for T3T1. [#3422] - Stellar: add support for StellarClaimClaimableBalanceOp. [#3434] - Cardano: Added support for tagged sets in CBOR (tag 258). [#3496] - Cardano: Added support for Conway certificates. [#3496] - Added ability to request Shamir backups with any number of groups/shares. [#3636] - Added flag for setting up device using SLIP39 "single". [#3868] - Added `--quality` argument to `trezorctl set homescreen`. [#3893] ### Changed - Renamed `trezorctl device self-test` command to `trezorctl device prodtest-t1`. [#3504] - Increased default JPEG quality for uploaded homescreen. [#3893] ### Incompatible changes - Renamed flag used for setting up device using BIP39 to `bip39`. [#3868] - Minimum required Python version is now 3.8. ## [0.13.8] (2023-10-19) [0.13.8]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.7...python/v0.13.8 ### Added - Added full support for Trezor Safe 3 (T2B1). - Added support for STM32F429I-DISC1 board [#2989] - Add support for address chunkification in Receive and Sign flow. [#3237] - Implement device authenticate command. [#3255] - trezorctl: support unlocking bootloader via `trezorctl device unlock-bootloader`. ### Changed - Use 'h' character for hardened BIP-32 components in help texts. [#3037] - trezorctl: Use 'h' character in generated descriptors. [#3037] - ClickUI: notify user in terminal that they should enter PIN or passphrase on Trezor. [#3203] - Internal names are used consistently in constants and names. Original model-based names are kept as aliases for backwards compatibility. - Trezor model detection will try to use the `internal_name` field. ### Fixed - Drop simple-rlp dependency and use internal copy [#3045] - Fixed printing Trezor model when validating firmware image [#3227] - Corrected vendor header signing keys for Safe 3 (T2B1). ## [0.13.7] (2023-06-02) [0.13.7]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.6...python/v0.13.7 ### Added - Recognize signing keys for T2B1. - Add possibility to call tutorial flow [#2795] - Add ability to change homescreen for Model R [#2967] - Recognize hw model field in vendor headers. [#3048] ## [0.13.6] (2023-04-24) [0.13.6]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.5...python/v0.13.6 ### Added - Signed Ethereum network and token definitions from host [#15] - Support SLIP-25 in get-descriptor. [#2541] - trezorctl: Support prompt configuration for `encrypt-keyvalue` / `decrypt-keyvalue`. [#2608] - Support for external reward addresses in Cardano CIP-36 registrations [#2692] - Auto-convert image to Trezor's homescreen format. [#2880] ### Changed - `trezorctl firmware verify` changed order of checks - fingerprint is validated before signatures. [#2745] ### Fixed - Removed attempt to initialize the device after wipe in bootloader mode [#2221] - Limit memory exhaustion in protobuf parser. [#2439] - `trezorctl ethereum sign-tx`: renamed `--gas-limit` shortcut to `-G` to avoid collision with `-t/--token` [#2535] - Fixed behavior of UDP transport search by path when full path is provided and prefix_search is True [#2786] - Fixed behavior of `trezorctl fw` with unsigned Trezor One firmwares. [#2801] - Improve typing information when `TrezorClient` has a more intelligent UI object. [#2832] - When enabling passphrase force-on-device, do not also prompt to enable passphrase if it is already enabled. [#2833] ## [0.13.5] (2022-12-28) [0.13.5]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.4...python/v0.13.5 ### Added - Add support for model field in firmware image. [#2701] - Add support for v3-style Trezor One signatures. [#2701] ### Changed - More structured information about signing keys for different models. [#2701] ### Incompatible changes - Instead of accepting a list of public keys, `FirmwareType.verify()` accepts a parameter configuring whether to use production or development keys. [#2701] ## [0.13.4] (2022-11-04) [0.13.4]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.3...python/v0.13.4 ### Added - Add UnlockPath message. [#2289] - Added new TOI formats - little endian full-color and even-high grayscale [#2414] - Add device set-busy command to trezorctl. [#2445] - Support SLIP-25 accounts in get-public-node and get-address. [#2517] - Add possibility to save emulator screenshots. [#2547] - Support for Cardano CIP-36 governance registration format [#2561] ### Removed - Remove DATA parameter from trezorctl cosi commit. - Remove firmware dumping capability. [#2433] ### Fixed - Fixed issue where type declarations were not visible to consumer packages. [#2542] ### Incompatible changes - Refactored firmware parsing and validation to a more object oriented approach. [#2576] ## [0.13.3] (2022-07-13) [0.13.3]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.2...python/v0.13.3 ### Added - Support for Cardano Babbage era transaction items [#2354] ### Fixed - Fix Click 7.x compatibility. [#2364] ## [0.13.2] (2022-06-30) [0.13.2]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.1...python/v0.13.2 ### Fixed - Fixed dependency error when running trezorctl without PIL. - Fixed dependency error when running trezorctl on Python 3.6 without rlp. - Fix `trezorctl --version` crash. [#1702] ## [0.13.1] (2022-06-29) [0.13.1]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.0...python/v0.13.1 ### Added - New exception type `DeviceIsBusy` indicates that the device is in use by another process. [#1026] - Support payment requests and GetNonce command. [#1430] - Add press_info() to DebugLink. [#1430] - Add support for blind EIP-712 signing for Trezor One [#1970] - Add ScriptUI for trezorctl, spawned by --script option [#2023] - Support T1 screenshot saving in Debuglink [#2093] - Support generating Electrum-compatible message signatures in CLI. [#2100] - Support Cardano Alonzo-era transaction items and --include-network-id flag [#2114] - trezorctl: Bitcoin commands can detect script type from derivation path. [#2159] - Add support for model R [#2230] - Add firmware get-hash command. [#2239] - Jump and stay in bootloader from firmware through SVC call reverse trampoline. [#2284] ### Changed - Unify boolean arguments/options in trezorlib commands to on/off [#2123] - Rename `normalize_nfc` to `prepare_message_bytes` in tools.py [#2126] - `trezorctl monero` network type arguments now accept symbolic names instead of numbers. [#2219] ### Fixed - trezorctl will correctly report that device is in use. [#1026] - Allow passing empty `message_hash` for domain-only EIP-712 hashes for Trezor T1 (i.e. when `primaryType`=`EIP712Domain`) [#2036] - Fixed error when printing protobuf message with a missing required field. [#2135] - Add compatibility with Click 8.1 [#2199] ## [0.13.0] - 2021-12-09 [0.13.0]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.4...python/v0.13.0 ### Added - `trezorctl firmware update` shows progress bar (Model T only) - Enabled session management via `EndSession` [#1227] - Added parameters to enable Cardano derivation when calling `init_device()`. [#1231] - two new trezorctl commands - `trezorctl firmware download` and `trezorctl firmware verify` [#1258] - Support no_script_type option in SignMessage. [#1586] - Support for Ethereum EIP1559 transactions [#1604] - Debuglink can automatically scroll through paginated views. [#1671] - Support for Taproot descriptors [#1710] - `trezorlib.stellar.from_envelope` was added, it includes support for the Stellar [TransactionV1](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0015.md#xdr) format transaction. [#1745] - Ethereum: support 64-bit chain IDs [#1771] - Support for Cardano multi-sig transactions, token minting, script addresses, multi-sig keys, minting keys and native script verification [#1772] - Added parameters to specify kind of Cardano derivation to all functions and `trezorctl` commands. [#1783] - Support for EIP-712 in library and `trezorctl ethereum sign-typed-data` [#1835] - Add script_pubkey field to TxInput message. [#1857] - Full type hinting checkable with pyright [#1893] ### Changed - protobuf is aware of `required` fields and default values [#379] - `trezorctl firmware-update` command changed to `trezorctl firmware update` [#1258] - `btc.sign_tx()` accepts keyword arguments for transaction metadata [#1266] - Raise `ValueError` when the txid for an input is not present in `prev_txes` during `btc.sign_tx` [#1442] - `trezorlib.mappings` was refactored for easier customization [#1449] - Refactor protobuf codec for better clarity [#1541] - `UdpTransport.wait_until_ready` no longer sets socket to nonblocking [#1668] - Cardano transaction parameters are now streamed into the device one by one instead of being sent as one large object [#1683] - `trezorlib.stellar` will refuse to process transactions containing MuxedAccount [#1838] - Use unified descriptors format. [#1885] - Introduce Trezor models as an abstraction over USB IDs, vendor strings, and possibly protobuf mappings. [#1967] ### Deprecated - instantiating protobuf objects with positional arguments is deprecated [#379] - `details` argument to `btc.sign_tx()` is deprecated. Use keyword arguments instead. [#379] - values of required fields must be supplied at instantiation time. Omitting them is deprecated. [#379] ### Removed - dropped Python 3.5 support [#810] - dropped debug-only `trezorctl debug show-text` functionality [#1531] - Removed support for Lisk [#1765] ### Fixed - fix operator precedence issue for ethereum sign-tx command [#1867] - Updated `tools/build_tx.py` to work with Blockbook's API protections. [#1896] - Fix PIN and passphrase entry in certain terminals on Windows [#1959] ### Incompatible changes - `client.init_device(derive_cardano=True)` must be used before calling Cardano functions. [#1231] - The type of argument to `ui.button_request(x)` is changed from int to ButtonRequest. The original int value can be accessed as `x.code` [#1671] - Due to transaction streaming in Cardano, it isn't possible to return the whole serialized transaction anymore. Instead the transaction hash, transaction witnesses and auxiliary data supplement are returned and the serialized transaction needs to be assembled by the client. [#1683] - `trezorlib.stellar` was reworked to use stellar-sdk instead of providing local implementations [#1745] - Cardano derivation now defaults to Icarus method. This will result in different keys for users with 24-word seed. [#1783] ## [0.12.4] - 2021-09-07 [0.12.4]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.3...python/v0.12.4 ### Fixed - trezorctl: fixed "Invalid value for " when using Click 8 and param is not specified [#1798] ## [0.12.3] - 2021-07-29 [0.12.3]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.2...python/v0.12.3 ### Added - `trezorctl btc get-descriptor` support [#1363] - `trezorctl btc reboot-to-bootloader` support [#1738] - distinguishing between temporary and permanent safety checks - trezorctl accepts PIN entered by letters (useful on laptops) - support for 50-digit PIN for T1 ### Changed - allowed Click 8.x as a requirement - replaced all references to Trezor Wallet with Trezor Suite, and modified all mentions of Beta Wallet ### Fixed - added missing requirement `attrs` - properly parse big numbers in `tools/build_tx.py` [#1257], [#1296] - it is now possible to set safety checks for T1 ## [0.12.2] - 2020-08-27 [0.12.2]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.1...python/v0.12.2 ### Added - `trezorlib.toif` module (moved from internal) can encode and decode TOIF image format - `trezorctl set homescreen` was improved and extended to support PNG images for Trezor T ### Changed - trezorctl will correctly notify the user if the image decoding library is missing ### Fixed - fix exception in `trezorctl btc get-address` [#1179] - fix exception in `trezorctl lisk sign-message` - fix exception in trezorctl commands that accept filenames [#1196] - fix "Invalid homescreen" error when un-setting homescreen ### Removed - removed option `--skip-vendor-header` from `trezorctl firmware-update` which did nothing [#1210] ## [0.12.1] - 2020-08-05 [0.12.1]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.0...python/v0.12.1 ### Added - `trezorctl set safety-checks` controls the new "safety checks" feature. [#1126] - `trezorctl btc get-address` can create multisig addresses. - the following commands are now equivalent in trezorctl: `firmware-update`, `firmware-upgrade`, `update-firmware`, `upgrade-firmware` - support for EXTERNAL input type [#38], [#1052] - support for ownership proofs - support for pre-authorized CoinJoin transactions [#37] - support for Cardano Shelley [#948] ### Changed - do not allow setting auto-lock delay unless PIN is configured ### Fixed - correctly calculate hashes for very small firmwares [f#1082] - unified file arguments in trezorctl - `TrezorClient.ping()` does not crash when device is PIN-locked ## [0.12.0] - 2020-04-01 [0.12.0]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.6...python/v0.12.0 ### Incompatible changes - `trezorlib.coins`, `trezorlib.tx_api`, and the file `coins.json`, were removed - `TrezorClient` argument `ui` is now mandatory. `state` argument was renamed to `session_id`. - UI callback `get_passphrase()` has a new argument `available_on_device`. - API for `cosi` module was changed - other changes may also introduce incompatibilities, please review the full list below ### Added - support for firmwares 1.9.0 and 2.3.0 - Model T now defaults to entering passphrase on device. New trezorctl option `-P` enforces entering passphrase on host. - support for "passphrase always on device" mode on model T - new trezorctl command `get-session` and option `-s` allows entering passphrase once for multiple subsequent trezorctl operations - built-in functionality of UdpTransport to wait until an emulator comes up, and the related command `trezorctl wait-for-emulator` - `trezorctl debug send-bytes` can send raw messages to the device [f#116] - when updating firmware, user is warned that the requested version does not match their device [f#823] - `trezorctl list` can now show name, model and id of device ### Changed - `trezorlib.tx_api.json_to_tx` was reduced to only support Bitcoin fields, and moved to `trezorlib.btc.from_json`. - API for `cosi` module was streamlined: `verify_m_of_n` is now `verify`, the old `verify` is `verify_combined` - internals of firmware parsing were reworked to support signing firmware headers - `get_default_client` respects `TREZOR_PATH` environment variable - UI callback `get_passphrase` has an additional argument `available_on_device`, indicating that the connected Trezor is capable of on-device entry - `Transport.write` and `read` method signatures changed to accept bytes instead of protobuf messages - trezorctl subcommands have a common `@with_client` decorator that manages exception handling and connecting to device ### Fixed - trezorctl does not print empty line when there is no output - trezorctl cleanly reports wire exceptions [f#226] ### Removed - `trezorlib.tx_api` was removed - `trezorlib.coins` and coin data was removed - `trezorlib.ckd_public`, which was deprecated in 0.10, was now removed. - `btc.sign_tx` will not preload transaction data from `prev_txes`, as usage with TxApi is being removed - PIN protection and passphrase protection for `ping()` command was removed - compatibility no-op code from trezorlib 0.9 was removed from `trezorlib.client` - `trezorlib.tools.CallException` was dropped, use `trezorlib.exceptions.TrezorFailure` instead ## [0.11.6] - 2019-12-30 [0.11.6]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.5...python/v0.11.6 ### Added - support for get-and-increase FIDO counter operation - support for setting wipe code - `trezorctl device recover` supports `--u2f-counter` option to set the FIDO counter to a custom value ### Changed - `trezorctl` command was reworked for ease of use and maintenance. See `trezorctl --help` and `OPTIONS.rst` for details. [f#510] - updated EOS transaction parser to match `cleos` in `delegatebw` and `undelegatebw` actions [f#680] [f#681] - `RecoveryDevice` does not set fields when doing dry-run recovery [f#666] ### Fixed - fixed "expand words" functionality in `trezorctl device recover` [f#778] ### Removed - trezorctl no longer interactively signs Bitcoin-like transactions, the only allowed input format is JSON. See [`docs/transaction-format.md`](docs/transaction-format.md) for details. - support for "load device by xprv" was removed from firmware and trezorlib ## [0.11.5] - 2019-09-26 [0.11.5]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.4...python/v0.11.5 ### Added - trezorctl can dump raw protobuf bytes in debug output [f#117] - trezorctl shows a warning when activating Shamir Backup if the device does not support it [f#445] - warnings are emitted when encountering unknown value for a protobuf enum [f#363] - debug messages show enum value names instead of raw numbers - support for packed repeated encoding in the protobuf decoder - in `trezorctl firmware-update`, the new `--beta` switch enables downloading beta firmwares. By default, only stable firmware is used. [f#411], [f#420] - in `trezorctl firmware-update`, the new `--bitcoin-only` switch enables downloading Bitcoin-only firmware - support for FIDO2 resident credential management - support for SD-protect features ### Changed - package directory structure was changed: `src` subdirectory contains sources and `tests` subdirectory contains tests, so that cwd is not cluttered - `trezorctl` script was moved into a module `trezorlib.cli.trezorctl` and is launched through the `entry_points` mechanism. This makes it usable on Windows - `pyblake2` is no longer required on Python 3.6 and up - input flows can only be used in with-block (only relevant for unit tests) - if not specified, trezorctl will set label to "SLIP-0014" in SLIP-0014 mode - in `clear_session` the client also forgets the passphrase state for TT [f#525] ### Fixed - trezorctl will properly check if a firmware is present on a new T1 [f#224] ### Removed - device test suite was moved out of trezor package ## [0.11.4] - 2019-07-31 [0.11.4]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.3...python/v0.11.4 ### Added - trezorctl support for SLIP-39 Shamir Backup - support for Binance Chain ## [0.11.3] - 2019-05-29 [0.11.3]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.2...python/v0.11.3 ### 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 - EOS support [f#87] - Tezos: add voting support [f#41] - `dict_to_proto` now allows enum values as strings ### Changed - Minimum firmware versions bumped to 1.8.0 and 2.1.0 - Cleaner errors when UI object is not supplied - Generated files are now part of the source tarball again. That means that `protoc` is no longer required. ### Fixed - Ethereum commands in trezorctl now work - Memory debugging tools now work again ### Removed - Tron and Ontology support removed until implementations exist in Trezor firmware ## [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) [f#41]: https://github.com/trezor/trezor-firmware/pull/41 [f#87]: https://github.com/trezor/trezor-firmware/pull/87 [f#116]: https://github.com/trezor/trezor-firmware/pull/116 [f#117]: https://github.com/trezor/trezor-firmware/pull/117 [f#224]: https://github.com/trezor/trezor-firmware/pull/224 [f#226]: https://github.com/trezor/trezor-firmware/pull/226 [f#363]: https://github.com/trezor/trezor-firmware/pull/363 [f#411]: https://github.com/trezor/trezor-firmware/pull/411 [f#420]: https://github.com/trezor/trezor-firmware/pull/420 [f#445]: https://github.com/trezor/trezor-firmware/pull/445 [f#510]: https://github.com/trezor/trezor-firmware/pull/510 [f#525]: https://github.com/trezor/trezor-firmware/pull/525 [f#666]: https://github.com/trezor/trezor-firmware/pull/666 [f#680]: https://github.com/trezor/trezor-firmware/pull/680 [f#681]: https://github.com/trezor/trezor-firmware/pull/681 [f#778]: https://github.com/trezor/trezor-firmware/pull/778 [f#823]: https://github.com/trezor/trezor-firmware/pull/823 [f#1082]: https://github.com/trezor/trezor-firmware/pull/1082 [#15]: https://github.com/trezor/trezor-firmware/pull/15 [#37]: https://github.com/trezor/trezor-firmware/pull/37 [#38]: https://github.com/trezor/trezor-firmware/pull/38 [#94]: https://github.com/trezor/python-trezor/pull/94 [#167]: https://github.com/trezor/python-trezor/pull/167 [#169]: https://github.com/trezor/python-trezor/pull/169 [#185]: https://github.com/trezor/python-trezor/pull/185 [#197]: https://github.com/trezor/python-trezor/pull/197 [#199]: https://github.com/trezor/python-trezor/pull/199 [#207]: https://github.com/trezor/python-trezor/pull/207 [#223]: https://github.com/trezor/python-trezor/pull/223 [#226]: https://github.com/trezor/python-trezor/pull/226 [#229]: https://github.com/trezor/python-trezor/pull/229 [#230]: https://github.com/trezor/python-trezor/pull/230 [#236]: https://github.com/trezor/python-trezor/pull/236 [#237]: https://github.com/trezor/python-trezor/pull/237 [#241]: https://github.com/trezor/python-trezor/pull/241 [#242]: https://github.com/trezor/python-trezor/pull/242 [#245]: https://github.com/trezor/python-trezor/pull/245 [#248]: https://github.com/trezor/python-trezor/pull/248 [#249]: https://github.com/trezor/python-trezor/pull/249 [#250]: https://github.com/trezor/python-trezor/pull/250 [#256]: https://github.com/trezor/python-trezor/pull/256 [#268]: https://github.com/trezor/python-trezor/pull/268 [#269]: https://github.com/trezor/python-trezor/pull/269 [#274]: https://github.com/trezor/python-trezor/pull/274 [#276]: https://github.com/trezor/python-trezor/pull/276 [#277]: https://github.com/trezor/python-trezor/pull/277 [#280]: https://github.com/trezor/python-trezor/pull/280 [#283]: https://github.com/trezor/python-trezor/pull/283 [#284]: https://github.com/trezor/python-trezor/pull/284 [#286]: https://github.com/trezor/python-trezor/pull/286 [#287]: https://github.com/trezor/python-trezor/pull/287 [#300]: https://github.com/trezor/python-trezor/pull/300 [#301]: https://github.com/trezor/python-trezor/pull/301 [#302]: https://github.com/trezor/python-trezor/pull/302 [#304]: https://github.com/trezor/python-trezor/pull/304 [#307]: https://github.com/trezor/python-trezor/pull/307 [#308]: https://github.com/trezor/python-trezor/pull/308 [#312]: https://github.com/trezor/python-trezor/pull/312 [#314]: https://github.com/trezor/python-trezor/pull/314 [#315]: https://github.com/trezor/python-trezor/pull/315 [#325]: https://github.com/trezor/python-trezor/pull/325 [#349]: https://github.com/trezor/python-trezor/pull/349 [#351]: https://github.com/trezor/python-trezor/pull/351 [#352]: https://github.com/trezor/python-trezor/pull/352 [#379]: https://github.com/trezor/trezor-firmware/pull/379 [#810]: https://github.com/trezor/trezor-firmware/pull/810 [#948]: https://github.com/trezor/trezor-firmware/pull/948 [#1026]: https://github.com/trezor/trezor-firmware/pull/1026 [#1052]: https://github.com/trezor/trezor-firmware/pull/1052 [#1126]: https://github.com/trezor/trezor-firmware/pull/1126 [#1179]: https://github.com/trezor/trezor-firmware/pull/1179 [#1196]: https://github.com/trezor/trezor-firmware/pull/1196 [#1210]: https://github.com/trezor/trezor-firmware/pull/1210 [#1227]: https://github.com/trezor/trezor-firmware/pull/1227 [#1231]: https://github.com/trezor/trezor-firmware/pull/1231 [#1257]: https://github.com/trezor/trezor-firmware/pull/1257 [#1258]: https://github.com/trezor/trezor-firmware/pull/1258 [#1266]: https://github.com/trezor/trezor-firmware/pull/1266 [#1296]: https://github.com/trezor/trezor-firmware/pull/1296 [#1363]: https://github.com/trezor/trezor-firmware/pull/1363 [#1430]: https://github.com/trezor/trezor-firmware/pull/1430 [#1442]: https://github.com/trezor/trezor-firmware/pull/1442 [#1449]: https://github.com/trezor/trezor-firmware/pull/1449 [#1531]: https://github.com/trezor/trezor-firmware/pull/1531 [#1541]: https://github.com/trezor/trezor-firmware/pull/1541 [#1586]: https://github.com/trezor/trezor-firmware/pull/1586 [#1604]: https://github.com/trezor/trezor-firmware/pull/1604 [#1668]: https://github.com/trezor/trezor-firmware/pull/1668 [#1671]: https://github.com/trezor/trezor-firmware/pull/1671 [#1683]: https://github.com/trezor/trezor-firmware/pull/1683 [#1702]: https://github.com/trezor/trezor-firmware/pull/1702 [#1710]: https://github.com/trezor/trezor-firmware/pull/1710 [#1738]: https://github.com/trezor/trezor-firmware/pull/1738 [#1745]: https://github.com/trezor/trezor-firmware/pull/1745 [#1765]: https://github.com/trezor/trezor-firmware/pull/1765 [#1771]: https://github.com/trezor/trezor-firmware/pull/1771 [#1772]: https://github.com/trezor/trezor-firmware/pull/1772 [#1783]: https://github.com/trezor/trezor-firmware/pull/1783 [#1798]: https://github.com/trezor/trezor-firmware/pull/1798 [#1835]: https://github.com/trezor/trezor-firmware/pull/1835 [#1838]: https://github.com/trezor/trezor-firmware/pull/1838 [#1857]: https://github.com/trezor/trezor-firmware/pull/1857 [#1867]: https://github.com/trezor/trezor-firmware/pull/1867 [#1885]: https://github.com/trezor/trezor-firmware/pull/1885 [#1893]: https://github.com/trezor/trezor-firmware/pull/1893 [#1896]: https://github.com/trezor/trezor-firmware/pull/1896 [#1959]: https://github.com/trezor/trezor-firmware/pull/1959 [#1967]: https://github.com/trezor/trezor-firmware/pull/1967 [#1970]: https://github.com/trezor/trezor-firmware/pull/1970 [#2023]: https://github.com/trezor/trezor-firmware/pull/2023 [#2036]: https://github.com/trezor/trezor-firmware/pull/2036 [#2093]: https://github.com/trezor/trezor-firmware/pull/2093 [#2100]: https://github.com/trezor/trezor-firmware/pull/2100 [#2114]: https://github.com/trezor/trezor-firmware/pull/2114 [#2123]: https://github.com/trezor/trezor-firmware/pull/2123 [#2126]: https://github.com/trezor/trezor-firmware/pull/2126 [#2135]: https://github.com/trezor/trezor-firmware/pull/2135 [#2159]: https://github.com/trezor/trezor-firmware/pull/2159 [#2199]: https://github.com/trezor/trezor-firmware/pull/2199 [#2219]: https://github.com/trezor/trezor-firmware/pull/2219 [#2221]: https://github.com/trezor/trezor-firmware/pull/2221 [#2230]: https://github.com/trezor/trezor-firmware/pull/2230 [#2239]: https://github.com/trezor/trezor-firmware/pull/2239 [#2284]: https://github.com/trezor/trezor-firmware/pull/2284 [#2289]: https://github.com/trezor/trezor-firmware/pull/2289 [#2354]: https://github.com/trezor/trezor-firmware/pull/2354 [#2364]: https://github.com/trezor/trezor-firmware/pull/2364 [#2414]: https://github.com/trezor/trezor-firmware/pull/2414 [#2433]: https://github.com/trezor/trezor-firmware/pull/2433 [#2439]: https://github.com/trezor/trezor-firmware/pull/2439 [#2445]: https://github.com/trezor/trezor-firmware/pull/2445 [#2517]: https://github.com/trezor/trezor-firmware/pull/2517 [#2535]: https://github.com/trezor/trezor-firmware/pull/2535 [#2541]: https://github.com/trezor/trezor-firmware/pull/2541 [#2542]: https://github.com/trezor/trezor-firmware/pull/2542 [#2547]: https://github.com/trezor/trezor-firmware/pull/2547 [#2561]: https://github.com/trezor/trezor-firmware/pull/2561 [#2576]: https://github.com/trezor/trezor-firmware/pull/2576 [#2608]: https://github.com/trezor/trezor-firmware/pull/2608 [#2692]: https://github.com/trezor/trezor-firmware/pull/2692 [#2701]: https://github.com/trezor/trezor-firmware/pull/2701 [#2745]: https://github.com/trezor/trezor-firmware/pull/2745 [#2786]: https://github.com/trezor/trezor-firmware/pull/2786 [#2795]: https://github.com/trezor/trezor-firmware/pull/2795 [#2801]: https://github.com/trezor/trezor-firmware/pull/2801 [#2832]: https://github.com/trezor/trezor-firmware/pull/2832 [#2833]: https://github.com/trezor/trezor-firmware/pull/2833 [#2880]: https://github.com/trezor/trezor-firmware/pull/2880 [#2919]: https://github.com/trezor/trezor-firmware/pull/2919 [#2967]: https://github.com/trezor/trezor-firmware/pull/2967 [#2989]: https://github.com/trezor/trezor-firmware/pull/2989 [#3037]: https://github.com/trezor/trezor-firmware/pull/3037 [#3045]: https://github.com/trezor/trezor-firmware/pull/3045 [#3048]: https://github.com/trezor/trezor-firmware/pull/3048 [#3203]: https://github.com/trezor/trezor-firmware/pull/3203 [#3208]: https://github.com/trezor/trezor-firmware/pull/3208 [#3227]: https://github.com/trezor/trezor-firmware/pull/3227 [#3237]: https://github.com/trezor/trezor-firmware/pull/3237 [#3255]: https://github.com/trezor/trezor-firmware/pull/3255 [#3359]: https://github.com/trezor/trezor-firmware/pull/3359 [#3364]: https://github.com/trezor/trezor-firmware/pull/3364 [#3422]: https://github.com/trezor/trezor-firmware/pull/3422 [#3434]: https://github.com/trezor/trezor-firmware/pull/3434 [#3496]: https://github.com/trezor/trezor-firmware/pull/3496 [#3504]: https://github.com/trezor/trezor-firmware/pull/3504 [#3636]: https://github.com/trezor/trezor-firmware/pull/3636 [#3868]: https://github.com/trezor/trezor-firmware/pull/3868 [#3893]: https://github.com/trezor/trezor-firmware/pull/3893 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315793.0 trezor-0.13.9/README.md0000664000175000017500000001127214636526521014627 0ustar00matejcikmatejcik# trezorlib [![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 command-line client for communicating with Trezor Hardware Wallet. See for more information. ## Install Python Trezor tools require Python 3.8 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/). On a typical system, you already have all you need. Install `trezor` with: ```sh pip3 install trezor ``` On Windows, you also need to either install [Trezor Bridge](https://suite.trezor.io/web/bridge/), or [libusb](https://github.com/libusb/libusb/wiki/Windows) and the appropriate [drivers](https://zadig.akeo.ie/). ### Firmware version requirements Current trezorlib version supports Trezor One version 1.8.0 and up, and Trezor T version 2.1.0 and up. For firmware versions below 1.8.0 and 2.1.0 respectively, the only supported operation is "upgrade firmware". Trezor One with firmware _older than 1.7.0_ and bootloader _older than 1.6.0_ (including pre-2021 fresh-out-of-the-box units) will not be recognized, unless you install HIDAPI support (see below). ### Installation options * **Ethereum**: To support Ethereum signing from command line, additional packages are needed. Install with: ```sh pip3 install trezor[ethereum] ``` * **Stellar**: To support Stellar signing from command line, additional packages are needed. Install with: ```sh pip3 install trezor[stellar] ``` * **Extended device authentication**: For user-friendly device authentication for Trezor Safe 3 and newer models (`trezorctl device authenticate` command), additional packages are needed. Install with: ```sh pip3 install trezor[authentication] ``` * **Firmware-less Trezor One**: If you are setting up a brand new Trezor One manufactured before 2021 (with pre-installed bootloader older than 1.6.0), you will need HIDAPI support. On Linux, you will need the following packages (or their equivalents) as prerequisites: `python3-dev`, `cython3`, `libusb-1.0-0-dev`, `libudev-dev`. Install with: ```sh pip3 install trezor[hidapi] ``` To install all four, use `pip3 install trezor[hidapi,ethereum,stellar,authentication]`. ### Distro packages Check out [Repology](https://repology.org/metapackage/python:trezor) to see if your operating system has an up-to-date python-trezor package. ### Installing latest version from GitHub ```sh pip3 install "git+https://github.com/trezor/trezor-firmware#egg=trezor&subdirectory=python" ``` ### Running from source Install the [Poetry](https://python-poetry.org/) tool, checkout `trezor-firmware` from git, and enter the poetry shell: ```sh pip3 install poetry git clone https://github.com/trezor/trezor-firmware cd trezor-firmware poetry install poetry shell ``` In this environment, trezorlib and the `trezorctl` tool is running from the live sources, so your changes are immediately effective. ## 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 [python/docs/](https://github.com/trezor/trezor-firmware/tree/master/python/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 Trezor and use its capabilities in your application. See examples here in the [tools/](https://github.com/trezor/trezor-firmware/tree/master/python/docs/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 If you want to change protobuf definitions, you will need to regenerate definitions in the `python/` subdirectory. First, make sure your submodules are up-to-date with: ```sh git submodule update --init --recursive ``` Then, rebuild the protobuf messages by running, from the `trezor-firmware` top-level directory: ```sh make gen ``` ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1719315826.1221068 trezor-0.13.9/bash_completion.d/0000775000175000017500000000000014636526562016742 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/bash_completion.d/trezorctl.sh0000664000175000017500000000077514636513242021326 0ustar00matejcikmatejcik_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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/pyrightconfig.json0000664000175000017500000000064314636513242017113 0ustar00matejcikmatejcik{ "include": [ "src/trezorlib", "tools", "helper-scripts" ], "stubPath": "./stubs", "pythonVersion": "3.8", "typeCheckingMode": "basic", "reportMissingImports": false, "reportUntypedFunctionDecorator": true, "reportUntypedClassDecorator": true, "reportMissingParameterType": true, "useLibraryCodeForTypes": false, "reportMissingModuleSource": false } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315793.0 trezor-0.13.9/requirements-optional.txt0000664000175000017500000000011114636526521020445 0ustar00matejcikmatejcikhidapi>=0.7.99.post20 web3>=5 Pillow>=10 stellar-sdk>=6 cryptography>=41 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315793.0 trezor-0.13.9/requirements.txt0000664000175000017500000000022214636526521016625 0ustar00matejcikmatejcikecdsa>=0.9 mnemonic>=0.20 requests>=2.4.0 click>=7,<8.2 libusb1>=1.6.4 construct>=2.9,!=2.10.55 typing_extensions>=4.7.1 construct-classes>=0.1.2 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1719315826.138107 trezor-0.13.9/setup.cfg0000664000175000017500000000076514636526562015203 0ustar00matejcikmatejcik[flake8] filename = *.py exclude = .tox/, build/, dist/, vendor/, src/trezorlib/messages/__init__.py ignore = E203, E221, E241, E402, E501, E704, E741, W503 per-file-ignores = helper-scripts/*:I tools/*:I tests/*:I known-modules = libusb1:[usb1],hidapi:[hid],PyQt5:[PyQt5.QtWidgets,PyQt5.QtGui,PyQt5.QtCore] [isort] profile = black [mypy] check_untyped_defs = True show_error_codes = True warn_unreachable = True disable_error_code = import [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315793.0 trezor-0.13.9/setup.py0000775000175000017500000000503314636526521015063 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import re from pathlib import Path from setuptools import find_packages, setup CWD = Path(__file__).resolve().parent install_requires = (CWD / "requirements.txt").read_text().splitlines() extras_require = { "hidapi": ["hidapi>=0.7.99.post20"], "ethereum": ["web3>=5"], "qt-widgets": ["PyQt5"], "extra": ["Pillow>=10"], "stellar": ["stellar-sdk>=6"], "authentication": ["cryptography>=41"], } extras_require["full"] = sum(extras_require.values(), []) def find_version(): version_file = (CWD / "src" / "trezorlib" / "__init__.py").read_text() 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") 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=(CWD / "README.md").read_text() + "\n\n" + (CWD / "CHANGELOG.md").read_text(), long_description_content_type="text/markdown", url="https://github.com/trezor/trezor-firmware/tree/master/python", package_data={"trezorlib": ["py.typed"]}, packages=find_packages("src"), package_dir={"": "src"}, entry_points={"console_scripts": ["trezorctl=trezorlib.cli.trezorctl:cli"]}, install_requires=install_requires, extras_require=extras_require, python_requires=">=3.8", 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", ], ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1719315826.1201067 trezor-0.13.9/src/0000775000175000017500000000000014636526562014141 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1719315826.1231067 trezor-0.13.9/src/trezor.egg-info/0000775000175000017500000000000014636526562017160 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315826.0 trezor-0.13.9/src/trezor.egg-info/PKG-INFO0000664000175000017500000012606614636526562020270 0ustar00matejcikmatejcikMetadata-Version: 2.1 Name: trezor Version: 0.13.9 Summary: Python library for communicating with Trezor Hardware Wallet Home-page: https://github.com/trezor/trezor-firmware/tree/master/python Author: Trezor Author-email: info@trezor.io License: LGPLv3 Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Programming Language :: Python :: 3 :: Only Requires-Python: >=3.8 Description-Content-Type: text/markdown Provides-Extra: hidapi Provides-Extra: ethereum Provides-Extra: qt-widgets Provides-Extra: extra Provides-Extra: stellar Provides-Extra: authentication Provides-Extra: full License-File: COPYING License-File: AUTHORS # trezorlib [![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 command-line client for communicating with Trezor Hardware Wallet. See for more information. ## Install Python Trezor tools require Python 3.8 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/). On a typical system, you already have all you need. Install `trezor` with: ```sh pip3 install trezor ``` On Windows, you also need to either install [Trezor Bridge](https://suite.trezor.io/web/bridge/), or [libusb](https://github.com/libusb/libusb/wiki/Windows) and the appropriate [drivers](https://zadig.akeo.ie/). ### Firmware version requirements Current trezorlib version supports Trezor One version 1.8.0 and up, and Trezor T version 2.1.0 and up. For firmware versions below 1.8.0 and 2.1.0 respectively, the only supported operation is "upgrade firmware". Trezor One with firmware _older than 1.7.0_ and bootloader _older than 1.6.0_ (including pre-2021 fresh-out-of-the-box units) will not be recognized, unless you install HIDAPI support (see below). ### Installation options * **Ethereum**: To support Ethereum signing from command line, additional packages are needed. Install with: ```sh pip3 install trezor[ethereum] ``` * **Stellar**: To support Stellar signing from command line, additional packages are needed. Install with: ```sh pip3 install trezor[stellar] ``` * **Extended device authentication**: For user-friendly device authentication for Trezor Safe 3 and newer models (`trezorctl device authenticate` command), additional packages are needed. Install with: ```sh pip3 install trezor[authentication] ``` * **Firmware-less Trezor One**: If you are setting up a brand new Trezor One manufactured before 2021 (with pre-installed bootloader older than 1.6.0), you will need HIDAPI support. On Linux, you will need the following packages (or their equivalents) as prerequisites: `python3-dev`, `cython3`, `libusb-1.0-0-dev`, `libudev-dev`. Install with: ```sh pip3 install trezor[hidapi] ``` To install all four, use `pip3 install trezor[hidapi,ethereum,stellar,authentication]`. ### Distro packages Check out [Repology](https://repology.org/metapackage/python:trezor) to see if your operating system has an up-to-date python-trezor package. ### Installing latest version from GitHub ```sh pip3 install "git+https://github.com/trezor/trezor-firmware#egg=trezor&subdirectory=python" ``` ### Running from source Install the [Poetry](https://python-poetry.org/) tool, checkout `trezor-firmware` from git, and enter the poetry shell: ```sh pip3 install poetry git clone https://github.com/trezor/trezor-firmware cd trezor-firmware poetry install poetry shell ``` In this environment, trezorlib and the `trezorctl` tool is running from the live sources, so your changes are immediately effective. ## 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 [python/docs/](https://github.com/trezor/trezor-firmware/tree/master/python/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 Trezor and use its capabilities in your application. See examples here in the [tools/](https://github.com/trezor/trezor-firmware/tree/master/python/docs/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 If you want to change protobuf definitions, you will need to regenerate definitions in the `python/` subdirectory. First, make sure your submodules are up-to-date with: ```sh git submodule update --init --recursive ``` Then, rebuild the protobuf messages by running, from the `trezor-firmware` top-level directory: ```sh make gen ``` # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.13.9] (2024-06-19) [0.13.9]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.8...python/v0.13.9 ### Added - trezorctl: Automatically go to bootloader when upgrading firmware. [#2919] - Support interaction-less upgrade. [#2919] - Added user adjustable brightness setting. [#3208] - Added Solana support. [#3359] - trezorctl: support for human-friendly Trezor Safe device authenticity check (requires separate installation of `cryptography` library). [#3364] - Added support for T3T1. [#3422] - Stellar: add support for StellarClaimClaimableBalanceOp. [#3434] - Cardano: Added support for tagged sets in CBOR (tag 258). [#3496] - Cardano: Added support for Conway certificates. [#3496] - Added ability to request Shamir backups with any number of groups/shares. [#3636] - Added flag for setting up device using SLIP39 "single". [#3868] - Added `--quality` argument to `trezorctl set homescreen`. [#3893] ### Changed - Renamed `trezorctl device self-test` command to `trezorctl device prodtest-t1`. [#3504] - Increased default JPEG quality for uploaded homescreen. [#3893] ### Incompatible changes - Renamed flag used for setting up device using BIP39 to `bip39`. [#3868] - Minimum required Python version is now 3.8. ## [0.13.8] (2023-10-19) [0.13.8]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.7...python/v0.13.8 ### Added - Added full support for Trezor Safe 3 (T2B1). - Added support for STM32F429I-DISC1 board [#2989] - Add support for address chunkification in Receive and Sign flow. [#3237] - Implement device authenticate command. [#3255] - trezorctl: support unlocking bootloader via `trezorctl device unlock-bootloader`. ### Changed - Use 'h' character for hardened BIP-32 components in help texts. [#3037] - trezorctl: Use 'h' character in generated descriptors. [#3037] - ClickUI: notify user in terminal that they should enter PIN or passphrase on Trezor. [#3203] - Internal names are used consistently in constants and names. Original model-based names are kept as aliases for backwards compatibility. - Trezor model detection will try to use the `internal_name` field. ### Fixed - Drop simple-rlp dependency and use internal copy [#3045] - Fixed printing Trezor model when validating firmware image [#3227] - Corrected vendor header signing keys for Safe 3 (T2B1). ## [0.13.7] (2023-06-02) [0.13.7]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.6...python/v0.13.7 ### Added - Recognize signing keys for T2B1. - Add possibility to call tutorial flow [#2795] - Add ability to change homescreen for Model R [#2967] - Recognize hw model field in vendor headers. [#3048] ## [0.13.6] (2023-04-24) [0.13.6]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.5...python/v0.13.6 ### Added - Signed Ethereum network and token definitions from host [#15] - Support SLIP-25 in get-descriptor. [#2541] - trezorctl: Support prompt configuration for `encrypt-keyvalue` / `decrypt-keyvalue`. [#2608] - Support for external reward addresses in Cardano CIP-36 registrations [#2692] - Auto-convert image to Trezor's homescreen format. [#2880] ### Changed - `trezorctl firmware verify` changed order of checks - fingerprint is validated before signatures. [#2745] ### Fixed - Removed attempt to initialize the device after wipe in bootloader mode [#2221] - Limit memory exhaustion in protobuf parser. [#2439] - `trezorctl ethereum sign-tx`: renamed `--gas-limit` shortcut to `-G` to avoid collision with `-t/--token` [#2535] - Fixed behavior of UDP transport search by path when full path is provided and prefix_search is True [#2786] - Fixed behavior of `trezorctl fw` with unsigned Trezor One firmwares. [#2801] - Improve typing information when `TrezorClient` has a more intelligent UI object. [#2832] - When enabling passphrase force-on-device, do not also prompt to enable passphrase if it is already enabled. [#2833] ## [0.13.5] (2022-12-28) [0.13.5]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.4...python/v0.13.5 ### Added - Add support for model field in firmware image. [#2701] - Add support for v3-style Trezor One signatures. [#2701] ### Changed - More structured information about signing keys for different models. [#2701] ### Incompatible changes - Instead of accepting a list of public keys, `FirmwareType.verify()` accepts a parameter configuring whether to use production or development keys. [#2701] ## [0.13.4] (2022-11-04) [0.13.4]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.3...python/v0.13.4 ### Added - Add UnlockPath message. [#2289] - Added new TOI formats - little endian full-color and even-high grayscale [#2414] - Add device set-busy command to trezorctl. [#2445] - Support SLIP-25 accounts in get-public-node and get-address. [#2517] - Add possibility to save emulator screenshots. [#2547] - Support for Cardano CIP-36 governance registration format [#2561] ### Removed - Remove DATA parameter from trezorctl cosi commit. - Remove firmware dumping capability. [#2433] ### Fixed - Fixed issue where type declarations were not visible to consumer packages. [#2542] ### Incompatible changes - Refactored firmware parsing and validation to a more object oriented approach. [#2576] ## [0.13.3] (2022-07-13) [0.13.3]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.2...python/v0.13.3 ### Added - Support for Cardano Babbage era transaction items [#2354] ### Fixed - Fix Click 7.x compatibility. [#2364] ## [0.13.2] (2022-06-30) [0.13.2]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.1...python/v0.13.2 ### Fixed - Fixed dependency error when running trezorctl without PIL. - Fixed dependency error when running trezorctl on Python 3.6 without rlp. - Fix `trezorctl --version` crash. [#1702] ## [0.13.1] (2022-06-29) [0.13.1]: https://github.com/trezor/trezor-firmware/compare/python/v0.13.0...python/v0.13.1 ### Added - New exception type `DeviceIsBusy` indicates that the device is in use by another process. [#1026] - Support payment requests and GetNonce command. [#1430] - Add press_info() to DebugLink. [#1430] - Add support for blind EIP-712 signing for Trezor One [#1970] - Add ScriptUI for trezorctl, spawned by --script option [#2023] - Support T1 screenshot saving in Debuglink [#2093] - Support generating Electrum-compatible message signatures in CLI. [#2100] - Support Cardano Alonzo-era transaction items and --include-network-id flag [#2114] - trezorctl: Bitcoin commands can detect script type from derivation path. [#2159] - Add support for model R [#2230] - Add firmware get-hash command. [#2239] - Jump and stay in bootloader from firmware through SVC call reverse trampoline. [#2284] ### Changed - Unify boolean arguments/options in trezorlib commands to on/off [#2123] - Rename `normalize_nfc` to `prepare_message_bytes` in tools.py [#2126] - `trezorctl monero` network type arguments now accept symbolic names instead of numbers. [#2219] ### Fixed - trezorctl will correctly report that device is in use. [#1026] - Allow passing empty `message_hash` for domain-only EIP-712 hashes for Trezor T1 (i.e. when `primaryType`=`EIP712Domain`) [#2036] - Fixed error when printing protobuf message with a missing required field. [#2135] - Add compatibility with Click 8.1 [#2199] ## [0.13.0] - 2021-12-09 [0.13.0]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.4...python/v0.13.0 ### Added - `trezorctl firmware update` shows progress bar (Model T only) - Enabled session management via `EndSession` [#1227] - Added parameters to enable Cardano derivation when calling `init_device()`. [#1231] - two new trezorctl commands - `trezorctl firmware download` and `trezorctl firmware verify` [#1258] - Support no_script_type option in SignMessage. [#1586] - Support for Ethereum EIP1559 transactions [#1604] - Debuglink can automatically scroll through paginated views. [#1671] - Support for Taproot descriptors [#1710] - `trezorlib.stellar.from_envelope` was added, it includes support for the Stellar [TransactionV1](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0015.md#xdr) format transaction. [#1745] - Ethereum: support 64-bit chain IDs [#1771] - Support for Cardano multi-sig transactions, token minting, script addresses, multi-sig keys, minting keys and native script verification [#1772] - Added parameters to specify kind of Cardano derivation to all functions and `trezorctl` commands. [#1783] - Support for EIP-712 in library and `trezorctl ethereum sign-typed-data` [#1835] - Add script_pubkey field to TxInput message. [#1857] - Full type hinting checkable with pyright [#1893] ### Changed - protobuf is aware of `required` fields and default values [#379] - `trezorctl firmware-update` command changed to `trezorctl firmware update` [#1258] - `btc.sign_tx()` accepts keyword arguments for transaction metadata [#1266] - Raise `ValueError` when the txid for an input is not present in `prev_txes` during `btc.sign_tx` [#1442] - `trezorlib.mappings` was refactored for easier customization [#1449] - Refactor protobuf codec for better clarity [#1541] - `UdpTransport.wait_until_ready` no longer sets socket to nonblocking [#1668] - Cardano transaction parameters are now streamed into the device one by one instead of being sent as one large object [#1683] - `trezorlib.stellar` will refuse to process transactions containing MuxedAccount [#1838] - Use unified descriptors format. [#1885] - Introduce Trezor models as an abstraction over USB IDs, vendor strings, and possibly protobuf mappings. [#1967] ### Deprecated - instantiating protobuf objects with positional arguments is deprecated [#379] - `details` argument to `btc.sign_tx()` is deprecated. Use keyword arguments instead. [#379] - values of required fields must be supplied at instantiation time. Omitting them is deprecated. [#379] ### Removed - dropped Python 3.5 support [#810] - dropped debug-only `trezorctl debug show-text` functionality [#1531] - Removed support for Lisk [#1765] ### Fixed - fix operator precedence issue for ethereum sign-tx command [#1867] - Updated `tools/build_tx.py` to work with Blockbook's API protections. [#1896] - Fix PIN and passphrase entry in certain terminals on Windows [#1959] ### Incompatible changes - `client.init_device(derive_cardano=True)` must be used before calling Cardano functions. [#1231] - The type of argument to `ui.button_request(x)` is changed from int to ButtonRequest. The original int value can be accessed as `x.code` [#1671] - Due to transaction streaming in Cardano, it isn't possible to return the whole serialized transaction anymore. Instead the transaction hash, transaction witnesses and auxiliary data supplement are returned and the serialized transaction needs to be assembled by the client. [#1683] - `trezorlib.stellar` was reworked to use stellar-sdk instead of providing local implementations [#1745] - Cardano derivation now defaults to Icarus method. This will result in different keys for users with 24-word seed. [#1783] ## [0.12.4] - 2021-09-07 [0.12.4]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.3...python/v0.12.4 ### Fixed - trezorctl: fixed "Invalid value for " when using Click 8 and param is not specified [#1798] ## [0.12.3] - 2021-07-29 [0.12.3]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.2...python/v0.12.3 ### Added - `trezorctl btc get-descriptor` support [#1363] - `trezorctl btc reboot-to-bootloader` support [#1738] - distinguishing between temporary and permanent safety checks - trezorctl accepts PIN entered by letters (useful on laptops) - support for 50-digit PIN for T1 ### Changed - allowed Click 8.x as a requirement - replaced all references to Trezor Wallet with Trezor Suite, and modified all mentions of Beta Wallet ### Fixed - added missing requirement `attrs` - properly parse big numbers in `tools/build_tx.py` [#1257], [#1296] - it is now possible to set safety checks for T1 ## [0.12.2] - 2020-08-27 [0.12.2]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.1...python/v0.12.2 ### Added - `trezorlib.toif` module (moved from internal) can encode and decode TOIF image format - `trezorctl set homescreen` was improved and extended to support PNG images for Trezor T ### Changed - trezorctl will correctly notify the user if the image decoding library is missing ### Fixed - fix exception in `trezorctl btc get-address` [#1179] - fix exception in `trezorctl lisk sign-message` - fix exception in trezorctl commands that accept filenames [#1196] - fix "Invalid homescreen" error when un-setting homescreen ### Removed - removed option `--skip-vendor-header` from `trezorctl firmware-update` which did nothing [#1210] ## [0.12.1] - 2020-08-05 [0.12.1]: https://github.com/trezor/trezor-firmware/compare/python/v0.12.0...python/v0.12.1 ### Added - `trezorctl set safety-checks` controls the new "safety checks" feature. [#1126] - `trezorctl btc get-address` can create multisig addresses. - the following commands are now equivalent in trezorctl: `firmware-update`, `firmware-upgrade`, `update-firmware`, `upgrade-firmware` - support for EXTERNAL input type [#38], [#1052] - support for ownership proofs - support for pre-authorized CoinJoin transactions [#37] - support for Cardano Shelley [#948] ### Changed - do not allow setting auto-lock delay unless PIN is configured ### Fixed - correctly calculate hashes for very small firmwares [f#1082] - unified file arguments in trezorctl - `TrezorClient.ping()` does not crash when device is PIN-locked ## [0.12.0] - 2020-04-01 [0.12.0]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.6...python/v0.12.0 ### Incompatible changes - `trezorlib.coins`, `trezorlib.tx_api`, and the file `coins.json`, were removed - `TrezorClient` argument `ui` is now mandatory. `state` argument was renamed to `session_id`. - UI callback `get_passphrase()` has a new argument `available_on_device`. - API for `cosi` module was changed - other changes may also introduce incompatibilities, please review the full list below ### Added - support for firmwares 1.9.0 and 2.3.0 - Model T now defaults to entering passphrase on device. New trezorctl option `-P` enforces entering passphrase on host. - support for "passphrase always on device" mode on model T - new trezorctl command `get-session` and option `-s` allows entering passphrase once for multiple subsequent trezorctl operations - built-in functionality of UdpTransport to wait until an emulator comes up, and the related command `trezorctl wait-for-emulator` - `trezorctl debug send-bytes` can send raw messages to the device [f#116] - when updating firmware, user is warned that the requested version does not match their device [f#823] - `trezorctl list` can now show name, model and id of device ### Changed - `trezorlib.tx_api.json_to_tx` was reduced to only support Bitcoin fields, and moved to `trezorlib.btc.from_json`. - API for `cosi` module was streamlined: `verify_m_of_n` is now `verify`, the old `verify` is `verify_combined` - internals of firmware parsing were reworked to support signing firmware headers - `get_default_client` respects `TREZOR_PATH` environment variable - UI callback `get_passphrase` has an additional argument `available_on_device`, indicating that the connected Trezor is capable of on-device entry - `Transport.write` and `read` method signatures changed to accept bytes instead of protobuf messages - trezorctl subcommands have a common `@with_client` decorator that manages exception handling and connecting to device ### Fixed - trezorctl does not print empty line when there is no output - trezorctl cleanly reports wire exceptions [f#226] ### Removed - `trezorlib.tx_api` was removed - `trezorlib.coins` and coin data was removed - `trezorlib.ckd_public`, which was deprecated in 0.10, was now removed. - `btc.sign_tx` will not preload transaction data from `prev_txes`, as usage with TxApi is being removed - PIN protection and passphrase protection for `ping()` command was removed - compatibility no-op code from trezorlib 0.9 was removed from `trezorlib.client` - `trezorlib.tools.CallException` was dropped, use `trezorlib.exceptions.TrezorFailure` instead ## [0.11.6] - 2019-12-30 [0.11.6]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.5...python/v0.11.6 ### Added - support for get-and-increase FIDO counter operation - support for setting wipe code - `trezorctl device recover` supports `--u2f-counter` option to set the FIDO counter to a custom value ### Changed - `trezorctl` command was reworked for ease of use and maintenance. See `trezorctl --help` and `OPTIONS.rst` for details. [f#510] - updated EOS transaction parser to match `cleos` in `delegatebw` and `undelegatebw` actions [f#680] [f#681] - `RecoveryDevice` does not set fields when doing dry-run recovery [f#666] ### Fixed - fixed "expand words" functionality in `trezorctl device recover` [f#778] ### Removed - trezorctl no longer interactively signs Bitcoin-like transactions, the only allowed input format is JSON. See [`docs/transaction-format.md`](docs/transaction-format.md) for details. - support for "load device by xprv" was removed from firmware and trezorlib ## [0.11.5] - 2019-09-26 [0.11.5]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.4...python/v0.11.5 ### Added - trezorctl can dump raw protobuf bytes in debug output [f#117] - trezorctl shows a warning when activating Shamir Backup if the device does not support it [f#445] - warnings are emitted when encountering unknown value for a protobuf enum [f#363] - debug messages show enum value names instead of raw numbers - support for packed repeated encoding in the protobuf decoder - in `trezorctl firmware-update`, the new `--beta` switch enables downloading beta firmwares. By default, only stable firmware is used. [f#411], [f#420] - in `trezorctl firmware-update`, the new `--bitcoin-only` switch enables downloading Bitcoin-only firmware - support for FIDO2 resident credential management - support for SD-protect features ### Changed - package directory structure was changed: `src` subdirectory contains sources and `tests` subdirectory contains tests, so that cwd is not cluttered - `trezorctl` script was moved into a module `trezorlib.cli.trezorctl` and is launched through the `entry_points` mechanism. This makes it usable on Windows - `pyblake2` is no longer required on Python 3.6 and up - input flows can only be used in with-block (only relevant for unit tests) - if not specified, trezorctl will set label to "SLIP-0014" in SLIP-0014 mode - in `clear_session` the client also forgets the passphrase state for TT [f#525] ### Fixed - trezorctl will properly check if a firmware is present on a new T1 [f#224] ### Removed - device test suite was moved out of trezor package ## [0.11.4] - 2019-07-31 [0.11.4]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.3...python/v0.11.4 ### Added - trezorctl support for SLIP-39 Shamir Backup - support for Binance Chain ## [0.11.3] - 2019-05-29 [0.11.3]: https://github.com/trezor/trezor-firmware/compare/python/v0.11.2...python/v0.11.3 ### 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 - EOS support [f#87] - Tezos: add voting support [f#41] - `dict_to_proto` now allows enum values as strings ### Changed - Minimum firmware versions bumped to 1.8.0 and 2.1.0 - Cleaner errors when UI object is not supplied - Generated files are now part of the source tarball again. That means that `protoc` is no longer required. ### Fixed - Ethereum commands in trezorctl now work - Memory debugging tools now work again ### Removed - Tron and Ontology support removed until implementations exist in Trezor firmware ## [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) [f#41]: https://github.com/trezor/trezor-firmware/pull/41 [f#87]: https://github.com/trezor/trezor-firmware/pull/87 [f#116]: https://github.com/trezor/trezor-firmware/pull/116 [f#117]: https://github.com/trezor/trezor-firmware/pull/117 [f#224]: https://github.com/trezor/trezor-firmware/pull/224 [f#226]: https://github.com/trezor/trezor-firmware/pull/226 [f#363]: https://github.com/trezor/trezor-firmware/pull/363 [f#411]: https://github.com/trezor/trezor-firmware/pull/411 [f#420]: https://github.com/trezor/trezor-firmware/pull/420 [f#445]: https://github.com/trezor/trezor-firmware/pull/445 [f#510]: https://github.com/trezor/trezor-firmware/pull/510 [f#525]: https://github.com/trezor/trezor-firmware/pull/525 [f#666]: https://github.com/trezor/trezor-firmware/pull/666 [f#680]: https://github.com/trezor/trezor-firmware/pull/680 [f#681]: https://github.com/trezor/trezor-firmware/pull/681 [f#778]: https://github.com/trezor/trezor-firmware/pull/778 [f#823]: https://github.com/trezor/trezor-firmware/pull/823 [f#1082]: https://github.com/trezor/trezor-firmware/pull/1082 [#15]: https://github.com/trezor/trezor-firmware/pull/15 [#37]: https://github.com/trezor/trezor-firmware/pull/37 [#38]: https://github.com/trezor/trezor-firmware/pull/38 [#94]: https://github.com/trezor/python-trezor/pull/94 [#167]: https://github.com/trezor/python-trezor/pull/167 [#169]: https://github.com/trezor/python-trezor/pull/169 [#185]: https://github.com/trezor/python-trezor/pull/185 [#197]: https://github.com/trezor/python-trezor/pull/197 [#199]: https://github.com/trezor/python-trezor/pull/199 [#207]: https://github.com/trezor/python-trezor/pull/207 [#223]: https://github.com/trezor/python-trezor/pull/223 [#226]: https://github.com/trezor/python-trezor/pull/226 [#229]: https://github.com/trezor/python-trezor/pull/229 [#230]: https://github.com/trezor/python-trezor/pull/230 [#236]: https://github.com/trezor/python-trezor/pull/236 [#237]: https://github.com/trezor/python-trezor/pull/237 [#241]: https://github.com/trezor/python-trezor/pull/241 [#242]: https://github.com/trezor/python-trezor/pull/242 [#245]: https://github.com/trezor/python-trezor/pull/245 [#248]: https://github.com/trezor/python-trezor/pull/248 [#249]: https://github.com/trezor/python-trezor/pull/249 [#250]: https://github.com/trezor/python-trezor/pull/250 [#256]: https://github.com/trezor/python-trezor/pull/256 [#268]: https://github.com/trezor/python-trezor/pull/268 [#269]: https://github.com/trezor/python-trezor/pull/269 [#274]: https://github.com/trezor/python-trezor/pull/274 [#276]: https://github.com/trezor/python-trezor/pull/276 [#277]: https://github.com/trezor/python-trezor/pull/277 [#280]: https://github.com/trezor/python-trezor/pull/280 [#283]: https://github.com/trezor/python-trezor/pull/283 [#284]: https://github.com/trezor/python-trezor/pull/284 [#286]: https://github.com/trezor/python-trezor/pull/286 [#287]: https://github.com/trezor/python-trezor/pull/287 [#300]: https://github.com/trezor/python-trezor/pull/300 [#301]: https://github.com/trezor/python-trezor/pull/301 [#302]: https://github.com/trezor/python-trezor/pull/302 [#304]: https://github.com/trezor/python-trezor/pull/304 [#307]: https://github.com/trezor/python-trezor/pull/307 [#308]: https://github.com/trezor/python-trezor/pull/308 [#312]: https://github.com/trezor/python-trezor/pull/312 [#314]: https://github.com/trezor/python-trezor/pull/314 [#315]: https://github.com/trezor/python-trezor/pull/315 [#325]: https://github.com/trezor/python-trezor/pull/325 [#349]: https://github.com/trezor/python-trezor/pull/349 [#351]: https://github.com/trezor/python-trezor/pull/351 [#352]: https://github.com/trezor/python-trezor/pull/352 [#379]: https://github.com/trezor/trezor-firmware/pull/379 [#810]: https://github.com/trezor/trezor-firmware/pull/810 [#948]: https://github.com/trezor/trezor-firmware/pull/948 [#1026]: https://github.com/trezor/trezor-firmware/pull/1026 [#1052]: https://github.com/trezor/trezor-firmware/pull/1052 [#1126]: https://github.com/trezor/trezor-firmware/pull/1126 [#1179]: https://github.com/trezor/trezor-firmware/pull/1179 [#1196]: https://github.com/trezor/trezor-firmware/pull/1196 [#1210]: https://github.com/trezor/trezor-firmware/pull/1210 [#1227]: https://github.com/trezor/trezor-firmware/pull/1227 [#1231]: https://github.com/trezor/trezor-firmware/pull/1231 [#1257]: https://github.com/trezor/trezor-firmware/pull/1257 [#1258]: https://github.com/trezor/trezor-firmware/pull/1258 [#1266]: https://github.com/trezor/trezor-firmware/pull/1266 [#1296]: https://github.com/trezor/trezor-firmware/pull/1296 [#1363]: https://github.com/trezor/trezor-firmware/pull/1363 [#1430]: https://github.com/trezor/trezor-firmware/pull/1430 [#1442]: https://github.com/trezor/trezor-firmware/pull/1442 [#1449]: https://github.com/trezor/trezor-firmware/pull/1449 [#1531]: https://github.com/trezor/trezor-firmware/pull/1531 [#1541]: https://github.com/trezor/trezor-firmware/pull/1541 [#1586]: https://github.com/trezor/trezor-firmware/pull/1586 [#1604]: https://github.com/trezor/trezor-firmware/pull/1604 [#1668]: https://github.com/trezor/trezor-firmware/pull/1668 [#1671]: https://github.com/trezor/trezor-firmware/pull/1671 [#1683]: https://github.com/trezor/trezor-firmware/pull/1683 [#1702]: https://github.com/trezor/trezor-firmware/pull/1702 [#1710]: https://github.com/trezor/trezor-firmware/pull/1710 [#1738]: https://github.com/trezor/trezor-firmware/pull/1738 [#1745]: https://github.com/trezor/trezor-firmware/pull/1745 [#1765]: https://github.com/trezor/trezor-firmware/pull/1765 [#1771]: https://github.com/trezor/trezor-firmware/pull/1771 [#1772]: https://github.com/trezor/trezor-firmware/pull/1772 [#1783]: https://github.com/trezor/trezor-firmware/pull/1783 [#1798]: https://github.com/trezor/trezor-firmware/pull/1798 [#1835]: https://github.com/trezor/trezor-firmware/pull/1835 [#1838]: https://github.com/trezor/trezor-firmware/pull/1838 [#1857]: https://github.com/trezor/trezor-firmware/pull/1857 [#1867]: https://github.com/trezor/trezor-firmware/pull/1867 [#1885]: https://github.com/trezor/trezor-firmware/pull/1885 [#1893]: https://github.com/trezor/trezor-firmware/pull/1893 [#1896]: https://github.com/trezor/trezor-firmware/pull/1896 [#1959]: https://github.com/trezor/trezor-firmware/pull/1959 [#1967]: https://github.com/trezor/trezor-firmware/pull/1967 [#1970]: https://github.com/trezor/trezor-firmware/pull/1970 [#2023]: https://github.com/trezor/trezor-firmware/pull/2023 [#2036]: https://github.com/trezor/trezor-firmware/pull/2036 [#2093]: https://github.com/trezor/trezor-firmware/pull/2093 [#2100]: https://github.com/trezor/trezor-firmware/pull/2100 [#2114]: https://github.com/trezor/trezor-firmware/pull/2114 [#2123]: https://github.com/trezor/trezor-firmware/pull/2123 [#2126]: https://github.com/trezor/trezor-firmware/pull/2126 [#2135]: https://github.com/trezor/trezor-firmware/pull/2135 [#2159]: https://github.com/trezor/trezor-firmware/pull/2159 [#2199]: https://github.com/trezor/trezor-firmware/pull/2199 [#2219]: https://github.com/trezor/trezor-firmware/pull/2219 [#2221]: https://github.com/trezor/trezor-firmware/pull/2221 [#2230]: https://github.com/trezor/trezor-firmware/pull/2230 [#2239]: https://github.com/trezor/trezor-firmware/pull/2239 [#2284]: https://github.com/trezor/trezor-firmware/pull/2284 [#2289]: https://github.com/trezor/trezor-firmware/pull/2289 [#2354]: https://github.com/trezor/trezor-firmware/pull/2354 [#2364]: https://github.com/trezor/trezor-firmware/pull/2364 [#2414]: https://github.com/trezor/trezor-firmware/pull/2414 [#2433]: https://github.com/trezor/trezor-firmware/pull/2433 [#2439]: https://github.com/trezor/trezor-firmware/pull/2439 [#2445]: https://github.com/trezor/trezor-firmware/pull/2445 [#2517]: https://github.com/trezor/trezor-firmware/pull/2517 [#2535]: https://github.com/trezor/trezor-firmware/pull/2535 [#2541]: https://github.com/trezor/trezor-firmware/pull/2541 [#2542]: https://github.com/trezor/trezor-firmware/pull/2542 [#2547]: https://github.com/trezor/trezor-firmware/pull/2547 [#2561]: https://github.com/trezor/trezor-firmware/pull/2561 [#2576]: https://github.com/trezor/trezor-firmware/pull/2576 [#2608]: https://github.com/trezor/trezor-firmware/pull/2608 [#2692]: https://github.com/trezor/trezor-firmware/pull/2692 [#2701]: https://github.com/trezor/trezor-firmware/pull/2701 [#2745]: https://github.com/trezor/trezor-firmware/pull/2745 [#2786]: https://github.com/trezor/trezor-firmware/pull/2786 [#2795]: https://github.com/trezor/trezor-firmware/pull/2795 [#2801]: https://github.com/trezor/trezor-firmware/pull/2801 [#2832]: https://github.com/trezor/trezor-firmware/pull/2832 [#2833]: https://github.com/trezor/trezor-firmware/pull/2833 [#2880]: https://github.com/trezor/trezor-firmware/pull/2880 [#2919]: https://github.com/trezor/trezor-firmware/pull/2919 [#2967]: https://github.com/trezor/trezor-firmware/pull/2967 [#2989]: https://github.com/trezor/trezor-firmware/pull/2989 [#3037]: https://github.com/trezor/trezor-firmware/pull/3037 [#3045]: https://github.com/trezor/trezor-firmware/pull/3045 [#3048]: https://github.com/trezor/trezor-firmware/pull/3048 [#3203]: https://github.com/trezor/trezor-firmware/pull/3203 [#3208]: https://github.com/trezor/trezor-firmware/pull/3208 [#3227]: https://github.com/trezor/trezor-firmware/pull/3227 [#3237]: https://github.com/trezor/trezor-firmware/pull/3237 [#3255]: https://github.com/trezor/trezor-firmware/pull/3255 [#3359]: https://github.com/trezor/trezor-firmware/pull/3359 [#3364]: https://github.com/trezor/trezor-firmware/pull/3364 [#3422]: https://github.com/trezor/trezor-firmware/pull/3422 [#3434]: https://github.com/trezor/trezor-firmware/pull/3434 [#3496]: https://github.com/trezor/trezor-firmware/pull/3496 [#3504]: https://github.com/trezor/trezor-firmware/pull/3504 [#3636]: https://github.com/trezor/trezor-firmware/pull/3636 [#3868]: https://github.com/trezor/trezor-firmware/pull/3868 [#3893]: https://github.com/trezor/trezor-firmware/pull/3893 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315826.0 trezor-0.13.9/src/trezor.egg-info/SOURCES.txt0000664000175000017500000000570214636526562021050 0ustar00matejcikmatejcikAUTHORS CHANGELOG.md COPYING MANIFEST.in README.md pyrightconfig.json requirements-optional.txt requirements.txt setup.cfg setup.py tox.ini bash_completion.d/trezorctl.sh src/trezor.egg-info/PKG-INFO src/trezor.egg-info/SOURCES.txt src/trezor.egg-info/dependency_links.txt src/trezor.egg-info/entry_points.txt src/trezor.egg-info/not-zip-safe src/trezor.egg-info/requires.txt src/trezor.egg-info/top_level.txt src/trezorlib/__init__.py src/trezorlib/_ed25519.py src/trezorlib/_rlp.py src/trezorlib/authentication.py src/trezorlib/binance.py src/trezorlib/btc.py src/trezorlib/cardano.py src/trezorlib/client.py src/trezorlib/cosi.py src/trezorlib/debuglink.py src/trezorlib/definitions.py src/trezorlib/device.py src/trezorlib/eos.py src/trezorlib/ethereum.py src/trezorlib/exceptions.py src/trezorlib/fido.py src/trezorlib/log.py src/trezorlib/mapping.py src/trezorlib/merkle_tree.py src/trezorlib/messages.py src/trezorlib/misc.py src/trezorlib/models.py src/trezorlib/monero.py src/trezorlib/nem.py src/trezorlib/protobuf.py src/trezorlib/py.typed src/trezorlib/ripple.py src/trezorlib/solana.py src/trezorlib/stellar.py src/trezorlib/tezos.py src/trezorlib/toif.py src/trezorlib/tools.py src/trezorlib/ui.py src/trezorlib/_internal/__init__.py src/trezorlib/_internal/emulator.py src/trezorlib/_internal/firmware_headers.py src/trezorlib/_internal/translations.py src/trezorlib/cli/__init__.py src/trezorlib/cli/binance.py src/trezorlib/cli/btc.py src/trezorlib/cli/cardano.py src/trezorlib/cli/cosi.py src/trezorlib/cli/crypto.py src/trezorlib/cli/debug.py src/trezorlib/cli/device.py src/trezorlib/cli/eos.py src/trezorlib/cli/ethereum.py src/trezorlib/cli/fido.py src/trezorlib/cli/firmware.py src/trezorlib/cli/monero.py src/trezorlib/cli/nem.py src/trezorlib/cli/ripple.py src/trezorlib/cli/settings.py src/trezorlib/cli/solana.py src/trezorlib/cli/stellar.py src/trezorlib/cli/tezos.py src/trezorlib/cli/trezorctl.py src/trezorlib/firmware/__init__.py src/trezorlib/firmware/consts.py src/trezorlib/firmware/core.py src/trezorlib/firmware/legacy.py src/trezorlib/firmware/models.py src/trezorlib/firmware/util.py src/trezorlib/firmware/vendor.py src/trezorlib/qt/__init__.py src/trezorlib/qt/pinmatrix.py src/trezorlib/transport/__init__.py src/trezorlib/transport/bridge.py src/trezorlib/transport/hid.py src/trezorlib/transport/protocol.py src/trezorlib/transport/udp.py src/trezorlib/transport/webusb.py stubs/PIL/Image.pyi tests/test_btc.py tests/test_cosi.py tests/test_firmware.py tests/test_merkle_tree.py tests/test_nem.py tests/test_protobuf_encoding.py tests/test_protobuf_misc.py tests/test_rlp.py tests/test_stellar.py tests/test_tools.py tests/test_transport.py tools/build_tx.py tools/deserialize_tx.py tools/encfs_aes_getpass.py tools/firmware-fingerprint.py tools/helloworld.py tools/mem_flashblock.py tools/mem_read.py tools/mem_write.py tools/mnemonic_check.py tools/pwd_reader.py tools/pybridge.py tools/rng_entropy_collector.py tools/trezor-otp.py tools/trezorctl_script_client.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315826.0 trezor-0.13.9/src/trezor.egg-info/dependency_links.txt0000664000175000017500000000000114636526562023226 0ustar00matejcikmatejcik ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315826.0 trezor-0.13.9/src/trezor.egg-info/entry_points.txt0000664000175000017500000000007214636526562022455 0ustar00matejcikmatejcik[console_scripts] trezorctl = trezorlib.cli.trezorctl:cli ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315826.0 trezor-0.13.9/src/trezor.egg-info/not-zip-safe0000664000175000017500000000000114636526562021406 0ustar00matejcikmatejcik ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315826.0 trezor-0.13.9/src/trezor.egg-info/requires.txt0000664000175000017500000000060214636526562021556 0ustar00matejcikmatejcikecdsa>=0.9 mnemonic>=0.20 requests>=2.4.0 click<8.2,>=7 libusb1>=1.6.4 construct!=2.10.55,>=2.9 typing_extensions>=4.7.1 construct-classes>=0.1.2 [authentication] cryptography>=41 [ethereum] web3>=5 [extra] Pillow>=10 [full] hidapi>=0.7.99.post20 web3>=5 PyQt5 Pillow>=10 stellar-sdk>=6 cryptography>=41 [hidapi] hidapi>=0.7.99.post20 [qt-widgets] PyQt5 [stellar] stellar-sdk>=6 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315826.0 trezor-0.13.9/src/trezor.egg-info/top_level.txt0000664000175000017500000000001214636526562021703 0ustar00matejcikmatejciktrezorlib ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1719315826.1291068 trezor-0.13.9/src/trezorlib/0000775000175000017500000000000014636526562016155 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315819.0 trezor-0.13.9/src/trezorlib/__init__.py0000664000175000017500000000130014636526553020260 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . __version__ = "0.13.9" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/_ed25519.py0000664000175000017500000001665214636513242017665 0ustar00matejcikmatejcik# ed25519.py - Optimized version of the reference implementation of Ed25519 # downloaded from https://github.com/pyca/ed25519 # # Written in 2011? by Daniel J. Bernstein # 2013 by Donald Stufft # 2013 by Alex Gaynor # 2013 by Greg Price # # 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 # . """ 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: int = 2**255 - 19 l: int = 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: 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") ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1719315826.130107 trezor-0.13.9/src/trezorlib/_internal/0000775000175000017500000000000014636526562020130 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/_internal/__init__.py0000664000175000017500000000000014636513242022216 0ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/_internal/emulator.py0000664000175000017500000002160214636513242022322 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import logging import os import subprocess import time from pathlib import Path from typing import Any, Dict, Iterable, List, Optional, Sequence, TextIO, Union, cast from ..debuglink import TrezorClientDebugLink from ..transport.udp import UdpTransport LOG = logging.getLogger(__name__) EMULATOR_WAIT_TIME = 60 def _rm_f(path: Path) -> None: try: path.unlink() except FileNotFoundError: pass class Emulator: STORAGE_FILENAME: str def __init__( self, executable: Path, profile_dir: str, *, logfile: Union[TextIO, str, Path, None] = None, storage: Optional[bytes] = None, headless: bool = False, debug: bool = True, auto_interact: bool = True, extra_args: Iterable[str] = (), ) -> None: self.executable = Path(executable).resolve() if not executable.exists(): raise ValueError(f"emulator executable not found: {self.executable}") self.profile_dir = Path(profile_dir).resolve() if not self.profile_dir.exists(): self.profile_dir.mkdir(parents=True) elif not self.profile_dir.is_dir(): raise ValueError("profile_dir is not a directory") self.workdir = self.profile_dir self.storage = self.profile_dir / self.STORAGE_FILENAME if storage: self.storage.write_bytes(storage) if logfile: self.logfile = logfile else: self.logfile = self.profile_dir / "trezor.log" # Using `client` property instead to assert `not None` self._client: Optional[TrezorClientDebugLink] = None self.process: Optional[subprocess.Popen] = None self.port = 21324 self.headless = headless self.debug = debug self.auto_interact = auto_interact self.extra_args = list(extra_args) # To save all screenshots properly in one directory between restarts self.restart_amount = 0 @property def client(self) -> TrezorClientDebugLink: """So that type-checkers do not see `client` as `Optional`. (it is not None between `start()` and `stop()` calls) """ if self._client is None: raise RuntimeError return self._client def make_args(self) -> List[str]: return [] def make_env(self) -> Dict[str, str]: return os.environ.copy() def _get_transport(self) -> UdpTransport: return UdpTransport(f"127.0.0.1:{self.port}") def wait_until_ready(self, timeout: float = EMULATOR_WAIT_TIME) -> None: assert self.process is not None, "Emulator not started" transport = self._get_transport() transport.open() LOG.info("Waiting for emulator to come up...") start = time.monotonic() try: while True: if transport._ping(): break if self.process.poll() is not None: raise RuntimeError("Emulator process died") elapsed = time.monotonic() - start if elapsed >= timeout: raise TimeoutError("Can't connect to emulator") time.sleep(0.1) finally: transport.close() LOG.info(f"Emulator ready after {time.monotonic() - start:.3f} seconds") def wait(self, timeout: Optional[float] = None) -> int: assert self.process is not None, "Emulator not started" ret = self.process.wait(timeout=timeout) self.process = None self.stop() return ret def launch_process(self) -> subprocess.Popen: args = self.make_args() env = self.make_env() # Opening the file if it is not already opened if hasattr(self.logfile, "write"): output = self.logfile else: assert isinstance(self.logfile, (str, Path)) output = open(self.logfile, "w") return subprocess.Popen( [str(self.executable)] + args + self.extra_args, cwd=self.workdir, stdout=cast(TextIO, output), stderr=subprocess.STDOUT, env=env, ) def start(self) -> None: if self.process: if self.process.poll() is not None: # process has died, stop and start again LOG.info("Starting from a stopped process.") self.stop() else: # process is running, no need to start again return self.process = self.launch_process() try: self.wait_until_ready() except TimeoutError: # Assuming that after the default 60-second timeout, the process is stuck LOG.warning(f"Emulator did not come up after {EMULATOR_WAIT_TIME} seconds") self.process.kill() raise (self.profile_dir / "trezor.pid").write_text(str(self.process.pid) + "\n") (self.profile_dir / "trezor.port").write_text(str(self.port) + "\n") transport = self._get_transport() self._client = TrezorClientDebugLink( transport, auto_interact=self.auto_interact ) self._client.open() def stop(self) -> None: if self._client: self._client.close() self._client = None if self.process: LOG.info("Terminating emulator...") start = time.monotonic() self.process.terminate() try: self.process.wait(EMULATOR_WAIT_TIME) end = time.monotonic() LOG.info(f"Emulator shut down after {end - start:.3f} seconds") except subprocess.TimeoutExpired: LOG.info("Emulator seems stuck. Sending kill signal.") self.process.kill() _rm_f(self.profile_dir / "trezor.pid") _rm_f(self.profile_dir / "trezor.port") self.process = None def restart(self) -> None: # preserving the recording directory between restarts self.restart_amount += 1 prev_screenshot_dir = self.client.debug.screenshot_recording_dir self.stop() self.start() if prev_screenshot_dir: self.client.debug.start_recording( prev_screenshot_dir, refresh_index=self.restart_amount ) def __enter__(self) -> "Emulator": return self def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: self.stop() def get_storage(self) -> bytes: return self.storage.read_bytes() class CoreEmulator(Emulator): STORAGE_FILENAME = "trezor.flash" def __init__( self, *args: Any, port: Optional[int] = None, main_args: Sequence[str] = ("-m", "main"), workdir: Optional[Path] = None, sdcard: Optional[bytes] = None, disable_animation: bool = True, heap_size: str = "20M", **kwargs: Any, ) -> None: super().__init__(*args, **kwargs) if workdir is not None: self.workdir = Path(workdir).resolve() self.sdcard = self.profile_dir / "trezor.sdcard" if sdcard is not None: self.sdcard.write_bytes(sdcard) if port: self.port = port self.disable_animation = disable_animation self.main_args = list(main_args) self.heap_size = heap_size def make_env(self) -> Dict[str, str]: env = super().make_env() env.update( TREZOR_PROFILE_DIR=str(self.profile_dir), TREZOR_PROFILE=str(self.profile_dir), TREZOR_UDP_PORT=str(self.port), ) if self.headless: env["SDL_VIDEODRIVER"] = "dummy" if self.headless or self.disable_animation: env["TREZOR_DISABLE_FADE"] = "1" env["TREZOR_DISABLE_ANIMATION"] = "1" return env def make_args(self) -> List[str]: pyopt = "-O0" if self.debug else "-O1" return ( [pyopt, "-X", f"heapsize={self.heap_size}"] + self.main_args + self.extra_args ) class LegacyEmulator(Emulator): STORAGE_FILENAME = "emulator.img" def make_env(self) -> Dict[str, str]: env = super().make_env() if self.headless: env["SDL_VIDEODRIVER"] = "dummy" return env ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/_internal/firmware_headers.py0000664000175000017500000004015414636513242024004 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import typing as t from copy import copy from dataclasses import asdict from enum import Enum import click import construct as c from construct_classes import Struct from typing_extensions import Protocol, Self, runtime_checkable from .. import cosi, firmware from ..firmware import models as fw_models SYM_OK = click.style("\u2714", fg="green") SYM_FAIL = click.style("\u274c", fg="red") class Status(Enum): VALID = click.style("VALID", fg="green", bold=True) INVALID = click.style("INVALID", fg="red", bold=True) MISSING = click.style("MISSING", fg="blue", bold=True) DEVEL = click.style("DEVEL", fg="red", bold=True) def is_ok(self) -> bool: return self is Status.VALID or self is Status.DEVEL VHASH_DEVEL = bytes.fromhex( "c5b4d40cb76911392122c8d1c277937e49c69b2aaf818001ec5c7663fcce258f" ) def _make_dev_keys(*key_bytes: bytes) -> t.Sequence[bytes]: return [k * 32 for k in key_bytes] def all_zero(data: bytes) -> bool: return all(b == 0 for b in data) def _check_signature_any(fw: "SignableImageProto", is_devel: bool = False) -> Status: if not fw.signature_present(): return Status.MISSING try: fw.verify() return Status.VALID if not is_devel else Status.DEVEL except Exception: pass try: fw.verify(dev_keys=True) return Status.DEVEL except Exception: return Status.INVALID # ====================== formatting functions ==================== class LiteralStr(str): pass def _format_container( pb: t.Union[c.Container, Struct, dict], indent: int = 0, sep: str = " " * 4, truncate_after: t.Optional[int] = 64, truncate_to: t.Optional[int] = 32, ) -> str: def mostly_printable(bytes: bytes) -> bool: 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: t.Any, indent: int) -> str: level = sep * indent leadin = sep * (indent + 1) if isinstance(value, LiteralStr): return value if isinstance(value, list): # short list of simple values if not value or isinstance(value[0], (int, bool, Enum)): return repr(value) # long list, one line per entry lines = ["[", level + "]"] lines[1:1] = [leadin + pformat(x, indent + 1) for x in value] return "\n".join(lines) if isinstance(value, Struct): value = asdict(value) if isinstance(value, dict): lines = ["{"] for key, val in value.items(): if key.startswith("_"): continue if val is None or val == []: continue lines.append(leadin + key + ": " + pformat(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 = value.hex() return f"{length} bytes {output}{suffix}" if isinstance(value, Enum): return str(value) return repr(value) return pformat(pb, indent) def _format_version(version: t.Tuple[int, ...]) -> str: return ".".join(str(i) for i in version) def format_header( header: firmware.core.FirmwareHeader, code_hashes: t.Sequence[bytes], digest: bytes, sig_status: Status, ) -> str: header_dict = asdict(header) header_out = header_dict.copy() for key, val in header_out.items(): if "version" in key: header_out[key] = LiteralStr(_format_version(val)) hashes_out = [] for expected, actual in zip(header.hashes, code_hashes): status = SYM_OK if expected == actual else SYM_FAIL hashes_out.append(LiteralStr(f"{status} {expected.hex()}")) if all(all_zero(h) for h in header.hashes): hash_status = Status.MISSING elif header.hashes != code_hashes: hash_status = Status.INVALID else: hash_status = Status.VALID header_out["hashes"] = hashes_out all_ok = SYM_OK if hash_status.is_ok() and sig_status.is_ok() else SYM_FAIL output = [ "Firmware Header " + _format_container(header_out), f"Fingerprint: {click.style(digest.hex(), bold=True)}", f"{all_ok} Signature is {sig_status.value}, hashes are {hash_status.value}", ] return "\n".join(output) # =========================== functionality implementations =============== class SignableImageProto(Protocol): NAME: t.ClassVar[str] @classmethod def parse(cls, data: bytes) -> Self: """Parse binary data into an image of this type.""" ... def digest(self) -> bytes: """Calculate digest that will be signed / verified.""" ... def verify(self, dev_keys: bool = False) -> None: """Verify signature of the image. If dev_keys is True, verify using development keys. If selected, a production image will fail verification. """ ... def build(self) -> bytes: """Reconstruct binary representation of the image.""" ... def format(self, verbose: bool = False) -> str: """Generate printable information about the image.""" ... def signature_present(self) -> bool: """Check if the image has a signature.""" ... def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]: """Return public keys that should be used to sign the image. This does _not_ return the keys with which the image is actually signed. In particular, `image.public_keys()` will return the production keys even if the image is signed with development keys. If dev_keys is True, return development keys. """ ... @runtime_checkable class CosiSignedImage(SignableImageProto, Protocol): DEV_KEYS: t.ClassVar[t.Sequence[bytes]] = [] def insert_signature(self, signature: bytes, sigmask: int) -> None: ... @runtime_checkable class LegacySignedImage(SignableImageProto, Protocol): def slots(self) -> t.Iterable[int]: ... def insert_signature(self, slot: int, key_index: int, signature: bytes) -> None: ... def public_keys( self, dev_keys: bool = False, signature_version: int = 3 ) -> t.Sequence[bytes]: """Return public keys that should be used to sign the image. This does _not_ return the keys with which the image is actually signed. In particular, `image.public_keys()` will return the production keys even if the image is signed with development keys. If dev_keys is True, return development keys. Specifying signature_version allows to return keys for a different signature scheme version. The default is the newest version 3. """ ... class CosiSignatureHeaderProto(Protocol): hw_model: t.Union[fw_models.Model, bytes] signature: bytes sigmask: int class CosiSignedMixin: def signature_present(self) -> bool: header = self.get_header() return not all_zero(header.signature) or header.sigmask != 0 def insert_signature(self, signature: bytes, sigmask: int) -> None: self.get_header().signature = signature self.get_header().sigmask = sigmask def get_header(self) -> CosiSignatureHeaderProto: raise NotImplementedError def get_model_keys(self, dev_keys: bool) -> fw_models.ModelKeys: hw_model = self.get_header().hw_model model = fw_models.Model.from_hw_model(hw_model) return model.model_keys(dev_keys) class VendorHeader(firmware.VendorHeader, CosiSignedMixin): NAME: t.ClassVar[str] = "vendorheader" DEV_KEYS = _make_dev_keys(b"\x44", b"\x45") SUBCON = c.Struct(*firmware.VendorHeader.SUBCON.subcons, c.Terminated) def get_header(self) -> CosiSignatureHeaderProto: return self def _format(self, terse: bool) -> str: if not terse: output = [ "Vendor Header " + _format_container(self), f"Pubkey bundle hash: {self.vhash().hex()}", ] else: output = [ "Vendor Header for {vendor} version {version} ({size} bytes)".format( vendor=click.style(self.text, bold=True), version=_format_version(self.version), size=self.header_len, ), ] if not terse: output.append(f"Fingerprint: {click.style(self.digest().hex(), bold=True)}") sig_status = _check_signature_any(self) sym = SYM_OK if sig_status.is_ok() else SYM_FAIL output.append(f"{sym} Signature is {sig_status.value}") return "\n".join(output) def format(self, verbose: bool = False) -> str: return self._format(terse=False) def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]: return self.get_model_keys(dev_keys).bootloader_keys class VendorFirmware(firmware.VendorFirmware, CosiSignedMixin): NAME: t.ClassVar[str] = "firmware" DEV_KEYS = _make_dev_keys(b"\x47", b"\x48") def get_header(self) -> CosiSignatureHeaderProto: return self.firmware.header def format(self, verbose: bool = False) -> str: vh = copy(self.vendor_header) vh.__class__ = VendorHeader assert isinstance(vh, VendorHeader) is_devel = self.vendor_header.vhash() == VHASH_DEVEL return ( vh._format(terse=not verbose) + "\n" + format_header( self.firmware.header, self.firmware.code_hashes(), self.digest(), _check_signature_any(self, is_devel), ) ) def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]: """Return public keys that should be used to sign the image. In vendor firmware, the public keys are stored in the vendor header. There is no choice of development keys. If that is required, you need to create an image with a development vendor header. """ return self.vendor_header.pubkeys class BootloaderImage(firmware.FirmwareImage, CosiSignedMixin): NAME: t.ClassVar[str] = "bootloader" DEV_KEYS = _make_dev_keys(b"\x41", b"\x42") def get_header(self) -> CosiSignatureHeaderProto: return self.header def format(self, verbose: bool = False) -> str: return format_header( self.header, self.code_hashes(), self.digest(), _check_signature_any(self), ) def verify(self, dev_keys: bool = False) -> None: self.validate_code_hashes() public_keys = self.public_keys(dev_keys) try: cosi.verify( self.header.signature, self.digest(), self.get_model_keys(dev_keys).boardloader_sigs_needed, public_keys, self.header.sigmask, ) except Exception: raise firmware.InvalidSignatureError("Invalid bootloader signature") def public_keys(self, dev_keys: bool = False) -> t.Sequence[bytes]: return self.get_model_keys(dev_keys).boardloader_keys class LegacyFirmware(firmware.LegacyFirmware): NAME: t.ClassVar[str] = "legacy_firmware_v1" def signature_present(self) -> bool: return any(i != 0 for i in self.key_indexes) or any( not all_zero(sig) for sig in self.signatures ) def insert_signature(self, slot: int, key_index: int, signature: bytes) -> None: if not 0 <= slot < firmware.V1_SIGNATURE_SLOTS: raise ValueError("Invalid slot number") if not 0 < key_index <= len(fw_models.LEGACY_V1V2.firmware_keys): raise ValueError("Invalid key index") self.key_indexes[slot] = key_index self.signatures[slot] = signature def format(self, verbose: bool = False) -> str: contents = asdict(self).copy() del contents["embedded_v2"] if self.embedded_v2: em = copy(self.embedded_v2) em.__class__ = LegacyV2Firmware assert isinstance(em, LegacyV2Firmware) embedded_content = "\nEmbedded V2 header: " + em.format(verbose=verbose) else: embedded_content = "" return _format_container(contents) + embedded_content def public_keys( self, dev_keys: bool = False, signature_version: int = 2 ) -> t.Sequence[bytes]: if dev_keys: return fw_models.LEGACY_V1V2_DEV.firmware_keys else: return fw_models.LEGACY_V1V2.firmware_keys def slots(self) -> t.Iterable[int]: return self.key_indexes class LegacyV2Firmware(firmware.LegacyV2Firmware): NAME: t.ClassVar[str] = "legacy_firmware_v2" def signature_present(self) -> bool: return any(i != 0 for i in self.header.v1_key_indexes) or any( not all_zero(sig) for sig in self.header.v1_signatures ) def insert_signature(self, slot: int, key_index: int, signature: bytes) -> None: if not 0 <= slot < firmware.V1_SIGNATURE_SLOTS: raise ValueError("Invalid slot number") if not 0 < key_index <= len(firmware.V1_BOOTLOADER_KEYS): raise ValueError("Invalid key index") if not isinstance(self.header.v1_key_indexes, list): self.header.v1_key_indexes = list(self.header.v1_key_indexes) if not isinstance(self.header.v1_signatures, list): self.header.v1_signatures = list(self.header.v1_signatures) self.header.v1_key_indexes[slot] = key_index self.header.v1_signatures[slot] = signature def format(self, verbose: bool = False) -> str: return format_header( self.header, self.code_hashes(), self.digest(), _check_signature_any(self), ) def public_keys( self, dev_keys: bool = False, signature_version: int = 3 ) -> t.Sequence[bytes]: keymap: t.Dict[t.Tuple[int, bool], fw_models.ModelKeys] = { (3, False): fw_models.LEGACY_V3, (3, True): fw_models.LEGACY_V3_DEV, (2, False): fw_models.LEGACY_V1V2, (2, True): fw_models.LEGACY_V1V2_DEV, } if not (signature_version, dev_keys) in keymap: raise ValueError("Unsupported signature version") return keymap[signature_version, dev_keys].firmware_keys def slots(self) -> t.Iterable[int]: return self.header.v1_key_indexes def parse_image(image: bytes) -> SignableImageProto: try: return VendorFirmware.parse(image) except c.ConstructError: pass try: return VendorHeader.parse(image) except c.ConstructError: pass try: firmware_img = firmware.core.FirmwareImage.parse(image) if firmware_img.header.magic == firmware.core.HeaderType.BOOTLOADER: return BootloaderImage.parse(image) if firmware_img.header.magic == firmware.core.HeaderType.FIRMWARE: return LegacyV2Firmware.parse(image) raise ValueError("Unrecognized firmware header magic") except c.ConstructError: pass try: return LegacyFirmware.parse(image) except c.ConstructError: pass raise ValueError("Unrecognized firmware type") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/_internal/translations.py0000664000175000017500000002344314636513242023220 0ustar00matejcikmatejcikfrom __future__ import annotations import json import typing as t import unicodedata from hashlib import sha256 from pathlib import Path import construct as c from construct_classes import Struct, subcon from typing_extensions import Self, TypedDict from ..firmware.models import Model from ..models import TrezorModel from ..tools import EnumAdapter, TupleAdapter # All sections need to be aligned to 2 bytes for the offset tables using u16 to work properly ALIGNMENT = 2 # "align end of struct" subcon. The builtin c.Aligned does not do the right thing, # because it assumes that the alignment is relative to the start of the subcon, not the # start of the whole struct. # TODO this spelling may or may not align in context of the stream as a whole (as # opposed to the containing struct). This is prooobably not a problem -- we want the # top-level alignment to always be ALIGNMENT anyway. But if someone were to use some # of the structs separately, they might get a surprise. Maybe. Didn't test this. ALIGN_SUBCON = c.Padding( lambda ctx: (ALIGNMENT - (ctx._io.tell() % ALIGNMENT)) % ALIGNMENT ) JsonFontInfo = t.Dict[str, str] Order = t.Dict[int, str] VersionTuple = t.Tuple[int, int, int, int] class JsonHeader(TypedDict): language: str version: str class JsonDef(TypedDict): header: JsonHeader translations: dict[str, str] fonts: dict[str, JsonFontInfo] def version_from_json(json_str: str) -> VersionTuple: version_digits = [int(v) for v in json_str.split(".")] if len(version_digits) < 4: version_digits.extend([0] * (4 - len(version_digits))) return t.cast(VersionTuple, tuple(version_digits)) def _normalize(what: str) -> str: return unicodedata.normalize("NFKC", what) def offsets_seq(data: t.Iterable[bytes]) -> t.Iterator[int]: offset = 0 for item in data: yield offset offset += len(item) yield offset class Header(Struct): language: str model: Model firmware_version: VersionTuple data_len: int data_hash: bytes # fmt: off SUBCON = c.Struct( "magic" / c.Const(b"TR"), "language" / c.PaddedString(8, "ascii"), # BCP47 language tag "model" / EnumAdapter(c.Bytes(4), Model), "firmware_version" / TupleAdapter(c.Int8ul, c.Int8ul, c.Int8ul, c.Int8ul), "data_len" / c.Int16ul, "data_hash" / c.Bytes(32), ALIGN_SUBCON, c.Terminated, ) # fmt: on class Proof(Struct): merkle_proof: list[bytes] sigmask: int signature: bytes # fmt: off SUBCON = c.Struct( "merkle_proof" / c.PrefixedArray(c.Int8ul, c.Bytes(32)), "sigmask" / c.Byte, "signature" / c.Bytes(64), ALIGN_SUBCON, c.Terminated, ) # fmt: on class BlobTable(Struct): offsets: list[tuple[int, int]] data: bytes SENTINEL: t.ClassVar[int] = 0xFFFF # fmt: off SUBCON = c.Struct( "_length" / c.Rebuild(c.Int16ul, c.len_(c.this.offsets) - 1), "offsets" / c.Array(c.this._length + 1, TupleAdapter(c.Int16ul, c.Int16ul)), "data" / c.GreedyBytes, ALIGN_SUBCON, c.Terminated, ) # fmt: on @classmethod def from_items(cls, items: dict[int, bytes]) -> Self: assert not any(key >= cls.SENTINEL for key in items.keys()) keys = sorted(items.keys()) items_sorted = [items[key] for key in keys] offsets = list(offsets_seq(items_sorted)) keys.append(cls.SENTINEL) assert len(keys) == len(offsets) return cls( offsets=list(zip(keys, offsets)), data=b"".join(items_sorted), ) def __len__(self) -> int: return len(self.offsets) - 1 def get(self, id: int) -> bytes | None: if id == self.SENTINEL: return None for key, offset in self.offsets: if key == id: return self.data[offset : self.offsets[key + 1][1]] return None class TranslatedStrings(Struct): offsets: list[int] strings: bytes # fmt: off SUBCON = c.Struct( "_length" / c.Rebuild(c.Int16ul, c.len_(c.this.offsets) - 1), "offsets" / c.Array(c.this._length + 1, c.Int16ul), "strings" / c.GreedyBytes, ALIGN_SUBCON, c.Terminated, ) # fmt: on @classmethod def from_items(cls, items: list[str]) -> Self: item_bytes = [_normalize(item).encode("utf-8") for item in items] offsets = list(offsets_seq(item_bytes)) return cls(offsets=offsets, strings=b"".join(item_bytes)) def __len__(self) -> int: return len(self.offsets) - 1 def get(self, idx: int) -> str | None: if idx >= len(self.offsets) - 1: return None return self.strings[self.offsets[idx] : self.offsets[idx + 1]].decode("utf-8") # =========== class Font(BlobTable): @classmethod def from_file(cls, file: Path) -> Self: json_content = json.loads(file.read_text()) assert all(len(codepoint) == 1 for codepoint in json_content) raw_content = { ord(codepoint): bytes.fromhex(data) for codepoint, data in json_content.items() } return cls.from_items(raw_content) class FontsTable(BlobTable): @classmethod def from_dir(cls, model_fonts: dict[str, str], font_dir: Path) -> Self: """Example structure of the font dict: (The beginning number corresponds to the C representation of each font) { "1_FONT_NORMAL": "font_tthoves_regular_21_cs.json", "2_FONT_BOLD": "font_tthoves_bold_17_cs.json", "3_FONT_MONO": "font_robotomono_medium_20_cs.json", "4_FONT_BIG": null, "5_FONT_DEMIBOLD": "font_tthoves_demibold_21_cs.json" } """ fonts = {} for font_name, file_name in model_fonts.items(): if not file_name: continue file_path = font_dir / file_name font_num = int(font_name.split("_")[0]) try: fonts[font_num] = Font.from_file(file_path).build() except Exception as e: raise ValueError(f"Failed to load font {file_name}") from e return cls.from_items(fonts) def get_font(self, font_id: int) -> Font | None: font_bytes = self.get(font_id) if font_bytes is None: return None return Font.parse(font_bytes) # ========= class Payload(Struct): translations_bytes: bytes fonts_bytes: bytes # fmt: off SUBCON = c.Struct( "translations_bytes" / c.Prefixed(c.Int16ul, c.GreedyBytes), "fonts_bytes" / c.Prefixed(c.Int16ul, c.GreedyBytes), c.Terminated, ) # fmt: on class TranslationsBlob(Struct): header_bytes: bytes proof_bytes: bytes payload: Payload = subcon(Payload) # fmt: off SUBCON = c.Struct( "magic" / c.Const(b"TRTR00"), "total_length" / c.Rebuild( c.Int16ul, ( c.len_(c.this.header_bytes) + c.len_(c.this.proof_bytes) + c.len_(c.this.payload.translations_bytes) + c.len_(c.this.payload.fonts_bytes) + 2 * 4 # sizeof(u16) * number of fields ) ), "_start_offset" / c.Tell, "header_bytes" / c.Prefixed(c.Int16ul, c.GreedyBytes), "proof_bytes" / c.Prefixed(c.Int16ul, c.GreedyBytes), "payload" / Payload.SUBCON, "_end_offset" / c.Tell, c.Terminated, c.Check(c.this.total_length == c.this._end_offset - c.this._start_offset), ) # fmt: on @property def header(self): return Header.parse(self.header_bytes) @property def proof(self): return Proof.parse(self.proof_bytes) @proof.setter def proof(self, proof: Proof): self.proof_bytes = proof.build() @property def translations(self): return TranslatedStrings.parse(self.payload.translations_bytes) @property def fonts(self): return FontsTable.parse(self.payload.fonts_bytes) def build(self) -> bytes: assert len(self.header_bytes) % ALIGNMENT == 0 assert len(self.proof_bytes) % ALIGNMENT == 0 assert len(self.payload.translations_bytes) % ALIGNMENT == 0 assert len(self.payload.fonts_bytes) % ALIGNMENT == 0 return super().build() # ==================== def order_from_json(json_order: dict[str, str]) -> Order: return {int(k): v for k, v in json_order.items()} def blob_from_defs( lang_data: JsonDef, order: Order, model: TrezorModel, version: VersionTuple, fonts_dir: Path, ) -> TranslationsBlob: json_header: JsonHeader = lang_data["header"] # order translations -- python dicts keep insertion order translations_ordered: list[str] = [ lang_data["translations"].get(key, "") for _, key in sorted(order.items()) ] translations = TranslatedStrings.from_items(translations_ordered) if model.internal_name not in lang_data["fonts"]: raise ValueError( f"Model {model.internal_name} not found in header for {json_header['language']} v{json_header['version']}" ) model_fonts = lang_data["fonts"][model.internal_name] fonts = FontsTable.from_dir(model_fonts, fonts_dir) translations_bytes = translations.build() assert len(translations_bytes) % ALIGNMENT == 0 fonts_bytes = fonts.build() assert len(fonts_bytes) % ALIGNMENT == 0 payload = Payload( translations_bytes=translations_bytes, fonts_bytes=fonts_bytes, ) data = payload.build() header = Header( language=json_header["language"], model=Model.from_trezor_model(model), firmware_version=version, data_len=len(data), data_hash=sha256(data).digest(), ) return TranslationsBlob( header_bytes=header.build(), proof_bytes=b"", payload=payload, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/_rlp.py0000664000175000017500000000310714636513242017453 0ustar00matejcikmatejcik# inspired by core/src/trezor/crypto/rlp.py import typing as t from collections.abc import Sequence if t.TYPE_CHECKING: RLPItem = t.Union[t.Sequence["RLPItem"], bytes, int] def _byte_size(x: int) -> int: if x < 0: raise ValueError("only unsigned ints are supported") return (x.bit_length() + 7) // 8 def _int_to_bytes(n: int) -> bytes: """Convert to a correctly sized bytes object.""" return n.to_bytes(_byte_size(n), "big") def _encode_with_length(value: bytes, header_byte: int) -> bytes: length = len(value) if length == 1 and value[0] <= 0x7F: return value elif length <= 55: return (header_byte + length).to_bytes(1, "big") + value else: encoded_length = _int_to_bytes(length) return ( (header_byte + 55 + len(encoded_length)).to_bytes(1, "big") + encoded_length + value ) def encode(value: "RLPItem") -> bytes: """Encode lists or objects to bytes.""" if isinstance(value, int): # ints are stored as byte strings value = _int_to_bytes(value) # sanity check: `str` is a Sequence so it would be incorrectly # picked up by the Sequence branch below assert not isinstance(value, str) # check for bytes type first, because bytes is a Sequence too if isinstance(value, bytes): header_byte = 0x80 elif isinstance(value, Sequence): header_byte = 0xC0 value = b"".join(encode(item) for item in value) else: raise TypeError("Unsupported type") return _encode_with_length(value, header_byte) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315331.0 trezor-0.13.9/src/trezorlib/authentication.py0000664000175000017500000003020314636525603021537 0ustar00matejcikmatejcikfrom __future__ import annotations import io import logging import secrets import typing as t from importlib import metadata from . import device from .client import TrezorClient try: cryptography_version = metadata.version("cryptography") vsplit = [int(x) for x in cryptography_version.split(".")] if vsplit[0] < 41: raise ImportError( "cryptography>=41 is required for this module, " f"found cryptography=={cryptography_version}" ) except ImportError as e: raise ImportError("cryptography>=41 is required for this module") from e from cryptography import exceptions, x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, utils LOG = logging.getLogger(__name__) def _pk_p256(pubkey_hex: str) -> ec.EllipticCurvePublicKey: return ec.EllipticCurvePublicKey.from_encoded_point( ec.SECP256R1(), bytes.fromhex(pubkey_hex) ) CHALLENGE_HEADER = b"AuthenticateDevice:" class RootCertificate(t.NamedTuple): name: str device: str devel: bool pubkey: ec.EllipticCurvePublicKey ROOT_PUBLIC_KEYS = [ RootCertificate( "Trezor Company", "Trezor Safe 3", False, _pk_p256( "04ca97480ac0d7b1e6efafe518cd433cec2bf8ab9822d76eafd34363b55d63e60" "380bff20acc75cde03cffcb50ab6f8ce70c878e37ebc58ff7cca0a83b16b15fa5" ), ), RootCertificate( "Trezor Company", "Trezor Safe 5", False, _pk_p256( "041854b27fb1d9f65abb66828e78c9dc0ca301e66081ab0c6a4d104f9df1cd0ad" "5a7c75f77a8c092f55cf825d2abaf734f934c9394d5e75f75a5a06a5ee9be93ae" ), ), RootCertificate( "TESTING ENVIRONMENT. DO NOT USE THIS DEVICE", "Trezor Safe 3", True, _pk_p256( "047f77368dea2d4d61e989f474a56723c3212dacf8a808d8795595ef38441427c" "4389bc454f02089d7f08b873005e4c28d432468997871c0bf286fd3861e21e96a" ), ), RootCertificate( "TESTING ENVIRONMENT. DO NOT USE THIS DEVICE", "Trezor Safe 5", True, _pk_p256( "04e48b69cd7962068d3cca3bcc6b1747ef496c1e28b5529e34ad7295215ea161d" "be8fb08ae0479568f9d2cb07630cb3e52f4af0692102da5873559e45e9fa72959" ), ), ] class DeviceNotAuthentic(Exception): pass class Certificate: def __init__(self, cert_bytes: bytes) -> None: self.cert_bytes = cert_bytes self.cert = x509.load_der_x509_certificate(cert_bytes) def __str__(self) -> str: return self.cert.subject.rfc4514_string() def public_key_bytes(self) -> bytes: return self.cert.public_key().public_bytes( serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint, ) def verify(self, signature: bytes, message: bytes) -> None: cert_pubkey = self.cert.public_key() assert isinstance(cert_pubkey, ec.EllipticCurvePublicKey) cert_pubkey.verify( self.fix_signature(signature), message, ec.ECDSA(hashes.SHA256()), ) def verify_by(self, pubkey: ec.EllipticCurvePublicKey) -> None: algo_params = self.cert.signature_algorithm_parameters assert isinstance(algo_params, ec.ECDSA) pubkey.verify( self.fix_signature(self.cert.signature), self.cert.tbs_certificate_bytes, algo_params, ) def _check_ca_extensions(self) -> bool: """Check that this certificate is a valid Trezor CA. KeyUsage must be present and allow certificate signing. BasicConstraints must be present, have the cA flag and a pathLenConstraint. Any unrecognized non-critical extension is allowed. Any unrecognized critical extension is disallowed. """ missing_extension_classes = {x509.KeyUsage, x509.BasicConstraints} passed = True for ext in self.cert.extensions: missing_extension_classes.discard(type(ext.value)) if isinstance(ext.value, x509.KeyUsage): if not ext.value.key_cert_sign: LOG.error( "Not a valid CA certificate: %s (keyCertSign not set)", self ) passed = False elif isinstance(ext.value, x509.BasicConstraints): if not ext.value.ca: LOG.error("Not a valid CA certificate: %s (cA not set)", self) passed = False if ext.value.path_length is None: LOG.error( "Not a valid CA certificate: %s (pathLenConstraint missing)", self, ) passed = False elif ext.critical: LOG.error( "Unknown critical extension %s in CA certificate: %s", self, type(ext.value).__name__, ) passed = False for ext in missing_extension_classes: LOG.error("Missing extension %s in CA certificate: %s", ext.__name__, self) passed = False return passed def is_issued_by(self, issuer: "Certificate", path_len: int) -> bool: """Check if this certificate was issued by an issuer. Returns True if: * our `issuer` is the same as issuer's `subject`, * the issuer is a valid CA, that is: - has the cA flag set - has a valid pathLenConstraint - pathLenConstraint does not exceed the current path length. * the issuer's public key signs this certificate. """ if issuer.cert.subject != self.cert.issuer: LOG.error("Certificate %s is not issued by %s.", self, issuer) return False if not issuer._check_ca_extensions(): return False basic_constraints = issuer.cert.extensions.get_extension_for_class( x509.BasicConstraints ).value assert basic_constraints.path_length is not None # check_ca_extensions if basic_constraints.path_length < path_len: LOG.error( "Issuer %s was not permitted to issue certificate %s", issuer, self ) return False try: pubkey = issuer.cert.public_key() assert isinstance(pubkey, ec.EllipticCurvePublicKey) self.verify_by(pubkey) return True except exceptions.InvalidSignature: LOG.error("Issuer %s did not sign certificate %s.", issuer, self) return False @staticmethod def _decode_signature_permissive(sig_bytes: bytes) -> tuple[int, int]: if len(sig_bytes) > 73: raise ValueError("Unsupported DER signature: too long.") reader = io.BytesIO(sig_bytes) tag = reader.read(1) if tag != b"\x30": raise ValueError("Invalid DER signature: not a sequence.") length = reader.read(1)[0] if length != len(sig_bytes) - 2: raise ValueError("Invalid DER signature: invalid length.") def read_int() -> int: tag = reader.read(1) if tag != b"\x02": raise ValueError("Invalid DER signature: not an integer.") length = reader.read(1)[0] if length > 33: raise ValueError("Invalid DER signature: integer too long.") return int.from_bytes(reader.read(length), "big") r = read_int() s = read_int() if reader.tell() != len(sig_bytes): raise ValueError("Invalid DER signature: trailing data.") return r, s @staticmethod def fix_signature(sig_bytes: bytes) -> bytes: r, s = Certificate._decode_signature_permissive(sig_bytes) reencoded = utils.encode_dss_signature(r, s) if reencoded != sig_bytes: LOG.info( "Re-encoding malformed signature: %s -> %s", sig_bytes.hex(), reencoded.hex(), ) return reencoded def verify_authentication_response( challenge: bytes, signature: bytes, cert_chain: t.Iterable[bytes], *, whitelist: t.Collection[bytes] | None, allow_development_devices: bool = False, root_pubkey: bytes | ec.EllipticCurvePublicKey | None = None, ) -> None: """Evaluate the response to an AuthenticateDevice call. Performs all steps and logs their results via the logging facility. (The log can be accessed via the `LOG` object in this module.) When done, raises DeviceNotAuthentic if the device is not authentic. The optional argument `root_pubkey` allows you to specify a root public key either as an `ec.EllipticCurvePublicKey` object or as a byte-string representing P-256 public key. """ if isinstance(root_pubkey, (bytes, bytearray, memoryview)): root_pubkey = ec.EllipticCurvePublicKey.from_encoded_point( ec.SECP256R1(), root_pubkey ) challenge_bytes = ( len(CHALLENGE_HEADER).to_bytes(1, "big") + CHALLENGE_HEADER + len(challenge).to_bytes(1, "big") + challenge ) cert_chain_iter = iter(cert_chain) failed = False try: cert = Certificate(next(cert_chain_iter)) except Exception: LOG.error("Failed to parse device certificate.") raise DeviceNotAuthentic try: cert.verify(signature, challenge_bytes) except exceptions.InvalidSignature: LOG.error("Challenge verification failed.") failed = True else: LOG.debug("Challenge verified successfully.") cert_label = "Device certificate" for i, issuer_bytes in enumerate(cert_chain_iter, 1): try: ca_cert = Certificate(issuer_bytes) except Exception: LOG.error(f"Failed to parse CA certificate #{i}.") failed = True continue if whitelist is None: LOG.warning("Skipping public key whitelist check.") else: if ca_cert.public_key_bytes() not in whitelist: LOG.error(f"CA certificate #{i} not in whitelist: %s", ca_cert) failed = True if not cert.is_issued_by(ca_cert, i - 1): failed = True else: LOG.debug(f"{cert_label} verified successfully: %s", cert) cert = ca_cert cert_label = f"CA #{i} certificate" if root_pubkey is not None: try: cert.verify_by(root_pubkey) except Exception: LOG.error(f"{cert_label} was not issued by the specified root.") failed = True else: LOG.info(f"{cert_label} was issued by the specified root.") else: for root in ROOT_PUBLIC_KEYS: try: cert.verify_by(root.pubkey) except Exception: continue else: LOG.debug(f"{cert_label} verified successfully: %s", cert) if root.devel: if not allow_development_devices: level = logging.ERROR failed = True else: level = logging.WARNING else: level = logging.DEBUG LOG.log( level, "Successfully verified a %s manufactured by %s.", root.device, root.name, ) break else: LOG.error(f"{cert_label} was issued by an unknown root.") failed = True if failed: raise DeviceNotAuthentic def authenticate_device( client: TrezorClient, challenge: bytes | None = None, *, whitelist: t.Collection[bytes] | None = None, allow_development_devices: bool = False, root_pubkey: bytes | ec.EllipticCurvePublicKey | None = None, ) -> None: if challenge is None: challenge = secrets.token_bytes(16) resp = device.authenticate(client, challenge) return verify_authentication_response( challenge, resp.signature, resp.certificates, whitelist=whitelist, allow_development_devices=allow_development_devices, root_pubkey=root_pubkey, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/binance.py0000664000175000017500000000535414636513242020124 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from typing import TYPE_CHECKING from . import messages from .protobuf import dict_to_proto from .tools import expect, session if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType from .tools import Address @expect(messages.BinanceAddress, field="address", ret_type=str) def get_address( client: "TrezorClient", address_n: "Address", show_display: bool = False, chunkify: bool = False, ) -> "MessageType": return client.call( messages.BinanceGetAddress( address_n=address_n, show_display=show_display, chunkify=chunkify ) ) @expect(messages.BinancePublicKey, field="public_key", ret_type=bytes) def get_public_key( client: "TrezorClient", address_n: "Address", show_display: bool = False ) -> "MessageType": return client.call( messages.BinanceGetPublicKey(address_n=address_n, show_display=show_display) ) @session def sign_tx( client: "TrezorClient", address_n: "Address", tx_json: dict, chunkify: bool = False ) -> messages.BinanceSignedTx: msg = tx_json["msgs"][0] tx_msg = tx_json.copy() tx_msg["msg_count"] = 1 tx_msg["address_n"] = address_n tx_msg["chunkify"] = chunkify envelope = dict_to_proto(messages.BinanceSignTx, tx_msg) response = client.call(envelope) if not isinstance(response, messages.BinanceTxRequest): raise RuntimeError( "Invalid response, expected BinanceTxRequest, received " + type(response).__name__ ) if "refid" in msg: msg = dict_to_proto(messages.BinanceCancelMsg, msg) elif "inputs" in msg: msg = dict_to_proto(messages.BinanceTransferMsg, msg) elif "ordertype" in msg: msg = dict_to_proto(messages.BinanceOrderMsg, msg) else: raise ValueError("can not determine msg type") response = client.call(msg) if not isinstance(response, messages.BinanceSignedTx): raise RuntimeError( "Invalid response, expected BinanceSignedTx, received " + type(response).__name__ ) return response ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/btc.py0000664000175000017500000003562614636513242017302 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import warnings from copy import copy from decimal import Decimal from typing import TYPE_CHECKING, Any, AnyStr, List, Optional, Sequence, Tuple # TypedDict is not available in typing for python < 3.8 from typing_extensions import Protocol, TypedDict from . import exceptions, messages from .tools import expect, prepare_message_bytes, session if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType from .tools import Address class ScriptSig(TypedDict): asm: str hex: str class ScriptPubKey(TypedDict): asm: str hex: str type: str reqSigs: int addresses: List[str] class Vin(TypedDict): txid: str vout: int sequence: int coinbase: str scriptSig: "ScriptSig" txinwitness: List[str] class Vout(TypedDict): value: float int: int scriptPubKey: "ScriptPubKey" class Transaction(TypedDict): txid: str hash: str version: int size: int vsize: int weight: int locktime: int vin: List[Vin] vout: List[Vout] class TxCacheType(Protocol): def __getitem__(self, __key: bytes) -> messages.TransactionType: ... def __contains__(self, __key: bytes) -> bool: ... def from_json(json_dict: "Transaction") -> messages.TransactionType: def make_input(vin: "Vin") -> messages.TxInputType: if "coinbase" in vin: return messages.TxInputType( prev_hash=b"\0" * 32, prev_index=0xFFFFFFFF, # signed int -1 script_sig=bytes.fromhex(vin["coinbase"]), sequence=vin["sequence"], ) else: return messages.TxInputType( prev_hash=bytes.fromhex(vin["txid"]), prev_index=vin["vout"], script_sig=bytes.fromhex(vin["scriptSig"]["hex"]), sequence=vin["sequence"], ) def make_bin_output(vout: "Vout") -> messages.TxOutputBinType: return messages.TxOutputBinType( amount=int(Decimal(vout["value"]) * (10**8)), script_pubkey=bytes.fromhex(vout["scriptPubKey"]["hex"]), ) return messages.TransactionType( version=json_dict["version"], lock_time=json_dict.get("locktime", 0), inputs=[make_input(vin) for vin in json_dict["vin"]], bin_outputs=[make_bin_output(vout) for vout in json_dict["vout"]], ) @expect(messages.PublicKey) def get_public_node( client: "TrezorClient", n: "Address", ecdsa_curve_name: Optional[str] = None, show_display: bool = False, coin_name: Optional[str] = None, script_type: messages.InputScriptType = messages.InputScriptType.SPENDADDRESS, ignore_xpub_magic: bool = False, unlock_path: Optional[List[int]] = None, unlock_path_mac: Optional[bytes] = None, ) -> "MessageType": if unlock_path: res = client.call( messages.UnlockPath(address_n=unlock_path, mac=unlock_path_mac) ) if not isinstance(res, messages.UnlockedPathRequest): raise exceptions.TrezorException("Unexpected message") 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, ignore_xpub_magic=ignore_xpub_magic, ) ) @expect(messages.Address, field="address", ret_type=str) def get_address(*args: Any, **kwargs: Any): return get_authenticated_address(*args, **kwargs) @expect(messages.Address) def get_authenticated_address( client: "TrezorClient", coin_name: str, n: "Address", show_display: bool = False, multisig: Optional[messages.MultisigRedeemScriptType] = None, script_type: messages.InputScriptType = messages.InputScriptType.SPENDADDRESS, ignore_xpub_magic: bool = False, unlock_path: Optional[List[int]] = None, unlock_path_mac: Optional[bytes] = None, chunkify: bool = False, ) -> "MessageType": if unlock_path: res = client.call( messages.UnlockPath(address_n=unlock_path, mac=unlock_path_mac) ) if not isinstance(res, messages.UnlockedPathRequest): raise exceptions.TrezorException("Unexpected message") return client.call( messages.GetAddress( address_n=n, coin_name=coin_name, show_display=show_display, multisig=multisig, script_type=script_type, ignore_xpub_magic=ignore_xpub_magic, chunkify=chunkify, ) ) @expect(messages.OwnershipId, field="ownership_id", ret_type=bytes) def get_ownership_id( client: "TrezorClient", coin_name: str, n: "Address", multisig: Optional[messages.MultisigRedeemScriptType] = None, script_type: messages.InputScriptType = messages.InputScriptType.SPENDADDRESS, ) -> "MessageType": return client.call( messages.GetOwnershipId( address_n=n, coin_name=coin_name, multisig=multisig, script_type=script_type, ) ) def get_ownership_proof( client: "TrezorClient", coin_name: str, n: "Address", multisig: Optional[messages.MultisigRedeemScriptType] = None, script_type: messages.InputScriptType = messages.InputScriptType.SPENDADDRESS, user_confirmation: bool = False, ownership_ids: Optional[List[bytes]] = None, commitment_data: Optional[bytes] = None, preauthorized: bool = False, ) -> Tuple[bytes, bytes]: if preauthorized: res = client.call(messages.DoPreauthorized()) if not isinstance(res, messages.PreauthorizedRequest): raise exceptions.TrezorException("Unexpected message") res = client.call( messages.GetOwnershipProof( address_n=n, coin_name=coin_name, script_type=script_type, multisig=multisig, user_confirmation=user_confirmation, ownership_ids=ownership_ids, commitment_data=commitment_data, ) ) if not isinstance(res, messages.OwnershipProof): raise exceptions.TrezorException("Unexpected message") return res.ownership_proof, res.signature @expect(messages.MessageSignature) def sign_message( client: "TrezorClient", coin_name: str, n: "Address", message: AnyStr, script_type: messages.InputScriptType = messages.InputScriptType.SPENDADDRESS, no_script_type: bool = False, chunkify: bool = False, ) -> "MessageType": return client.call( messages.SignMessage( coin_name=coin_name, address_n=n, message=prepare_message_bytes(message), script_type=script_type, no_script_type=no_script_type, chunkify=chunkify, ) ) def verify_message( client: "TrezorClient", coin_name: str, address: str, signature: bytes, message: AnyStr, chunkify: bool = False, ) -> bool: try: resp = client.call( messages.VerifyMessage( address=address, signature=signature, message=prepare_message_bytes(message), coin_name=coin_name, chunkify=chunkify, ) ) except exceptions.TrezorFailure: return False return isinstance(resp, messages.Success) @session def sign_tx( client: "TrezorClient", coin_name: str, inputs: Sequence[messages.TxInputType], outputs: Sequence[messages.TxOutputType], details: Optional[messages.SignTx] = None, prev_txes: Optional["TxCacheType"] = None, payment_reqs: Sequence[messages.TxAckPaymentRequest] = (), preauthorized: bool = False, unlock_path: Optional[List[int]] = None, unlock_path_mac: Optional[bytes] = None, **kwargs: Any, ) -> Tuple[Sequence[Optional[bytes]], bytes]: """Sign a Bitcoin-like transaction. Returns a list of signatures (one for each provided input) and the network-serialized transaction. In addition to the required arguments, it is possible to specify additional transaction properties (version, lock time, expiry...). Each additional argument must correspond to a field in the `SignTx` data type. Note that some fields (`inputs_count`, `outputs_count`, `coin_name`) will be inferred from the arguments and cannot be overriden by kwargs. """ if prev_txes is None: prev_txes = {} if details is not None: warnings.warn( "'details' argument is deprecated, use kwargs instead", DeprecationWarning, stacklevel=2, ) signtx = details signtx.coin_name = coin_name signtx.inputs_count = len(inputs) signtx.outputs_count = len(outputs) else: signtx = messages.SignTx( coin_name=coin_name, inputs_count=len(inputs), outputs_count=len(outputs), ) for name, value in kwargs.items(): if hasattr(signtx, name): setattr(signtx, name, value) if unlock_path: res = client.call( messages.UnlockPath(address_n=unlock_path, mac=unlock_path_mac) ) if not isinstance(res, messages.UnlockedPathRequest): raise exceptions.TrezorException("Unexpected message") elif preauthorized: res = client.call(messages.DoPreauthorized()) if not isinstance(res, messages.PreauthorizedRequest): raise exceptions.TrezorException("Unexpected message") res = client.call(signtx) # Prepare structure for signatures signatures: List[Optional[bytes]] = [None] * len(inputs) serialized_tx = b"" def copy_tx_meta(tx: messages.TransactionType) -> messages.TransactionType: tx_copy = copy(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 this_tx = messages.TransactionType( inputs=inputs, outputs=outputs, inputs_cnt=len(inputs), outputs_cnt=len(outputs), # pick either kw-provided or default value from the SignTx request version=signtx.version, ) 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(f"Signature for index {idx} already filled") signatures[idx] = sig if res.request_type == R.TXFINISHED: break assert res.details is not None, "device did not provide details" # Device asked for one more information, let's process it. if res.details.tx_hash is not None: if res.details.tx_hash not in prev_txes: raise ValueError( f"Previous transaction {res.details.tx_hash.hex()} not available" ) current_tx = prev_txes[res.details.tx_hash] else: current_tx = this_tx if res.request_type == R.TXPAYMENTREQ: assert res.details.request_index is not None msg = payment_reqs[res.details.request_index] res = client.call(msg) else: msg = messages.TransactionType() if res.request_type == R.TXMETA: msg = copy_tx_meta(current_tx) elif res.request_type in (R.TXINPUT, R.TXORIGINPUT): assert res.details.request_index is not None msg.inputs = [current_tx.inputs[res.details.request_index]] elif res.request_type == R.TXOUTPUT: assert res.details.request_index is not None 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]] elif res.request_type == R.TXORIGOUTPUT: assert res.details.request_index is not None msg.outputs = [current_tx.outputs[res.details.request_index]] elif res.request_type == R.TXEXTRADATA: assert res.details.extra_data_offset is not None assert res.details.extra_data_len is not None assert current_tx.extra_data is not None o, l = res.details.extra_data_offset, res.details.extra_data_len msg.extra_data = current_tx.extra_data[o : o + l] else: raise exceptions.TrezorException( f"Unknown request type - {res.request_type}." ) res = client.call(messages.TxAck(tx=msg)) if not isinstance(res, messages.TxRequest): raise exceptions.TrezorException("Unexpected message") for i, sig in zip(inputs, signatures): if i.script_type != messages.InputScriptType.EXTERNAL and sig is None: raise exceptions.TrezorException("Some signatures are missing!") return signatures, serialized_tx @expect(messages.Success, field="message", ret_type=str) def authorize_coinjoin( client: "TrezorClient", coordinator: str, max_rounds: int, max_coordinator_fee_rate: int, max_fee_per_kvbyte: int, n: "Address", coin_name: str, script_type: messages.InputScriptType = messages.InputScriptType.SPENDADDRESS, ) -> "MessageType": return client.call( messages.AuthorizeCoinJoin( coordinator=coordinator, max_rounds=max_rounds, max_coordinator_fee_rate=max_coordinator_fee_rate, max_fee_per_kvbyte=max_fee_per_kvbyte, address_n=n, coin_name=coin_name, script_type=script_type, ) ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cardano.py0000664000175000017500000010470414636513242020133 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from ipaddress import ip_address from itertools import chain from typing import ( TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Optional, Sequence, Tuple, Type, TypeVar, Union, ) from . import exceptions, messages, tools from .tools import expect if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType PROTOCOL_MAGICS = { "mainnet": 764824073, "testnet_preprod": 1, "testnet_preview": 2, "testnet_legacy": 1097911063, } NETWORK_IDS = {"mainnet": 1, "testnet": 0} MAX_CHUNK_SIZE = 1024 REQUIRED_FIELDS_TRANSACTION = ("inputs", "outputs") REQUIRED_FIELDS_INPUT = ("prev_hash", "prev_index") REQUIRED_FIELDS_CERTIFICATE = ("type",) REQUIRED_FIELDS_POOL_PARAMETERS = ( "pool_id", "vrf_key_hash", "pledge", "cost", "margin", "reward_account", "owners", ) REQUIRED_FIELDS_TOKEN_GROUP = ("policy_id", "tokens") REQUIRED_FIELDS_CVOTE_REGISTRATION = ( "staking_path", "nonce", ) REQUIRED_FIELDS_CVOTE_DELEGATION = ("vote_public_key", "weight") INCOMPLETE_OUTPUT_ERROR_MESSAGE = "The output is missing some fields" INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY = "The output's token_bundle entry is invalid" INVALID_MINT_TOKEN_BUNDLE_ENTRY = "The mint token_bundle entry is invalid" InputWithPath = Tuple[messages.CardanoTxInput, List[int]] CollateralInputWithPath = Tuple[messages.CardanoTxCollateralInput, List[int]] AssetGroupWithTokens = Tuple[messages.CardanoAssetGroup, List[messages.CardanoToken]] OutputWithData = Tuple[ messages.CardanoTxOutput, List[AssetGroupWithTokens], List[messages.CardanoTxInlineDatumChunk], List[messages.CardanoTxReferenceScriptChunk], ] OutputItem = Union[ messages.CardanoTxOutput, messages.CardanoAssetGroup, messages.CardanoToken, messages.CardanoTxInlineDatumChunk, messages.CardanoTxReferenceScriptChunk, ] CertificateItem = Union[ messages.CardanoTxCertificate, messages.CardanoPoolOwner, messages.CardanoPoolRelayParameters, ] MintItem = Union[ messages.CardanoTxMint, messages.CardanoAssetGroup, messages.CardanoToken ] PoolOwnersAndRelays = Tuple[ List[messages.CardanoPoolOwner], List[messages.CardanoPoolRelayParameters] ] CertificateWithPoolOwnersAndRelays = Tuple[ messages.CardanoTxCertificate, Optional[PoolOwnersAndRelays] ] Path = List[int] Witness = Tuple[Path, bytes] AuxiliaryDataSupplement = Dict[str, Union[int, bytes]] SignTxResponse = Dict[str, Union[bytes, List[Witness], AuxiliaryDataSupplement]] Chunk = TypeVar( "Chunk", bound=Union[ messages.CardanoTxInlineDatumChunk, messages.CardanoTxReferenceScriptChunk ], ) def parse_optional_bytes(value: Optional[str]) -> Optional[bytes]: return bytes.fromhex(value) if value is not None else None def parse_optional_int(value: Optional[str]) -> Optional[int]: return int(value) if value is not None else None def create_address_parameters( address_type: messages.CardanoAddressType, address_n: List[int], address_n_staking: Optional[List[int]] = None, staking_key_hash: Optional[bytes] = None, block_index: Optional[int] = None, tx_index: Optional[int] = None, certificate_index: Optional[int] = None, script_payment_hash: Optional[bytes] = None, script_staking_hash: Optional[bytes] = None, ) -> messages.CardanoAddressParametersType: certificate_pointer = None if address_type in ( messages.CardanoAddressType.POINTER, messages.CardanoAddressType.POINTER_SCRIPT, ): certificate_pointer = _create_certificate_pointer( block_index, tx_index, certificate_index ) return messages.CardanoAddressParametersType( address_type=address_type, address_n=address_n, address_n_staking=address_n_staking, staking_key_hash=staking_key_hash, certificate_pointer=certificate_pointer, script_payment_hash=script_payment_hash, script_staking_hash=script_staking_hash, ) def _create_certificate_pointer( block_index: Optional[int], tx_index: Optional[int], certificate_index: Optional[int], ) -> messages.CardanoBlockchainPointerType: if block_index is None or tx_index is None or certificate_index is None: raise ValueError("Invalid pointer parameters") return messages.CardanoBlockchainPointerType( block_index=block_index, tx_index=tx_index, certificate_index=certificate_index ) def parse_input(tx_input: dict) -> InputWithPath: if not all(k in tx_input for k in REQUIRED_FIELDS_INPUT): raise ValueError("The input is missing some fields") path = tools.parse_path(tx_input.get("path", "")) return ( messages.CardanoTxInput( prev_hash=bytes.fromhex(tx_input["prev_hash"]), prev_index=tx_input["prev_index"], ), path, ) def parse_output(output: dict) -> OutputWithData: contains_address = "address" in output contains_address_type = "addressType" in output if "amount" not in output: raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE) if not (contains_address or contains_address_type): raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE) address = output.get("address") address_parameters = None if contains_address_type: address_parameters = _parse_address_parameters( output, INCOMPLETE_OUTPUT_ERROR_MESSAGE ) token_bundle = [] if "token_bundle" in output: token_bundle = _parse_token_bundle(output["token_bundle"], is_mint=False) datum_hash = parse_optional_bytes(output.get("datum_hash")) serialization_format = messages.CardanoTxOutputSerializationFormat.ARRAY_LEGACY if "format" in output: serialization_format = output["format"] inline_datum_size, inline_datum_chunks = _parse_chunkable_data( parse_optional_bytes(output.get("inline_datum")), messages.CardanoTxInlineDatumChunk, ) reference_script_size, reference_script_chunks = _parse_chunkable_data( parse_optional_bytes(output.get("reference_script")), messages.CardanoTxReferenceScriptChunk, ) return ( messages.CardanoTxOutput( address=address, address_parameters=address_parameters, amount=int(output["amount"]), asset_groups_count=len(token_bundle), datum_hash=datum_hash, format=serialization_format, inline_datum_size=inline_datum_size, reference_script_size=reference_script_size, ), token_bundle, inline_datum_chunks, reference_script_chunks, ) def _parse_token_bundle( token_bundle: Iterable[dict], is_mint: bool ) -> List[AssetGroupWithTokens]: error_message: str if is_mint: error_message = INVALID_MINT_TOKEN_BUNDLE_ENTRY else: error_message = INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY result = [] for token_group in token_bundle: if not all(k in token_group for k in REQUIRED_FIELDS_TOKEN_GROUP): raise ValueError(error_message) tokens = _parse_tokens(token_group["tokens"], is_mint) result.append( ( messages.CardanoAssetGroup( policy_id=bytes.fromhex(token_group["policy_id"]), tokens_count=len(tokens), ), tokens, ) ) return result def _parse_tokens(tokens: Iterable[dict], is_mint: bool) -> List[messages.CardanoToken]: error_message: str if is_mint: error_message = INVALID_MINT_TOKEN_BUNDLE_ENTRY else: error_message = INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY result = [] for token in tokens: if "asset_name_bytes" not in token: raise ValueError(error_message) mint_amount = None amount = None if is_mint: if "mint_amount" not in token: raise ValueError(error_message) mint_amount = int(token["mint_amount"]) else: if "amount" not in token: raise ValueError(error_message) amount = int(token["amount"]) result.append( messages.CardanoToken( asset_name_bytes=bytes.fromhex(token["asset_name_bytes"]), amount=amount, mint_amount=mint_amount, ) ) return result def _parse_address_parameters( address_parameters: dict, error_message: str ) -> messages.CardanoAddressParametersType: if "addressType" not in address_parameters: raise ValueError(error_message) payment_path = tools.parse_path(address_parameters.get("path", "")) staking_path = tools.parse_path(address_parameters.get("stakingPath", "")) staking_key_hash_bytes = parse_optional_bytes( address_parameters.get("stakingKeyHash") ) script_payment_hash = parse_optional_bytes( address_parameters.get("scriptPaymentHash") ) script_staking_hash = parse_optional_bytes( address_parameters.get("scriptStakingHash") ) return create_address_parameters( messages.CardanoAddressType(address_parameters["addressType"]), payment_path, staking_path, staking_key_hash_bytes, address_parameters.get("blockIndex"), address_parameters.get("txIndex"), address_parameters.get("certificateIndex"), script_payment_hash, script_staking_hash, ) def _parse_chunkable_data( data: Optional[bytes], chunk_type: Type[Chunk] ) -> Tuple[int, List[Chunk]]: if data is None: return 0, [] data_size = len(data) data_chunks = [chunk_type(data=chunk) for chunk in _create_data_chunks(data)] return data_size, data_chunks def _create_data_chunks(data: bytes) -> Iterator[bytes]: processed_size = 0 while processed_size < len(data): yield data[processed_size : (processed_size + MAX_CHUNK_SIZE)] processed_size += MAX_CHUNK_SIZE def parse_native_script(native_script: dict) -> messages.CardanoNativeScript: if "type" not in native_script: raise ValueError("Script is missing some fields") type = native_script["type"] scripts = [ parse_native_script(sub_script) for sub_script in native_script.get("scripts", ()) ] key_hash = parse_optional_bytes(native_script.get("key_hash")) key_path = tools.parse_path(native_script.get("key_path", "")) required_signatures_count = parse_optional_int( native_script.get("required_signatures_count") ) invalid_before = parse_optional_int(native_script.get("invalid_before")) invalid_hereafter = parse_optional_int(native_script.get("invalid_hereafter")) return messages.CardanoNativeScript( type=type, scripts=scripts, key_hash=key_hash, key_path=key_path, required_signatures_count=required_signatures_count, invalid_before=invalid_before, invalid_hereafter=invalid_hereafter, ) def parse_certificate(certificate: dict) -> CertificateWithPoolOwnersAndRelays: CERTIFICATE_MISSING_FIELDS_ERROR = ValueError( "The certificate is missing some fields" ) if not all(k in certificate for k in REQUIRED_FIELDS_CERTIFICATE): raise CERTIFICATE_MISSING_FIELDS_ERROR certificate_type = certificate["type"] if certificate_type == messages.CardanoCertificateType.STAKE_DELEGATION: if "pool" not in certificate: raise CERTIFICATE_MISSING_FIELDS_ERROR path, script_hash, key_hash = _parse_credential( certificate, CERTIFICATE_MISSING_FIELDS_ERROR ) return ( messages.CardanoTxCertificate( type=certificate_type, path=path, pool=bytes.fromhex(certificate["pool"]), script_hash=script_hash, key_hash=key_hash, ), None, ) elif certificate_type in ( messages.CardanoCertificateType.STAKE_REGISTRATION, messages.CardanoCertificateType.STAKE_DEREGISTRATION, ): path, script_hash, key_hash = _parse_credential( certificate, CERTIFICATE_MISSING_FIELDS_ERROR ) return ( messages.CardanoTxCertificate( type=certificate_type, path=path, script_hash=script_hash, key_hash=key_hash, ), None, ) elif certificate_type in ( messages.CardanoCertificateType.STAKE_REGISTRATION_CONWAY, messages.CardanoCertificateType.STAKE_DEREGISTRATION_CONWAY, ): if "deposit" not in certificate: raise CERTIFICATE_MISSING_FIELDS_ERROR path, script_hash, key_hash = _parse_credential( certificate, CERTIFICATE_MISSING_FIELDS_ERROR ) return ( messages.CardanoTxCertificate( type=certificate_type, path=path, script_hash=script_hash, key_hash=key_hash, deposit=int(certificate["deposit"]), ), None, ) elif certificate_type == messages.CardanoCertificateType.STAKE_POOL_REGISTRATION: pool_parameters = certificate["pool_parameters"] if any( required_param not in pool_parameters for required_param in REQUIRED_FIELDS_POOL_PARAMETERS ): raise CERTIFICATE_MISSING_FIELDS_ERROR pool_metadata: Optional[messages.CardanoPoolMetadataType] if pool_parameters.get("metadata") is not None: pool_metadata = messages.CardanoPoolMetadataType( url=pool_parameters["metadata"]["url"], hash=bytes.fromhex(pool_parameters["metadata"]["hash"]), ) else: pool_metadata = None owners = [ _parse_pool_owner(pool_owner) for pool_owner in pool_parameters.get("owners", []) ] relays = [ _parse_pool_relay(pool_relay) for pool_relay in pool_parameters.get("relays", []) ] return ( messages.CardanoTxCertificate( type=certificate_type, pool_parameters=messages.CardanoPoolParametersType( pool_id=bytes.fromhex(pool_parameters["pool_id"]), vrf_key_hash=bytes.fromhex(pool_parameters["vrf_key_hash"]), pledge=int(pool_parameters["pledge"]), cost=int(pool_parameters["cost"]), margin_numerator=int(pool_parameters["margin"]["numerator"]), margin_denominator=int(pool_parameters["margin"]["denominator"]), reward_account=pool_parameters["reward_account"], metadata=pool_metadata, owners_count=len(owners), relays_count=len(relays), ), ), (owners, relays), ) if certificate_type == messages.CardanoCertificateType.VOTE_DELEGATION: if "drep" not in certificate: raise CERTIFICATE_MISSING_FIELDS_ERROR path, script_hash, key_hash = _parse_credential( certificate, CERTIFICATE_MISSING_FIELDS_ERROR ) return ( messages.CardanoTxCertificate( type=certificate_type, path=path, script_hash=script_hash, key_hash=key_hash, drep=messages.CardanoDRep( type=messages.CardanoDRepType(certificate["drep"]["type"]), key_hash=parse_optional_bytes(certificate["drep"].get("key_hash")), script_hash=parse_optional_bytes( certificate["drep"].get("script_hash") ), ), ), None, ) else: raise ValueError("Unknown certificate type") def _parse_credential( obj: dict, error: ValueError ) -> Tuple[List[int], Optional[bytes], Optional[bytes]]: if not any(k in obj for k in ("path", "script_hash", "key_hash")): raise error path = tools.parse_path(obj.get("path", "")) script_hash = parse_optional_bytes(obj.get("script_hash")) key_hash = parse_optional_bytes(obj.get("key_hash")) return path, script_hash, key_hash def _parse_pool_owner(pool_owner: dict) -> messages.CardanoPoolOwner: if "staking_key_path" in pool_owner: return messages.CardanoPoolOwner( staking_key_path=tools.parse_path(pool_owner["staking_key_path"]) ) return messages.CardanoPoolOwner( staking_key_hash=bytes.fromhex(pool_owner["staking_key_hash"]) ) def _parse_pool_relay(pool_relay: dict) -> messages.CardanoPoolRelayParameters: pool_relay_type = messages.CardanoPoolRelayType(pool_relay["type"]) if pool_relay_type == messages.CardanoPoolRelayType.SINGLE_HOST_IP: ipv4_address_packed = ( ip_address(pool_relay["ipv4_address"]).packed if "ipv4_address" in pool_relay else None ) ipv6_address_packed = ( ip_address(pool_relay["ipv6_address"]).packed if "ipv6_address" in pool_relay else None ) return messages.CardanoPoolRelayParameters( type=pool_relay_type, port=int(pool_relay["port"]), ipv4_address=ipv4_address_packed, ipv6_address=ipv6_address_packed, ) elif pool_relay_type == messages.CardanoPoolRelayType.SINGLE_HOST_NAME: return messages.CardanoPoolRelayParameters( type=pool_relay_type, port=int(pool_relay["port"]), host_name=pool_relay["host_name"], ) elif pool_relay_type == messages.CardanoPoolRelayType.MULTIPLE_HOST_NAME: return messages.CardanoPoolRelayParameters( type=pool_relay_type, host_name=pool_relay["host_name"], ) raise ValueError("Unknown pool relay type") def parse_withdrawal(withdrawal: dict) -> messages.CardanoTxWithdrawal: WITHDRAWAL_MISSING_FIELDS_ERROR = ValueError( "The withdrawal is missing some fields" ) if "amount" not in withdrawal: raise WITHDRAWAL_MISSING_FIELDS_ERROR path, script_hash, key_hash = _parse_credential( withdrawal, WITHDRAWAL_MISSING_FIELDS_ERROR ) return messages.CardanoTxWithdrawal( path=path, amount=int(withdrawal["amount"]), script_hash=script_hash, key_hash=key_hash, ) def parse_auxiliary_data( auxiliary_data: Optional[dict], ) -> Optional[messages.CardanoTxAuxiliaryData]: if auxiliary_data is None: return None AUXILIARY_DATA_MISSING_FIELDS_ERROR = ValueError( "Auxiliary data is missing some fields" ) # include all provided fields so we can test validation in FW hash = parse_optional_bytes(auxiliary_data.get("hash")) cvote_registration_parameters = None if "cvote_registration_parameters" in auxiliary_data: cvote_registration = auxiliary_data["cvote_registration_parameters"] if not all(k in cvote_registration for k in REQUIRED_FIELDS_CVOTE_REGISTRATION): raise AUXILIARY_DATA_MISSING_FIELDS_ERROR serialization_format = cvote_registration.get("format") delegations = [] for delegation in cvote_registration.get("delegations", []): if not all(k in delegation for k in REQUIRED_FIELDS_CVOTE_DELEGATION): raise AUXILIARY_DATA_MISSING_FIELDS_ERROR delegations.append( messages.CardanoCVoteRegistrationDelegation( vote_public_key=bytes.fromhex(delegation["vote_public_key"]), weight=int(delegation["weight"]), ) ) voting_purpose = None if serialization_format == messages.CardanoCVoteRegistrationFormat.CIP36: voting_purpose = cvote_registration.get("voting_purpose") cvote_registration_parameters = messages.CardanoCVoteRegistrationParametersType( vote_public_key=parse_optional_bytes( cvote_registration.get("vote_public_key") ), staking_path=tools.parse_path(cvote_registration["staking_path"]), nonce=cvote_registration["nonce"], payment_address=cvote_registration.get("payment_address"), payment_address_parameters=( _parse_address_parameters( cvote_registration["payment_address_parameters"], str(AUXILIARY_DATA_MISSING_FIELDS_ERROR), ) if "payment_address_parameters" in cvote_registration else None ), format=serialization_format, delegations=delegations, voting_purpose=voting_purpose, ) if hash is None and cvote_registration_parameters is None: raise AUXILIARY_DATA_MISSING_FIELDS_ERROR return messages.CardanoTxAuxiliaryData( hash=hash, cvote_registration_parameters=cvote_registration_parameters, ) def parse_mint(mint: Iterable[dict]) -> List[AssetGroupWithTokens]: return _parse_token_bundle(mint, is_mint=True) def parse_script_data_hash(script_data_hash: Optional[str]) -> Optional[bytes]: return parse_optional_bytes(script_data_hash) def parse_collateral_input(collateral_input: dict) -> CollateralInputWithPath: if not all(k in collateral_input for k in REQUIRED_FIELDS_INPUT): raise ValueError("The collateral input is missing some fields") path = tools.parse_path(collateral_input.get("path", "")) return ( messages.CardanoTxCollateralInput( prev_hash=bytes.fromhex(collateral_input["prev_hash"]), prev_index=collateral_input["prev_index"], ), path, ) def parse_required_signer(required_signer: dict) -> messages.CardanoTxRequiredSigner: key_hash = parse_optional_bytes(required_signer.get("key_hash")) key_path = tools.parse_path(required_signer.get("key_path", "")) return messages.CardanoTxRequiredSigner( key_hash=key_hash, key_path=key_path, ) def parse_reference_input(reference_input: dict) -> messages.CardanoTxReferenceInput: if not all(k in reference_input for k in REQUIRED_FIELDS_INPUT): raise ValueError("The reference input is missing some fields") return messages.CardanoTxReferenceInput( prev_hash=bytes.fromhex(reference_input["prev_hash"]), prev_index=reference_input["prev_index"], ) def parse_additional_witness_request( additional_witness_request: dict, ) -> Path: if "path" not in additional_witness_request: raise ValueError("Invalid additional witness request") return tools.parse_path(additional_witness_request["path"]) def _get_witness_requests( inputs: Sequence[InputWithPath], certificates: Sequence[CertificateWithPoolOwnersAndRelays], withdrawals: Sequence[messages.CardanoTxWithdrawal], collateral_inputs: Sequence[CollateralInputWithPath], required_signers: Sequence[messages.CardanoTxRequiredSigner], additional_witness_requests: Sequence[Path], signing_mode: messages.CardanoTxSigningMode, ) -> List[messages.CardanoTxWitnessRequest]: paths = set() # don't gather paths from tx elements in MULTISIG_TRANSACTION signing mode if signing_mode != messages.CardanoTxSigningMode.MULTISIG_TRANSACTION: for _, path in inputs: if path: paths.add(tuple(path)) for certificate, pool_owners_and_relays in certificates: if ( certificate.type in ( messages.CardanoCertificateType.STAKE_DEREGISTRATION, messages.CardanoCertificateType.STAKE_DELEGATION, messages.CardanoCertificateType.STAKE_REGISTRATION_CONWAY, messages.CardanoCertificateType.STAKE_DEREGISTRATION_CONWAY, messages.CardanoCertificateType.VOTE_DELEGATION, ) and certificate.path ): paths.add(tuple(certificate.path)) elif ( certificate.type == messages.CardanoCertificateType.STAKE_POOL_REGISTRATION and pool_owners_and_relays is not None ): owners, _ = pool_owners_and_relays for pool_owner in owners: if pool_owner.staking_key_path: paths.add(tuple(pool_owner.staking_key_path)) for withdrawal in withdrawals: if withdrawal.path: paths.add(tuple(withdrawal.path)) # gather Plutus-related paths if signing_mode == messages.CardanoTxSigningMode.PLUTUS_TRANSACTION: for _, path in collateral_inputs: if path: paths.add(tuple(path)) # add required_signers and additional_witness_requests in all cases for required_signer in required_signers: if required_signer.key_path: paths.add(tuple(required_signer.key_path)) for additional_witness_request in additional_witness_requests: paths.add(tuple(additional_witness_request)) sorted_paths = sorted([list(path) for path in paths]) return [messages.CardanoTxWitnessRequest(path=path) for path in sorted_paths] def _get_inputs_items(inputs: List[InputWithPath]) -> Iterator[messages.CardanoTxInput]: for input, _ in inputs: yield input def _get_outputs_items(outputs: List[OutputWithData]) -> Iterator[OutputItem]: for output_with_data in outputs: yield from _get_output_items(output_with_data) def _get_output_items(output_with_data: OutputWithData) -> Iterator[OutputItem]: ( output, asset_groups, inline_datum_chunks, reference_script_chunks, ) = output_with_data yield output for asset_group, tokens in asset_groups: yield asset_group yield from tokens yield from inline_datum_chunks yield from reference_script_chunks def _get_certificates_items( certificates: Sequence[CertificateWithPoolOwnersAndRelays], ) -> Iterator[CertificateItem]: for certificate, pool_owners_and_relays in certificates: yield certificate if pool_owners_and_relays is not None: owners, relays = pool_owners_and_relays yield from owners yield from relays def _get_mint_items(mint: Sequence[AssetGroupWithTokens]) -> Iterator[MintItem]: if not mint: return yield messages.CardanoTxMint(asset_groups_count=len(mint)) for asset_group, tokens in mint: yield asset_group yield from tokens def _get_collateral_inputs_items( collateral_inputs: Sequence[CollateralInputWithPath], ) -> Iterator[messages.CardanoTxCollateralInput]: for collateral_input, _ in collateral_inputs: yield collateral_input # ====== Client functions ====== # @expect(messages.CardanoAddress, field="address", ret_type=str) def get_address( client: "TrezorClient", address_parameters: messages.CardanoAddressParametersType, protocol_magic: int = PROTOCOL_MAGICS["mainnet"], network_id: int = NETWORK_IDS["mainnet"], show_display: bool = False, derivation_type: messages.CardanoDerivationType = messages.CardanoDerivationType.ICARUS, chunkify: bool = False, ) -> "MessageType": return client.call( messages.CardanoGetAddress( address_parameters=address_parameters, protocol_magic=protocol_magic, network_id=network_id, show_display=show_display, derivation_type=derivation_type, chunkify=chunkify, ) ) @expect(messages.CardanoPublicKey) def get_public_key( client: "TrezorClient", address_n: List[int], derivation_type: messages.CardanoDerivationType = messages.CardanoDerivationType.ICARUS, show_display: bool = False, ) -> "MessageType": return client.call( messages.CardanoGetPublicKey( address_n=address_n, derivation_type=derivation_type, show_display=show_display, ) ) @expect(messages.CardanoNativeScriptHash) def get_native_script_hash( client: "TrezorClient", native_script: messages.CardanoNativeScript, display_format: messages.CardanoNativeScriptHashDisplayFormat = messages.CardanoNativeScriptHashDisplayFormat.HIDE, derivation_type: messages.CardanoDerivationType = messages.CardanoDerivationType.ICARUS, ) -> "MessageType": return client.call( messages.CardanoGetNativeScriptHash( script=native_script, display_format=display_format, derivation_type=derivation_type, ) ) def sign_tx( client: "TrezorClient", signing_mode: messages.CardanoTxSigningMode, inputs: List[InputWithPath], outputs: List[OutputWithData], fee: int, ttl: Optional[int], validity_interval_start: Optional[int], certificates: Sequence[CertificateWithPoolOwnersAndRelays] = (), withdrawals: Sequence[messages.CardanoTxWithdrawal] = (), protocol_magic: int = PROTOCOL_MAGICS["mainnet"], network_id: int = NETWORK_IDS["mainnet"], auxiliary_data: Optional[messages.CardanoTxAuxiliaryData] = None, mint: Sequence[AssetGroupWithTokens] = (), script_data_hash: Optional[bytes] = None, collateral_inputs: Sequence[CollateralInputWithPath] = (), required_signers: Sequence[messages.CardanoTxRequiredSigner] = (), collateral_return: Optional[OutputWithData] = None, total_collateral: Optional[int] = None, reference_inputs: Sequence[messages.CardanoTxReferenceInput] = (), additional_witness_requests: Sequence[Path] = (), derivation_type: messages.CardanoDerivationType = messages.CardanoDerivationType.ICARUS, include_network_id: bool = False, chunkify: bool = False, tag_cbor_sets: bool = False, ) -> Dict[str, Any]: UNEXPECTED_RESPONSE_ERROR = exceptions.TrezorException("Unexpected response") witness_requests = _get_witness_requests( inputs, certificates, withdrawals, collateral_inputs, required_signers, additional_witness_requests, signing_mode, ) response = client.call( messages.CardanoSignTxInit( signing_mode=signing_mode, inputs_count=len(inputs), outputs_count=len(outputs), fee=fee, ttl=ttl, validity_interval_start=validity_interval_start, certificates_count=len(certificates), withdrawals_count=len(withdrawals), protocol_magic=protocol_magic, network_id=network_id, has_auxiliary_data=auxiliary_data is not None, minting_asset_groups_count=len(mint), script_data_hash=script_data_hash, collateral_inputs_count=len(collateral_inputs), required_signers_count=len(required_signers), has_collateral_return=collateral_return is not None, total_collateral=total_collateral, reference_inputs_count=len(reference_inputs), witness_requests_count=len(witness_requests), derivation_type=derivation_type, include_network_id=include_network_id, chunkify=chunkify, tag_cbor_sets=tag_cbor_sets, ) ) if not isinstance(response, messages.CardanoTxItemAck): raise UNEXPECTED_RESPONSE_ERROR for tx_item in chain( _get_inputs_items(inputs), _get_outputs_items(outputs), _get_certificates_items(certificates), withdrawals, ): response = client.call(tx_item) if not isinstance(response, messages.CardanoTxItemAck): raise UNEXPECTED_RESPONSE_ERROR sign_tx_response: Dict[str, Any] = {} if auxiliary_data is not None: auxiliary_data_supplement = client.call(auxiliary_data) if not isinstance( auxiliary_data_supplement, messages.CardanoTxAuxiliaryDataSupplement ): raise UNEXPECTED_RESPONSE_ERROR if ( auxiliary_data_supplement.type != messages.CardanoTxAuxiliaryDataSupplementType.NONE ): sign_tx_response["auxiliary_data_supplement"] = ( auxiliary_data_supplement.__dict__ ) response = client.call(messages.CardanoTxHostAck()) if not isinstance(response, messages.CardanoTxItemAck): raise UNEXPECTED_RESPONSE_ERROR for tx_item in chain( _get_mint_items(mint), _get_collateral_inputs_items(collateral_inputs), required_signers, ): response = client.call(tx_item) if not isinstance(response, messages.CardanoTxItemAck): raise UNEXPECTED_RESPONSE_ERROR if collateral_return is not None: for tx_item in _get_output_items(collateral_return): response = client.call(tx_item) if not isinstance(response, messages.CardanoTxItemAck): raise UNEXPECTED_RESPONSE_ERROR for reference_input in reference_inputs: response = client.call(reference_input) if not isinstance(response, messages.CardanoTxItemAck): raise UNEXPECTED_RESPONSE_ERROR sign_tx_response["witnesses"] = [] for witness_request in witness_requests: response = client.call(witness_request) if not isinstance(response, messages.CardanoTxWitnessResponse): raise UNEXPECTED_RESPONSE_ERROR sign_tx_response["witnesses"].append( { "type": response.type, "pub_key": response.pub_key, "signature": response.signature, "chain_code": response.chain_code, } ) response = client.call(messages.CardanoTxHostAck()) if not isinstance(response, messages.CardanoTxBodyHash): raise UNEXPECTED_RESPONSE_ERROR sign_tx_response["tx_hash"] = response.tx_hash response = client.call(messages.CardanoTxHostAck()) if not isinstance(response, messages.CardanoSignTxFinished): raise UNEXPECTED_RESPONSE_ERROR return sign_tx_response ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1719315826.1321068 trezor-0.13.9/src/trezorlib/cli/0000775000175000017500000000000014636526562016724 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/__init__.py0000664000175000017500000001614214636513242021030 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import functools import sys from contextlib import contextmanager from typing import TYPE_CHECKING, Any, Callable, Dict, Optional import click from .. import exceptions, transport from ..client import TrezorClient from ..ui import ClickUI, ScriptUI if TYPE_CHECKING: # Needed to enforce a return value from decorators # More details: https://www.python.org/dev/peps/pep-0612/ from typing import TypeVar from typing_extensions import Concatenate, ParamSpec from ..transport import Transport from ..ui import TrezorClientUI P = ParamSpec("P") R = TypeVar("R") class ChoiceType(click.Choice): def __init__(self, typemap: Dict[str, Any], case_sensitive: bool = True) -> None: super().__init__(list(typemap.keys())) self.case_sensitive = case_sensitive if case_sensitive: self.typemap = typemap else: self.typemap = {k.lower(): v for k, v in typemap.items()} def convert(self, value: Any, param: Any, ctx: click.Context) -> Any: if value in self.typemap.values(): return value value = super().convert(value, param, ctx) if isinstance(value, str) and not self.case_sensitive: value = value.lower() return self.typemap[value] class TrezorConnection: def __init__( self, path: str, session_id: Optional[bytes], passphrase_on_host: bool, script: bool, ) -> None: self.path = path self.session_id = session_id self.passphrase_on_host = passphrase_on_host self.script = script def get_transport(self) -> "Transport": try: # look for transport without prefix search return transport.get_transport(self.path, prefix_search=False) except Exception: # most likely not found. try again below. pass # look for transport with prefix search # if this fails, we want the exception to bubble up to the caller return transport.get_transport(self.path, prefix_search=True) def get_ui(self) -> "TrezorClientUI": if self.script: # It is alright to return just the class object instead of instance, # as the ScriptUI class object itself is the implementation of TrezorClientUI # (ScriptUI is just a set of staticmethods) return ScriptUI else: return ClickUI(passphrase_on_host=self.passphrase_on_host) def get_client(self) -> TrezorClient: transport = self.get_transport() ui = self.get_ui() return TrezorClient(transport, ui=ui, session_id=self.session_id) @contextmanager def client_context(self): """Get a client instance as a context manager. Handle errors in a manner appropriate for end-users. Usage: >>> with obj.client_context() as client: >>> do_your_actions_here() """ try: client = self.get_client() except transport.DeviceIsBusy: click.echo("Device is in use by another process.") sys.exit(1) except Exception: click.echo("Failed to find a Trezor device.") if self.path is not None: click.echo(f"Using path: {self.path}") sys.exit(1) try: yield client except exceptions.Cancelled: # handle cancel action click.echo("Action was cancelled.") sys.exit(1) except exceptions.TrezorException as e: # handle any Trezor-sent exceptions as user-readable raise click.ClickException(str(e)) from e # other exceptions may cause a traceback def with_client(func: "Callable[Concatenate[TrezorClient, P], R]") -> "Callable[P, R]": """Wrap a Click command in `with obj.client_context() as client`. Sessions are handled transparently. The user is warned when session did not resume cleanly. The session is closed after the command completes - unless the session was resumed, in which case it should remain open. """ @click.pass_obj @functools.wraps(func) def trezorctl_command_with_client( obj: TrezorConnection, *args: "P.args", **kwargs: "P.kwargs" ) -> "R": with obj.client_context() as client: session_was_resumed = obj.session_id == client.session_id if not session_was_resumed and obj.session_id is not None: # tried to resume but failed click.echo("Warning: failed to resume session.", err=True) try: return func(client, *args, **kwargs) finally: if not session_was_resumed: try: client.end_session() except Exception: pass # the return type of @click.pass_obj is improperly specified and pyright doesn't # understand that it converts f(obj, *args, **kwargs) to f(*args, **kwargs) return trezorctl_command_with_client # type: ignore [is incompatible with return type] class AliasedGroup(click.Group): """Command group that handles aliases and Click 6.x compatibility. Click 7.0 silently switched all underscore_commands to dash-commands. This implementation of `click.Group` responds to underscore_commands by invoking the respective dash-command. Supply an `aliases` dict at construction time to provide an alternative list of command names: >>> @click.command(cls=AliasedGroup, aliases={"do_bar", do_foo}) >>> def cli(): >>> ... If these commands are not known at the construction time, they can be set later: >>> @click.command(cls=AliasedGroup) >>> def cli(): >>> ... >>> >>> @cli.command() >>> def do_foo(): >>> ... >>> >>> cli.aliases={"do_bar", do_foo} """ def __init__( self, aliases: Optional[Dict[str, click.Command]] = None, *args: Any, **kwargs: Any, ) -> None: super().__init__(*args, **kwargs) self.aliases = aliases or {} def get_command(self, ctx: click.Context, cmd_name: str) -> Optional[click.Command]: cmd_name = cmd_name.replace("_", "-") # try to look up the real name cmd = super().get_command(ctx, cmd_name) if cmd: return cmd # look for a backwards compatibility alias if cmd_name in self.aliases: return self.aliases[cmd_name] return None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/binance.py0000664000175000017500000000466514636513242020677 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import json from typing import TYPE_CHECKING, TextIO import click from .. import binance, tools from . import with_client if TYPE_CHECKING: from .. import messages from ..client import TrezorClient PATH_HELP = "BIP-32 path to key, e.g. m/44h/714h/0h/0/0" @click.group(name="binance") def cli() -> None: """Binance Chain commands.""" @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @click.option("-C", "--chunkify", is_flag=True) @with_client def get_address( client: "TrezorClient", address: str, show_display: bool, chunkify: bool ) -> str: """Get Binance address for specified path.""" address_n = tools.parse_path(address) return binance.get_address(client, address_n, show_display, chunkify) @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @with_client def get_public_key(client: "TrezorClient", address: str, show_display: bool) -> str: """Get Binance public key.""" address_n = tools.parse_path(address) return binance.get_public_key(client, address_n, show_display).hex() @cli.command() @click.argument("file", type=click.File("r")) @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-f", "--file", "_ignore", is_flag=True, hidden=True, expose_value=False) @click.option("-C", "--chunkify", is_flag=True) @with_client def sign_tx( client: "TrezorClient", address: str, file: TextIO, chunkify: bool ) -> "messages.BinanceSignedTx": """Sign Binance transaction. Transaction must be provided as a JSON file. """ address_n = tools.parse_path(address) return binance.sign_tx(client, address_n, json.load(file), chunkify=chunkify) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/btc.py0000664000175000017500000003542114636513242020042 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import base64 import json from typing import TYPE_CHECKING, Dict, List, Optional, TextIO, Tuple import click import construct as c from .. import btc, messages, protobuf, tools from . import ChoiceType, with_client if TYPE_CHECKING: from ..client import TrezorClient PURPOSE_BIP44 = 44 PURPOSE_BIP48 = 48 PURPOSE_BIP49 = 49 PURPOSE_BIP84 = 84 PURPOSE_BIP86 = 86 PURPOSE_SLIP25 = 10025 INPUT_SCRIPTS = { "address": messages.InputScriptType.SPENDADDRESS, "segwit": messages.InputScriptType.SPENDWITNESS, "p2shsegwit": messages.InputScriptType.SPENDP2SHWITNESS, "taproot": messages.InputScriptType.SPENDTAPROOT, "pkh": messages.InputScriptType.SPENDADDRESS, "wpkh": messages.InputScriptType.SPENDWITNESS, "sh-wpkh": messages.InputScriptType.SPENDP2SHWITNESS, "tr": messages.InputScriptType.SPENDTAPROOT, } OUTPUT_SCRIPTS = { "address": messages.OutputScriptType.PAYTOADDRESS, "segwit": messages.OutputScriptType.PAYTOWITNESS, "p2shsegwit": messages.OutputScriptType.PAYTOP2SHWITNESS, "taproot": messages.OutputScriptType.PAYTOTAPROOT, "pkh": messages.OutputScriptType.PAYTOADDRESS, "wpkh": messages.OutputScriptType.PAYTOWITNESS, "sh-wpkh": messages.OutputScriptType.PAYTOP2SHWITNESS, "tr": messages.OutputScriptType.PAYTOTAPROOT, } BIP_PURPOSE_TO_DEFAULT_SCRIPT_TYPE = { PURPOSE_BIP44: messages.InputScriptType.SPENDADDRESS, PURPOSE_BIP49: messages.InputScriptType.SPENDP2SHWITNESS, PURPOSE_BIP84: messages.InputScriptType.SPENDWITNESS, PURPOSE_BIP86: messages.InputScriptType.SPENDTAPROOT, PURPOSE_SLIP25: messages.InputScriptType.SPENDTAPROOT, } SCRIPT_TYPE_TO_BIP_PURPOSES = { messages.InputScriptType.SPENDADDRESS: (PURPOSE_BIP44,), messages.InputScriptType.SPENDP2SHWITNESS: (PURPOSE_BIP49,), messages.InputScriptType.SPENDWITNESS: (PURPOSE_BIP84,), messages.InputScriptType.SPENDTAPROOT: (PURPOSE_BIP86, PURPOSE_SLIP25), } ACCOUNT_TYPE_TO_BIP_PURPOSE = { "bip44": PURPOSE_BIP44, "bip49": PURPOSE_BIP49, "bip84": PURPOSE_BIP84, "bip86": PURPOSE_BIP86, "slip25": PURPOSE_SLIP25, } BIP48_SCRIPT_TYPES = { tools.H_(0): messages.InputScriptType.SPENDMULTISIG, tools.H_(1): messages.InputScriptType.SPENDP2SHWITNESS, tools.H_(2): messages.InputScriptType.SPENDWITNESS, } DEFAULT_COIN = "Bitcoin" XpubStruct = c.Struct( "version" / c.Int32ub, "depth" / c.Int8ub, "fingerprint" / c.Int32ub, "child_num" / c.Int32ub, "chain_code" / c.Bytes(32), "key" / c.Bytes(33), c.Terminated, ) def xpub_deserialize(xpubstr: str) -> Tuple[str, messages.HDNodeType]: xpub_bytes = tools.b58check_decode(xpubstr) data = XpubStruct.parse(xpub_bytes) if data.key[0] == 0: private_key = data.key[1:] public_key = None else: public_key = data.key private_key = None node = messages.HDNodeType( depth=data.depth, fingerprint=data.fingerprint, child_num=data.child_num, chain_code=data.chain_code, public_key=public_key, # type: ignore [Argument of type "Unknown | None" cannot be assigned to parameter "public_key" of type "bytes"] private_key=private_key, ) return data.version, node def guess_script_type_from_path(address_n: List[int]) -> messages.InputScriptType: if len(address_n) < 1 or not tools.is_hardened(address_n[0]): return messages.InputScriptType.SPENDADDRESS purpose = tools.unharden(address_n[0]) if purpose in BIP_PURPOSE_TO_DEFAULT_SCRIPT_TYPE: return BIP_PURPOSE_TO_DEFAULT_SCRIPT_TYPE[purpose] if purpose == PURPOSE_BIP48 and len(address_n) >= 4: script_type_field = address_n[3] if script_type_field in BIP48_SCRIPT_TYPES: return BIP48_SCRIPT_TYPES[script_type_field] return messages.InputScriptType.SPENDADDRESS def get_unlock_path(address_n: List[int]) -> Optional[List[int]]: if address_n and address_n[0] == tools.H_(10025): return address_n[:1] return None @click.group(name="btc") def cli() -> None: """Bitcoin and Bitcoin-like coins commands.""" # # Address functions # @cli.command() @click.option("-c", "--coin", default=DEFAULT_COIN) @click.option("-n", "--address", required=True, help="BIP-32 path") @click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS)) @click.option("-d", "--show-display", is_flag=True) @click.option("-x", "--multisig-xpub", multiple=True, help="XPUBs of multisig owners") @click.option("-m", "--multisig-threshold", type=int, help="Number of signatures") @click.option( "-N", "--multisig-suffix-length", help="BIP-32 suffix length for multisig", type=int, default=2, ) @click.option("-C", "--chunkify", is_flag=True) @with_client def get_address( client: "TrezorClient", coin: str, address: str, script_type: Optional[messages.InputScriptType], show_display: bool, multisig_xpub: List[str], multisig_threshold: Optional[int], multisig_suffix_length: int, chunkify: bool, ) -> str: """Get address for specified path. To obtain a multisig address, provide XPUBs of all signers (including your own) in the intended order. All XPUBs should be on the same level. By default, it is assumed that the XPUBs are on the account level, and the last two components of --address should be derived from all of them. For BIP-45 multisig: \b $ trezorctl btc get-public-node -n m/45h/0 xpub0101 $ trezorctl btc get-address -n m/45h/0/0/7 -m 3 -x xpub0101 -x xpub0202 -x xpub0303 This assumes that the other signers also created xpubs at address "m/45h/i". For all the signers, the final keys will be derived with the "/0/7" suffix. You can specify a different suffix length by using the -N option. For example, to use final xpubs, specify '-N 0'. """ address_n = tools.parse_path(address) if script_type is None: script_type = guess_script_type_from_path(address_n) multisig: Optional[messages.MultisigRedeemScriptType] if multisig_xpub: if multisig_threshold is None: raise click.ClickException("Please specify signature threshold") multisig_suffix = address_n[-multisig_suffix_length:] nodes = [xpub_deserialize(x)[1] for x in multisig_xpub] multisig = messages.MultisigRedeemScriptType( nodes=nodes, address_n=multisig_suffix, m=multisig_threshold ) if script_type == messages.InputScriptType.SPENDADDRESS: script_type = messages.InputScriptType.SPENDMULTISIG else: multisig = None return btc.get_address( client, coin, address_n, show_display, script_type=script_type, multisig=multisig, unlock_path=get_unlock_path(address_n), chunkify=chunkify, ) @cli.command() @click.option("-c", "--coin", default=DEFAULT_COIN) @click.option("-n", "--address", required=True, help="BIP-32 path, e.g. m/44h/0h/0h") @click.option("-e", "--curve") @click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS)) @click.option("-d", "--show-display", is_flag=True) @with_client def get_public_node( client: "TrezorClient", coin: str, address: str, curve: Optional[str], script_type: Optional[messages.InputScriptType], show_display: bool, ) -> dict: """Get public node of given path.""" address_n = tools.parse_path(address) if script_type is None: script_type = guess_script_type_from_path(address_n) result = btc.get_public_node( client, address_n, ecdsa_curve_name=curve, show_display=show_display, coin_name=coin, script_type=script_type, unlock_path=get_unlock_path(address_n), ) return { "node": { "depth": result.node.depth, "fingerprint": "%08x" % result.node.fingerprint, "child_num": result.node.child_num, "chain_code": result.node.chain_code.hex(), "public_key": result.node.public_key.hex(), }, "xpub": result.xpub, } def _append_descriptor_checksum(desc: str) -> str: checksum = tools.descriptor_checksum(desc) return f"{desc}#{checksum}" def _get_descriptor( client: "TrezorClient", coin: Optional[str], account: int, purpose: Optional[int], script_type: Optional[messages.InputScriptType], show_display: bool, ) -> str: if purpose is None: if script_type is None: script_type = messages.InputScriptType.SPENDADDRESS purpose = SCRIPT_TYPE_TO_BIP_PURPOSES[script_type][0] elif script_type is None: script_type = BIP_PURPOSE_TO_DEFAULT_SCRIPT_TYPE[purpose] else: if purpose not in SCRIPT_TYPE_TO_BIP_PURPOSES[script_type]: raise ValueError("Invalid script type for account type") coin = coin or DEFAULT_COIN if coin == "Bitcoin": coin_type = 0 elif coin == "Testnet" or coin == "Regtest": coin_type = 1 else: raise ValueError("Unsupported coin") path = f"m/{purpose}h/{coin_type}h/{account}h" if purpose == PURPOSE_SLIP25: if script_type == messages.InputScriptType.SPENDTAPROOT: path += "/1h" else: raise ValueError("Unsupported SLIP25 script type") n = tools.parse_path(path) pub = btc.get_public_node( client, n, show_display=show_display, coin_name=coin, script_type=script_type, ignore_xpub_magic=True, unlock_path=get_unlock_path(n), ) # Starting with core 2.6.5 the descriptor is included in the response. if pub.descriptor is not None: return pub.descriptor if script_type == messages.InputScriptType.SPENDADDRESS: fmt = "pkh({})" elif script_type == messages.InputScriptType.SPENDP2SHWITNESS: fmt = "sh(wpkh({}))" elif script_type == messages.InputScriptType.SPENDWITNESS: fmt = "wpkh({})" elif script_type == messages.InputScriptType.SPENDTAPROOT: fmt = "tr({})" else: raise ValueError("Unsupported script type") fingerprint = pub.root_fingerprint if pub.root_fingerprint is not None else 0 descriptor = f"[{fingerprint:08x}{path[1:]}]{pub.xpub}/<0;1>/*" return _append_descriptor_checksum(fmt.format(descriptor)) @cli.command() @click.option("-c", "--coin") @click.option( "-n", "--account", required=True, type=int, help="account index (0 = first account)" ) @click.option("-a", "--account-type", type=ChoiceType(ACCOUNT_TYPE_TO_BIP_PURPOSE)) @click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS)) @click.option("-d", "--show-display", is_flag=True) @with_client def get_descriptor( client: "TrezorClient", coin: Optional[str], account: int, account_type: Optional[int], script_type: Optional[messages.InputScriptType], show_display: bool, ) -> str: """Get descriptor of given account.""" try: return _get_descriptor( client, coin, account, account_type, script_type, show_display ) except ValueError as e: raise click.ClickException(str(e)) # # Signing functions # @cli.command() @click.option("-c", "--coin", is_flag=True, hidden=True, expose_value=False) @click.option("-C", "--chunkify", is_flag=True) @click.argument("json_file", type=click.File()) @with_client def sign_tx(client: "TrezorClient", json_file: TextIO, chunkify: bool) -> None: """Sign transaction. Transaction data must be provided in a JSON file. See `transaction-format.md` for description. You can use `tools/build_tx.py` from the source distribution to build the required JSON file interactively: $ python3 tools/build_tx.py | trezorctl btc sign-tx - """ data = json.load(json_file) coin = data.get("coin_name", DEFAULT_COIN) details = data.get("details", {}) inputs = [ protobuf.dict_to_proto(messages.TxInputType, i) for i in data.get("inputs", ()) ] outputs = [ protobuf.dict_to_proto(messages.TxOutputType, output) for output in data.get("outputs", ()) ] prev_txes = { bytes.fromhex(txid): protobuf.dict_to_proto(messages.TransactionType, tx) for txid, tx in data.get("prev_txes", {}).items() } _, serialized_tx = btc.sign_tx( client, coin, inputs, outputs, prev_txes=prev_txes, chunkify=chunkify, **details, ) click.echo() click.echo("Signed Transaction:") click.echo(serialized_tx.hex()) # # Message functions # @cli.command() @click.option("-c", "--coin", default=DEFAULT_COIN) @click.option("-n", "--address", required=True, help="BIP-32 path") @click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS)) @click.option( "-e", "--electrum-compat", is_flag=True, help="Generate Electrum-compatible signature", ) @click.option("-C", "--chunkify", is_flag=True) @click.argument("message") @with_client def sign_message( client: "TrezorClient", coin: str, address: str, message: str, script_type: Optional[messages.InputScriptType], electrum_compat: bool, chunkify: bool, ) -> Dict[str, str]: """Sign message using address of given path.""" address_n = tools.parse_path(address) if script_type is None: script_type = guess_script_type_from_path(address_n) res = btc.sign_message( client, coin, address_n, message, script_type, electrum_compat, chunkify=chunkify, ) return { "message": message, "address": res.address, "signature": base64.b64encode(res.signature).decode(), } @cli.command() @click.option("-c", "--coin", default=DEFAULT_COIN) @click.option("-C", "--chunkify", is_flag=True) @click.argument("address") @click.argument("signature") @click.argument("message") @with_client def verify_message( client: "TrezorClient", coin: str, address: str, signature: str, message: str, chunkify: bool, ) -> bool: """Verify message.""" signature_bytes = base64.b64decode(signature) return btc.verify_message( client, coin, address, signature_bytes, message, chunkify=chunkify ) # # deprecated interactive signing # ALL BELOW is legacy code and will be dropped ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/cardano.py0000664000175000017500000002535014636513242020701 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import json from typing import TYPE_CHECKING, Optional, TextIO import click from .. import cardano, messages, tools from . import ChoiceType, with_client if TYPE_CHECKING: from ..client import TrezorClient PATH_HELP = "BIP-32 path to key, e.g. m/44h/1815h/0h/0/0" TESTNET_CHOICES = { "preprod": "testnet_preprod", "preview": "testnet_preview", "legacy": "testnet_legacy", } @click.group(name="cardano") def cli() -> None: """Cardano commands.""" @cli.command() @click.argument("file", type=click.File("r")) @click.option("-f", "--file", "_ignore", is_flag=True, hidden=True, expose_value=False) @click.option( "-s", "--signing-mode", required=True, type=ChoiceType({m.name: m for m in messages.CardanoTxSigningMode}), ) @click.option( "-p", "--protocol-magic", type=int, default=cardano.PROTOCOL_MAGICS["mainnet"] ) @click.option("-N", "--network-id", type=int, default=cardano.NETWORK_IDS["mainnet"]) @click.option("-t", "--testnet", type=ChoiceType(TESTNET_CHOICES)) @click.option( "-D", "--derivation-type", type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}), default=messages.CardanoDerivationType.ICARUS, ) @click.option("-i", "--include-network-id", is_flag=True) @click.option("-C", "chunkify", is_flag=True) @click.option("-T", "--tag-cbor-sets", is_flag=True) @with_client def sign_tx( client: "TrezorClient", file: TextIO, signing_mode: messages.CardanoTxSigningMode, protocol_magic: int, network_id: int, testnet: str, derivation_type: messages.CardanoDerivationType, include_network_id: bool, chunkify: bool, tag_cbor_sets: bool, ) -> cardano.SignTxResponse: """Sign Cardano transaction.""" transaction = json.load(file) if testnet: protocol_magic = cardano.PROTOCOL_MAGICS[testnet] network_id = cardano.NETWORK_IDS["testnet"] inputs = [cardano.parse_input(input) for input in transaction["inputs"]] outputs = [cardano.parse_output(output) for output in transaction["outputs"]] fee = transaction["fee"] ttl = transaction.get("ttl") validity_interval_start = transaction.get("validity_interval_start") certificates = [ cardano.parse_certificate(certificate) for certificate in transaction.get("certificates", ()) ] withdrawals = [ cardano.parse_withdrawal(withdrawal) for withdrawal in transaction.get("withdrawals", ()) ] auxiliary_data = cardano.parse_auxiliary_data(transaction.get("auxiliary_data")) mint = cardano.parse_mint(transaction.get("mint", ())) script_data_hash = cardano.parse_script_data_hash( transaction.get("script_data_hash") ) collateral_inputs = [ cardano.parse_collateral_input(collateral_input) for collateral_input in transaction.get("collateral_inputs", ()) ] required_signers = [ cardano.parse_required_signer(required_signer) for required_signer in transaction.get("required_signers", ()) ] collateral_return = ( cardano.parse_output(transaction["collateral_return"]) if transaction.get("collateral_return") else None ) total_collateral = transaction.get("total_collateral") reference_inputs = [ cardano.parse_reference_input(reference_input) for reference_input in transaction.get("reference_inputs", ()) ] additional_witness_requests = [ cardano.parse_additional_witness_request(p) for p in transaction["additional_witness_requests"] ] client.init_device(derive_cardano=True) sign_tx_response = cardano.sign_tx( client, signing_mode, inputs, outputs, fee, ttl, validity_interval_start, certificates, withdrawals, protocol_magic, network_id, auxiliary_data, mint, script_data_hash, collateral_inputs, required_signers, collateral_return, total_collateral, reference_inputs, additional_witness_requests, derivation_type=derivation_type, include_network_id=include_network_id, chunkify=chunkify, tag_cbor_sets=tag_cbor_sets, ) sign_tx_response["tx_hash"] = sign_tx_response["tx_hash"].hex() sign_tx_response["witnesses"] = [ { "type": witness["type"], "pub_key": witness["pub_key"].hex(), "signature": witness["signature"].hex(), "chain_code": ( witness["chain_code"].hex() if witness["chain_code"] is not None else None ), } for witness in sign_tx_response["witnesses"] ] auxiliary_data_supplement = sign_tx_response.get("auxiliary_data_supplement") if auxiliary_data_supplement: auxiliary_data_supplement["auxiliary_data_hash"] = auxiliary_data_supplement[ "auxiliary_data_hash" ].hex() cvote_registration_signature = auxiliary_data_supplement.get( "cvote_registration_signature" ) if cvote_registration_signature: auxiliary_data_supplement["cvote_registration_signature"] = ( cvote_registration_signature.hex() ) sign_tx_response["auxiliary_data_supplement"] = auxiliary_data_supplement return sign_tx_response @cli.command() @click.option("-n", "--address", type=str, default="", help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @click.option( "-t", "--address-type", type=ChoiceType({m.name: m for m in messages.CardanoAddressType}), default="BASE", ) @click.option("-s", "--staking-address", type=str, default="") @click.option("-h", "--staking-key-hash", type=str, default=None) @click.option("-b", "--block_index", type=int, default=None) @click.option("-x", "--tx_index", type=int, default=None) @click.option("-c", "--certificate_index", type=int, default=None) @click.option("--script-payment-hash", type=str, default=None) @click.option("--script-staking-hash", type=str, default=None) @click.option( "-p", "--protocol-magic", type=int, default=cardano.PROTOCOL_MAGICS["mainnet"] ) @click.option("-N", "--network-id", type=int, default=cardano.NETWORK_IDS["mainnet"]) @click.option("-e", "--testnet", type=ChoiceType(TESTNET_CHOICES)) @click.option( "-D", "--derivation-type", type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}), default=messages.CardanoDerivationType.ICARUS, ) @click.option("-C", "--chunkify", is_flag=True) @with_client def get_address( client: "TrezorClient", address: str, address_type: messages.CardanoAddressType, staking_address: str, staking_key_hash: Optional[str], block_index: Optional[int], tx_index: Optional[int], certificate_index: Optional[int], script_payment_hash: Optional[str], script_staking_hash: Optional[str], protocol_magic: int, network_id: int, show_display: bool, testnet: str, derivation_type: messages.CardanoDerivationType, chunkify: bool, ) -> str: """ Get Cardano address. All address types require the address, address_type, protocol_magic and network_id parameters. When deriving a base address you can choose to include staking info as staking_address or staking_key_hash - one has to be chosen. When deriving a pointer address you need to specify the block_index, tx_index and certificate_index parameters. Byron, enterprise and reward addresses only require the general parameters. """ if testnet: protocol_magic = cardano.PROTOCOL_MAGICS[testnet] network_id = cardano.NETWORK_IDS["testnet"] staking_key_hash_bytes = cardano.parse_optional_bytes(staking_key_hash) script_payment_hash_bytes = cardano.parse_optional_bytes(script_payment_hash) script_staking_hash_bytes = cardano.parse_optional_bytes(script_staking_hash) address_parameters = cardano.create_address_parameters( address_type, tools.parse_path(address), tools.parse_path(staking_address), staking_key_hash_bytes, block_index, tx_index, certificate_index, script_payment_hash_bytes, script_staking_hash_bytes, ) client.init_device(derive_cardano=True) return cardano.get_address( client, address_parameters, protocol_magic, network_id, show_display, derivation_type=derivation_type, chunkify=chunkify, ) @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option( "-D", "--derivation-type", type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}), default=messages.CardanoDerivationType.ICARUS, ) @click.option("-d", "--show-display", is_flag=True) @with_client def get_public_key( client: "TrezorClient", address: str, derivation_type: messages.CardanoDerivationType, show_display: bool, ) -> messages.CardanoPublicKey: """Get Cardano public key.""" address_n = tools.parse_path(address) client.init_device(derive_cardano=True) return cardano.get_public_key( client, address_n, derivation_type=derivation_type, show_display=show_display ) @cli.command() @click.argument("file", type=click.File("r")) @click.option( "-d", "--display-format", type=ChoiceType({m.name: m for m in messages.CardanoNativeScriptHashDisplayFormat}), default="HIDE", ) @click.option( "-D", "--derivation-type", type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}), default=messages.CardanoDerivationType.ICARUS, ) @with_client def get_native_script_hash( client: "TrezorClient", file: TextIO, display_format: messages.CardanoNativeScriptHashDisplayFormat, derivation_type: messages.CardanoDerivationType, ) -> messages.CardanoNativeScriptHash: """Get Cardano native script hash.""" native_script_json = json.load(file) native_script = cardano.parse_native_script(native_script_json) client.init_device(derive_cardano=True) return cardano.get_native_script_hash( client, native_script, display_format, derivation_type=derivation_type ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/cosi.py0000664000175000017500000000477014636513242020232 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from typing import TYPE_CHECKING, Optional import click from .. import cosi, tools from . import with_client if TYPE_CHECKING: from .. import messages from ..client import TrezorClient PATH_HELP = "BIP-32 path, e.g. m/10018h/0h" @click.group(name="cosi") def cli() -> None: """CoSi (Cothority / collective signing) commands.""" def cosi_warn(client: "TrezorClient") -> None: if client.features.model == "1" and client.version < (1, 11, 2): click.echo("WARNING: CoSi signing on your Trezor is insecure.") click.echo("Please update your Trezor to firmware version 1.11.2 or newer.") click.echo("If you used CoSi in the past, consider rotating your keys.") raise click.Abort() @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.argument("data_deprecated", required=False) @with_client def commit( client: "TrezorClient", address: str, data_deprecated: Optional[str] ) -> "messages.CosiCommitment": """Ask device to commit to CoSi signing.""" cosi_warn(client) if data_deprecated is not None: click.echo("Warning: data argument is deprecated", err=True) address_n = tools.parse_path(address) return cosi.commit(client, address_n) @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.argument("data") @click.argument("global_commitment") @click.argument("global_pubkey") @with_client def sign( client: "TrezorClient", address: str, data: str, global_commitment: str, global_pubkey: str, ) -> "messages.CosiSignature": """Ask device to sign using CoSi.""" cosi_warn(client) address_n = tools.parse_path(address) return cosi.sign( client, address_n, bytes.fromhex(data), bytes.fromhex(global_commitment), bytes.fromhex(global_pubkey), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/crypto.py0000664000175000017500000000672114636513242020613 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from typing import TYPE_CHECKING, Tuple import click from .. import misc, tools from . import ChoiceType, with_client if TYPE_CHECKING: from ..client import TrezorClient PROMPT_TYPE = ChoiceType( { "always": (True, True), "never": (False, False), "decrypt": (False, True), "encrypt": (True, False), } ) @click.group(name="crypto") def cli() -> None: """Miscellaneous cryptography features.""" @cli.command() @click.argument("size", type=int) @with_client def get_entropy(client: "TrezorClient", size: int) -> str: """Get random bytes from device.""" return misc.get_entropy(client, size).hex() @cli.command() @click.option("-n", "--address", required=True, help="BIP-32 path, e.g. m/10016h/0") @click.option( "-p", "--prompt", type=PROMPT_TYPE, default="always", help="Prompt for passphrase" ) @click.argument("key") @click.argument("value") @with_client def encrypt_keyvalue( client: "TrezorClient", address: str, key: str, value: str, prompt: Tuple[bool, bool], ) -> str: """Encrypt value by given key and path. The `prompt` option controls whether the device will prompt for confirmation on encrypting and decrypting the value. The default is to prompt for both encryption and decryption. You must provide the same `prompt` option to the `decrypt-keyvalue` command, otherwise the decryption will fail. """ ask_on_encrypt, ask_on_decrypt = prompt address_n = tools.parse_path(address) return misc.encrypt_keyvalue( client, address_n, key, value.encode(), ask_on_encrypt=ask_on_encrypt, ask_on_decrypt=ask_on_decrypt, ).hex() @cli.command() @click.option("-n", "--address", required=True, help="BIP-32 path, e.g. m/10016h/0") @click.option( "-p", "--prompt", type=PROMPT_TYPE, default="always", help="Prompt for passphrase" ) @click.argument("key") @click.argument("value") @with_client def decrypt_keyvalue( client: "TrezorClient", address: str, key: str, value: str, prompt: Tuple[bool, bool], ) -> bytes: """Decrypt value by given key and path. The `prompt` option controls whether the device will prompt for confirmation on encrypting and decrypting the value. The default is to prompt for both encryption and decryption. You must use the same `prompt` value that you used for encryption, otherwise the decryption will fail. I.e., it is not possible to encrypt with "--prompt=decrypt" and decrypt with "--prompt=never". """ ask_on_encrypt, ask_on_decrypt = prompt address_n = tools.parse_path(address) return misc.decrypt_keyvalue( client, address_n, key, bytes.fromhex(value), ask_on_encrypt=ask_on_encrypt, ask_on_decrypt=ask_on_decrypt, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/debug.py0000664000175000017500000000736014636513242020361 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from typing import TYPE_CHECKING, Union import click from .. import mapping, messages, protobuf from ..client import TrezorClient from ..debuglink import TrezorClientDebugLink from ..debuglink import prodtest_t1 as debuglink_prodtest_t1 from ..debuglink import record_screen from . import with_client if TYPE_CHECKING: from . import TrezorConnection @click.group(name="debug") def cli() -> None: """Miscellaneous debug features.""" @cli.command() @click.argument("message_name_or_type") @click.argument("hex_data") @click.pass_obj def send_bytes( obj: "TrezorConnection", message_name_or_type: str, hex_data: str ) -> None: """Send raw bytes to Trezor. Message type and message data must be specified separately, due to how message chunking works on the transport level. Message length is calculated and sent automatically, and it is currently impossible to explicitly specify invalid length. MESSAGE_NAME_OR_TYPE can either be a number, or a name from the MessageType enum, in which case the value of that enum is used. """ if message_name_or_type.isdigit(): message_type = int(message_name_or_type) else: message_type = getattr(messages.MessageType, message_name_or_type) if not isinstance(message_type, int): raise click.ClickException("Invalid message type.") try: message_data = bytes.fromhex(hex_data) except Exception as e: raise click.ClickException("Invalid hex data.") from e transport = obj.get_transport() transport.begin_session() transport.write(message_type, message_data) response_type, response_data = transport.read() transport.end_session() click.echo(f"Response type: {response_type}") click.echo(f"Response data: {response_data.hex()}") try: msg = mapping.DEFAULT_MAPPING.decode(response_type, response_data) click.echo("Parsed message:") click.echo(protobuf.format_message(msg)) except Exception as e: click.echo(f"Could not parse response: {e}") @cli.command() @click.argument("directory", required=False) @click.option("-s", "--stop", is_flag=True, help="Stop the recording") @click.pass_obj def record(obj: "TrezorConnection", directory: Union[str, None], stop: bool) -> None: """Record screen changes into a specified directory. Recording can be stopped with `-s / --stop` option. """ record_screen_from_connection(obj, None if stop else directory) def record_screen_from_connection( obj: "TrezorConnection", directory: Union[str, None] ) -> None: """Record screen helper to transform TrezorConnection into TrezorClientDebugLink.""" transport = obj.get_transport() debug_client = TrezorClientDebugLink(transport, auto_interact=False) debug_client.open() record_screen(debug_client, directory, report_func=click.echo) debug_client.close() @cli.command() @with_client def prodtest_t1(client: "TrezorClient") -> str: """Perform a prodtest on Model One. Only available on PRODTEST firmware and on T1B1. Formerly named self-test. """ return debuglink_prodtest_t1(client) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315793.0 trezor-0.13.9/src/trezorlib/cli/device.py0000664000175000017500000003464214636526521020541 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from __future__ import annotations import logging import secrets import sys import typing as t import click import requests from .. import debuglink, device, exceptions, messages, ui from . import ChoiceType, with_client if t.TYPE_CHECKING: from ..client import TrezorClient from ..protobuf import MessageType from . import TrezorConnection RECOVERY_DEVICE_INPUT_METHOD = { "scrambled": messages.RecoveryDeviceInputMethod.ScrambledWords, "matrix": messages.RecoveryDeviceInputMethod.Matrix, } BACKUP_TYPE = { "bip39": messages.BackupType.Bip39, "single": messages.BackupType.Slip39_Single_Extendable, "shamir": messages.BackupType.Slip39_Basic, "advanced": messages.BackupType.Slip39_Advanced, } SD_PROTECT_OPERATIONS = { "on": messages.SdProtectOperationType.ENABLE, "off": messages.SdProtectOperationType.DISABLE, "refresh": messages.SdProtectOperationType.REFRESH, } LOG = logging.getLogger(__name__) @click.group(name="device") def cli() -> None: """Device management commands - setup, recover seed, wipe, etc.""" @cli.command() @click.option( "-b", "--bootloader", help="Wipe device in bootloader mode. This also erases the firmware.", is_flag=True, ) @with_client def wipe(client: "TrezorClient", bootloader: bool) -> str: """Reset device to factory defaults and remove all private data.""" if bootloader: if not client.features.bootloader_mode: click.echo("Please switch your device to bootloader mode.") sys.exit(1) else: click.echo("Wiping user data and firmware!") else: if client.features.bootloader_mode: click.echo( "Your device is in bootloader mode. This operation would also erase firmware." ) click.echo( 'Specify "--bootloader" if that is what you want, or disconnect and reconnect device in normal mode.' ) click.echo("Aborting.") sys.exit(1) else: click.echo("Wiping user data!") try: return device.wipe(client) except exceptions.TrezorFailure as e: click.echo("Action failed: {} {}".format(*e.args)) sys.exit(3) @cli.command() @click.option("-m", "--mnemonic", multiple=True) @click.option("-p", "--pin", default="") @click.option("-r", "--passphrase-protection", is_flag=True) @click.option("-l", "--label", default="") @click.option("-i", "--ignore-checksum", is_flag=True) @click.option("-s", "--slip0014", is_flag=True) @click.option("-b", "--needs-backup", is_flag=True) @click.option("-n", "--no-backup", is_flag=True) @with_client def load( client: "TrezorClient", mnemonic: t.Sequence[str], pin: str, passphrase_protection: bool, label: str, ignore_checksum: bool, slip0014: bool, needs_backup: bool, no_backup: bool, ) -> str: """Upload seed and custom configuration to the device. This functionality is only available in debug mode. """ if slip0014 and mnemonic: raise click.ClickException("Cannot use -s and -m together.") if slip0014: mnemonic = [" ".join(["all"] * 12)] if not label: label = "SLIP-0014" try: return debuglink.load_device( client, mnemonic=list(mnemonic), pin=pin, passphrase_protection=passphrase_protection, label=label, skip_checksum=ignore_checksum, needs_backup=needs_backup, no_backup=no_backup, ) except exceptions.TrezorFailure as e: if e.code == messages.FailureType.UnexpectedMessage: raise click.ClickException( "Unrecognized message. Make sure your Trezor is using debug firmware." ) else: raise @cli.command() @click.option("-w", "--words", type=click.Choice(["12", "18", "24"]), default="24") @click.option("-e", "--expand", is_flag=True) @click.option("-p", "--pin-protection", is_flag=True) @click.option("-r", "--passphrase-protection", is_flag=True) @click.option("-l", "--label") @click.option("-u", "--u2f-counter", default=None, type=int) @click.option( "-i", "--input_method", "-t", "--type", type=ChoiceType(RECOVERY_DEVICE_INPUT_METHOD), default="scrambled", ) @click.option("-d", "--dry-run", is_flag=True) @click.option("-b", "--unlock-repeated-backup", is_flag=True) @with_client def recover( client: "TrezorClient", words: str, expand: bool, pin_protection: bool, passphrase_protection: bool, label: str | None, u2f_counter: int, input_method: messages.RecoveryDeviceInputMethod, dry_run: bool, unlock_repeated_backup: bool, ) -> "MessageType": """Start safe recovery workflow.""" if input_method == messages.RecoveryDeviceInputMethod.ScrambledWords: input_callback = ui.mnemonic_words(expand) else: input_callback = ui.matrix_words click.echo(ui.RECOVERY_MATRIX_DESCRIPTION) if dry_run and unlock_repeated_backup: raise click.ClickException("Cannot use -d and -b together.") type = None if dry_run: type = messages.RecoveryType.DryRun if unlock_repeated_backup: type = messages.RecoveryType.UnlockRepeatedBackup return device.recover( client, word_count=int(words), passphrase_protection=passphrase_protection, pin_protection=pin_protection, label=label, u2f_counter=u2f_counter, input_callback=input_callback, input_method=input_method, type=type, ) @cli.command() @click.option("-e", "--show-entropy", is_flag=True) @click.option("-t", "--strength", type=click.Choice(["128", "192", "256"])) @click.option("-r", "--passphrase-protection", is_flag=True) @click.option("-p", "--pin-protection", is_flag=True) @click.option("-l", "--label") @click.option("-u", "--u2f-counter", default=0) @click.option("-s", "--skip-backup", is_flag=True) @click.option("-n", "--no-backup", is_flag=True) @click.option("-b", "--backup-type", type=ChoiceType(BACKUP_TYPE)) @with_client def setup( client: "TrezorClient", show_entropy: bool, strength: int | None, passphrase_protection: bool, pin_protection: bool, label: str | None, u2f_counter: int, skip_backup: bool, no_backup: bool, backup_type: messages.BackupType | None, ) -> str: """Perform device setup and generate new seed.""" if strength: strength = int(strength) BT = messages.BackupType if backup_type is None: if client.version >= (2, 7, 1): # SLIP39 extendable was introduced in 2.7.1 backup_type = BT.Slip39_Single_Extendable else: # this includes both T1 and older trezor-cores backup_type = BT.Bip39 if ( backup_type in (BT.Slip39_Single_Extendable, BT.Slip39_Basic, BT.Slip39_Basic_Extendable) and messages.Capability.Shamir not in client.features.capabilities ) or ( backup_type in (BT.Slip39_Advanced, BT.Slip39_Advanced_Extendable) and messages.Capability.ShamirGroups not in client.features.capabilities ): click.echo( "WARNING: Your Trezor device does not indicate support for the requested\n" "backup type. Traditional BIP39 backup may be generated instead." ) return device.reset( client, display_random=show_entropy, strength=strength, passphrase_protection=passphrase_protection, pin_protection=pin_protection, label=label, u2f_counter=u2f_counter, skip_backup=skip_backup, no_backup=no_backup, backup_type=backup_type, ) @cli.command() @click.option("-t", "--group-threshold", type=int) @click.option("-g", "--group", "groups", type=(int, int), multiple=True, metavar="T N") @with_client def backup( client: "TrezorClient", group_threshold: int | None = None, groups: t.Sequence[tuple[int, int]] = (), ) -> str: """Perform device seed backup.""" return device.backup(client, group_threshold, groups) @cli.command() @click.argument("operation", type=ChoiceType(SD_PROTECT_OPERATIONS)) @with_client def sd_protect( client: "TrezorClient", operation: messages.SdProtectOperationType ) -> str: """Secure the device with SD card protection. When SD card protection is enabled, a randomly generated secret is stored on the SD card. During every PIN checking and unlocking operation this secret is combined with the entered PIN value to decrypt data stored on the device. The SD card will thus be needed every time you unlock the device. The options are: \b on - Generate SD card secret and use it to protect the PIN and storage. off - Remove SD card secret protection. refresh - Replace the current SD card secret with a new one. """ if client.features.model == "1": raise click.ClickException("Trezor One does not support SD card protection.") return device.sd_protect(client, operation) @cli.command() @click.pass_obj def reboot_to_bootloader(obj: "TrezorConnection") -> str: """Reboot device into bootloader mode. Currently only supported on Trezor Model One. """ # avoid using @with_client because it closes the session afterwards, # which triggers double prompt on device with obj.client_context() as client: return device.reboot_to_bootloader(client) @cli.command() @with_client def tutorial(client: "TrezorClient") -> str: """Show on-device tutorial.""" return device.show_device_tutorial(client) @cli.command() @with_client def unlock_bootloader(client: "TrezorClient") -> str: """Unlocks bootloader. Irreversible.""" return device.unlock_bootloader(client) @cli.command() @click.argument("enable", type=ChoiceType({"on": True, "off": False}), required=False) @click.option( "-e", "--expiry", type=int, help="Dialog expiry in seconds.", ) @with_client def set_busy(client: "TrezorClient", enable: bool | None, expiry: int | None) -> str: """Show a "Do not disconnect" dialog.""" if enable is False: return device.set_busy(client, None) if expiry is None: raise click.ClickException("Missing option '-e' / '--expiry'.") if expiry <= 0: raise click.ClickException( f"Invalid value for '-e' / '--expiry': '{expiry}' is not a positive integer." ) return device.set_busy(client, expiry * 1000) PUBKEY_WHITELIST_URL_TEMPLATE = ( "https://data.trezor.io/firmware/{model}/authenticity.json" ) @cli.command() @click.argument("hex_challenge", required=False) @click.option("-R", "--root", type=click.File("rb"), help="Custom root certificate.") @click.option( "-r", "--raw", is_flag=True, help="Print raw cryptographic data and exit." ) @click.option( "-s", "--skip-whitelist", is_flag=True, help="Do not check intermediate certificates against the whitelist.", ) @with_client def authenticate( client: "TrezorClient", hex_challenge: str | None, root: t.BinaryIO | None, raw: bool | None, skip_whitelist: bool | None, ) -> None: """Verify the authenticity of the device. Use the --raw option to get the raw challenge, signature, and certificate data. Otherwise, trezorctl will attempt to decode the signatures and check their authenticity. By default, it will also check the public keys against a whitelist downloaded from Trezor servers. You can skip this check with the --skip-whitelist option. \b When not using --raw, 'cryptography' library is required. You can install it via: pip3 install trezor[authentication] """ if hex_challenge is None: hex_challenge = secrets.token_hex(32) challenge = bytes.fromhex(hex_challenge) if raw: msg = device.authenticate(client, challenge) click.echo(f"Challenge: {hex_challenge}") click.echo(f"Signature of challenge: {msg.signature.hex()}") click.echo(f"Device certificate: {msg.certificates[0].hex()}") for cert in msg.certificates[1:]: click.echo(f"CA certificate: {cert.hex()}") return try: from .. import authentication except ImportError as e: click.echo("Failed to import the authentication module.") click.echo(f"Error: {e}") click.echo("Make sure you have the required dependencies:") click.echo(" pip3 install trezor[authentication]") sys.exit(4) if root is not None: root_bytes = root.read() else: root_bytes = None class ColoredFormatter(logging.Formatter): LEVELS = { logging.ERROR: click.style("ERROR", fg="red"), logging.WARNING: click.style("WARNING", fg="yellow"), logging.INFO: click.style("INFO", fg="blue"), logging.DEBUG: click.style("OK", fg="green"), } def format(self, record: logging.LogRecord) -> str: prefix = self.LEVELS[record.levelno] bold_args = tuple( click.style(str(arg), bold=True) for arg in record.args or () ) return f"[{prefix}] {record.msg}" % bold_args handler = logging.StreamHandler() handler.setFormatter(ColoredFormatter()) authentication.LOG.addHandler(handler) authentication.LOG.setLevel(logging.DEBUG) if skip_whitelist: whitelist = None else: whitelist_json = requests.get( PUBKEY_WHITELIST_URL_TEMPLATE.format( model=client.model.internal_name.lower() ) ).json() whitelist = [bytes.fromhex(pk) for pk in whitelist_json["ca_pubkeys"]] try: authentication.authenticate_device( client, challenge, root_pubkey=root_bytes, whitelist=whitelist ) except authentication.DeviceNotAuthentic: click.echo("Device is not authentic.") sys.exit(5) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/eos.py0000664000175000017500000000411014636513242020047 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import json from typing import TYPE_CHECKING, TextIO import click from .. import eos, tools from . import with_client if TYPE_CHECKING: from .. import messages from ..client import TrezorClient PATH_HELP = "BIP-32 path, e.g. m/44h/194h/0h/0/0" @click.group(name="eos") def cli() -> None: """EOS commands.""" @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @with_client def get_public_key(client: "TrezorClient", address: str, show_display: bool) -> str: """Get Eos public key in base58 encoding.""" address_n = tools.parse_path(address) res = eos.get_public_key(client, address_n, show_display) return f"WIF: {res.wif_public_key}\nRaw: {res.raw_public_key.hex()}" @cli.command() @click.argument("file", type=click.File("r")) @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-f", "--file", "_ignore", is_flag=True, hidden=True, expose_value=False) @click.option("-C", "--chunkify", is_flag=True) @with_client def sign_transaction( client: "TrezorClient", address: str, file: TextIO, chunkify: bool ) -> "messages.EosSignedTx": """Sign EOS transaction.""" tx_json = json.load(file) address_n = tools.parse_path(address) return eos.sign_tx( client, address_n, tx_json["transaction"], tx_json["chain_id"], chunkify=chunkify, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/ethereum.py0000664000175000017500000004631614636513242021115 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import json import re import sys import tarfile from decimal import Decimal from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, List, NoReturn, Optional, TextIO, cast import click from .. import _rlp, definitions, ethereum, tools from ..messages import EthereumDefinitions from . import with_client if TYPE_CHECKING: import web3 from eth_typing import ChecksumAddress # noqa: I900 from web3.types import Wei from ..client import TrezorClient PATH_HELP = "BIP-32 path, e.g. m/44h/60h/0h/0/0" # fmt: off ETHER_UNITS = { 'wei': 1, 'kwei': 1000, 'babbage': 1000, 'femtoether': 1000, 'mwei': 1000000, 'lovelace': 1000000, 'picoether': 1000000, 'gwei': 1000000000, 'shannon': 1000000000, 'nanoether': 1000000000, 'nano': 1000000000, 'szabo': 1000000000000, 'microether': 1000000000000, 'micro': 1000000000000, 'finney': 1000000000000000, 'milliether': 1000000000000000, 'milli': 1000000000000000, 'ether': 1000000000000000000, 'eth': 1000000000000000000, } # fmt: on # So that we can import the web3 library only when really used and reuse the instance _WEB3_INSTANCE: Optional["web3.Web3"] = None def _print_eth_dependencies_and_die() -> NoReturn: click.echo("Ethereum requirements not installed.") click.echo("Please run:") click.echo() click.echo(" pip install trezor[ethereum]") sys.exit(1) def _get_web3() -> "web3.Web3": global _WEB3_INSTANCE if _WEB3_INSTANCE is None: try: import web3 _WEB3_INSTANCE = web3.Web3() except ModuleNotFoundError: _print_eth_dependencies_and_die() return _WEB3_INSTANCE def _amount_to_int( ctx: click.Context, param: Any, value: Optional[str] ) -> Optional[int]: if value is None: return None if value.isdigit(): return int(value) try: number, unit = re.match(r"^(\d+(?:.\d+)?)([a-z]+)", value).groups() # type: ignore ["groups" is not a known attribute of "None"] scale = ETHER_UNITS[unit] decoded_number = Decimal(number) return int(decoded_number * scale) except Exception: raise click.BadParameter("Amount not understood") def _parse_access_list( ctx: click.Context, param: Any, value: str ) -> List[ethereum.messages.EthereumAccessList]: try: return [_parse_access_list_item(val) for val in value] except Exception: raise click.BadParameter("Access List format invalid") def _parse_access_list_item(value: str) -> ethereum.messages.EthereumAccessList: try: arr = value.split(":") address, storage_keys = arr[0], arr[1:] storage_keys_bytes = [ethereum.decode_hex(key) for key in storage_keys] return ethereum.messages.EthereumAccessList( address=address, storage_keys=storage_keys_bytes ) except Exception: raise click.BadParameter("Access List format invalid") def _list_units(ctx: click.Context, param: Any, value: bool) -> None: if not value or ctx.resilient_parsing: return maxlen = max(len(k) for k in ETHER_UNITS.keys()) + 1 for unit, scale in ETHER_UNITS.items(): click.echo("{:{maxlen}}: {}".format(unit, scale, maxlen=maxlen)) ctx.exit() def _erc20_contract( token_address: "ChecksumAddress", to_address: str, amount: int ) -> str: min_abi = [ { "name": "transfer", "type": "function", "constant": False, "inputs": [ {"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}, ], "outputs": [{"name": "", "type": "bool"}], } ] contract = _get_web3().eth.contract(address=token_address, abi=min_abi) return contract.encodeABI("transfer", [to_address, amount]) def _format_access_list( access_list: List[ethereum.messages.EthereumAccessList], ) -> "_rlp.RLPItem": return [ (ethereum.decode_hex(item.address), item.storage_keys) for item in access_list ] def _hex_or_file(data: str) -> bytes: path = Path(data) if path.is_file(): return path.read_bytes() if data.startswith("0x"): data = data[2:] try: return bytes.fromhex(data) except ValueError as e: raise click.ClickException(f"Invalid hex or file path: {data}") from e class CliSource(definitions.Source): network: Optional[bytes] = None token: Optional[bytes] = None delegate: definitions.Source = definitions.NullSource() def get_network(self, chain_id: int) -> Optional[bytes]: if self.network is not None: return self.network return self.delegate.get_network(chain_id) def get_network_by_slip44(self, slip44: int) -> Optional[bytes]: if self.network is not None: return self.network return self.delegate.get_network_by_slip44(slip44) def get_token(self, chain_id: int, address: Any) -> Optional[bytes]: if self.token is not None: return self.token return self.delegate.get_token(chain_id, address) DEFINITIONS_SOURCE = CliSource() ##################### # # commands start here @click.group(name="ethereum") @click.option( "-d", "--definitions", "defs", help="Source for Ethereum definition blobs." ) @click.option( "-a", "--auto-definitions", is_flag=True, help="Automatically download required definitions from trezor.io", ) @click.option("--network", help="Network definition blob.") @click.option("--token", help="Token definition blob.") def cli( defs: Optional[str], auto_definitions: Optional[bool], network: Optional[str], token: Optional[str], ) -> None: """Ethereum commands. Most Ethereum commands now require the host to specify definition of a network and possibly an ERC-20 token. These definitions can be automatically fetched using the `-a` option. You can also specify a custom definition source using the `-d` option. Allowable values are: \b - HTTP or HTTPS URL - path to local directory - path to local tar archive \b For debugging purposes, it is possible to force use a specific network and token definition by using the `--network` and `--token` options. These options accept either a path to a file with a binary blob, or a hex-encoded string. """ if auto_definitions: if defs is not None: raise click.ClickException( "Cannot use --definitions and --auto-definitions at the same time." ) DEFINITIONS_SOURCE.delegate = definitions.UrlSource() elif defs is not None: path = Path(defs) if path.is_dir(): DEFINITIONS_SOURCE.delegate = definitions.FilesystemSource(path) elif path.is_file() and tarfile.is_tarfile(path): DEFINITIONS_SOURCE.delegate = definitions.TarSource(path) elif defs.startswith("http"): DEFINITIONS_SOURCE.delegate = definitions.UrlSource(defs) else: raise click.ClickException("Unrecognized --definitions value.") if network is not None: DEFINITIONS_SOURCE.network = _hex_or_file(network) if token is not None: DEFINITIONS_SOURCE.token = _hex_or_file(token) @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @click.option("-C", "--chunkify", is_flag=True) @with_client def get_address( client: "TrezorClient", address: str, show_display: bool, chunkify: bool ) -> str: """Get Ethereum address in hex encoding.""" address_n = tools.parse_path(address) network = ethereum.network_from_address_n(address_n, DEFINITIONS_SOURCE) return ethereum.get_address(client, address_n, show_display, network, chunkify) @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @with_client def get_public_node(client: "TrezorClient", address: str, show_display: bool) -> dict: """Get Ethereum public node of given path.""" address_n = tools.parse_path(address) result = ethereum.get_public_node(client, address_n, show_display=show_display) return { "node": { "depth": result.node.depth, "fingerprint": "%08x" % result.node.fingerprint, "child_num": result.node.child_num, "chain_code": result.node.chain_code.hex(), "public_key": result.node.public_key.hex(), }, "xpub": result.xpub, } @cli.command() @click.option( "-c", "--chain-id", type=int, default=1, help="EIP-155 chain id (replay protection)" ) @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option( "-g", "--gas-limit", type=int, help="Gas limit (required for offline signing)" ) @click.option( "-G", "--gas-price", help="Gas price (required for offline signing)", callback=_amount_to_int, ) @click.option( "-i", "--nonce", type=int, help="Transaction counter (required for offline signing)" ) @click.option("-d", "--data", help="Data as hex string, e.g. 0x12345678") @click.option("-p", "--publish", is_flag=True, help="Publish transaction via RPC") @click.option("-x", "--tx-type", type=int, help="TX type") @click.option("-t", "--token", help="ERC20 token address") @click.option( "-a", "--access-list", help="Access List", callback=_parse_access_list, multiple=True, ) @click.option("--max-gas-fee", help="Max Gas Fee (EIP1559)", callback=_amount_to_int) @click.option( "--max-priority-fee", help="Max Priority Fee (EIP1559)", callback=_amount_to_int, ) @click.option("-e", "--eip2718-type", type=int, help="EIP2718 tx type") @click.option( "--list-units", is_flag=True, help="List known currency units and exit.", is_eager=True, callback=_list_units, expose_value=False, ) @click.option("-C", "--chunkify", is_flag=True) @click.argument("to_address") @click.argument("amount", callback=_amount_to_int) @with_client def sign_tx( client: "TrezorClient", chain_id: int, address: str, amount: int, gas_limit: Optional[int], gas_price: Optional[int], nonce: Optional[int], data: Optional[str], publish: bool, to_address: str, tx_type: Optional[int], token: Optional[str], max_gas_fee: Optional[int], max_priority_fee: Optional[int], access_list: List[ethereum.messages.EthereumAccessList], eip2718_type: Optional[int], chunkify: bool, ) -> str: """Sign (and optionally publish) Ethereum transaction. Use TO_ADDRESS as destination address, or set to "" for contract creation. Specify a contract address with the --token option to send an ERC20 token. You can specify AMOUNT and gas price either as a number of wei, or you can use a unit suffix. Use the --list-units option to show all known currency units. ERC20 token amounts are specified in eth/wei, custom units are not supported. If any of gas price, gas limit and nonce is not specified, this command will try to connect to an ethereum node and auto-fill these values. You can configure the connection with WEB3_PROVIDER_URI environment variable. """ is_eip1559 = eip2718_type == 2 if ( (not is_eip1559 and gas_price is None) or any(x is None for x in (gas_limit, nonce)) or publish ) and not _get_web3().is_connected(): click.echo("Failed to connect to Ethereum node.") click.echo( "If you want to sign offline, make sure you provide --gas-price, " "--gas-limit and --nonce arguments" ) sys.exit(1) if data is not None and token is not None: click.echo("Can't send tokens and custom data at the same time") sys.exit(1) encoded_network = DEFINITIONS_SOURCE.get_network(chain_id) address_n = tools.parse_path(address) from_address = ethereum.get_address( client, address_n, encoded_network=encoded_network ) if token: data = _erc20_contract(cast("ChecksumAddress", token), to_address, amount) to_address = token amount = 0 if data: # use token definition regardless of whether the data is an ERC-20 transfer # -- this might prove useful in the future encoded_token = DEFINITIONS_SOURCE.get_token(chain_id, to_address) data_bytes = ethereum.decode_hex(data) else: # force use provided token definition even if no data (that is what the user # seems to want) encoded_token = DEFINITIONS_SOURCE.token data_bytes = b"" if gas_limit is None: gas_limit = _get_web3().eth.estimate_gas( { "to": to_address, "from": from_address, "value": cast("Wei", amount), "data": data_bytes, } ) if nonce is None: nonce = _get_web3().eth.get_transaction_count( cast("ChecksumAddress", from_address) ) assert gas_limit is not None assert nonce is not None defs = EthereumDefinitions( encoded_network=encoded_network, encoded_token=encoded_token, ) if is_eip1559: assert max_gas_fee is not None assert max_priority_fee is not None sig = ethereum.sign_tx_eip1559( client, n=address_n, nonce=nonce, gas_limit=gas_limit, to=to_address, value=amount, data=data_bytes, chain_id=chain_id, max_gas_fee=max_gas_fee, max_priority_fee=max_priority_fee, access_list=access_list, definitions=defs, chunkify=chunkify, ) else: if gas_price is None: gas_price = _get_web3().eth.gas_price assert gas_price is not None sig = ethereum.sign_tx( client, n=address_n, tx_type=tx_type, nonce=nonce, gas_price=gas_price, gas_limit=gas_limit, to=to_address, value=amount, data=data_bytes, chain_id=chain_id, definitions=defs, chunkify=chunkify, ) to = ethereum.decode_hex(to_address) if is_eip1559: transaction_items = [ chain_id, nonce, max_priority_fee, max_gas_fee, gas_limit, to, amount, data_bytes, _format_access_list(access_list) if access_list is not None else [], *sig, ] elif tx_type is None: transaction_items = [nonce, gas_price, gas_limit, to, amount, data_bytes, *sig] else: transaction_items = [ tx_type, nonce, gas_price, gas_limit, to, amount, data_bytes, *sig, ] transaction = _rlp.encode(transaction_items) if eip2718_type is not None: eip2718_prefix = eip2718_type.to_bytes(1, "big") else: eip2718_prefix = b"" tx_bytes = eip2718_prefix + transaction if publish: tx_hash = _get_web3().eth.send_raw_transaction(tx_bytes).hex() return f"Transaction published with ID: {tx_hash}" else: return f"Signed raw transaction:\n0x{tx_bytes.hex()}" @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-C", "--chunkify", is_flag=True) @click.argument("message") @with_client def sign_message( client: "TrezorClient", address: str, message: str, chunkify: bool ) -> Dict[str, str]: """Sign message with Ethereum address.""" address_n = tools.parse_path(address) network = ethereum.network_from_address_n(address_n, DEFINITIONS_SOURCE) ret = ethereum.sign_message(client, address_n, message, network, chunkify=chunkify) output = { "message": message, "address": ret.address, "signature": f"0x{ret.signature.hex()}", } return output @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option( "--metamask-v4-compat/--no-metamask-v4-compat", default=True, help="Be compatible with Metamask's signTypedData_v4 implementation", ) @click.argument("file", type=click.File("r")) @with_client def sign_typed_data( client: "TrezorClient", address: str, metamask_v4_compat: bool, file: TextIO ) -> Dict[str, str]: """Sign typed data (EIP-712) with Ethereum address. Currently NOT supported: - arrays of arrays - recursive structs """ address_n = tools.parse_path(address) network = ethereum.network_from_address_n(address_n, DEFINITIONS_SOURCE) defs = EthereumDefinitions(encoded_network=network) data = json.loads(file.read()) ret = ethereum.sign_typed_data( client, address_n, data, metamask_v4_compat=metamask_v4_compat, definitions=defs, ) output = { "address": ret.address, "signature": f"0x{ret.signature.hex()}", } return output @cli.command() @click.option("-C", "--chunkify", is_flag=True) @click.argument("address") @click.argument("signature") @click.argument("message") @with_client def verify_message( client: "TrezorClient", address: str, signature: str, message: str, chunkify: bool, ) -> bool: """Verify message signed with Ethereum address.""" signature_bytes = ethereum.decode_hex(signature) return ethereum.verify_message( client, address, signature_bytes, message, chunkify=chunkify ) @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.argument("domain_hash_hex") @click.argument("message_hash_hex") @with_client def sign_typed_data_hash( client: "TrezorClient", address: str, domain_hash_hex: str, message_hash_hex: str ) -> Dict[str, str]: """ Sign hash of typed data (EIP-712) with Ethereum address. For T1 backward compatibility. MESSAGE_HASH_HEX can be set to an empty string '' for domain-only hashes. """ address_n = tools.parse_path(address) domain_hash = ethereum.decode_hex(domain_hash_hex) message_hash = ethereum.decode_hex(message_hash_hex) if message_hash_hex else None network = ethereum.network_from_address_n(address_n, DEFINITIONS_SOURCE) ret = ethereum.sign_typed_data_hash( client, address_n, domain_hash, message_hash, network ) output = { "domain_hash": domain_hash_hex, "message_hash": message_hash_hex, "address": ret.address, "signature": f"0x{ret.signature.hex()}", } return output ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/fido.py0000664000175000017500000001047314636513242020213 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from typing import TYPE_CHECKING import click from .. import fido from . import with_client if TYPE_CHECKING: from ..client import TrezorClient ALGORITHM_NAME = {-7: "ES256 (ECDSA w/ SHA-256)", -8: "EdDSA"} CURVE_NAME = {1: "P-256 (secp256r1)", 6: "Ed25519"} @click.group(name="fido") def cli() -> None: """FIDO2, U2F and WebAuthN management commands.""" @cli.group() def credentials() -> None: """Manage FIDO2 resident credentials.""" @credentials.command(name="list") @with_client def credentials_list(client: "TrezorClient") -> None: """List all resident credentials on the device.""" creds = fido.list_credentials(client) for cred in creds: click.echo("") click.echo(f"WebAuthn credential at index {cred.index}:") if cred.rp_id is not None: click.echo(f" Relying party ID: {cred.rp_id}") if cred.rp_name is not None: click.echo(f" Relying party name: {cred.rp_name}") if cred.user_id is not None: click.echo(f" User ID: {cred.user_id.hex()}") if cred.user_name is not None: click.echo(f" User name: {cred.user_name}") if cred.user_display_name is not None: click.echo(f" User display name: {cred.user_display_name}") if cred.creation_time is not None: click.echo(f" Creation time: {cred.creation_time}") if cred.hmac_secret is not None: click.echo(f" hmac-secret enabled: {cred.hmac_secret}") if cred.use_sign_count is not None: click.echo(f" Use signature counter: {cred.use_sign_count}") if cred.algorithm is not None: algorithm = ALGORITHM_NAME.get(cred.algorithm, cred.algorithm) click.echo(f" Algorithm: {algorithm}") if cred.curve is not None: curve = CURVE_NAME.get(cred.curve, cred.curve) click.echo(f" Curve: {curve}") # TODO: could be made required in WebAuthnCredential assert cred.id is not None click.echo(f" Credential ID: {cred.id.hex()}") if not creds: click.echo("There are no resident credentials stored on the device.") @credentials.command(name="add") @click.argument("hex_credential_id") @with_client def credentials_add(client: "TrezorClient", hex_credential_id: str) -> str: """Add the credential with the given ID as a resident credential. HEX_CREDENTIAL_ID is the credential ID as a hexadecimal string. """ return fido.add_credential(client, bytes.fromhex(hex_credential_id)) @credentials.command(name="remove") @click.option( "-i", "--index", required=True, type=click.IntRange(0, 99), help="Credential index." ) @with_client def credentials_remove(client: "TrezorClient", index: int) -> str: """Remove the resident credential at the given index.""" return fido.remove_credential(client, index) # # FIDO counter operations # @cli.group() def counter() -> None: """Get or set the FIDO/U2F counter value.""" @counter.command(name="set") @click.argument("counter", type=int) @with_client def counter_set(client: "TrezorClient", counter: int) -> str: """Set FIDO/U2F counter value.""" return fido.set_counter(client, counter) @counter.command(name="get-next") @with_client def counter_get_next(client: "TrezorClient") -> int: """Get-and-increase value of FIDO/U2F counter. FIDO counter value cannot be read directly. On each U2F exchange, the counter value is returned and atomically increased. This command performs the same operation and returns the counter value. """ return fido.get_next_counter(client) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/firmware.py0000664000175000017500000006302514636513242021107 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import os import sys import time from pathlib import Path from typing import ( TYPE_CHECKING, Any, BinaryIO, Dict, Iterable, List, Optional, Tuple, Union, ) from urllib.parse import urlparse import click import requests from .. import device, exceptions, firmware, messages, models from ..firmware import models as fw_models from ..models import TrezorModel from . import ChoiceType, with_client if TYPE_CHECKING: from ..client import TrezorClient from . import TrezorConnection MODEL_CHOICE = ChoiceType( { "T1B1": models.T1B1, "T2T1": models.T2T1, "T2B1": models.T2B1, "T3T1": models.T3T1, # aliases "1": models.T1B1, "one": models.T1B1, "t": models.T2T1, "r": models.T2B1, "safe3": models.T2B1, "s3": models.T2B1, "safe5": models.T3T1, "s5": models.T3T1, }, case_sensitive=False, ) def _print_version(version: Tuple[int, int, int, int]) -> None: major, minor, patch, build = version click.echo(f"Firmware version {major}.{minor}.{patch} build {build}") def _is_bootloader_onev2(client: "TrezorClient") -> bool: """Check if bootloader is capable of installing the Trezor One v2 firmware directly. This is the case from bootloader version 1.8.0, and also holds for firmware version 1.8.0 because that installs the appropriate bootloader. """ f = client.features version = (f.major_version, f.minor_version, f.patch_version) bootloader_onev2 = f.major_version == 1 and version >= (1, 8, 0) return bootloader_onev2 def _get_file_name_from_url(url: str) -> str: """Parse the name of the file being downloaded from the specific url.""" full_path = urlparse(url).path return os.path.basename(full_path) def _print_firmware_model(hw_model: Union[bytes, fw_models.Model]) -> None: try: model_name = fw_models.Model.from_hw_model(hw_model).name click.echo(f"{model_name} firmware image.") return except ValueError: pass assert isinstance(hw_model, bytes) if all(0x20 <= b < 0x80 for b in hw_model): # isascii model_name = hw_model.decode("ascii") click.echo(f"Unknown hardware model: {model_name}") return click.echo(f"Suspicious hardware model code: {hw_model.hex()} ({hw_model!r})") def print_firmware_version(fw: "firmware.FirmwareType") -> None: """Print out the firmware version and details.""" if isinstance(fw, firmware.LegacyFirmware): if fw.embedded_v2: click.echo("Trezor One firmware with embedded v2 image (1.8.0 or later)") _print_version(fw.embedded_v2.header.version) else: click.echo("Trezor One firmware image.") elif isinstance(fw, firmware.LegacyV2Firmware): click.echo("Trezor One v2 firmware (1.8.0 or later)") _print_version(fw.header.version) elif isinstance(fw, firmware.VendorFirmware): _print_firmware_model(fw.vendor_header.hw_model) vendor = fw.vendor_header.text vendor_version = "{}.{}".format(*fw.vendor_header.version) click.echo(f"Vendor header from {vendor}, version {vendor_version}") _print_version(fw.firmware.header.version) def validate_signatures( fw: "firmware.FirmwareType", prompt_unsigned: bool = True ) -> None: """Check the signatures on the firmware. Prints the validity status. In case of Trezor One v1 prompts the user (as the signature is missing). Exits if the validation fails. """ try: fw.verify() except firmware.Unsigned: if not prompt_unsigned or not isinstance( fw, (firmware.LegacyFirmware, firmware.LegacyV2Firmware) ): click.echo("Firmware is not signed, aborting.") sys.exit(4) # allow legacy firmware without signatures if not click.confirm("No signatures found. Continue?", default=False): sys.exit(1) if firmware.is_onev2(fw): try: assert fw.embedded_v2 is not None fw.embedded_v2.verify_unsigned() except firmware.FirmwareIntegrityError as e: click.echo(e) click.echo("Firmware validation failed, aborting.") sys.exit(4) click.echo("Unsigned firmware looking OK.") except firmware.FirmwareIntegrityError as e: try: fw.verify(dev_keys=True) click.echo("WARNING: Firmware for development kit only.") except firmware.FirmwareIntegrityError: click.echo(e) click.echo("Firmware validation failed, aborting.") sys.exit(4) def validate_fingerprint( fw: "firmware.FirmwareType", expected_fingerprint: Optional[str] = None, ) -> None: """Determine and validate the firmware fingerprint. Prints the fingerprint. Exits if the validation fails. """ fingerprint = fw.digest().hex() click.echo(f"Firmware fingerprint: {fingerprint}") if firmware.is_onev2(fw): assert fw.embedded_v2 is not None fingerprint_onev2 = fw.embedded_v2.digest().hex() click.echo(f"Embedded v2 image fingerprint: {fingerprint_onev2}") if expected_fingerprint and fingerprint != expected_fingerprint: click.echo(f"Expected fingerprint: {expected_fingerprint}") click.echo("Fingerprints do not match, aborting.") sys.exit(5) def check_device_match( fw: "firmware.FirmwareType", model: TrezorModel, bootloader_onev2: bool ) -> None: """Validate if the device and firmware are compatible. Prints error message and exits if the validation fails. """ if (model is not models.T1B1) != isinstance(fw, firmware.VendorFirmware): click.echo("Firmware does not match your device, aborting.") sys.exit(3) if ( bootloader_onev2 and isinstance(fw, firmware.LegacyFirmware) and not fw.embedded_v2 ): click.echo("Firmware is too old for your device. Aborting.") sys.exit(3) elif not bootloader_onev2 and isinstance(fw, firmware.LegacyV2Firmware): click.echo("You need to upgrade to bootloader 1.8.0 first.") sys.exit(3) def get_all_firmware_releases( model: TrezorModel, bitcoin_only: bool, beta: bool ) -> List[Dict[str, Any]]: """Get sorted list of all releases suitable for inputted parameters""" url = f"https://data.trezor.io/firmware/{model.internal_name.lower()}/releases.json" req = requests.get(url) req.raise_for_status() releases = req.json() if not releases: raise click.ClickException("Failed to get list of releases") if bitcoin_only: releases = [r for r in releases if "url_bitcoinonly" in r] # filter releases according to channel field releases_stable = [ r for r in releases if "channel" not in r or r["channel"] == "stable" ] releases_beta = [r for r in releases if "channel" in r and r["channel"] == "beta"] if beta: releases = releases_stable + releases_beta else: releases = releases_stable releases.sort(key=lambda r: r["version"], reverse=True) return releases def get_url_and_fingerprint_from_release( release: dict, bitcoin_only: bool, ) -> Tuple[str, str]: """Get appropriate url and fingerprint from release dictionary.""" if bitcoin_only: url = release["url_bitcoinonly"] fingerprint = release["fingerprint_bitcoinonly"] else: url = release["url"] fingerprint = release["fingerprint"] url_prefix = "data/" if not url.startswith(url_prefix): click.echo(f"Unsupported URL found: {url}") sys.exit(1) final_url = "https://data.trezor.io/" + url[len(url_prefix) :] return final_url, fingerprint def find_specified_firmware_version( model: TrezorModel, version: str, beta: bool, bitcoin_only: bool, ) -> Tuple[str, str]: """Get the url from which to download the firmware and its expected fingerprint. If the specified version is not found, exits with a failure. """ want_version = [int(x) for x in version.split(".")] releases = get_all_firmware_releases(model, bitcoin_only, beta) for release in releases: if release["version"] == want_version: return get_url_and_fingerprint_from_release(release, bitcoin_only) click.echo(f"Version {version} for {model.internal_name} could not be found.") sys.exit(1) def _should_use_bitcoin_only(features: messages.Features) -> bool: # in bootloader, decide by unit indicator # TODO determine by fw vendor if installed? if features.bootloader_mode: return bool(features.unit_btconly) # in firmware, check whether current firmware is bitcoin-only if messages.Capability.Bitcoin_like not in features.capabilities: return True # universal firmware by default return False def find_best_firmware_version( client: "TrezorClient", version: Optional[str], beta: bool, bitcoin_only: Optional[bool], ) -> Tuple[str, str]: """Get the url from which to download the firmware and its expected fingerprint. When the version (X.Y.Z) is specified, checks for that specific release. Otherwise takes the latest one. If the specified version is not found, prints the closest available version (higher than the specified one, if existing). """ if bitcoin_only is None: bitcoin_only = _should_use_bitcoin_only(client.features) def version_str(version: Iterable[int]) -> str: return ".".join(map(str, version)) f = client.features releases = get_all_firmware_releases(client.model, bitcoin_only, beta) highest_version = releases[0]["version"] if version: want_version = [int(x) for x in version.split(".")] if len(want_version) != 3: click.echo("Please use the 'X.Y.Z' version format.") if want_version[0] != f.major_version: click.echo( f"Warning: Trezor {client.model.name} firmware version should be " f"{f.major_version}.X.Y (requested: {version})" ) else: want_version = highest_version click.echo(f"Best available version: {version_str(want_version)}") # Identifying the release we will install # It may happen that the different version will need to be installed first confirm_different_version = False while True: # The want_version can be changed below, need to redefine it want_version_str = version_str(want_version) try: release = next(r for r in releases if r["version"] == want_version) except StopIteration: click.echo(f"Version {want_version_str} not found for your device.") # look for versions starting with the lowest for release in reversed(releases): closest_version = release["version"] if closest_version > want_version: # stop at first that is higher than the requested break else: raise click.ClickException("No versions were found!") # if there was no break, the newest is used click.echo(f"Closest available version: {version_str(closest_version)}") if not beta and want_version > highest_version: click.echo("Hint: specify --beta to look for a beta release.") sys.exit(1) # It can be impossible to update from a very old version directly # to the newer one, in that case update to the minimal # compatible version first # Choosing the version key to compare based on (not) being in BL mode client_version = [f.major_version, f.minor_version, f.patch_version] if f.bootloader_mode: key_to_compare = "min_bootloader_version" else: key_to_compare = "min_firmware_version" if key_to_compare in release and release[key_to_compare] > client_version: need_version = release["min_firmware_version"] need_version_str = version_str(need_version) click.echo( f"Version {need_version_str} is required before upgrading to {want_version_str}." ) want_version = need_version confirm_different_version = True else: break if confirm_different_version: installing_different = f"Installing version {want_version_str} instead." if version is None: click.echo(installing_different) else: ok = click.confirm(installing_different + " Continue?", default=True) if not ok: sys.exit(1) return get_url_and_fingerprint_from_release(release, bitcoin_only) def download_firmware_data(url: str) -> bytes: try: click.echo(f"Downloading from {url}") r = requests.get(url) r.raise_for_status() return r.content except requests.exceptions.HTTPError as err: click.echo(f"Error downloading file: {err}") sys.exit(3) def validate_firmware( firmware_data: bytes, fingerprint: Optional[str] = None, model: Optional[TrezorModel] = None, bootloader_onev2: Optional[bool] = None, prompt_unsigned: bool = True, ) -> None: """Validate the firmware through multiple tests. - parsing it properly - containing valid signatures and fingerprint (when chosen) - being compatible with the device (when chosen) """ try: fw = firmware.parse(firmware_data) except Exception as e: click.echo(e) sys.exit(2) print_firmware_version(fw) validate_fingerprint(fw, fingerprint) validate_signatures(fw, prompt_unsigned=prompt_unsigned) if model is not None and bootloader_onev2 is not None: check_device_match(fw, model, bootloader_onev2) click.echo("Firmware is appropriate for your device.") def extract_embedded_fw( firmware_data: bytes, bootloader_onev2: bool, ) -> bytes: """Modify the firmware data for sending into Trezor, if necessary.""" # special handling for embedded_v2-OneV2 format: # for bootloader < 1.8, keep the embedding # for bootloader 1.8.0 and up, strip the old OneV1 header if ( bootloader_onev2 and firmware_data[:4] == b"TRZR" and firmware_data[256 : 256 + 4] == b"TRZF" ): click.echo("Extracting embedded_v2 firmware image.") return firmware_data[256:] return firmware_data def upload_firmware_into_device( client: "TrezorClient", firmware_data: bytes, ) -> None: """Perform the final act of loading the firmware into Trezor.""" f = client.features try: if f.major_version == 1 and f.firmware_present is not False: # Trezor One does not send ButtonRequest click.echo("Please confirm the action on your Trezor device") click.echo("Uploading...\r", nl=False) with click.progressbar( label="Uploading", length=len(firmware_data), show_eta=False ) as bar: firmware.update(client, firmware_data, bar.update) except exceptions.Cancelled: click.echo("Update aborted on device.") except exceptions.TrezorException as e: click.echo(f"Update failed: {e}") sys.exit(3) def _is_strict_update(client: "TrezorClient", firmware_data: bytes) -> bool: """Check if the firmware is from the same vendor and the firmware is newer than the currently installed firmware. """ try: fw = firmware.parse(firmware_data) except Exception as e: click.echo(e) sys.exit(2) if not isinstance(fw, firmware.VendorFirmware): return False f = client.features cur_version = (f.major_version, f.minor_version, f.patch_version, 0) return ( fw.vendor_header.text == f.fw_vendor and fw.firmware.header.version > cur_version and fw.vendor_header.trust.is_full_trust() ) def _get_firmware_header_size(firmware_data: bytes) -> int: """Returns size of vendor and image headers""" try: fw = firmware.parse(firmware_data) except Exception as e: click.echo(e) sys.exit(2) if isinstance(fw, firmware.VendorFirmware): return fw.firmware.header.header_len + fw.vendor_header.header_len return 0 @click.group(name="firmware") def cli() -> None: """Firmware commands.""" @cli.command() # fmt: off @click.argument("filename", type=click.File("rb")) @click.option("-c", "--check-device", is_flag=True, help="Validate device compatibility") @click.option("--fingerprint", help="Expected firmware fingerprint in hex") @click.pass_obj # fmt: on def verify( obj: "TrezorConnection", filename: BinaryIO, check_device: bool, fingerprint: Optional[str], ) -> None: """Verify the integrity of the firmware data stored in a file. By default the device is not checked and does not need to be connected. Its validation must be specified. In case of validation failure exits with the appropriate exit code. """ # Deciding if to take the device into account bootloader_onev2: Optional[bool] model: Optional[TrezorModel] if check_device: with obj.client_context() as client: bootloader_onev2 = _is_bootloader_onev2(client) model = client.model else: bootloader_onev2 = None model = None firmware_data = filename.read() validate_firmware( firmware_data=firmware_data, fingerprint=fingerprint, bootloader_onev2=bootloader_onev2, model=model, prompt_unsigned=False, ) @cli.command() # fmt: off @click.option("-o", "--output", type=click.File("wb"), help="Output file to save firmware data to") @click.option("-v", "--version", help="Which version to download") @click.option("-m", "--model", type=MODEL_CHOICE, help="Which model to download firmware for") @click.option("-s", "--skip-check", is_flag=True, help="Do not validate firmware integrity") @click.option("--beta", is_flag=True, help="Use firmware from BETA channel") @click.option("--bitcoin-only/--universal", is_flag=True, default=None, help="Download bitcoin-only or universal firmware (defaults to universal)") @click.option("--fingerprint", help="Expected firmware fingerprint in hex") @click.pass_obj # fmt: on def download( obj: "TrezorConnection", output: Optional[BinaryIO], model: Optional[TrezorModel], version: Optional[str], skip_check: bool, fingerprint: Optional[str], beta: bool, bitcoin_only: Optional[bool], ) -> None: """Download and save the firmware image. Validation is done by default, can be omitted by "-s" or "--skip-check". When fingerprint or output file are not set, take them from SL servers. """ # When a version is specified, we do not even need the client connection # (and we will not be checking device when validating) if model and version: url, fp = find_specified_firmware_version( model, version, beta, bool(bitcoin_only) ) bootloader_onev2 = None else: with obj.client_context() as client: url, fp = find_best_firmware_version( client=client, version=version, beta=beta, bitcoin_only=bitcoin_only ) bootloader_onev2 = _is_bootloader_onev2(client) if model is not None and model != client.model: click.echo("Warning: ignoring --model option.") model = client.model firmware_data = download_firmware_data(url) if not fingerprint: fingerprint = fp if not skip_check: validate_firmware( firmware_data=firmware_data, fingerprint=fingerprint, bootloader_onev2=bootloader_onev2, model=model, ) if not output: output = open(_get_file_name_from_url(url), "wb") output.write(firmware_data) output.close() click.echo(f"Firmware saved under {output.name}.") @cli.command() # fmt: off @click.option("-f", "--filename", type=click.File("rb"), help="File containing firmware data") @click.option("-u", "--url", help="Where to get the firmware from - full link") @click.option("-v", "--version", help="Which version to download") @click.option("-s", "--skip-check", is_flag=True, help="Do not validate firmware integrity") @click.option("-n", "--dry-run", is_flag=True, help="Perform all steps but do not actually upload the firmware") @click.option("-l", "--language", help="Language code, blob, or URL") @click.option("--beta", is_flag=True, help="Use firmware from BETA channel") @click.option("--bitcoin-only/--universal", is_flag=True, default=None, help="Download bitcoin-only or universal firmware (defaults to universal)") @click.option("--raw", is_flag=True, help="Push raw firmware data to Trezor") @click.option("--fingerprint", help="Expected firmware fingerprint in hex") # fmt: on @click.pass_obj def update( obj: "TrezorConnection", filename: Optional[BinaryIO], url: Optional[str], version: Optional[str], skip_check: bool, fingerprint: Optional[str], raw: bool, dry_run: bool, beta: bool, bitcoin_only: Optional[bool], language: Optional[str], ) -> None: """Upload new firmware to device. You can specify a filename or URL from which the firmware can be downloaded. You can also explicitly specify a firmware version that you want. Otherwise, trezorctl will attempt to find latest available version from data.trezor.io. If you provide a fingerprint via the --fingerprint option, it will be checked against downloaded firmware fingerprint. Otherwise fingerprint is checked against data.trezor.io information, if available. """ with obj.client_context() as client: if sum(bool(x) for x in (filename, url, version)) > 1: click.echo("You can use only one of: filename, url, version.") sys.exit(1) language_data = b"" if language is not None: if client.features.bootloader_mode: click.echo("Language data cannot be uploaded in bootloader mode.") sys.exit(1) assert language is not None try: language_data = Path(language).read_bytes() except Exception: try: language_data = requests.get(language).content except Exception: raise click.ClickException( f"Failed to load translations from {language}" ) from None if filename: firmware_data = filename.read() else: if not url: url, fp = find_best_firmware_version( client=client, version=version, beta=beta, bitcoin_only=bitcoin_only ) if not fingerprint: fingerprint = fp firmware_data = download_firmware_data(url) if not raw and not skip_check: validate_firmware( firmware_data=firmware_data, fingerprint=fingerprint, bootloader_onev2=_is_bootloader_onev2(client), model=client.model, ) if not raw: firmware_data = extract_embedded_fw( firmware_data=firmware_data, bootloader_onev2=_is_bootloader_onev2(client), ) if dry_run: click.echo("Dry run. Not uploading firmware to device.") return if not client.features.bootloader_mode: if _is_strict_update(client, firmware_data): header_size = _get_firmware_header_size(firmware_data) device.reboot_to_bootloader( client, boot_command=messages.BootCommand.INSTALL_UPGRADE, firmware_header=firmware_data[:header_size], language_data=language_data, ) else: if language_data: click.echo( "WARNING: Seamless installation not possible, language data will not be uploaded." ) device.reboot_to_bootloader(client) click.echo("Waiting for bootloader...") while True: time.sleep(0.5) try: obj.get_transport() break except Exception: pass with obj.client_context() as client: if not client.features.bootloader_mode: click.echo("Please switch your device to bootloader mode.") sys.exit(1) upload_firmware_into_device(client=client, firmware_data=firmware_data) @cli.command() @click.argument("hex_challenge", required=False) @with_client def get_hash(client: "TrezorClient", hex_challenge: Optional[str]) -> str: """Get a hash of the installed firmware combined with the optional challenge.""" challenge = bytes.fromhex(hex_challenge) if hex_challenge else None return firmware.get_hash(client, challenge).hex() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/monero.py0000664000175000017500000000461114636513242020566 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from typing import TYPE_CHECKING, Dict import click from .. import messages, monero, tools from . import ChoiceType, with_client if TYPE_CHECKING: from ..client import TrezorClient PATH_HELP = "BIP-32 path, e.g. m/44h/128h/0h" @click.group(name="monero") def cli() -> None: """Monero commands.""" @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @click.option( "-t", "--network-type", type=ChoiceType({m.name: m for m in messages.MoneroNetworkType}), default=messages.MoneroNetworkType.MAINNET, ) @click.option("-C", "--chunkify", is_flag=True) @with_client def get_address( client: "TrezorClient", address: str, show_display: bool, network_type: messages.MoneroNetworkType, chunkify: bool, ) -> bytes: """Get Monero address for specified path.""" address_n = tools.parse_path(address) return monero.get_address(client, address_n, show_display, network_type, chunkify) @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option( "-t", "--network-type", type=ChoiceType({m.name: m for m in messages.MoneroNetworkType}), default=messages.MoneroNetworkType.MAINNET, ) @with_client def get_watch_key( client: "TrezorClient", address: str, network_type: messages.MoneroNetworkType ) -> Dict[str, str]: """Get Monero watch key for specified path.""" address_n = tools.parse_path(address) res = monero.get_watch_key(client, address_n, network_type) # TODO: could be made required in MoneroWatchKey assert res.address is not None assert res.watch_key is not None return {"address": res.address.decode(), "watch_key": res.watch_key.hex()} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/nem.py0000664000175000017500000000475714636513242020061 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import json from typing import TYPE_CHECKING, Optional, TextIO import click import requests from .. import nem, tools from . import with_client if TYPE_CHECKING: from ..client import TrezorClient PATH_HELP = "BIP-32 path, e.g. m/44h/134h/0h/0h" @click.group(name="nem") def cli() -> None: """NEM commands.""" @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-N", "--network", type=int, default=0x68) @click.option("-d", "--show-display", is_flag=True) @click.option("-C", "--chunkify", is_flag=True) @with_client def get_address( client: "TrezorClient", address: str, network: int, show_display: bool, chunkify: bool, ) -> str: """Get NEM address for specified path.""" address_n = tools.parse_path(address) return nem.get_address(client, address_n, network, show_display, chunkify) @cli.command() @click.argument("file", type=click.File("r")) @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-f", "--file", "_ignore", is_flag=True, hidden=True, expose_value=False) @click.option("-b", "--broadcast", help="NIS to announce transaction to") @click.option("-C", "--chunkify", is_flag=True) @with_client def sign_tx( client: "TrezorClient", address: str, file: TextIO, broadcast: Optional[str], chunkify: bool, ) -> dict: """Sign (and optionally broadcast) NEM transaction. Transaction file is expected in the NIS (RequestPrepareAnnounce) format. """ address_n = tools.parse_path(address) transaction = nem.sign_tx(client, address_n, json.load(file), chunkify=chunkify) payload = {"data": transaction.data.hex(), "signature": transaction.signature.hex()} if broadcast: return requests.post(f"{broadcast}/transaction/announce", json=payload).json() else: return payload ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/ripple.py0000664000175000017500000000421414636513242020561 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import json from typing import TYPE_CHECKING, TextIO import click from .. import ripple, tools from . import with_client if TYPE_CHECKING: from ..client import TrezorClient PATH_HELP = "BIP-32 path to key, e.g. m/44h/144h/0h/0/0" @click.group(name="ripple") def cli() -> None: """Ripple commands.""" @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @click.option("-C", "--chunkify", is_flag=True) @with_client def get_address( client: "TrezorClient", address: str, show_display: bool, chunkify: bool ) -> str: """Get Ripple address""" address_n = tools.parse_path(address) return ripple.get_address(client, address_n, show_display, chunkify) @cli.command() @click.argument("file", type=click.File("r")) @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-f", "--file", "_ignore", is_flag=True, hidden=True, expose_value=False) @click.option("-C", "--chunkify", is_flag=True) @with_client def sign_tx(client: "TrezorClient", address: str, file: TextIO, chunkify: bool) -> None: """Sign Ripple transaction""" address_n = tools.parse_path(address) msg = ripple.create_sign_tx_msg(json.load(file)) result = ripple.sign_tx(client, address_n, msg, chunkify=chunkify) click.echo("Signature:") click.echo(result.signature.hex()) click.echo() click.echo("Serialized tx including the signature:") click.echo(result.serialized_tx.hex()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315793.0 trezor-0.13.9/src/trezorlib/cli/settings.py0000664000175000017500000003625614636526521021145 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from __future__ import annotations import io from pathlib import Path from typing import TYPE_CHECKING, Optional, cast import click import requests from .. import device, messages, toif from . import AliasedGroup, ChoiceType, with_client if TYPE_CHECKING: from ..client import TrezorClient try: from PIL import Image PIL_AVAILABLE = True except ImportError: PIL_AVAILABLE = False ROTATION = {"north": 0, "east": 90, "south": 180, "west": 270} SAFETY_LEVELS = { "strict": messages.SafetyCheckLevel.Strict, "prompt": messages.SafetyCheckLevel.PromptTemporarily, } T1_TR_IMAGE_SIZE = (128, 64) def image_to_t1(filename: Path) -> bytes: if not PIL_AVAILABLE: raise click.ClickException( "Image library is missing. Please install via 'pip install Pillow'." ) if filename.suffix == ".toif": raise click.ClickException("TOIF images not supported on Trezor One") try: image = Image.open(filename) except Exception as e: raise click.ClickException("Failed to load image") from e if image.size != T1_TR_IMAGE_SIZE: if click.confirm( f"Image is not 128x64, but {image.size}. Do you want to resize it automatically?", default=True, ): image = image.resize(T1_TR_IMAGE_SIZE, Image.Resampling.LANCZOS) else: raise click.ClickException("Wrong size of the image - should be 128x64") image = image.convert("1") return image.tobytes("raw", "1") def image_to_toif(filename: Path, width: int, height: int, greyscale: bool) -> bytes: if filename.suffix == ".toif": try: toif_image = toif.from_bytes(filename.read_bytes()) image = toif_image.to_image() except Exception as e: raise click.ClickException("TOIF file is corrupted") from e elif not PIL_AVAILABLE: raise click.ClickException( "Image library is missing. Please install via 'pip install Pillow'." ) else: try: image = Image.open(filename) toif_image = toif.from_image(image) except Exception as e: raise click.ClickException( "Failed to convert image to Trezor format" ) from e if toif_image.size != (width, height): if click.confirm( f"Image is not {width}x{height}, but {image.size[0]}x{image.size[1]}. Do you want to resize it automatically?", default=True, ): image = image.resize((width, height), Image.Resampling.LANCZOS) else: raise click.ClickException( f"Wrong size of image - should be {width}x{height}" ) if greyscale: image = image.convert("1") toif_image = toif.from_image(image) if not greyscale and toif_image.mode != toif.ToifMode.full_color: raise click.ClickException("Wrong image mode - should be full_color") if greyscale and toif_image.mode != toif.ToifMode.grayscale_eh: raise click.ClickException("Wrong image mode - should be grayscale_eh") return toif_image.to_bytes() def image_to_jpeg(filename: Path, width: int, height: int, quality: int = 90) -> bytes: if filename.suffix in (".jpg", ".jpeg") and not PIL_AVAILABLE: click.echo("Warning: Image library is missing, skipping image validation.") return filename.read_bytes() if not PIL_AVAILABLE: raise click.ClickException( "Image library is missing. Please install via 'pip install Pillow'." ) try: image = Image.open(filename) except Exception as e: raise click.ClickException("Failed to open image") from e if image.size != (width, height): if click.confirm( f"Image is not {width}x{height}, but {image.size[0]}x{image.size[1]}. Do you want to resize it automatically?", default=True, ): image = image.resize((width, height), Image.Resampling.LANCZOS) else: raise click.ClickException( f"Wrong size of image - should be {width}x{height}" ) if image.mode != "RGB": image = image.convert("RGB") buf = io.BytesIO() image.save(buf, format="jpeg", progressive=False, quality=quality) return buf.getvalue() def _should_remove(enable: Optional[bool], remove: bool) -> bool: """Helper to decide whether to remove something or not. Needed for backwards compatibility purposes, so we can support both positive (enable) and negative (remove) args. """ if remove and enable: raise click.ClickException("Argument and option contradict each other") if remove or enable is False: return True return False @click.group(name="set") def cli() -> None: """Device settings.""" @cli.command() @click.option("-r", "--remove", is_flag=True, hidden=True) @click.argument("enable", type=ChoiceType({"on": True, "off": False}), required=False) @with_client def pin(client: "TrezorClient", enable: Optional[bool], remove: bool) -> str: """Set, change or remove PIN.""" # Remove argument is there for backwards compatibility return device.change_pin(client, remove=_should_remove(enable, remove)) @cli.command() @click.option("-r", "--remove", is_flag=True, hidden=True) @click.argument("enable", type=ChoiceType({"on": True, "off": False}), required=False) @with_client def wipe_code(client: "TrezorClient", enable: Optional[bool], remove: bool) -> str: """Set or remove the wipe code. The wipe code functions as a "self-destruct PIN". If the wipe code is ever entered into any PIN entry dialog, then all private data will be immediately removed and the device will be reset to factory defaults. """ # Remove argument is there for backwards compatibility return device.change_wipe_code(client, remove=_should_remove(enable, remove)) @cli.command() # keep the deprecated -l/--label option, make it do nothing @click.option("-l", "--label", "_ignore", is_flag=True, hidden=True, expose_value=False) @click.argument("label") @with_client def label(client: "TrezorClient", label: str) -> str: """Set new device label.""" return device.apply_settings(client, label=label) @cli.command() @with_client def brightness(client: "TrezorClient") -> str: """Set display brightness.""" return device.set_brightness(client) @cli.command() @click.argument("enable", type=ChoiceType({"on": True, "off": False})) @with_client def haptic_feedback(client: "TrezorClient", enable: bool) -> str: """Enable or disable haptic feedback.""" return device.apply_settings(client, haptic_feedback=enable) @cli.command() @click.argument("path_or_url", required=False) @click.option( "-r", "--remove", is_flag=True, default=False, help="Switch back to english." ) @click.option("-d/-D", "--display/--no-display", default=None) @with_client def language( client: "TrezorClient", path_or_url: str | None, remove: bool, display: bool | None ) -> str: """Set new language with translations.""" if remove != (path_or_url is None): raise click.ClickException("Either provide a path or URL or use --remove") if remove: language_data = b"" else: assert path_or_url is not None if path_or_url.endswith(".json"): raise click.ClickException( "Provided file is a JSON file, not a blob file.\n" "Generate blobs by running `python core/translations/cli.py gen` in root." ) try: language_data = Path(path_or_url).read_bytes() except Exception: try: language_data = requests.get(path_or_url).content except Exception: raise click.ClickException( f"Failed to load translations from {path_or_url}" ) from None return device.change_language( client, language_data=language_data, show_display=display ) @cli.command() @click.argument("rotation", type=ChoiceType(ROTATION)) @with_client def display_rotation(client: "TrezorClient", rotation: int) -> str: """Set display rotation. Configure display rotation for Trezor Model T. The options are north, east, south or west. """ return device.apply_settings(client, display_rotation=rotation) @cli.command() @click.argument("delay", type=str) @with_client def auto_lock_delay(client: "TrezorClient", delay: str) -> str: """Set auto-lock delay (in seconds).""" if not client.features.pin_protection: raise click.ClickException("Set up a PIN first") value, unit = delay[:-1], delay[-1:] units = {"s": 1, "m": 60, "h": 3600} if unit in units: seconds = float(value) * units[unit] else: seconds = float(delay) # assume seconds if no unit is specified return device.apply_settings(client, auto_lock_delay_ms=int(seconds * 1000)) @cli.command() @click.argument("flags") @with_client def flags(client: "TrezorClient", flags: str) -> str: """Set device flags.""" if flags.lower().startswith("0b"): flags_int = int(flags, 2) elif flags.lower().startswith("0x"): flags_int = int(flags, 16) else: flags_int = int(flags) return device.apply_flags(client, flags=flags_int) @cli.command() @click.argument("filename") @click.option( "-f", "--filename", "_ignore", is_flag=True, hidden=True, expose_value=False ) @click.option("-q", "--quality", type=int, default=90, help="JPEG quality (0-100)") @with_client def homescreen(client: "TrezorClient", filename: str, quality: int) -> str: """Set new homescreen. To revert to default homescreen, use 'trezorctl set homescreen default' """ if filename == "default": img = b"" else: path = Path(filename) if not path.exists() or not path.is_file(): raise click.ClickException("Cannot open file") if client.features.model == "1": img = image_to_t1(path) else: if client.features.homescreen_format == messages.HomescreenFormat.Jpeg: width = ( client.features.homescreen_width if client.features.homescreen_width is not None else 240 ) height = ( client.features.homescreen_height if client.features.homescreen_height is not None else 240 ) img = image_to_jpeg(path, width, height, quality) elif client.features.homescreen_format == messages.HomescreenFormat.ToiG: width = client.features.homescreen_width height = client.features.homescreen_height if width is None or height is None: raise click.ClickException("Device did not report homescreen size.") img = image_to_toif(path, width, height, True) elif ( client.features.homescreen_format == messages.HomescreenFormat.Toif or client.features.homescreen_format is None ): width = ( client.features.homescreen_width if client.features.homescreen_width is not None else 144 ) height = ( client.features.homescreen_height if client.features.homescreen_height is not None else 144 ) img = image_to_toif(path, width, height, False) else: raise click.ClickException( "Unknown image format requested by the device." ) return device.apply_settings(client, homescreen=img) @cli.command() @click.option( "--always", is_flag=True, help='Persist the "prompt" setting across Trezor reboots.' ) @click.argument("level", type=ChoiceType(SAFETY_LEVELS)) @with_client def safety_checks( client: "TrezorClient", always: bool, level: messages.SafetyCheckLevel ) -> str: """Set safety check level. Set to "strict" to get the full Trezor security (default setting). Set to "prompt" if you want to be able to allow potentially unsafe actions, such as mismatching coin keys or extreme fees. This is a power-user feature. Use with caution. """ if always and level == messages.SafetyCheckLevel.PromptTemporarily: level = messages.SafetyCheckLevel.PromptAlways return device.apply_settings(client, safety_checks=level) @cli.command() @click.argument("enable", type=ChoiceType({"on": True, "off": False})) @with_client def experimental_features(client: "TrezorClient", enable: bool) -> str: """Enable or disable experimental message types. This is a developer feature. Use with caution. """ return device.apply_settings(client, experimental_features=enable) # # passphrase operations # # Using special class AliasedGroup, so we can support multiple commands # to invoke the same function to keep backwards compatibility @cli.command(cls=AliasedGroup, name="passphrase") def passphrase_main() -> None: """Enable, disable or configure passphrase protection.""" # this exists in order to support command aliases for "enable-passphrase" # and "disable-passphrase". Otherwise `passphrase` would just take an argument. # Cast for type-checking purposes passphrase = cast(AliasedGroup, passphrase_main) @passphrase.command(name="on") @click.option("-f/-F", "--force-on-device/--no-force-on-device", default=None) @with_client def passphrase_on(client: "TrezorClient", force_on_device: Optional[bool]) -> str: """Enable passphrase.""" if client.features.passphrase_protection is not True: use_passphrase = True else: use_passphrase = None return device.apply_settings( client, use_passphrase=use_passphrase, passphrase_always_on_device=force_on_device, ) @passphrase.command(name="off") @with_client def passphrase_off(client: "TrezorClient") -> str: """Disable passphrase.""" return device.apply_settings(client, use_passphrase=False) # Registering the aliases for backwards compatibility # (these are not shown in --help docs) passphrase.aliases = { "enabled": passphrase_on, "disabled": passphrase_off, } @passphrase.command(name="hide") @click.argument("hide", type=ChoiceType({"on": True, "off": False})) @with_client def hide_passphrase_from_host(client: "TrezorClient", hide: bool) -> str: """Enable or disable hiding passphrase coming from host. This is a developer feature. Use with caution. """ return device.apply_settings(client, hide_passphrase_from_host=hide) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/solana.py0000664000175000017500000000476714636513242020560 0ustar00matejcikmatejcikimport json from typing import TYPE_CHECKING, Optional, TextIO import click from .. import messages, solana, tools from . import with_client if TYPE_CHECKING: from ..client import TrezorClient PATH_HELP = "BIP-32 path to key, e.g. m/44h/501h/0h/0h" DEFAULT_PATH = "m/44h/501h/0h/0h" @click.group(name="solana") def cli() -> None: """Solana commands.""" @cli.command() @click.option("-n", "--address", default=DEFAULT_PATH, help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @with_client def get_public_key( client: "TrezorClient", address: str, show_display: bool, ) -> messages.SolanaPublicKey: """Get Solana public key.""" address_n = tools.parse_path(address) return solana.get_public_key(client, address_n, show_display) @cli.command() @click.option("-n", "--address", default=DEFAULT_PATH, help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @click.option("-C", "--chunkify", is_flag=True) @with_client def get_address( client: "TrezorClient", address: str, show_display: bool, chunkify: bool, ) -> messages.SolanaAddress: """Get Solana address.""" address_n = tools.parse_path(address) return solana.get_address(client, address_n, show_display, chunkify) @cli.command() @click.argument("serialized_tx", type=str) @click.option("-n", "--address", default=DEFAULT_PATH, help=PATH_HELP) @click.option("-a", "--additional-information-file", type=click.File("r")) @with_client def sign_tx( client: "TrezorClient", address: str, serialized_tx: str, additional_information_file: Optional[TextIO], ) -> messages.SolanaTxSignature: """Sign Solana transaction.""" address_n = tools.parse_path(address) additional_information = None if additional_information_file: raw_additional_information = json.load(additional_information_file) additional_information = messages.SolanaTxAdditionalInfo( token_accounts_infos=[ messages.SolanaTxTokenAccountInfo( base_address=token_account["base_address"], token_program=token_account["token_program"], token_mint=token_account["token_mint"], token_account=token_account["token_account"], ) for token_account in raw_additional_information["token_accounts_infos"] ] ) return solana.sign_tx( client, address_n, bytes.fromhex(serialized_tx), additional_information, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/stellar.py0000664000175000017500000000646214636513242020743 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import base64 import sys from typing import TYPE_CHECKING import click from .. import stellar, tools from . import with_client if TYPE_CHECKING: from ..client import TrezorClient try: from stellar_sdk import ( FeeBumpTransactionEnvelope, parse_transaction_envelope_from_xdr, ) except ImportError: pass PATH_HELP = "BIP32 path. Always use hardened paths and the m/44h/148h/ prefix" @click.group(name="stellar") def cli() -> None: """Stellar commands.""" @cli.command() @click.option( "-n", "--address", required=False, help=PATH_HELP, default=stellar.DEFAULT_BIP32_PATH, ) @click.option("-d", "--show-display", is_flag=True) @click.option("-C", "--chunkify", is_flag=True) @with_client def get_address( client: "TrezorClient", address: str, show_display: bool, chunkify: bool ) -> str: """Get Stellar public address.""" address_n = tools.parse_path(address) return stellar.get_address(client, address_n, show_display, chunkify) @cli.command() @click.option( "-n", "--address", required=False, help=PATH_HELP, default=stellar.DEFAULT_BIP32_PATH, ) @click.option( "-n", "--network-passphrase", default=stellar.DEFAULT_NETWORK_PASSPHRASE, required=False, help="Network passphrase (blank for public network).", ) @click.argument("b64envelope") @with_client def sign_transaction( client: "TrezorClient", b64envelope: str, address: str, network_passphrase: str ) -> bytes: """Sign a base64-encoded transaction envelope. For testnet transactions, use the following network passphrase: 'Test SDF Network ; September 2015' """ if not stellar.HAVE_STELLAR_SDK: click.echo("Stellar requirements not installed.") click.echo("Please run:") click.echo() click.echo(" pip install stellar-sdk") sys.exit(1) try: envelope = parse_transaction_envelope_from_xdr(b64envelope, network_passphrase) except Exception: click.echo( "Failed to parse XDR.\n" "Make sure to pass a valid TransactionEnvelope object.\n" "You can check whether the data you submitted is valid TransactionEnvelope object " "through XDRViewer - https://laboratory.stellar.org/#xdr-viewer\n" ) sys.exit(1) if isinstance(envelope, FeeBumpTransactionEnvelope): click.echo("FeeBumpTransactionEnvelope is not supported") sys.exit(1) address_n = tools.parse_path(address) tx, operations = stellar.from_envelope(envelope) resp = stellar.sign_tx(client, tx, operations, address_n, network_passphrase) return base64.b64encode(resp.signature) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/tezos.py0000664000175000017500000000460514636513242020436 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import json from typing import TYPE_CHECKING, TextIO import click from .. import messages, protobuf, tezos, tools from . import with_client if TYPE_CHECKING: from ..client import TrezorClient PATH_HELP = "BIP-32 path, e.g. m/44h/1729h/0h" @click.group(name="tezos") def cli() -> None: """Tezos commands.""" @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @click.option("-C", "--chunkify", is_flag=True) @with_client def get_address( client: "TrezorClient", address: str, show_display: bool, chunkify: bool ) -> str: """Get Tezos address for specified path.""" address_n = tools.parse_path(address) return tezos.get_address(client, address_n, show_display, chunkify) @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @with_client def get_public_key(client: "TrezorClient", address: str, show_display: bool) -> str: """Get Tezos public key.""" address_n = tools.parse_path(address) return tezos.get_public_key(client, address_n, show_display) @cli.command() @click.argument("file", type=click.File("r")) @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-f", "--file", "_ignore", is_flag=True, hidden=True, expose_value=False) @click.option("-C", "--chunkify", is_flag=True) @with_client def sign_tx( client: "TrezorClient", address: str, file: TextIO, chunkify: bool ) -> messages.TezosSignedTx: """Sign Tezos transaction.""" address_n = tools.parse_path(address) msg = protobuf.dict_to_proto(messages.TezosSignTx, json.load(file)) return tezos.sign_tx(client, address_n, msg, chunkify=chunkify) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cli/trezorctl.py0000775000175000017500000003076114636513242021327 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import json import logging import os import time from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, TypeVar, cast import click from .. import __version__, log, messages, protobuf, ui from ..client import TrezorClient from ..transport import DeviceIsBusy, enumerate_devices from ..transport.udp import UdpTransport from . import ( AliasedGroup, TrezorConnection, binance, btc, cardano, cosi, crypto, debug, device, eos, ethereum, fido, firmware, monero, nem, ripple, settings, solana, stellar, tezos, with_client, ) F = TypeVar("F", bound=Callable) if TYPE_CHECKING: from ..transport import Transport LOG = logging.getLogger(__name__) COMMAND_ALIASES = { "change-pin": settings.pin, "enable-passphrase": settings.passphrase_on, "disable-passphrase": settings.passphrase_off, "wipe-device": device.wipe, "reset-device": device.setup, "recovery-device": device.recover, "backup-device": device.backup, "sd-protect": device.sd_protect, "load-device": device.load, "prodtest-t1": debug.prodtest_t1, "get-entropy": crypto.get_entropy, "encrypt-keyvalue": crypto.encrypt_keyvalue, "decrypt-keyvalue": crypto.decrypt_keyvalue, # currency name aliases: "bnb": binance.cli, "eth": ethereum.cli, "ada": cardano.cli, "sol": solana.cli, "xmr": monero.cli, "xrp": ripple.cli, "xlm": stellar.cli, "xtz": tezos.cli, # firmware aliases: "fw": firmware.cli, "update-firmware": firmware.update, "upgrade-firmware": firmware.update, "firmware-upgrade": firmware.update, "firmware-update": firmware.update, } class TrezorctlGroup(AliasedGroup): """Command group that handles compatibility for trezorctl. With trezorctl 0.11.5, we started to convert old-style long commands (such as "binance-sign-tx") to command groups ("binance") with subcommands ("sign-tx"). The `TrezorctlGroup` can perform subcommand lookup: if a command "binance-sign-tx" does not exist in the default group, it tries to find "sign-tx" subcommand of "binance" group. """ def get_command(self, ctx: click.Context, cmd_name: str) -> Optional[click.Command]: cmd = super().get_command(ctx, cmd_name) if cmd: return cmd # the subsequent lookups rely on dash-separated command names cmd_name = cmd_name.replace("_", "-") # look for subcommand in btc - "sign-tx" is now "btc sign-tx" cmd = btc.cli.get_command(ctx, cmd_name) if cmd: return cmd # Old-style top-level commands looked like this: binance-sign-tx. # We are moving to 'binance' command with 'sign-tx' subcommand. try: command, subcommand = cmd_name.split("-", maxsplit=1) # get_command can return None and the following line will fail. # We don't care, we ignore the exception anyway. return super().get_command(ctx, command).get_command(ctx, subcommand) # type: ignore [get_command] except Exception: pass return None def set_result_callback(self) -> Callable[[F], F]: """Set a function called to format the return value of a command. Compatibility wrapper for Click 7.x `resultcallback` and >=8.1 `result_callback` """ # Click 7.x uses `resultcallback` to configure the callback, and # `result_callback` to store its value. # Click 8.x uses `result_callback` to configure the callback, and # `_result_callback` to store its value. # Click 8.0 has a `resultcallback` function that emits a warning and delegates # to `result_callback`. Click 8.1 removes this function. # # This means that there is no reasonable way to use `hasattr` to detect where we # are, unless we want to look at the private `_result_callback` attribute. # Instead, we look at Click version and hope for the best. from click import __version__ as click_version if click_version.startswith("7."): return super().resultcallback() # type: ignore [Cannot access attribute] else: return super().result_callback() def configure_logging(verbose: int) -> None: if verbose: log.enable_debug_output(verbose) log.OMITTED_MESSAGES.add(messages.Features) @click.command( cls=TrezorctlGroup, context_settings={"max_content_width": 400}, aliases=COMMAND_ALIASES, ) @click.option( "-p", "--path", help="Select device by specific path.", default=os.environ.get("TREZOR_PATH"), ) @click.option("-v", "--verbose", count=True, help="Show communication messages.") @click.option( "-j", "--json", "is_json", is_flag=True, help="Print result as JSON object" ) @click.option( "-P", "--passphrase-on-host", is_flag=True, help="Enter passphrase on host.", ) @click.option( "-S", "--script", is_flag=True, help="Use UI for usage in scripts.", ) @click.option( "-s", "--session-id", metavar="HEX", help="Resume given session ID.", default=os.environ.get("TREZOR_SESSION_ID"), ) @click.option( "-r", "--record", help="Record screen changes into a specified directory.", ) @click.version_option(version=__version__) @click.pass_context def cli_main( ctx: click.Context, path: str, verbose: int, is_json: bool, passphrase_on_host: bool, script: bool, session_id: Optional[str], record: Optional[str], ) -> None: configure_logging(verbose) bytes_session_id: Optional[bytes] = None if session_id is not None: try: bytes_session_id = bytes.fromhex(session_id) except ValueError: raise click.ClickException(f"Not a valid session id: {session_id}") ctx.obj = TrezorConnection(path, bytes_session_id, passphrase_on_host, script) # Optionally record the screen into a specified directory. if record: debug.record_screen_from_connection(ctx.obj, record) # Creating a cli function that has the right types for future usage cli = cast(TrezorctlGroup, cli_main) @cli.set_result_callback() def print_result(res: Any, is_json: bool, script: bool, **kwargs: Any) -> None: if is_json: if isinstance(res, protobuf.MessageType): res = protobuf.to_dict(res, hexlify_bytes=True) # No newlines for scripts, pretty-print for users if script: click.echo(json.dumps(res)) else: click.echo(json.dumps(res, sort_keys=True, indent=4)) else: if isinstance(res, list): for line in res: click.echo(line) elif isinstance(res, dict): for k, v in res.items(): if isinstance(v, dict): for kk, vv in v.items(): click.echo(f"{k}.{kk}: {vv}") else: click.echo(f"{k}: {v}") elif isinstance(res, protobuf.MessageType): click.echo(protobuf.format_message(res)) elif res is not None: click.echo(res) @cli.set_result_callback() @click.pass_obj def stop_recording_action(obj: TrezorConnection, *args: Any, **kwargs: Any) -> None: """Stop recording screen changes when the recording was started by `cli_main`. (When user used the `-r / --record` option of `trezorctl` command.) It allows for isolating screen directories only for specific actions/commands. """ if kwargs.get("record"): debug.record_screen_from_connection(obj, None) def format_device_name(features: messages.Features) -> str: model = features.model or "1" if features.bootloader_mode: return f"Trezor {model} bootloader" label = features.label or "(unnamed)" return f"{label} [Trezor {model}, {features.device_id}]" # # Common functions # @cli.command(name="list") @click.option("-n", "no_resolve", is_flag=True, help="Do not resolve Trezor names") def list_devices(no_resolve: bool) -> Optional[Iterable["Transport"]]: """List connected Trezor devices.""" if no_resolve: return enumerate_devices() for transport in enumerate_devices(): try: client = TrezorClient(transport, ui=ui.ClickUI()) description = format_device_name(client.features) client.end_session() except DeviceIsBusy: description = "Device is in use by another process" except Exception: description = "Failed to read details" click.echo(f"{transport} - {description}") return None @cli.command() def version() -> str: """Show version of trezorctl/trezorlib.""" return __version__ # # Basic device functions # @cli.command() @click.argument("message") @click.option("-b", "--button-protection", is_flag=True) @with_client def ping(client: "TrezorClient", message: str, button_protection: bool) -> str: """Send ping message.""" return client.ping(message, button_protection=button_protection) @cli.command() @click.pass_obj def get_session(obj: TrezorConnection) -> str: """Get a session ID for subsequent commands. Unlocks Trezor with a passphrase and returns a session ID. Use this session ID with `trezorctl -s SESSION_ID`, or set it to an environment variable `TREZOR_SESSION_ID`, to avoid having to enter passphrase for subsequent commands. The session ID is valid until another client starts using Trezor, until the next get-session call, or until Trezor is disconnected. """ # make sure session is not resumed obj.session_id = None with obj.client_context() as client: if client.features.model == "1" and client.version < (1, 9, 0): raise click.ClickException( "Upgrade your firmware to enable session support." ) client.ensure_unlocked() if client.session_id is None: raise click.ClickException("Passphrase not enabled or firmware too old.") else: return client.session_id.hex() @cli.command() @with_client def clear_session(client: "TrezorClient") -> None: """Clear session (remove cached PIN, passphrase, etc.).""" return client.clear_session() @cli.command() @with_client def get_features(client: "TrezorClient") -> messages.Features: """Retrieve device features and settings.""" return client.features @cli.command() def usb_reset() -> None: """Perform USB reset on stuck devices. This can fix LIBUSB_ERROR_PIPE and similar errors when connecting to a device in a messed state. """ from ..transport.webusb import WebUsbTransport WebUsbTransport.enumerate(usb_reset=True) @cli.command() @click.option("-t", "--timeout", type=float, default=10, help="Timeout in seconds") @click.pass_obj def wait_for_emulator(obj: TrezorConnection, timeout: float) -> None: """Wait until Trezor Emulator comes up. Tries to connect to emulator and returns when it succeeds. """ path = obj.path if path: if not path.startswith("udp:"): raise click.ClickException(f"You must use UDP path, not {path}") path = path.replace("udp:", "") start = time.monotonic() UdpTransport(path).wait_until_ready(timeout) end = time.monotonic() LOG.info(f"Waited for {end - start:.3f} seconds") # # Basic coin functions # cli.add_command(binance.cli) cli.add_command(btc.cli) cli.add_command(cardano.cli) cli.add_command(cosi.cli) cli.add_command(crypto.cli) cli.add_command(device.cli) cli.add_command(eos.cli) cli.add_command(ethereum.cli) cli.add_command(fido.cli) cli.add_command(monero.cli) cli.add_command(nem.cli) cli.add_command(ripple.cli) cli.add_command(settings.cli) cli.add_command(solana.cli) cli.add_command(stellar.cli) cli.add_command(tezos.cli) cli.add_command(firmware.cli) cli.add_command(debug.cli) # # Main # if __name__ == "__main__": cli() # pylint: disable=E1120 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/client.py0000664000175000017500000004570314636513242020005 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import logging import os import warnings from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar from mnemonic import Mnemonic from . import exceptions, mapping, messages, models from .log import DUMP_BYTES from .messages import Capability from .tools import expect, parse_path, session if TYPE_CHECKING: from .protobuf import MessageType from .transport import Transport from .ui import TrezorClientUI UI = TypeVar("UI", bound="TrezorClientUI") LOG = logging.getLogger(__name__) MAX_PASSPHRASE_LENGTH = 50 MAX_PIN_LENGTH = 50 PASSPHRASE_ON_DEVICE = object() PASSPHRASE_TEST_PATH = parse_path("44h/1h/0h/0/0") OUTDATED_FIRMWARE_ERROR = """ Your Trezor firmware is out of date. Update it with the following command: trezorctl firmware-update Or visit https://suite.trezor.io/ """.strip() def get_default_client( path: Optional[str] = None, ui: Optional["TrezorClientUI"] = None, **kwargs: Any ) -> "TrezorClient": """Get a client for a connected Trezor device. Returns a TrezorClient instance with minimum fuss. If path is specified, does a prefix-search for the specified device. Otherwise, uses the value of TREZOR_PATH env variable, or finds first connected Trezor. If no UI is supplied, instantiates the default CLI UI. """ from .transport import get_transport from .ui import ClickUI if path is None: path = os.getenv("TREZOR_PATH") transport = get_transport(path, prefix_search=True) if ui is None: ui = ClickUI() return TrezorClient(transport, ui, **kwargs) class TrezorClient(Generic[UI]): """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). """ model: models.TrezorModel transport: "Transport" session_id: Optional[bytes] ui: UI features: messages.Features def __init__( self, transport: "Transport", ui: UI, session_id: Optional[bytes] = None, derive_cardano: Optional[bool] = None, model: Optional[models.TrezorModel] = None, _init_device: bool = True, ) -> None: """Create a TrezorClient instance. 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 `session_id` you might have saved in the previous session. If you do, the user might not need to enter their passphrase again. You can provide Trezor model information. If not provided, it is detected from the model name reported at initialization time. By default, the instance will open a connection to the Trezor device, send an `Initialize` message, set up the `features` field from the response, and connect to a session. By specifying `_init_device=False`, this step is skipped. Notably, this means that `client.features` is unset. Use `client.init_device()` or `client.refresh_features()` to fix that, otherwise A LOT OF THINGS will break. Only use this if you are _sure_ that you know what you are doing. This feature might be removed at any time. """ LOG.info(f"creating client instance for device: {transport.get_path()}") # Here, self.model could be set to None. Unless _init_device is False, it will # get correctly reconfigured as part of the init_device flow. self.model = model # type: ignore ["None" is incompatible with "TrezorModel"] if self.model: self.mapping = self.model.default_mapping else: self.mapping = mapping.DEFAULT_MAPPING self.transport = transport self.ui = ui self.session_counter = 0 self.session_id = session_id if _init_device: self.init_device(session_id=session_id, derive_cardano=derive_cardano) def open(self) -> None: if self.session_counter == 0: self.transport.begin_session() self.session_counter += 1 def close(self) -> None: self.session_counter = max(self.session_counter - 1, 0) if self.session_counter == 0: # TODO call EndSession here? self.transport.end_session() def cancel(self) -> None: self._raw_write(messages.Cancel()) def call_raw(self, msg: "MessageType") -> "MessageType": __tracebackhide__ = True # for pytest # pylint: disable=W0612 self._raw_write(msg) return self._raw_read() def _raw_write(self, msg: "MessageType") -> None: __tracebackhide__ = True # for pytest # pylint: disable=W0612 LOG.debug( f"sending message: {msg.__class__.__name__}", extra={"protobuf": msg}, ) msg_type, msg_bytes = self.mapping.encode(msg) LOG.log( DUMP_BYTES, f"encoded as type {msg_type} ({len(msg_bytes)} bytes): {msg_bytes.hex()}", ) self.transport.write(msg_type, msg_bytes) def _raw_read(self) -> "MessageType": __tracebackhide__ = True # for pytest # pylint: disable=W0612 msg_type, msg_bytes = self.transport.read() LOG.log( DUMP_BYTES, f"received type {msg_type} ({len(msg_bytes)} bytes): {msg_bytes.hex()}", ) msg = self.mapping.decode(msg_type, msg_bytes) LOG.debug( f"received message: {msg.__class__.__name__}", extra={"protobuf": msg}, ) return msg def _callback_pin(self, msg: messages.PinMatrixRequest) -> "MessageType": try: pin = self.ui.get_pin(msg.type) except exceptions.Cancelled: self.call_raw(messages.Cancel()) raise if any(d not in "123456789" for d in pin) or not ( 1 <= len(pin) <= MAX_PIN_LENGTH ): self.call_raw(messages.Cancel()) raise ValueError("Invalid 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: messages.PassphraseRequest) -> "MessageType": available_on_device = Capability.PassphraseEntry in self.features.capabilities def send_passphrase( passphrase: Optional[str] = None, on_device: Optional[bool] = None ) -> "MessageType": msg = messages.PassphraseAck(passphrase=passphrase, on_device=on_device) resp = self.call_raw(msg) if isinstance(resp, messages.Deprecated_PassphraseStateRequest): self.session_id = resp.state resp = self.call_raw(messages.Deprecated_PassphraseStateAck()) return resp # short-circuit old style entry if msg._on_device is True: return send_passphrase(None, None) try: passphrase = self.ui.get_passphrase(available_on_device=available_on_device) except exceptions.Cancelled: self.call_raw(messages.Cancel()) raise if passphrase is PASSPHRASE_ON_DEVICE: if not available_on_device: self.call_raw(messages.Cancel()) raise RuntimeError("Device is not capable of entering passphrase") else: return send_passphrase(on_device=True) # else process host-entered passphrase if not isinstance(passphrase, str): raise RuntimeError("Passphrase must be a str") passphrase = Mnemonic.normalize_string(passphrase) if len(passphrase) > MAX_PASSPHRASE_LENGTH: self.call_raw(messages.Cancel()) raise ValueError("Passphrase too long") return send_passphrase(passphrase, on_device=False) def _callback_button(self, msg: messages.ButtonRequest) -> "MessageType": __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) return self._raw_read() @session def call(self, msg: "MessageType") -> "MessageType": 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 def _refresh_features(self, features: messages.Features) -> None: """Update internal fields based on passed-in Features message.""" if not self.model: # Trezor Model One bootloader 1.8.0 or older does not send model name model = models.by_internal_name(features.internal_model) if model is None: model = models.by_name(features.model or "1") if model is None: raise RuntimeError( "Unsupported Trezor model" f" (internal_model: {features.internal_model}, model: {features.model})" ) self.model = model if features.vendor not in self.model.vendors: raise RuntimeError("Unsupported device") self.features = features self.version = ( self.features.major_version, self.features.minor_version, self.features.patch_version, ) self.check_firmware_version(warn_only=True) if self.features.session_id is not None: self.session_id = self.features.session_id self.features.session_id = None @session def refresh_features(self) -> messages.Features: """Reload features from the device. Should be called after changing settings or performing operations that affect device state. """ resp = self.call_raw(messages.GetFeatures()) if not isinstance(resp, messages.Features): raise exceptions.TrezorException("Unexpected response to GetFeatures") self._refresh_features(resp) return resp @session def init_device( self, *, session_id: Optional[bytes] = None, new_session: bool = False, derive_cardano: Optional[bool] = None, ) -> Optional[bytes]: """Initialize the device and return a session ID. You can optionally specify a session ID. If the session still exists on the device, the same session ID will be returned and the session is resumed. Otherwise a different session ID is returned. Specify `new_session=True` to open a fresh session. Since firmware version 1.9.0/2.3.0, the previous session will remain cached on the device, and can be resumed by calling `init_device` again with the appropriate session ID. If neither `new_session` nor `session_id` is specified, the current session ID will be reused. If no session ID was cached, a new session ID will be allocated and returned. # Version notes: Trezor One older than 1.9.0 does not have session management. Optional arguments have no effect and the function returns None Trezor T older than 2.3.0 does not have session cache. Requesting a new session will overwrite the old one. In addition, this function will always return None. A valid session_id can be obtained from the `session_id` attribute, but only after a passphrase-protected call is performed. You can use the following code: >>> client.init_device() >>> client.ensure_unlocked() >>> valid_session_id = client.session_id """ if new_session: self.session_id = None elif session_id is not None: self.session_id = session_id resp = self.call_raw( messages.Initialize( session_id=self.session_id, derive_cardano=derive_cardano, ) ) if isinstance(resp, messages.Failure): # can happen if `derive_cardano` does not match the current session raise exceptions.TrezorFailure(resp) if not isinstance(resp, messages.Features): raise exceptions.TrezorException("Unexpected response to Initialize") if self.session_id is not None and resp.session_id == self.session_id: LOG.info("Successfully resumed session") elif session_id is not None: LOG.info("Failed to resume session") # TT < 2.3.0 compatibility: # _refresh_features will clear out the session_id field. We want this function # to return its value, so that callers can rely on it being either a valid # session_id, or None if we can't do that. # Older TT FW does not report session_id in Features and self.session_id might # be invalid because TT will not allocate a session_id until a passphrase # exchange happens. reported_session_id = resp.session_id self._refresh_features(resp) return reported_session_id def is_outdated(self) -> bool: if self.features.bootloader_mode: return False return self.version < self.model.minimum_version def check_firmware_version(self, warn_only: bool = False) -> None: if self.is_outdated(): if warn_only: warnings.warn("Firmware is out of date", stacklevel=2) else: raise exceptions.OutdatedFirmwareError(OUTDATED_FIRMWARE_ERROR) @expect(messages.Success, field="message", ret_type=str) def ping( self, msg: str, button_protection: bool = False, ) -> "MessageType": # 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: # XXX this should be: `with self:` try: self.open() resp = self.call_raw(messages.Ping(message=msg)) if isinstance(resp, messages.ButtonRequest): # device is PIN-locked. # respond and hope for the best resp = self._callback_button(resp) return resp finally: self.close() return self.call( messages.Ping(message=msg, button_protection=button_protection) ) def get_device_id(self) -> Optional[str]: return self.features.device_id @session def lock(self, *, _refresh_features: bool = True) -> None: """Lock the device. If the device does not have a PIN configured, this will do nothing. Otherwise, a lock screen will be shown and the device will prompt for PIN before further actions. This call does _not_ invalidate passphrase cache. If passphrase is in use, the device will not prompt for it after unlocking. To invalidate passphrase cache, use `end_session()`. To lock _and_ invalidate passphrase cache, use `clear_session()`. """ # Private argument _refresh_features can be used internally to avoid # refreshing in cases where we will refresh soon anyway. This is used # in TrezorClient.clear_session() self.call(messages.LockDevice()) if _refresh_features: self.refresh_features() @session def ensure_unlocked(self) -> None: """Ensure the device is unlocked and a passphrase is cached. If the device is locked, this will prompt for PIN. If passphrase is enabled and no passphrase is cached for the current session, the device will also prompt for passphrase. After calling this method, further actions on the device will not prompt for PIN or passphrase until the device is locked or the session becomes invalid. """ from .btc import get_address get_address(self, "Testnet", PASSPHRASE_TEST_PATH) self.refresh_features() def end_session(self) -> None: """Close the current session and clear cached passphrase. The session will become invalid until `init_device()` is called again. If passphrase is enabled, further actions will prompt for it again. This is a no-op in bootloader mode, as it does not support session management. """ # since: 2.3.4, 1.9.4 try: if not self.features.bootloader_mode: self.call(messages.EndSession()) except exceptions.TrezorFailure: # A failure most likely means that the FW version does not support # the EndSession call. We ignore the failure and clear the local session_id. # The client-side end result is identical. pass self.session_id = None @session def clear_session(self) -> None: """Lock the device and present a fresh session. The current session will be invalidated and a new one will be started. If the device has PIN enabled, it will become locked. Equivalent to calling `lock()`, `end_session()` and `init_device()`. """ self.lock(_refresh_features=False) self.end_session() self.init_device(new_session=True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/cosi.py0000664000175000017500000001470414636513242017461 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import warnings from functools import reduce from typing import TYPE_CHECKING, Iterable, Optional, Sequence, Tuple from . import _ed25519, messages from .tools import expect if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType from .tools import Address # 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_combined( signature: Ed25519Signature, digest: bytes, pub_key: Ed25519PublicPoint ) -> None: """Verify Ed25519 signature. Raise exception if the signature is invalid. A CoSi combined signature is equivalent to a plain Ed25519 signature with a public key that is a combination of the cosigners' public keys. This function takes the combined public key and performs simple Ed25519 verification. """ # XXX this *might* change to bool function _ed25519.checkvalid(signature, digest, pub_key) def verify( signature: Ed25519Signature, digest: bytes, sigs_required: int, keys: Sequence[Ed25519PublicPoint], mask: int, ) -> None: """Verify a CoSi multi-signature. Raise exception if the signature is invalid. This function verifies a M-of-N signature scheme. The arguments are: - the minimum number M of signatures required - public keys of all N possible cosigners - a bitmask specifying which of the N cosigners have produced the signature. The verification checks that the mask specifies at least M cosigners, then combines the selected public keys and verifies the signature against the combined key. """ if sigs_required < 1: raise ValueError("At least one signer must be specified.") if mask.bit_length() > len(keys): raise ValueError("Sigmask specifies more public keys than provided.") selected_keys = [key for i, key in enumerate(keys) if mask & (1 << i)] if len(selected_keys) < sigs_required: raise _ed25519.SignatureMismatch("Insufficient number of signatures.") global_pk = combine_keys(selected_keys) return verify_combined(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)) def sign_with_privkeys(digest: bytes, privkeys: Sequence[bytes]) -> bytes: """Locally produce a CoSi signature from a list of private keys.""" pubkeys = [pubkey_from_privkey(sk) for sk in privkeys] nonces = [get_nonce(sk, digest, i) for i, sk in enumerate(privkeys)] global_pk = combine_keys(pubkeys) global_R = combine_keys(R for _, R in nonces) sigs = [ sign_with_privkey(digest, sk, global_pk, r, global_R) for sk, (r, _) in zip(privkeys, nonces) ] return combine_sig(global_R, sigs) # ====== Client functions ====== # @expect(messages.CosiCommitment) def commit( client: "TrezorClient", n: "Address", data: Optional[bytes] = None ) -> "MessageType": if data is not None: warnings.warn( "'data' argument is deprecated", DeprecationWarning, stacklevel=2, ) return client.call(messages.CosiCommit(address_n=n)) @expect(messages.CosiSignature) def sign( client: "TrezorClient", n: "Address", data: bytes, global_commitment: bytes, global_pubkey: bytes, ) -> "MessageType": return client.call( messages.CosiSign( address_n=n, data=data, global_commitment=global_commitment, global_pubkey=global_pubkey, ) ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/debuglink.py0000664000175000017500000014100114636513242020457 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import json import logging import re import textwrap import time from copy import deepcopy from datetime import datetime from enum import IntEnum from itertools import zip_longest from pathlib import Path from typing import ( TYPE_CHECKING, Any, Callable, Dict, Generator, Iterable, Iterator, List, Optional, Sequence, Tuple, Type, Union, overload, ) from mnemonic import Mnemonic from typing_extensions import Literal from . import mapping, messages, models, protobuf from .client import TrezorClient from .exceptions import TrezorFailure from .log import DUMP_BYTES from .tools import expect if TYPE_CHECKING: from .messages import PinMatrixRequestType from .transport import Transport ExpectedMessage = Union[ protobuf.MessageType, Type[protobuf.MessageType], "MessageFilter" ] AnyDict = Dict[str, Any] EXPECTED_RESPONSES_CONTEXT_LINES = 3 LOG = logging.getLogger(__name__) class UnstructuredJSONReader: """Contains data-parsing helpers for JSON data that have unknown structure.""" def __init__(self, json_str: str) -> None: self.json_str = json_str # We may not receive valid JSON, e.g. from an old model in upgrade tests try: self.dict: "AnyDict" = json.loads(json_str) except json.JSONDecodeError: self.dict = {} def top_level_value(self, key: str) -> Any: return self.dict.get(key) def find_objects_with_key_and_value(self, key: str, value: Any) -> List["AnyDict"]: def recursively_find(data: Any) -> Iterator[Any]: if isinstance(data, dict): if data.get(key) == value: yield data for val in data.values(): yield from recursively_find(val) elif isinstance(data, list): for item in data: yield from recursively_find(item) return list(recursively_find(self.dict)) def find_unique_object_with_key_and_value( self, key: str, value: Any ) -> Optional["AnyDict"]: objects = self.find_objects_with_key_and_value(key, value) if not objects: return None assert len(objects) == 1 return objects[0] def find_values_by_key( self, key: str, only_type: Optional[type] = None ) -> List[Any]: def recursively_find(data: Any) -> Iterator[Any]: if isinstance(data, dict): if key in data: yield data[key] for val in data.values(): yield from recursively_find(val) elif isinstance(data, list): for item in data: yield from recursively_find(item) values = list(recursively_find(self.dict)) if only_type is not None: values = [v for v in values if isinstance(v, only_type)] return values def find_unique_value_by_key( self, key: str, default: Any, only_type: Optional[type] = None ) -> Any: values = self.find_values_by_key(key, only_type=only_type) if not values: return default assert len(values) == 1 return values[0] class LayoutContent(UnstructuredJSONReader): """Contains helper functions to extract specific parts of the layout.""" def __init__(self, json_tokens: Sequence[str]) -> None: json_str = "".join(json_tokens) super().__init__(json_str) def main_component(self) -> str: """Getting the main component of the layout.""" return self.top_level_value("component") or "no main component" def all_components(self) -> List[str]: """Getting all components of the layout.""" return self.find_values_by_key("component", only_type=str) def visible_screen(self) -> str: """String representation of a current screen content. Example: SIGN TRANSACTION -------------------- You are about to sign 3 actions. ******************** ICON_CANCEL, -, CONFIRM """ title_separator = f"\n{20*'-'}\n" btn_separator = f"\n{20*'*'}\n" visible = "" if self.title(): visible += self.title() visible += title_separator visible += self.screen_content() visible_buttons = self.button_contents() if visible_buttons: visible += btn_separator visible += ", ".join(visible_buttons) return visible def _get_str_or_dict_text(self, key: str) -> str: value = self.find_unique_value_by_key(key, "") if isinstance(value, dict): return value["text"] return value def title(self) -> str: """Getting text that is displayed as a title and potentially subtitle.""" # There could be possibly subtitle as well title_parts: List[str] = [] title = self._get_str_or_dict_text("title") if title: title_parts.append(title) subtitle = self.subtitle() if subtitle: title_parts.append(subtitle) return "\n".join(title_parts) def subtitle(self) -> str: """Getting text that is displayed as a subtitle.""" subtitle = self._get_str_or_dict_text("subtitle") return subtitle def text_content(self) -> str: """What is on the screen, in one long string, so content can be asserted regardless of newlines. Also getting rid of possible ellipsis. """ content = self.screen_content().replace("\n", " ") if content.endswith("..."): content = content[:-3] if content.startswith("..."): content = content[3:] return content def screen_content(self) -> str: """Getting text that is displayed in the main part of the screen. Preserving the line breaks. """ # Look for paragraphs first (will match most of the time for TT) paragraphs = self.raw_content_paragraphs() if paragraphs: main_text_blocks: List[str] = [] for par in paragraphs: par_content = "" for line_or_newline in par: par_content += line_or_newline par_content.replace("\n", " ") main_text_blocks.append(par_content) return "\n".join(main_text_blocks) # Formatted text formatted_text = self.find_unique_object_with_key_and_value( "component", "FormattedText" ) if formatted_text: text_lines = formatted_text["text"] return "".join(text_lines) # Check the choice_page - mainly for TR choice_page = self.find_unique_object_with_key_and_value( "component", "ChoicePage" ) if choice_page: left = choice_page.get("prev_choice", {}).get("content", "") middle = choice_page.get("current_choice", {}).get("content", "") right = choice_page.get("next_choice", {}).get("content", "") return " ".join(choice for choice in (left, middle, right) if choice) # Screen content - in TR share words screen_content = self.find_unique_value_by_key( "screen_content", default="", only_type=str ) if screen_content: return screen_content # Flow page - for TR flow_page = self.find_unique_value_by_key( "flow_page", default={}, only_type=dict ) if flow_page: text_lines = flow_page["text"] return "".join(text_lines) # Looking for any "text": "something" values text_values = self.find_values_by_key("text", only_type=str) if text_values: return "\n".join(text_values) # Default when not finding anything return self.main_component() def raw_content_paragraphs(self) -> Optional[List[List[str]]]: """Getting raw paragraphs as sent from Rust.""" return self.find_unique_value_by_key("paragraphs", default=None, only_type=list) def tt_check_seed_button_contents(self) -> List[str]: """Getting list of button contents.""" buttons: List[str] = [] button_objects = self.find_objects_with_key_and_value("component", "Button") for button in button_objects: if button.get("icon"): buttons.append("ICON") elif "text" in button: buttons.append(button["text"]) return buttons def button_contents(self) -> List[str]: """Getting list of button contents.""" buttons = self.find_unique_value_by_key("buttons", default={}, only_type=dict) def get_button_content(btn_key: str) -> str: button_obj = buttons.get(btn_key, {}) if button_obj.get("component") == "Button": if "icon" in button_obj: return button_obj["icon"] elif "text" in button_obj: return button_obj["text"] elif button_obj.get("component") == "HoldToConfirm": text = button_obj.get("loader", {}).get("text", "") duration = button_obj.get("loader", {}).get("duration", "") return f"{text} ({duration}ms)" # default value return "-" button_keys = ("left_btn", "middle_btn", "right_btn") return [get_button_content(btn_key) for btn_key in button_keys] def seed_words(self) -> List[str]: """Get all the seed words on the screen in order. Example content: "1. ladybug\n2. acid\n3. academic\n4. afraid" -> ["ladybug", "acid", "academic", "afraid"] """ words: List[str] = [] for line in self.screen_content().split("\n"): # Dot after index is optional (present on TT, not on TR) match = re.match(r"^\s*\d+\.? (\w+)$", line) if match: words.append(match.group(1)) return words def pin(self) -> str: """Get PIN from the layout.""" assert "PinKeyboard" in self.all_components() return self.find_unique_value_by_key("pin", default="", only_type=str) def passphrase(self) -> str: """Get passphrase from the layout.""" assert "PassphraseKeyboard" in self.all_components() return self.find_unique_value_by_key("passphrase", default="", only_type=str) def page_count(self) -> int: """Get number of pages for the layout.""" return ( self.find_unique_value_by_key( "scrollbar_page_count", default=0, only_type=int ) or self.find_unique_value_by_key("page_count", default=0, only_type=int) or 1 ) def active_page(self) -> int: """Get current page index of the layout.""" return self.find_unique_value_by_key("active_page", default=0, only_type=int) def tt_pin_digits_order(self) -> str: """In what order the PIN buttons are shown on the screen. Only for TT.""" return self.top_level_value("digits_order") or "no digits order" def get_middle_choice(self) -> str: """What is the choice being selected right now.""" return self.choice_items()[1] def choice_items(self) -> Tuple[str, str, str]: """Getting actions for all three possible buttons.""" choice_obj = self.find_unique_value_by_key( "choice_page", default={}, only_type=dict ) if not choice_obj: raise RuntimeError("No choice_page object in trace") choice_keys = ("prev_choice", "current_choice", "next_choice") return tuple( choice_obj.get(choice, {}).get("content", "") for choice in choice_keys ) def footer(self) -> str: footer = self.find_unique_object_with_key_and_value("component", "Footer") if not footer: return "" return footer.get("description", "") + " " + footer.get("instruction", "") def multipage_content(layouts: List[LayoutContent]) -> str: """Get overall content from multiple-page layout.""" return "".join(layout.text_content() for layout in layouts) class DebugLink: def __init__(self, transport: "Transport", auto_interact: bool = True) -> None: self.transport = transport self.allow_interactions = auto_interact self.mapping = mapping.DEFAULT_MAPPING # To be set by TrezorClientDebugLink (is not known during creation time) self.model: Optional[models.TrezorModel] = None self.version: Tuple[int, int, int] = (0, 0, 0) # Where screenshots are being saved self.screenshot_recording_dir: Optional[str] = None # For T1 screenshotting functionality in DebugUI self.t1_take_screenshots = False self.t1_screenshot_directory: Optional[Path] = None self.t1_screenshot_counter = 0 # Optional file for saving text representation of the screen self.screen_text_file: Optional[Path] = None self.last_screen_content = "" @property def legacy_ui(self) -> bool: """Differences between UI1 and UI2.""" return self.version < (2, 6, 0) @property def legacy_debug(self) -> bool: """Differences in handling debug events and LayoutContent.""" return self.version < (2, 6, 1) def set_screen_text_file(self, file_path: Optional[Path]) -> None: if file_path is not None: file_path.write_bytes(b"") self.screen_text_file = file_path def open(self) -> None: self.transport.begin_session() def close(self) -> None: self.transport.end_session() def _call(self, msg: protobuf.MessageType, nowait: bool = False) -> Any: LOG.debug( f"sending message: {msg.__class__.__name__}", extra={"protobuf": msg}, ) msg_type, msg_bytes = self.mapping.encode(msg) LOG.log( DUMP_BYTES, f"encoded as type {msg_type} ({len(msg_bytes)} bytes): {msg_bytes.hex()}", ) self.transport.write(msg_type, msg_bytes) if nowait: return None ret_type, ret_bytes = self.transport.read() LOG.log( DUMP_BYTES, f"received type {msg_type} ({len(msg_bytes)} bytes): {msg_bytes.hex()}", ) msg = self.mapping.decode(ret_type, ret_bytes) # Collapse tokens to make log use less lines. msg_for_log = msg if isinstance(msg, (messages.DebugLinkState, messages.DebugLinkLayout)): msg_for_log = deepcopy(msg) msg_for_log.tokens = ["".join(msg_for_log.tokens)] LOG.debug( f"received message: {msg_for_log.__class__.__name__}", extra={"protobuf": msg_for_log}, ) return msg def state(self) -> messages.DebugLinkState: return self._call(messages.DebugLinkGetState()) def read_layout(self) -> LayoutContent: return LayoutContent(self.state().tokens or []) def wait_layout(self, wait_for_external_change: bool = False) -> LayoutContent: # Next layout change will be caused by external event # (e.g. device being auto-locked or as a result of device_handler.run(xxx)) # and not by our debug actions/decisions. # Resetting the debug state so we wait for the next layout change # (and do not return the current state). if wait_for_external_change: self.reset_debug_events() obj = self._call(messages.DebugLinkGetState(wait_layout=True)) if isinstance(obj, messages.Failure): raise TrezorFailure(obj) return LayoutContent(obj.tokens) def reset_debug_events(self) -> None: # Only supported on TT and above certain version if (self.model is not models.T1B1) and not self.legacy_debug: return self._call(messages.DebugLinkResetDebugEvents()) return None def synchronize_at(self, layout_text: str, timeout: float = 5) -> LayoutContent: now = time.monotonic() while True: layout = self.read_layout() if layout_text in layout.json_str: return layout if time.monotonic() - now > timeout: raise RuntimeError("Timeout waiting for layout") time.sleep(0.1) def watch_layout(self, watch: bool) -> None: """Enable or disable watching layouts. If disabled, wait_layout will not work. The message is missing on T1. Use `TrezorClientDebugLink.watch_layout` for cross-version compatibility. """ self._call(messages.DebugLinkWatchLayout(watch=watch)) def encode_pin(self, pin: str, matrix: Optional[str] = None) -> str: """Transform correct PIN according to the displayed matrix.""" if matrix is None: matrix = self.state().matrix if matrix is None: # we are on trezor-core return pin return "".join([str(matrix.index(p) + 1) for p in pin]) def read_recovery_word(self) -> Tuple[Optional[str], Optional[int]]: state = self.state() return (state.recovery_fake_word, state.recovery_word_pos) def read_reset_word(self) -> str: state = self._call(messages.DebugLinkGetState(wait_word_list=True)) return state.reset_word def input( self, word: Optional[str] = None, button: Optional[messages.DebugButton] = None, physical_button: Optional[messages.DebugPhysicalButton] = None, swipe: Optional[messages.DebugSwipeDirection] = None, x: Optional[int] = None, y: Optional[int] = None, wait: Optional[bool] = None, hold_ms: Optional[int] = None, ) -> Optional[LayoutContent]: if not self.allow_interactions: return None args = sum(a is not None for a in (word, button, physical_button, swipe, x)) if args != 1: raise ValueError( "Invalid input - must use one of word, button, physical_button, swipe, click(x,y)" ) decision = messages.DebugLinkDecision( button=button, physical_button=physical_button, swipe=swipe, input=word, x=x, y=y, wait=wait, hold_ms=hold_ms, ) ret = self._call(decision, nowait=not wait) if ret is not None: return LayoutContent(ret.tokens) # Getting the current screen after the (nowait) decision self.save_current_screen_if_relevant(wait=False) return None def save_current_screen_if_relevant(self, wait: bool = True) -> None: """Optionally saving the textual screen output.""" if self.screen_text_file is None: return if wait: layout = self.wait_layout() else: layout = self.read_layout() self.save_debug_screen(layout.visible_screen()) def save_debug_screen(self, screen_content: str) -> None: if self.screen_text_file is None: return if not self.screen_text_file.exists(): self.screen_text_file.write_bytes(b"") # Not writing the same screen twice if screen_content == self.last_screen_content: return self.last_screen_content = screen_content with open(self.screen_text_file, "a") as f: f.write(screen_content) f.write("\n" + 80 * "/" + "\n") # Type overloads below make sure that when we supply `wait=True` into functions, # they will always return `LayoutContent` and we do not need to assert `is not None`. @overload def click(self, click: Tuple[int, int]) -> None: ... @overload def click(self, click: Tuple[int, int], wait: Literal[True]) -> LayoutContent: ... def click( self, click: Tuple[int, int], wait: bool = False ) -> Optional[LayoutContent]: x, y = click return self.input(x=x, y=y, wait=wait) # Made into separate function as `hold_ms: Optional[int]` in `click` # was causing problems with @overload def click_hold( self, click: Tuple[int, int], hold_ms: int ) -> Optional[LayoutContent]: x, y = click return self.input(x=x, y=y, hold_ms=hold_ms, wait=True) def press_yes(self, wait: bool = False) -> Optional[LayoutContent]: return self.input(button=messages.DebugButton.YES, wait=wait) def press_no(self, wait: bool = False) -> Optional[LayoutContent]: return self.input(button=messages.DebugButton.NO, wait=wait) def press_info(self, wait: bool = False) -> Optional[LayoutContent]: return self.input(button=messages.DebugButton.INFO, wait=wait) def swipe_up(self, wait: bool = False) -> Optional[LayoutContent]: return self.input(swipe=messages.DebugSwipeDirection.UP, wait=wait) def swipe_down(self, wait: bool = False) -> Optional[LayoutContent]: return self.input(swipe=messages.DebugSwipeDirection.DOWN, wait=wait) @overload def swipe_right(self) -> None: ... @overload def swipe_right(self, wait: Literal[True]) -> LayoutContent: ... def swipe_right(self, wait: bool = False) -> Union[LayoutContent, None]: return self.input(swipe=messages.DebugSwipeDirection.RIGHT, wait=wait) @overload def swipe_left(self) -> None: ... @overload def swipe_left(self, wait: Literal[True]) -> LayoutContent: ... def swipe_left(self, wait: bool = False) -> Union[LayoutContent, None]: return self.input(swipe=messages.DebugSwipeDirection.LEFT, wait=wait) @overload def press_left(self) -> None: ... @overload def press_left(self, wait: Literal[True]) -> LayoutContent: ... def press_left(self, wait: bool = False) -> Optional[LayoutContent]: return self.input( physical_button=messages.DebugPhysicalButton.LEFT_BTN, wait=wait ) @overload def press_middle(self) -> None: ... @overload def press_middle(self, wait: Literal[True]) -> LayoutContent: ... def press_middle(self, wait: bool = False) -> Optional[LayoutContent]: return self.input( physical_button=messages.DebugPhysicalButton.MIDDLE_BTN, wait=wait ) def press_middle_htc( self, hold_ms: int, extra_ms: int = 200 ) -> Optional[LayoutContent]: return self.press_htc( button=messages.DebugPhysicalButton.MIDDLE_BTN, hold_ms=hold_ms, extra_ms=extra_ms, ) @overload def press_right(self) -> None: ... @overload def press_right(self, wait: Literal[True]) -> LayoutContent: ... def press_right(self, wait: bool = False) -> Optional[LayoutContent]: return self.input( physical_button=messages.DebugPhysicalButton.RIGHT_BTN, wait=wait ) def press_right_htc( self, hold_ms: int, extra_ms: int = 200 ) -> Optional[LayoutContent]: return self.press_htc( button=messages.DebugPhysicalButton.RIGHT_BTN, hold_ms=hold_ms, extra_ms=extra_ms, ) def press_htc( self, button: messages.DebugPhysicalButton, hold_ms: int, extra_ms: int = 200 ) -> Optional[LayoutContent]: hold_ms = hold_ms + extra_ms # safety margin result = self.input( physical_button=button, hold_ms=hold_ms, ) # sleeping little longer for UI to update time.sleep(hold_ms / 1000 + 0.1) return result def stop(self) -> None: self._call(messages.DebugLinkStop(), nowait=True) def reseed(self, value: int) -> protobuf.MessageType: return self._call(messages.DebugLinkReseedRandom(value=value)) def start_recording( self, directory: str, refresh_index: Optional[int] = None ) -> None: self.screenshot_recording_dir = directory # Different recording logic between core and legacy if self.model is not models.T1B1: self._call( messages.DebugLinkRecordScreen( target_directory=directory, refresh_index=refresh_index ) ) else: self.t1_screenshot_directory = Path(directory) self.t1_screenshot_counter = 0 self.t1_take_screenshots = True def stop_recording(self) -> None: self.screenshot_recording_dir = None # Different recording logic between TT and T1 if self.model is not models.T1B1: self._call(messages.DebugLinkRecordScreen(target_directory=None)) else: self.t1_take_screenshots = False @expect(messages.DebugLinkMemory, field="memory", ret_type=bytes) def memory_read(self, address: int, length: int) -> protobuf.MessageType: return self._call(messages.DebugLinkMemoryRead(address=address, length=length)) def memory_write(self, address: int, memory: bytes, flash: bool = False) -> None: self._call( messages.DebugLinkMemoryWrite(address=address, memory=memory, flash=flash), nowait=True, ) def flash_erase(self, sector: int) -> None: self._call(messages.DebugLinkFlashErase(sector=sector), nowait=True) @expect(messages.Success) def erase_sd_card(self, format: bool = True) -> messages.Success: return self._call(messages.DebugLinkEraseSdCard(format=format)) def take_t1_screenshot_if_relevant(self) -> None: """Conditionally take screenshots on T1. TT handles them differently, see debuglink.start_recording. """ if self.model is models.T1B1 and self.t1_take_screenshots: self.save_screenshot_for_t1() def save_screenshot_for_t1(self) -> None: from PIL import Image layout = self.state().layout assert layout is not None assert len(layout) == 128 * 64 // 8 pixels: List[int] = [] for byteline in range(64 // 8): offset = byteline * 128 row = layout[offset : offset + 128] for bit in range(8): pixels.extend(bool(px & (1 << bit)) for px in row) im = Image.new("1", (128, 64)) im.putdata(pixels[::-1]) assert self.t1_screenshot_directory is not None img_location = ( self.t1_screenshot_directory / f"{self.t1_screenshot_counter:04d}.png" ) im.save(img_location) self.t1_screenshot_counter += 1 class NullDebugLink(DebugLink): def __init__(self) -> None: # Ignoring type error as self.transport will not be touched while using NullDebugLink super().__init__(None) # type: ignore [Argument of type "None" cannot be assigned to parameter "transport"] def open(self) -> None: pass def close(self) -> None: pass def _call( self, msg: protobuf.MessageType, nowait: bool = False ) -> Optional[messages.DebugLinkState]: if not nowait: if isinstance(msg, messages.DebugLinkGetState): return messages.DebugLinkState() else: raise RuntimeError("unexpected call to a fake debuglink") return None class DebugUI: INPUT_FLOW_DONE = object() def __init__(self, debuglink: DebugLink) -> None: self.debuglink = debuglink self.clear() def clear(self) -> None: self.pins: Optional[Iterator[str]] = None self.passphrase = "" self.input_flow: Union[ Generator[None, messages.ButtonRequest, None], object, None ] = None def _default_input_flow(self, br: messages.ButtonRequest) -> None: if br.code == messages.ButtonRequestType.PinEntry: self.debuglink.input(self.get_pin()) else: # Paginating (going as further as possible) and pressing Yes if br.pages is not None: for _ in range(br.pages - 1): self.debuglink.swipe_up(wait=True) if self.debuglink.model is models.T3T1: layout = self.debuglink.read_layout() if "PromptScreen" in layout.all_components(): self.debuglink.press_yes() elif "SwipeContent" in layout.all_components(): self.debuglink.swipe_up() else: self.debuglink.press_yes() else: self.debuglink.press_yes() def button_request(self, br: messages.ButtonRequest) -> None: self.debuglink.take_t1_screenshot_if_relevant() if self.input_flow is None: # Only calling screen-saver when not in input-flow # as it collides with wait-layout of input flows. # All input flows call debuglink.input(), so # recording their screens that way (as well as # possible swipes below). self.debuglink.save_current_screen_if_relevant(wait=True) self._default_input_flow(br) elif self.input_flow is self.INPUT_FLOW_DONE: raise AssertionError("input flow ended prematurely") else: try: assert isinstance(self.input_flow, Generator) self.input_flow.send(br) except StopIteration: self.input_flow = self.INPUT_FLOW_DONE def get_pin(self, code: Optional["PinMatrixRequestType"] = None) -> str: self.debuglink.take_t1_screenshot_if_relevant() if self.pins is None: raise RuntimeError("PIN requested but no sequence was configured") try: return self.debuglink.encode_pin(next(self.pins)) except StopIteration: raise AssertionError("PIN sequence ended prematurely") def get_passphrase(self, available_on_device: bool) -> str: self.debuglink.take_t1_screenshot_if_relevant() return self.passphrase class MessageFilter: def __init__(self, message_type: Type[protobuf.MessageType], **fields: Any) -> None: self.message_type = message_type self.fields: Dict[str, Any] = {} self.update_fields(**fields) def update_fields(self, **fields: Any) -> "MessageFilter": for name, value in fields.items(): try: self.fields[name] = self.from_message_or_type(value) except TypeError: self.fields[name] = value return self @classmethod def from_message_or_type( cls, message_or_type: "ExpectedMessage" ) -> "MessageFilter": if isinstance(message_or_type, cls): return message_or_type if isinstance(message_or_type, protobuf.MessageType): return cls.from_message(message_or_type) if isinstance(message_or_type, type) and issubclass( message_or_type, protobuf.MessageType ): return cls(message_or_type) raise TypeError("Invalid kind of expected response") @classmethod def from_message(cls, message: protobuf.MessageType) -> "MessageFilter": fields = {} for field in message.FIELDS.values(): value = getattr(message, field.name) if value in (None, [], protobuf.REQUIRED_FIELD_PLACEHOLDER): continue fields[field.name] = value return cls(type(message), **fields) def match(self, message: protobuf.MessageType) -> bool: if type(message) is not self.message_type: return False for field, expected_value in self.fields.items(): actual_value = getattr(message, field, None) if isinstance(expected_value, MessageFilter): if actual_value is None or not expected_value.match(actual_value): return False elif expected_value != actual_value: return False return True def to_string(self, maxwidth: int = 80) -> str: fields: List[Tuple[str, str]] = [] for field in self.message_type.FIELDS.values(): if field.name not in self.fields: continue value = self.fields[field.name] if isinstance(value, IntEnum): field_str = value.name elif isinstance(value, MessageFilter): field_str = value.to_string(maxwidth - 4) elif isinstance(value, protobuf.MessageType): field_str = protobuf.format_message(value) else: field_str = repr(value) field_str = textwrap.indent(field_str, " ").lstrip() fields.append((field.name, field_str)) pairs = [f"{k}={v}" for k, v in fields] oneline_str = ", ".join(pairs) if len(oneline_str) < maxwidth: return f"{self.message_type.__name__}({oneline_str})" else: item: List[str] = [] item.append(f"{self.message_type.__name__}(") for pair in pairs: item.append(f" {pair}") item.append(")") return "\n".join(item) class MessageFilterGenerator: def __getattr__(self, key: str) -> Callable[..., "MessageFilter"]: message_type = getattr(messages, key) return MessageFilter(message_type).update_fields message_filters = MessageFilterGenerator() 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: "Transport", auto_interact: bool = True) -> None: try: debug_transport = transport.find_debug() self.debug = DebugLink(debug_transport, auto_interact) # try to open debuglink, see if it works self.debug.open() self.debug.close() except Exception: if not auto_interact: self.debug = NullDebugLink() else: raise self.reset_debug_features() super().__init__(transport, ui=self.ui) # So that we can choose right screenshotting logic (T1 vs TT) # and know the supported debug capabilities self.debug.model = self.model self.debug.version = self.version def reset_debug_features(self) -> None: """Prepare the debugging client for a new testcase. Clears all debugging state that might have been modified by a testcase. """ self.ui: DebugUI = DebugUI(self.debug) self.in_with_statement = False self.expected_responses: Optional[List[MessageFilter]] = None self.actual_responses: Optional[List[protobuf.MessageType]] = None self.filters: Dict[ Type[protobuf.MessageType], Optional[Callable[[protobuf.MessageType], protobuf.MessageType]], ] = {} def ensure_open(self) -> None: """Only open session if there isn't already an open one.""" if self.session_counter == 0: self.open() def open(self) -> None: super().open() if self.session_counter == 1: self.debug.open() def close(self) -> None: if self.session_counter == 1: self.debug.close() super().close() def set_filter( self, message_type: Type[protobuf.MessageType], callback: Optional[Callable[[protobuf.MessageType], protobuf.MessageType]], ) -> None: """Configure a filter function for a specified message type. The `callback` must be a function that accepts a protobuf message, and returns a (possibly modified) protobuf message of the same type. Whenever a message is sent or received that matches `message_type`, `callback` is invoked on the message and its result is substituted for the original. Useful for test scenarios with an active malicious actor on the wire. """ if not self.in_with_statement: raise RuntimeError("Must be called inside 'with' statement") self.filters[message_type] = callback def _filter_message(self, msg: protobuf.MessageType) -> protobuf.MessageType: 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: Generator[None, Optional[messages.ButtonRequest], None] ) -> None: """Configure a sequence of input events for the current with-block. The `input_flow` must be a generator function. A `yield` statement in the input flow function waits for a ButtonRequest from the device, and returns its code. Example usage: >>> def input_flow(): >>> # wait for first button prompt >>> code = yield >>> assert code == ButtonRequestType.Other >>> # press No >>> client.debug.press_no() >>> >>> # wait for second button prompt >>> yield >>> # press Yes >>> client.debug.press_yes() >>> >>> with client: >>> client.set_input_flow(input_flow) >>> some_call(client) """ if not self.in_with_statement: raise RuntimeError("Must be called inside 'with' statement") 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 input_flow.send(None) # start the generator def watch_layout(self, watch: bool = True) -> None: """Enable or disable watching layout changes. Since trezor-core v2.3.2, it is necessary to call `watch_layout()` before using `debug.wait_layout()`, otherwise layout changes are not reported. """ if self.version >= (2, 3, 2): # version check is necessary because otherwise we cannot reliably detect # whether and where to wait for reply: # - T1 reports unknown debuglink messages on the wirelink # - TT < 2.3.0 does not reply to unknown debuglink messages due to a bug self.debug.watch_layout(watch) def __enter__(self) -> "TrezorClientDebugLink": # For usage in with/expected_responses if self.in_with_statement: raise RuntimeError("Do not nest!") self.in_with_statement = True return self def __exit__(self, exc_type: Any, value: Any, traceback: Any) -> None: __tracebackhide__ = True # for pytest # pylint: disable=W0612 # copy expected/actual responses before clearing them expected_responses = self.expected_responses actual_responses = self.actual_responses # grab a copy of the inputflow generator to raise an exception through it if isinstance(self.ui, DebugUI): input_flow = self.ui.input_flow else: input_flow = None self.reset_debug_features() if exc_type is None: # If no other exception was raised, evaluate missed responses # (raises AssertionError on mismatch) self._verify_responses(expected_responses, actual_responses) elif isinstance(input_flow, Generator): # Propagate the exception through the input flow, so that we see in # traceback where it is stuck. input_flow.throw(exc_type, value, traceback) def set_expected_responses( self, expected: List[Union["ExpectedMessage", Tuple[bool, "ExpectedMessage"]]] ) -> None: """Set a sequence of expected responses to client calls. Within a given with-block, the list of received responses from device must match the list of expected responses, otherwise an AssertionError is raised. If an expected response is given a field value other than None, that field value must exactly match the received field value. If a given field is None (or unspecified) in the expected response, the received field value is not checked. Each expected response can also be a tuple (bool, message). In that case, the expected response is only evaluated if the first field is True. This is useful for differentiating sequences between Trezor models: >>> trezor_one = client.features.model == "1" >>> client.set_expected_responses([ >>> messages.ButtonRequest(code=ConfirmOutput), >>> (trezor_one, messages.ButtonRequest(code=ConfirmOutput)), >>> messages.Success(), >>> ]) """ if not self.in_with_statement: raise RuntimeError("Must be called inside 'with' statement") # make sure all items are (bool, message) tuples expected_with_validity = ( e if isinstance(e, tuple) else (True, e) for e in expected ) # only apply those items that are (True, message) self.expected_responses = [ MessageFilter.from_message_or_type(expected) for valid, expected in expected_with_validity if valid ] self.actual_responses = [] def use_pin_sequence(self, pins: Iterable[str]) -> None: """Respond to PIN prompts from device with the provided PINs. The sequence must be at least as long as the expected number of PIN prompts. """ self.ui.pins = iter(pins) def use_passphrase(self, passphrase: str) -> None: """Respond to passphrase prompts from device with the provided passphrase.""" self.ui.passphrase = Mnemonic.normalize_string(passphrase) def use_mnemonic(self, mnemonic: str) -> None: """Use the provided mnemonic to respond to device. Only applies to T1, where device prompts the host for mnemonic words.""" self.mnemonic = Mnemonic.normalize_string(mnemonic).split(" ") def _raw_read(self) -> protobuf.MessageType: __tracebackhide__ = True # for pytest # pylint: disable=W0612 resp = super()._raw_read() resp = self._filter_message(resp) if self.actual_responses is not None: self.actual_responses.append(resp) return resp def _raw_write(self, msg: protobuf.MessageType) -> None: return super()._raw_write(self._filter_message(msg)) @staticmethod def _expectation_lines(expected: List[MessageFilter], current: int) -> List[str]: start_at = max(current - EXPECTED_RESPONSES_CONTEXT_LINES, 0) stop_at = min(current + EXPECTED_RESPONSES_CONTEXT_LINES + 1, len(expected)) output: List[str] = [] output.append("Expected responses:") if start_at > 0: output.append(f" (...{start_at} previous responses omitted)") for i in range(start_at, stop_at): exp = expected[i] prefix = " " if i != current else ">>> " output.append(textwrap.indent(exp.to_string(), prefix)) if stop_at < len(expected): omitted = len(expected) - stop_at output.append(f" (...{omitted} following responses omitted)") output.append("") return output @classmethod def _verify_responses( cls, expected: Optional[List[MessageFilter]], actual: Optional[List[protobuf.MessageType]], ) -> None: __tracebackhide__ = True # for pytest # pylint: disable=W0612 if expected is None and actual is None: return assert expected is not None assert actual is not None for i, (exp, act) in enumerate(zip_longest(expected, actual)): if exp is None: output = cls._expectation_lines(expected, i) output.append("No more messages were expected, but we got:") for resp in actual[i:]: output.append( textwrap.indent(protobuf.format_message(resp), " ") ) raise AssertionError("\n".join(output)) if act is None: output = cls._expectation_lines(expected, i) output.append("This and the following message was not received.") raise AssertionError("\n".join(output)) if not exp.match(act): output = cls._expectation_lines(expected, i) output.append("Actually received:") output.append(textwrap.indent(protobuf.format_message(act), " ")) raise AssertionError("\n".join(output)) def mnemonic_callback(self, _) -> str: word, pos = self.debug.read_recovery_word() if word: return word if pos: return self.mnemonic[pos - 1] raise RuntimeError("Unexpected call") @expect(messages.Success, field="message", ret_type=str) def load_device( client: "TrezorClient", mnemonic: Union[str, Iterable[str]], pin: Optional[str], passphrase_protection: bool, label: Optional[str], skip_checksum: bool = False, needs_backup: bool = False, no_backup: bool = False, ) -> protobuf.MessageType: if isinstance(mnemonic, str): mnemonic = [mnemonic] mnemonics = [Mnemonic.normalize_string(m) for m in mnemonic] if client.features.initialized: raise RuntimeError( "Device is initialized already. Call device.wipe() and try again." ) resp = client.call( messages.LoadDevice( mnemonics=mnemonics, pin=pin, passphrase_protection=passphrase_protection, label=label, skip_checksum=skip_checksum, needs_backup=needs_backup, no_backup=no_backup, ) ) client.init_device() return resp # keep the old name for compatibility load_device_by_mnemonic = load_device @expect(messages.Success, field="message", ret_type=str) def prodtest_t1(client: "TrezorClient") -> protobuf.MessageType: if client.features.bootloader_mode is not True: raise RuntimeError("Device must be in bootloader mode") return client.call( messages.ProdTestT1( payload=b"\x00\xFF\x55\xAA\x66\x99\x33\xCCABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!\x00\xFF\x55\xAA\x66\x99\x33\xCC" ) ) def record_screen( debug_client: "TrezorClientDebugLink", directory: Union[str, None], report_func: Union[Callable[[str], None], None] = None, ) -> None: """Record screen changes into a specified directory. Passing `None` as `directory` stops the recording. Creates subdirectories inside a specified directory, one for each session (for each new call of this function). (So that older screenshots are not overwritten by new ones.) Is available only for emulators, hardware devices are not capable of that. """ def get_session_screenshot_dir(directory: Path) -> Path: """Create and return screenshot dir for the current session, according to datetime.""" session_dir = directory / datetime.now().strftime("%Y-%m-%d_%H-%M-%S") session_dir.mkdir(parents=True, exist_ok=True) return session_dir if not _is_emulator(debug_client): raise RuntimeError("Recording is only supported on emulator.") if directory is None: debug_client.debug.stop_recording() if report_func is not None: report_func("Recording stopped.") else: # Transforming the directory into an absolute path, # because emulator demands it abs_directory = Path(directory).resolve() # Creating the dir when it does not exist yet if not abs_directory.exists(): abs_directory.mkdir(parents=True, exist_ok=True) # Getting a new screenshot dir for the current session current_session_dir = get_session_screenshot_dir(abs_directory) debug_client.debug.start_recording(str(current_session_dir)) if report_func is not None: report_func(f"Recording started into {current_session_dir}.") def _is_emulator(debug_client: "TrezorClientDebugLink") -> bool: """Check if we are connected to emulator, in contrast to hardware device.""" return debug_client.features.fw_vendor == "EMULATOR" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/definitions.py0000664000175000017500000001043214636513242021031 0ustar00matejcikmatejcikimport logging import tarfile import typing as t from pathlib import Path import construct as c import requests from construct_classes import Struct, subcon from . import cosi, merkle_tree from .messages import EthereumDefinitionType from .tools import EnumAdapter LOG = logging.getLogger(__name__) FORMAT_MAGIC = b"trzd1" DEFS_BASE_URL = "https://data.trezor.io/firmware/eth-definitions/" DEFINITIONS_DEV_SIGS_REQUIRED = 1 DEFINITIONS_DEV_PUBLIC_KEYS = [ bytes.fromhex(key) for key in ("db995fe25169d141cab9bbba92baa01f9f2e1ece7df4cb2ac05190f37fcc1f9d",) ] DEFINITIONS_SIGS_REQUIRED = 2 DEFINITIONS_PUBLIC_KEYS = [ bytes.fromhex(key) for key in ( "4334996343623e462f0fc93311fef1484ca23d2ff1eec6df1fa8eb7e3573b3db", "a9a22cc265a0cb1d6cb329bc0e60bc45df76b9ab28fb87b61136feaf8d8fdc96", "b8d2b21de27124f0511f903ae7e60e07961810a0b8f28ea755fa50367a8a2b8b", ) ] ProofFormat = c.PrefixedArray(c.Int8ul, c.Bytes(32)) class DefinitionPayload(Struct): magic: bytes data_type: EthereumDefinitionType timestamp: int data: bytes SUBCON = c.Struct( "magic" / c.Const(FORMAT_MAGIC), "data_type" / EnumAdapter(c.Int8ul, EthereumDefinitionType), "timestamp" / c.Int32ul, "data" / c.Prefixed(c.Int16ul, c.GreedyBytes), ) class Definition(Struct): payload: DefinitionPayload = subcon(DefinitionPayload) proof: t.List[bytes] sigmask: int signature: bytes SUBCON = c.Struct( "payload" / DefinitionPayload.SUBCON, "proof" / ProofFormat, "sigmask" / c.Int8ul, "signature" / c.Bytes(64), ) def verify(self, dev: bool = False) -> None: payload = self.payload.build() root = merkle_tree.evaluate_proof(payload, self.proof) cosi.verify( self.signature, root, DEFINITIONS_DEV_SIGS_REQUIRED, DEFINITIONS_DEV_PUBLIC_KEYS, self.sigmask, ) class Source: def fetch_path(self, *components: str) -> t.Optional[bytes]: raise NotImplementedError def get_network_by_slip44(self, slip44: int) -> t.Optional[bytes]: return self.fetch_path("slip44", str(slip44), "network.dat") def get_network(self, chain_id: int) -> t.Optional[bytes]: return self.fetch_path("chain-id", str(chain_id), "network.dat") def get_token(self, chain_id: int, address: t.AnyStr) -> t.Optional[bytes]: if isinstance(address, bytes): address_str = address.hex() elif address.startswith("0x"): address_str = address[2:] else: address_str = address address_str = address_str.lower() return self.fetch_path("chain-id", f"{chain_id}", f"token-{address_str}.dat") class NullSource(Source): def fetch_path(self, *components: str) -> t.Optional[bytes]: return None class FilesystemSource(Source): def __init__(self, root: Path) -> None: self.root = root def fetch_path(self, *components: str) -> t.Optional[bytes]: path = self.root.joinpath(*components) if not path.exists(): LOG.info("Requested definition at %s was not found", path) return None LOG.info("Reading definition from %s", path) return path.read_bytes() class UrlSource(Source): def __init__(self, base_url: str = DEFS_BASE_URL) -> None: self.base_url = base_url def fetch_path(self, *components: str) -> t.Optional[bytes]: url = self.base_url + "/".join(components) LOG.info("Downloading definition from %s", url) r = requests.get(url) if r.status_code == 404: LOG.info("Requested definition at %s was not found", url) return None r.raise_for_status() return r.content class TarSource(Source): def __init__(self, path: Path) -> None: self.archive = tarfile.open(path) def fetch_path(self, *components: str) -> t.Optional[bytes]: inner_name = "/".join(components) LOG.info("Extracting definition from %s:%s", self.archive.name, inner_name) try: return self.archive.extractfile(inner_name).read() # type: ignore [not a known attribute] except Exception: LOG.info("Requested definition at %s was not found", inner_name) return None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/device.py0000664000175000017500000002766014636513242017770 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from __future__ import annotations import os import time import warnings from typing import TYPE_CHECKING, Callable, Iterable, Optional from . import messages from .exceptions import Cancelled, TrezorException from .tools import Address, expect, session if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType RECOVERY_BACK = "\x08" # backspace character, sent literally @expect(messages.Success, field="message", ret_type=str) @session def apply_settings( client: "TrezorClient", label: Optional[str] = None, language: Optional[str] = None, use_passphrase: Optional[bool] = None, homescreen: Optional[bytes] = None, passphrase_always_on_device: Optional[bool] = None, auto_lock_delay_ms: Optional[int] = None, display_rotation: Optional[int] = None, safety_checks: Optional[messages.SafetyCheckLevel] = None, experimental_features: Optional[bool] = None, hide_passphrase_from_host: Optional[bool] = None, haptic_feedback: Optional[bool] = None, ) -> "MessageType": if language is not None: warnings.warn( "language ignored. Use change_language() to set device language.", DeprecationWarning, ) settings = messages.ApplySettings( label=label, use_passphrase=use_passphrase, homescreen=homescreen, passphrase_always_on_device=passphrase_always_on_device, auto_lock_delay_ms=auto_lock_delay_ms, display_rotation=display_rotation, safety_checks=safety_checks, experimental_features=experimental_features, hide_passphrase_from_host=hide_passphrase_from_host, haptic_feedback=haptic_feedback, ) out = client.call(settings) client.refresh_features() return out def _send_language_data( client: "TrezorClient", request: "messages.TranslationDataRequest", language_data: bytes, ) -> "MessageType": response: MessageType = request while not isinstance(response, messages.Success): assert isinstance(response, messages.TranslationDataRequest) data_length = response.data_length data_offset = response.data_offset chunk = language_data[data_offset : data_offset + data_length] response = client.call(messages.TranslationDataAck(data_chunk=chunk)) return response @expect(messages.Success, field="message", ret_type=str) @session def change_language( client: "TrezorClient", language_data: bytes, show_display: bool | None = None, ) -> "MessageType": data_length = len(language_data) msg = messages.ChangeLanguage(data_length=data_length, show_display=show_display) response = client.call(msg) if data_length > 0: assert isinstance(response, messages.TranslationDataRequest) response = _send_language_data(client, response, language_data) assert isinstance(response, messages.Success) client.refresh_features() # changing the language in features return response @expect(messages.Success, field="message", ret_type=str) @session def apply_flags(client: "TrezorClient", flags: int) -> "MessageType": out = client.call(messages.ApplyFlags(flags=flags)) client.refresh_features() return out @expect(messages.Success, field="message", ret_type=str) @session def change_pin(client: "TrezorClient", remove: bool = False) -> "MessageType": ret = client.call(messages.ChangePin(remove=remove)) client.refresh_features() return ret @expect(messages.Success, field="message", ret_type=str) @session def change_wipe_code(client: "TrezorClient", remove: bool = False) -> "MessageType": ret = client.call(messages.ChangeWipeCode(remove=remove)) client.refresh_features() return ret @expect(messages.Success, field="message", ret_type=str) @session def sd_protect( client: "TrezorClient", operation: messages.SdProtectOperationType ) -> "MessageType": ret = client.call(messages.SdProtect(operation=operation)) client.refresh_features() return ret @expect(messages.Success, field="message", ret_type=str) @session def wipe(client: "TrezorClient") -> "MessageType": ret = client.call(messages.WipeDevice()) if not client.features.bootloader_mode: client.init_device() return ret @session def recover( client: "TrezorClient", word_count: int = 24, passphrase_protection: bool = False, pin_protection: bool = True, label: Optional[str] = None, language: Optional[str] = None, input_callback: Optional[Callable] = None, input_method: messages.RecoveryDeviceInputMethod = messages.RecoveryDeviceInputMethod.ScrambledWords, dry_run: Optional[bool] = None, u2f_counter: Optional[int] = None, *, type: Optional[messages.RecoveryType] = None, ) -> "MessageType": if language is not None: warnings.warn( "language ignored. Use change_language() to set device language.", DeprecationWarning, ) if dry_run is not None: warnings.warn( "Use type=RecoveryType.DryRun instead!", DeprecationWarning, ) if type is not None: raise ValueError("Cannot use both dry_run and type simultaneously.") elif dry_run: type = messages.RecoveryType.DryRun else: type = messages.RecoveryType.NormalRecovery if type is None: type = messages.RecoveryType.NormalRecovery 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 type == messages.RecoveryType.NormalRecovery: raise RuntimeError( "Device already initialized. Call device.wipe() and try again." ) if u2f_counter is None: u2f_counter = int(time.time()) msg = messages.RecoveryDevice( word_count=word_count, enforce_wordlist=True, input_method=input_method, type=type, ) if type == messages.RecoveryType.NormalRecovery: # set additional parameters msg.passphrase_protection = passphrase_protection msg.pin_protection = pin_protection msg.label = label msg.u2f_counter = u2f_counter res = client.call(msg) while isinstance(res, messages.WordRequest): try: assert input_callback is not None inp = input_callback(res.type) res = client.call(messages.WordAck(word=inp)) except Cancelled: res = client.call(messages.Cancel()) client.init_device() return res @expect(messages.Success, field="message", ret_type=str) @session def reset( client: "TrezorClient", display_random: bool = False, strength: Optional[int] = None, passphrase_protection: bool = False, pin_protection: bool = True, label: Optional[str] = None, language: Optional[str] = None, u2f_counter: int = 0, skip_backup: bool = False, no_backup: bool = False, backup_type: messages.BackupType = messages.BackupType.Bip39, ) -> "MessageType": if language is not None: warnings.warn( "language ignored. Use change_language() to set device language.", DeprecationWarning, ) 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 = messages.ResetDevice( display_random=bool(display_random), strength=strength, passphrase_protection=bool(passphrase_protection), pin_protection=bool(pin_protection), label=label, u2f_counter=u2f_counter, skip_backup=bool(skip_backup), no_backup=bool(no_backup), backup_type=backup_type, ) resp = client.call(msg) if not isinstance(resp, messages.EntropyRequest): raise RuntimeError("Invalid response, expected EntropyRequest") external_entropy = os.urandom(32) # LOG.debug("Computer generated entropy: " + external_entropy.hex()) ret = client.call(messages.EntropyAck(entropy=external_entropy)) client.init_device() return ret @expect(messages.Success, field="message", ret_type=str) @session def backup( client: "TrezorClient", group_threshold: Optional[int] = None, groups: Iterable[tuple[int, int]] = (), ) -> "MessageType": ret = client.call( messages.BackupDevice( group_threshold=group_threshold, groups=[ messages.Slip39Group(member_threshold=t, member_count=c) for t, c in groups ], ) ) client.refresh_features() return ret @expect(messages.Success, field="message", ret_type=str) def cancel_authorization(client: "TrezorClient") -> "MessageType": return client.call(messages.CancelAuthorization()) @expect(messages.UnlockedPathRequest, field="mac", ret_type=bytes) def unlock_path(client: "TrezorClient", n: "Address") -> "MessageType": resp = client.call(messages.UnlockPath(address_n=n)) # Cancel the UnlockPath workflow now that we have the authentication code. try: client.call(messages.Cancel()) except Cancelled: return resp else: raise TrezorException("Unexpected response in UnlockPath flow") @session @expect(messages.Success, field="message", ret_type=str) def reboot_to_bootloader( client: "TrezorClient", boot_command: messages.BootCommand = messages.BootCommand.STOP_AND_WAIT, firmware_header: Optional[bytes] = None, language_data: bytes = b"", ) -> "MessageType": response = client.call( messages.RebootToBootloader( boot_command=boot_command, firmware_header=firmware_header, language_data_length=len(language_data), ) ) if isinstance(response, messages.TranslationDataRequest): response = _send_language_data(client, response, language_data) return response @session @expect(messages.Success, field="message", ret_type=str) def show_device_tutorial(client: "TrezorClient") -> "MessageType": return client.call(messages.ShowDeviceTutorial()) @session @expect(messages.Success, field="message", ret_type=str) def unlock_bootloader(client: "TrezorClient") -> "MessageType": return client.call(messages.UnlockBootloader()) @expect(messages.Success, field="message", ret_type=str) @session def set_busy(client: "TrezorClient", expiry_ms: Optional[int]) -> "MessageType": """Sets or clears the busy state of the device. In the busy state the device shows a "Do not disconnect" message instead of the homescreen. Setting `expiry_ms=None` clears the busy state. """ ret = client.call(messages.SetBusy(expiry_ms=expiry_ms)) client.refresh_features() return ret @expect(messages.AuthenticityProof) def authenticate(client: "TrezorClient", challenge: bytes): return client.call(messages.AuthenticateDevice(challenge=challenge)) @expect(messages.Success, field="message", ret_type=str) def set_brightness( client: "TrezorClient", value: Optional[int] = None ) -> "MessageType": return client.call(messages.SetBrightness(value=value)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/eos.py0000664000175000017500000002612214636513242017307 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from datetime import datetime from typing import TYPE_CHECKING, List, Tuple from . import exceptions, messages from .tools import b58decode, expect, session if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType from .tools import Address def name_to_number(name: str) -> int: length = len(name) value = 0 for i in range(0, 13): c = 0 if i < length and i < 13: c = char_to_symbol(name[i]) if i < 12: c &= 0x1F c <<= 64 - 5 * (i + 1) else: c &= 0x0F value |= c return value def char_to_symbol(c: str) -> int: if c >= "a" and c <= "z": return ord(c) - ord("a") + 6 elif c >= "1" and c <= "5": return ord(c) - ord("1") + 1 else: return 0 def parse_asset(asset: str) -> messages.EosAsset: amount_str, symbol_str = asset.split(" ") # "-1.0000" => ["-1", "0000"] => -10000 amount_parts = amount_str.split(".", maxsplit=1) amount = int("".join(amount_parts)) precision = 0 if len(amount_parts) > 1: precision = len(amount_parts[1]) # 4, "EOS" => b"\x04EOS" => little-endian uint32 symbol_bytes = bytes([precision]) + symbol_str.encode() symbol = int.from_bytes(symbol_bytes, "little") return messages.EosAsset(amount=amount, symbol=symbol) def public_key_to_buffer(pub_key: str) -> Tuple[int, bytes]: _t = 0 if pub_key[:3] == "EOS": pub_key = pub_key[3:] _t = 0 elif pub_key[:7] == "PUB_K1_": pub_key = pub_key[7:] _t = 0 elif pub_key[:7] == "PUB_R1_": pub_key = pub_key[7:] _t = 1 return _t, b58decode(pub_key, None)[:-4] def parse_common(action: dict) -> messages.EosActionCommon: authorization = [] for auth in action["authorization"]: authorization.append( messages.EosPermissionLevel( actor=name_to_number(auth["actor"]), permission=name_to_number(auth["permission"]), ) ) return messages.EosActionCommon( account=name_to_number(action["account"]), name=name_to_number(action["name"]), authorization=authorization, ) def parse_transfer(data: dict) -> messages.EosActionTransfer: return messages.EosActionTransfer( sender=name_to_number(data["from"]), receiver=name_to_number(data["to"]), memo=data["memo"], quantity=parse_asset(data["quantity"]), ) def parse_vote_producer(data: dict) -> messages.EosActionVoteProducer: producers = [] for producer in data["producers"]: producers.append(name_to_number(producer)) return messages.EosActionVoteProducer( voter=name_to_number(data["account"]), proxy=name_to_number(data["proxy"]), producers=producers, ) def parse_buy_ram(data: dict) -> messages.EosActionBuyRam: return messages.EosActionBuyRam( payer=name_to_number(data["payer"]), receiver=name_to_number(data["receiver"]), quantity=parse_asset(data["quant"]), ) def parse_buy_rambytes(data: dict) -> messages.EosActionBuyRamBytes: return messages.EosActionBuyRamBytes( payer=name_to_number(data["payer"]), receiver=name_to_number(data["receiver"]), bytes=int(data["bytes"]), ) def parse_sell_ram(data: dict) -> messages.EosActionSellRam: return messages.EosActionSellRam( account=name_to_number(data["account"]), bytes=int(data["bytes"]) ) def parse_delegate(data: dict) -> messages.EosActionDelegate: return messages.EosActionDelegate( sender=name_to_number(data["from"]), receiver=name_to_number(data["receiver"]), net_quantity=parse_asset(data["stake_net_quantity"]), cpu_quantity=parse_asset(data["stake_cpu_quantity"]), transfer=bool(data["transfer"]), ) def parse_undelegate(data: dict) -> messages.EosActionUndelegate: return messages.EosActionUndelegate( sender=name_to_number(data["from"]), receiver=name_to_number(data["receiver"]), net_quantity=parse_asset(data["unstake_net_quantity"]), cpu_quantity=parse_asset(data["unstake_cpu_quantity"]), ) def parse_refund(data: dict) -> messages.EosActionRefund: return messages.EosActionRefund(owner=name_to_number(data["owner"])) def parse_updateauth(data: dict) -> messages.EosActionUpdateAuth: auth = parse_authorization(data["auth"]) return messages.EosActionUpdateAuth( account=name_to_number(data["account"]), permission=name_to_number(data["permission"]), parent=name_to_number(data["parent"]), auth=auth, ) def parse_deleteauth(data: dict) -> messages.EosActionDeleteAuth: return messages.EosActionDeleteAuth( account=name_to_number(data["account"]), permission=name_to_number(data["permission"]), ) def parse_linkauth(data: dict) -> messages.EosActionLinkAuth: return messages.EosActionLinkAuth( account=name_to_number(data["account"]), code=name_to_number(data["code"]), type=name_to_number(data["type"]), requirement=name_to_number(data["requirement"]), ) def parse_unlinkauth(data: dict) -> messages.EosActionUnlinkAuth: return messages.EosActionUnlinkAuth( account=name_to_number(data["account"]), code=name_to_number(data["code"]), type=name_to_number(data["type"]), ) def parse_authorization(data: dict) -> messages.EosAuthorization: keys = [] for key in data["keys"]: _t, _k = public_key_to_buffer(key["key"]) keys.append( messages.EosAuthorizationKey(type=_t, key=_k, weight=int(key["weight"])) ) accounts = [] for account in data["accounts"]: accounts.append( messages.EosAuthorizationAccount( account=messages.EosPermissionLevel( actor=name_to_number(account["permission"]["actor"]), permission=name_to_number(account["permission"]["permission"]), ), weight=int(account["weight"]), ) ) waits = [] for wait in data["waits"]: waits.append( messages.EosAuthorizationWait( wait_sec=int(wait["wait_sec"]), weight=int(wait["weight"]) ) ) return messages.EosAuthorization( threshold=int(data["threshold"]), keys=keys, accounts=accounts, waits=waits ) def parse_new_account(data: dict) -> messages.EosActionNewAccount: owner = parse_authorization(data["owner"]) active = parse_authorization(data["active"]) return messages.EosActionNewAccount( creator=name_to_number(data["creator"]), name=name_to_number(data["name"]), owner=owner, active=active, ) def parse_unknown(data: str) -> messages.EosActionUnknown: data_bytes = bytes.fromhex(data) return messages.EosActionUnknown(data_size=len(data_bytes), data_chunk=data_bytes) def parse_action(action: dict) -> messages.EosTxActionAck: tx_action = messages.EosTxActionAck(common=parse_common(action)) data = action["data"] if action["account"] == "eosio": if action["name"] == "voteproducer": tx_action.vote_producer = parse_vote_producer(data) elif action["name"] == "buyram": tx_action.buy_ram = parse_buy_ram(data) elif action["name"] == "buyrambytes": tx_action.buy_ram_bytes = parse_buy_rambytes(data) elif action["name"] == "sellram": tx_action.sell_ram = parse_sell_ram(data) elif action["name"] == "delegatebw": tx_action.delegate = parse_delegate(data) elif action["name"] == "undelegatebw": tx_action.undelegate = parse_undelegate(data) elif action["name"] == "refund": tx_action.refund = parse_refund(data) elif action["name"] == "updateauth": tx_action.update_auth = parse_updateauth(data) elif action["name"] == "deleteauth": tx_action.delete_auth = parse_deleteauth(data) elif action["name"] == "linkauth": tx_action.link_auth = parse_linkauth(data) elif action["name"] == "unlinkauth": tx_action.unlink_auth = parse_unlinkauth(data) elif action["name"] == "newaccount": tx_action.new_account = parse_new_account(data) elif action["name"] == "transfer": tx_action.transfer = parse_transfer(data) else: tx_action.unknown = parse_unknown(data) return tx_action def parse_transaction_json( transaction: dict, ) -> Tuple[messages.EosTxHeader, List[messages.EosTxActionAck]]: header = messages.EosTxHeader( expiration=int( ( datetime.strptime(transaction["expiration"], "%Y-%m-%dT%H:%M:%S") - datetime(1970, 1, 1) ).total_seconds() ), ref_block_num=int(transaction["ref_block_num"]), ref_block_prefix=int(transaction["ref_block_prefix"]), max_net_usage_words=int(transaction["max_net_usage_words"]), max_cpu_usage_ms=int(transaction["max_cpu_usage_ms"]), delay_sec=int(transaction["delay_sec"]), ) actions = [parse_action(a) for a in transaction["actions"]] return header, actions # ====== Client functions ====== # @expect(messages.EosPublicKey) def get_public_key( client: "TrezorClient", n: "Address", show_display: bool = False ) -> "MessageType": response = client.call( messages.EosGetPublicKey(address_n=n, show_display=show_display) ) return response @session def sign_tx( client: "TrezorClient", address: "Address", transaction: dict, chain_id: str, chunkify: bool = False, ) -> messages.EosSignedTx: header, actions = parse_transaction_json(transaction) msg = messages.EosSignTx( address_n=address, chain_id=bytes.fromhex(chain_id), header=header, num_actions=len(actions), chunkify=chunkify, ) response = client.call(msg) try: while isinstance(response, messages.EosTxActionRequest): response = client.call(actions.pop(0)) except IndexError: # pop from empty list raise exceptions.TrezorException( "Reached end of operations without a signature." ) from None if not isinstance(response, messages.EosSignedTx): raise exceptions.TrezorException( f"Unexpected message: {response.__class__.__name__}" ) return response ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/ethereum.py0000664000175000017500000003305414636513242020341 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import re from typing import TYPE_CHECKING, Any, AnyStr, Dict, List, Optional, Tuple from . import definitions, exceptions, messages from .tools import expect, prepare_message_bytes, session, unharden if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType from .tools import Address def int_to_big_endian(value: int) -> bytes: return value.to_bytes((value.bit_length() + 7) // 8, "big") def decode_hex(value: str) -> bytes: if value.startswith(("0x", "0X")): return bytes.fromhex(value[2:]) else: return bytes.fromhex(value) def sanitize_typed_data(data: dict) -> dict: """Remove properties from a message object that are not defined per EIP-712.""" REQUIRED_KEYS = ("types", "primaryType", "domain", "message") sanitized_data = {key: data[key] for key in REQUIRED_KEYS} sanitized_data["types"].setdefault("EIP712Domain", []) return sanitized_data def is_array(type_name: str) -> bool: return type_name[-1] == "]" def typeof_array(type_name: str) -> str: return type_name[: type_name.rindex("[")] def parse_type_n(type_name: str) -> int: """Parse N from type. Example: "uint256" -> 256.""" match = re.search(r"\d+$", type_name) if match: return int(match.group(0)) else: raise ValueError(f"Could not parse type from {type_name}.") def parse_array_n(type_name: str) -> Optional[int]: """Parse N in type[] where "type" can itself be an array type.""" # sign that it is a dynamic array - we do not know if type_name.endswith("[]"): return None start_idx = type_name.rindex("[") + 1 return int(type_name[start_idx:-1]) def get_byte_size_for_int_type(int_type: str) -> int: return parse_type_n(int_type) // 8 def get_field_type(type_name: str, types: dict) -> messages.EthereumFieldType: data_type = None size = None entry_type = None struct_name = None if is_array(type_name): data_type = messages.EthereumDataType.ARRAY size = parse_array_n(type_name) member_typename = typeof_array(type_name) entry_type = get_field_type(member_typename, types) # Not supporting nested arrays currently if entry_type.data_type == messages.EthereumDataType.ARRAY: raise NotImplementedError("Nested arrays are not supported") elif type_name.startswith("uint"): data_type = messages.EthereumDataType.UINT size = get_byte_size_for_int_type(type_name) elif type_name.startswith("int"): data_type = messages.EthereumDataType.INT size = get_byte_size_for_int_type(type_name) elif type_name.startswith("bytes"): data_type = messages.EthereumDataType.BYTES size = None if type_name == "bytes" else parse_type_n(type_name) elif type_name == "string": data_type = messages.EthereumDataType.STRING elif type_name == "bool": data_type = messages.EthereumDataType.BOOL elif type_name == "address": data_type = messages.EthereumDataType.ADDRESS elif type_name in types: data_type = messages.EthereumDataType.STRUCT size = len(types[type_name]) struct_name = type_name else: raise ValueError(f"Unsupported type name: {type_name}") return messages.EthereumFieldType( data_type=data_type, size=size, entry_type=entry_type, struct_name=struct_name, ) def encode_data(value: Any, type_name: str) -> bytes: if type_name.startswith("bytes"): return decode_hex(value) elif type_name == "string": return value.encode() elif type_name.startswith(("int", "uint")): byte_length = get_byte_size_for_int_type(type_name) return int(value).to_bytes( byte_length, "big", signed=type_name.startswith("int") ) elif type_name == "bool": if not isinstance(value, bool): raise ValueError(f"Invalid bool value - {value}") return int(value).to_bytes(1, "big") elif type_name == "address": return decode_hex(value) # We should be receiving only atomic, non-array types raise ValueError(f"Unsupported data type for direct field encoding: {type_name}") def network_from_address_n( address_n: "Address", source: definitions.Source, ) -> Optional[bytes]: """Get network definition bytes based on address_n. Tries to extract the slip44 identifier and lookup the network definition. Returns None on failure. """ if len(address_n) < 2: return None # unharden the slip44 part if needed slip44 = unharden(address_n[1]) return source.get_network_by_slip44(slip44) # ====== Client functions ====== # @expect(messages.EthereumAddress, field="address", ret_type=str) def get_address( client: "TrezorClient", n: "Address", show_display: bool = False, encoded_network: Optional[bytes] = None, chunkify: bool = False, ) -> "MessageType": return client.call( messages.EthereumGetAddress( address_n=n, show_display=show_display, encoded_network=encoded_network, chunkify=chunkify, ) ) @expect(messages.EthereumPublicKey) def get_public_node( client: "TrezorClient", n: "Address", show_display: bool = False ) -> "MessageType": return client.call( messages.EthereumGetPublicKey(address_n=n, show_display=show_display) ) @session def sign_tx( client: "TrezorClient", n: "Address", nonce: int, gas_price: int, gas_limit: int, to: str, value: int, data: Optional[bytes] = None, chain_id: Optional[int] = None, tx_type: Optional[int] = None, definitions: Optional[messages.EthereumDefinitions] = None, chunkify: bool = False, ) -> Tuple[int, bytes, bytes]: if chain_id is None: raise exceptions.TrezorException("Chain ID cannot be undefined") msg = messages.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), to=to, chain_id=chain_id, tx_type=tx_type, definitions=definitions, chunkify=chunkify, ) if data is None: data = b"" msg.data_length = len(data) data, chunk = data[1024:], data[:1024] msg.data_initial_chunk = chunk response = client.call(msg) assert isinstance(response, messages.EthereumTxRequest) while response.data_length is not None: data_length = response.data_length data, chunk = data[data_length:], data[:data_length] response = client.call(messages.EthereumTxAck(data_chunk=chunk)) assert isinstance(response, messages.EthereumTxRequest) assert response.signature_v is not None assert response.signature_r is not None assert response.signature_s is not None # 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 @session def sign_tx_eip1559( client: "TrezorClient", n: "Address", *, nonce: int, gas_limit: int, to: str, value: int, data: bytes = b"", chain_id: int, max_gas_fee: int, max_priority_fee: int, access_list: Optional[List[messages.EthereumAccessList]] = None, definitions: Optional[messages.EthereumDefinitions] = None, chunkify: bool = False, ) -> Tuple[int, bytes, bytes]: length = len(data) data, chunk = data[1024:], data[:1024] msg = messages.EthereumSignTxEIP1559( address_n=n, nonce=int_to_big_endian(nonce), gas_limit=int_to_big_endian(gas_limit), value=int_to_big_endian(value), to=to, chain_id=chain_id, max_gas_fee=int_to_big_endian(max_gas_fee), max_priority_fee=int_to_big_endian(max_priority_fee), access_list=access_list, data_length=length, data_initial_chunk=chunk, definitions=definitions, chunkify=chunkify, ) response = client.call(msg) assert isinstance(response, messages.EthereumTxRequest) while response.data_length is not None: data_length = response.data_length data, chunk = data[data_length:], data[:data_length] response = client.call(messages.EthereumTxAck(data_chunk=chunk)) assert isinstance(response, messages.EthereumTxRequest) assert response.signature_v is not None assert response.signature_r is not None assert response.signature_s is not None return response.signature_v, response.signature_r, response.signature_s @expect(messages.EthereumMessageSignature) def sign_message( client: "TrezorClient", n: "Address", message: AnyStr, encoded_network: Optional[bytes] = None, chunkify: bool = False, ) -> "MessageType": return client.call( messages.EthereumSignMessage( address_n=n, message=prepare_message_bytes(message), encoded_network=encoded_network, chunkify=chunkify, ) ) @expect(messages.EthereumTypedDataSignature) def sign_typed_data( client: "TrezorClient", n: "Address", data: Dict[str, Any], *, metamask_v4_compat: bool = True, definitions: Optional[messages.EthereumDefinitions] = None, ) -> "MessageType": data = sanitize_typed_data(data) types = data["types"] request = messages.EthereumSignTypedData( address_n=n, primary_type=data["primaryType"], metamask_v4_compat=metamask_v4_compat, definitions=definitions, ) response = client.call(request) # Sending all the types while isinstance(response, messages.EthereumTypedDataStructRequest): struct_name = response.name members: List["messages.EthereumStructMember"] = [] for field in types[struct_name]: field_type = get_field_type(field["type"], types) struct_member = messages.EthereumStructMember( type=field_type, name=field["name"], ) members.append(struct_member) request = messages.EthereumTypedDataStructAck(members=members) response = client.call(request) # Sending the whole message that should be signed while isinstance(response, messages.EthereumTypedDataValueRequest): root_index = response.member_path[0] # Index 0 is for the domain data, 1 is for the actual message if root_index == 0: member_typename = "EIP712Domain" member_data = data["domain"] elif root_index == 1: member_typename = data["primaryType"] member_data = data["message"] else: client.cancel() raise exceptions.TrezorException("Root index can only be 0 or 1") # It can be asking for a nested structure (the member path being [X, Y, Z, ...]) # TODO: what to do when the value is missing (for example in recursive types)? for index in response.member_path[1:]: if isinstance(member_data, dict): member_def = types[member_typename][index] member_typename = member_def["type"] member_data = member_data[member_def["name"]] elif isinstance(member_data, list): member_typename = typeof_array(member_typename) member_data = member_data[index] # If we were asked for a list, first sending its length and we will be receiving # requests for individual elements later if isinstance(member_data, list): # Sending the length as uint16 encoded_data = len(member_data).to_bytes(2, "big") else: encoded_data = encode_data(member_data, member_typename) request = messages.EthereumTypedDataValueAck(value=encoded_data) response = client.call(request) return response def verify_message( client: "TrezorClient", address: str, signature: bytes, message: AnyStr, chunkify: bool = False, ) -> bool: try: resp = client.call( messages.EthereumVerifyMessage( address=address, signature=signature, message=prepare_message_bytes(message), chunkify=chunkify, ) ) except exceptions.TrezorFailure: return False return isinstance(resp, messages.Success) @expect(messages.EthereumTypedDataSignature) def sign_typed_data_hash( client: "TrezorClient", n: "Address", domain_hash: bytes, message_hash: Optional[bytes], encoded_network: Optional[bytes] = None, ) -> "MessageType": return client.call( messages.EthereumSignTypedHash( address_n=n, domain_separator_hash=domain_hash, message_hash=message_hash, encoded_network=encoded_network, ) ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/exceptions.py0000664000175000017500000000310114636513242020672 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from typing import TYPE_CHECKING if TYPE_CHECKING: from .messages import Failure class TrezorException(Exception): pass class TrezorFailure(TrezorException): def __init__(self, failure: "Failure") -> None: self.failure = failure self.code = failure.code self.message = failure.message super().__init__(self.code, self.message, self.failure) def __str__(self) -> str: from .messages import FailureType types = { getattr(FailureType, name): name for name in dir(FailureType) if not name.startswith("_") } if self.message is not None: return f"{types[self.code]}: {self.message}" else: return types[self.failure.code] class PinException(TrezorException): pass class Cancelled(TrezorException): pass class OutdatedFirmwareError(TrezorException): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/fido.py0000664000175000017500000000366114636513242017445 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from typing import TYPE_CHECKING, List from . import messages from .tools import expect if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType @expect( messages.WebAuthnCredentials, field="credentials", ret_type=List[messages.WebAuthnCredential], ) def list_credentials(client: "TrezorClient") -> "MessageType": return client.call(messages.WebAuthnListResidentCredentials()) @expect(messages.Success, field="message", ret_type=str) def add_credential(client: "TrezorClient", credential_id: bytes) -> "MessageType": return client.call( messages.WebAuthnAddResidentCredential(credential_id=credential_id) ) @expect(messages.Success, field="message", ret_type=str) def remove_credential(client: "TrezorClient", index: int) -> "MessageType": return client.call(messages.WebAuthnRemoveResidentCredential(index=index)) @expect(messages.Success, field="message", ret_type=str) def set_counter(client: "TrezorClient", u2f_counter: int) -> "MessageType": return client.call(messages.SetU2FCounter(u2f_counter=u2f_counter)) @expect(messages.NextU2FCounter, field="u2f_counter", ret_type=int) def get_next_counter(client: "TrezorClient") -> "MessageType": return client.call(messages.GetNextU2FCounter()) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1719315826.1321068 trezor-0.13.9/src/trezorlib/firmware/0000775000175000017500000000000014636526562017771 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/firmware/__init__.py0000664000175000017500000000677014636513242022103 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import typing as t from hashlib import blake2s from typing_extensions import Protocol, TypeGuard from .. import messages from ..tools import expect, session from .core import VendorFirmware from .legacy import LegacyFirmware, LegacyV2Firmware # re-exports: if True: # indented block prevents isort from messing with these until we upgrade to 5.x from .consts import * # noqa: F401, F403 from .core import * # noqa: F401, F403 from .legacy import * # noqa: F401, F403 from .util import ( # noqa: F401 FirmwareIntegrityError, InvalidSignatureError, Unsigned, ) from .vendor import * # noqa: F401, F403 if t.TYPE_CHECKING: from ..client import TrezorClient T = t.TypeVar("T", bound="FirmwareType") class FirmwareType(Protocol): @classmethod def parse(cls: t.Type[T], data: bytes) -> T: ... def verify(self, dev_keys: bool = False) -> None: ... def digest(self) -> bytes: ... def parse(data: bytes) -> "FirmwareType": try: if data[:4] == b"TRZR": return LegacyFirmware.parse(data) elif data[:4] == b"TRZV": return VendorFirmware.parse(data) elif data[:4] == b"TRZF": return LegacyV2Firmware.parse(data) else: raise ValueError("Unrecognized firmware image type") except Exception as e: raise FirmwareIntegrityError("Invalid firmware image") from e def is_onev2(fw: "FirmwareType") -> TypeGuard[LegacyFirmware]: return isinstance(fw, LegacyFirmware) and fw.embedded_v2 is not None # ====== Client functions ====== # @session def update( client: "TrezorClient", data: bytes, progress_update: t.Callable[[int], t.Any] = lambda _: None, ): 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)) progress_update(len(data)) if isinstance(resp, messages.Success): return else: raise RuntimeError(f"Unexpected result {resp}") # TREZORv2 method while isinstance(resp, messages.FirmwareRequest): length = resp.length payload = data[resp.offset : resp.offset + length] digest = blake2s(payload).digest() resp = client.call(messages.FirmwareUpload(payload=payload, hash=digest)) progress_update(length) if isinstance(resp, messages.Success): return else: raise RuntimeError(f"Unexpected message {resp}") @expect(messages.FirmwareHash, field="hash", ret_type=bytes) def get_hash(client: "TrezorClient", challenge: t.Optional[bytes]): return client.call(messages.GetFirmwareHash(challenge=challenge)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/firmware/consts.py0000664000175000017500000000217514636513242021650 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from . import models V1_SIGNATURE_SLOTS = 3 # === KEYS KEPT FOR COMPATIBILITY === # use `trezorlib.firmware.models` directly V1_BOOTLOADER_KEYS = models.LEGACY_V1V2.firmware_keys V2_BOARDLOADER_KEYS = models.T2T1.boardloader_keys V2_BOARDLOADER_DEV_KEYS = models.TREZOR_CORE_DEV.boardloader_keys V2_BOOTLOADER_KEYS = models.T2T1.bootloader_keys V2_BOOTLOADER_DEV_KEYS = models.TREZOR_CORE_DEV.bootloader_keys V2_SIGS_REQUIRED = models.T2T1.boardloader_sigs_needed ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/firmware/core.py0000664000175000017500000001420714636513242021266 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import typing as t from copy import copy from enum import Enum import construct as c from construct_classes import Struct, subcon from .. import cosi from ..tools import EnumAdapter, TupleAdapter from . import consts, util from .models import Model from .vendor import VendorHeader __all__ = [ "HeaderType", "FirmwareHeader", "FirmwareImage", "VendorFirmware", ] class HeaderType(Enum): FIRMWARE = b"TRZF" BOOTLOADER = b"TRZB" class FirmwareHeader(Struct): magic: HeaderType header_len: int expiry: int code_length: int version: t.Tuple[int, int, int, int] fix_version: t.Tuple[int, int, int, int] hw_model: t.Union[Model, bytes] hw_revision: int monotonic: int hashes: t.List[bytes] v1_signatures: t.List[bytes] v1_key_indexes: t.List[int] sigmask: int signature: bytes # fmt: off SUBCON = c.Struct( "_start_offset" / c.Tell, "magic" / EnumAdapter(c.Bytes(4), HeaderType), "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" / TupleAdapter(c.Int8ul, c.Int8ul, c.Int8ul, c.Int8ul), "fix_version" / TupleAdapter(c.Int8ul, c.Int8ul, c.Int8ul, c.Int8ul), "hw_model" / EnumAdapter(c.Bytes(4), Model), "hw_revision" / c.Int8ul, "monotonic" / c.Int8ul, "_reserved" / c.Padding(2), "hashes" / c.Bytes(32)[16], "v1_signatures" / c.Bytes(64)[consts.V1_SIGNATURE_SLOTS], "v1_key_indexes" / c.Int8ul[consts.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[0] > 1, c.Pointer( c.this._start_offset + 4, c.Rebuild(c.Int32ul, c.this._end_offset - c.this._start_offset) ), ), ) # fmt: on class FirmwareImage(Struct): """Raw firmware image. Consists of firmware header and code block. This is the expected format of firmware binaries for Trezor One, or bootloader images for Trezor T.""" header: FirmwareHeader = subcon(FirmwareHeader) _code_offset: int code: bytes SUBCON = c.Struct( "header" / FirmwareHeader.SUBCON, "_code_offset" / c.Tell, "code" / c.Bytes(c.this.header.code_length), c.Terminated, ) def get_hash_params(self) -> "util.FirmwareHashParameters": return Model.from_hw_model(self.header.hw_model).hash_params() def code_hashes(self) -> t.List[bytes]: """Calculate hashes of chunks of `code`. Assume that the first `code_offset` bytes of `code` are taken up by the header. """ hashes = [] hash_params = self.get_hash_params() # End offset for each chunk. Normally this would be (i+1)*chunk_size for i-th chunk, # but the first chunk is shorter by code_offset, so all end offsets are shifted. ends = [(i + 1) * hash_params.chunk_size - self._code_offset for i in range(16)] start = 0 for end in ends: chunk = self.code[start:end] # padding for last non-empty chunk if hash_params.padding_byte is not None and start < len(self.code) < end: chunk += hash_params.padding_byte[0:1] * (end - start - len(chunk)) if not chunk: hashes.append(b"\0" * 32) else: hashes.append(hash_params.hash_function(chunk).digest()) start = end return hashes def validate_code_hashes(self) -> None: if self.code_hashes() != self.header.hashes: raise util.FirmwareIntegrityError("Invalid firmware data.") def digest(self) -> bytes: hash_params = self.get_hash_params() header = copy(self.header) header.hashes = self.code_hashes() header.signature = b"\x00" * 64 header.sigmask = 0 header.v1_key_indexes = [0] * consts.V1_SIGNATURE_SLOTS header.v1_signatures = [b"\x00" * 64] * consts.V1_SIGNATURE_SLOTS return hash_params.hash_function(header.build()).digest() class VendorFirmware(Struct): """Firmware image prefixed by a vendor header. This is the expected format of firmware binaries for Trezor T.""" vendor_header: VendorHeader = subcon(VendorHeader) firmware: FirmwareImage = subcon(FirmwareImage) SUBCON = c.Struct( "vendor_header" / VendorHeader.SUBCON, "firmware" / FirmwareImage.SUBCON, c.Terminated, ) def digest(self) -> bytes: return self.firmware.digest() def verify(self, dev_keys: bool = False) -> None: self.firmware.validate_code_hashes() self.vendor_header.verify(dev_keys) digest = self.digest() try: cosi.verify( self.firmware.header.signature, digest, self.vendor_header.sig_m, self.vendor_header.pubkeys, self.firmware.header.sigmask, ) except Exception: raise util.InvalidSignatureError("Invalid firmware signature.") # XXX expiry is not used now # now = time.gmtime() # if time.gmtime(fw.vendor_header.expiry) < now: # raise ValueError("Vendor header expired.") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/firmware/legacy.py0000664000175000017500000001447614636513242021612 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import hashlib import typing as t from dataclasses import field import construct as c import ecdsa from construct_classes import Struct, subcon from . import consts, models, util from .core import FirmwareImage from .models import Model __all__ = [ "LegacyFirmware", "LegacyV2Firmware", "check_sig_v1", ] ZERO_SIG = b"\x00" * 64 def check_sig_v1( digest: bytes, key_indexes: t.Sequence[int], signatures: t.Sequence[bytes], sigs_required: int, public_keys: t.Sequence[bytes], ) -> None: """Validate signatures of `digest` using the Trezor One V1 method.""" distinct_indexes = set(i for i in key_indexes[:sigs_required] if i != 0) if not distinct_indexes: raise util.Unsigned if len(distinct_indexes) != sigs_required: raise util.InvalidSignatureError( f"Not enough distinct signatures (found {len(distinct_indexes)}, need {sigs_required})" ) if any(k != 0 for k in key_indexes[sigs_required:]) or any( sig != ZERO_SIG for sig in signatures[sigs_required:] ): raise util.InvalidSignatureError("Too many signatures") for i in range(sigs_required): key_idx = key_indexes[i] - 1 signature = signatures[i] if key_idx >= len(public_keys): # unknown pubkey raise util.InvalidSignatureError(f"Unknown key in slot {i}") verify = ecdsa.VerifyingKey.from_string( public_keys[key_idx], curve=ecdsa.curves.SECP256k1, hashfunc=hashlib.sha256, ) try: verify.verify_digest(signature, digest) except ecdsa.BadSignatureError as e: raise util.InvalidSignatureError(f"Invalid signature in slot {i}") from e def check_sig_signmessage( digest: bytes, key_indexes: t.Sequence[int], signatures: t.Sequence[bytes], sigs_required: int, public_keys: t.Sequence[bytes], ) -> None: """Validate signatures of `digest` using the Trezor One SignMessage method.""" btc_digest = hashlib.sha256(b"\x18Bitcoin Signed Message:\n\x20" + digest).digest() final_digest = hashlib.sha256(btc_digest).digest() check_sig_v1( final_digest, key_indexes, signatures, sigs_required, public_keys, ) class LegacyV2Firmware(FirmwareImage): """Firmware image in the format used by Trezor One 1.8.0 and newer.""" V3_FIRST_VERSION = (1, 12, 0) def get_hash_params(self) -> "util.FirmwareHashParameters": return Model.ONE.hash_params() def verify_v2(self, dev_keys: bool) -> None: if not dev_keys: public_keys = models.LEGACY_V1V2.firmware_keys else: public_keys = models.LEGACY_V1V2_DEV.firmware_keys self.validate_code_hashes() check_sig_v1( self.digest(), self.header.v1_key_indexes, self.header.v1_signatures, models.LEGACY_V1V2.firmware_sigs_needed, public_keys, ) def verify_v3(self, dev_keys: bool) -> None: if not dev_keys: model_keys = models.LEGACY_V3 else: model_keys = models.LEGACY_V3_DEV self.validate_code_hashes() check_sig_signmessage( self.digest(), self.header.v1_key_indexes, self.header.v1_signatures, model_keys.firmware_sigs_needed, model_keys.firmware_keys, ) def verify(self, dev_keys: bool = False) -> None: if self.header.version >= self.V3_FIRST_VERSION: try: self.verify_v3(dev_keys) except util.InvalidSignatureError: pass else: return self.verify_v2(dev_keys) def verify_unsigned(self) -> None: self.validate_code_hashes() if any(i != 0 for i in self.header.v1_key_indexes): raise util.InvalidSignatureError("Firmware is not unsigned.") class LegacyFirmware(Struct): """Legacy firmware image. Consists of a custom header and code block. This is the expected format of firmware binaries for Trezor One pre-1.8.0. The code block can optionally be interpreted as a new-style firmware image. That is the expected format of firmware binary for Trezor One version 1.8.0, which can be installed by both the older and the newer bootloader.""" key_indexes: t.List[int] signatures: t.List[bytes] code: bytes flags: t.Dict[str, t.Any] = field(default_factory=dict) embedded_v2: t.Optional[LegacyV2Firmware] = subcon(LegacyV2Firmware, default=None) # fmt: off SUBCON = c.Struct( "magic" / c.Const(b"TRZR"), "code_length" / c.Rebuild(c.Int32ul, c.len_(c.this.code)), "key_indexes" / c.Int8ul[consts.V1_SIGNATURE_SLOTS], # pylint: disable=E1136 "flags" / c.BitStruct( c.Padding(7), "restore_storage" / c.Flag, ), "_reserved" / c.Padding(52), "signatures" / c.Bytes(64)[consts.V1_SIGNATURE_SLOTS], "code" / c.Bytes(c.this.code_length), c.Terminated, "embedded_v2" / c.RestreamData(c.this.code, c.Optional(LegacyV2Firmware.SUBCON)), ) # fmt: on def digest(self) -> bytes: return hashlib.sha256(self.code).digest() def verify(self, dev_keys: bool = False) -> None: if not dev_keys: model_keys = models.LEGACY_V1V2 else: model_keys = models.LEGACY_V1V2_DEV check_sig_v1( self.digest(), self.key_indexes, self.signatures, model_keys.firmware_sigs_needed, model_keys.firmware_keys, ) if self.embedded_v2: self.embedded_v2.verify() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/firmware/models.py0000664000175000017500000002351714636513242021625 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import hashlib import typing as t from dataclasses import dataclass from enum import Enum from .util import FirmwareHashParameters if t.TYPE_CHECKING: from typing_extensions import Self from ..models import TrezorModel class Model(Enum): T1B1 = b"T1B1" T2T1 = b"T2T1" T3T1 = b"T3T1" T2B1 = b"T2B1" D001 = b"D001" D002 = b"D002" # legacy aliases ONE = b"T1B1" T = b"T2T1" R = b"T2B1" DISC1 = b"D001" DISC2 = b"D002" @classmethod def from_hw_model(cls, hw_model: t.Union["Self", bytes]) -> "Model": if isinstance(hw_model, cls): return hw_model if hw_model == b"\x00\x00\x00\x00": return cls.T2T1 raise ValueError(f"Unknown hardware model: {hw_model}") @classmethod def from_trezor_model(cls, trezor_model: "TrezorModel") -> "Self": return cls(trezor_model.internal_name.encode("ascii")) def model_keys(self, dev_keys: bool = False) -> "ModelKeys": if dev_keys: model_map = MODEL_MAP_DEV else: model_map = MODEL_MAP return model_map[self] def hash_params(self) -> "FirmwareHashParameters": return MODEL_HASH_PARAMS_MAP[self] @dataclass class ModelKeys: """Model-specific keys.""" production: bool boardloader_keys: t.Sequence[bytes] boardloader_sigs_needed: int bootloader_keys: t.Sequence[bytes] bootloader_sigs_needed: int firmware_keys: t.Sequence[bytes] firmware_sigs_needed: int LEGACY_V1V2 = ModelKeys( production=True, boardloader_keys=(), boardloader_sigs_needed=-1, bootloader_keys=(), bootloader_sigs_needed=-1, firmware_keys=[ bytes.fromhex(key) for key in ( "04d571b7f148c5e4232c3814f777d8faeaf1a84216c78d569b71041ffc768a5b2d810fc3bb134dd026b57e65005275aedef43e155f48fc11a32ec790a93312bd58", "0463279c0c0866e50c05c799d32bd6bab0188b6de06536d1109d2ed9ce76cb335c490e55aee10cc901215132e853097d5432eda06b792073bd7740c94ce4516cb1", "0443aedbb6f7e71c563f8ed2ef64ec9981482519e7ef4f4aa98b27854e8c49126d4956d300ab45fdc34cd26bc8710de0a31dbdf6de7435fd0b492be70ac75fde58", "04877c39fd7c62237e038235e9c075dab261630f78eeb8edb92487159fffedfdf6046c6f8b881fa407c4a4ce6c28de0b19c1f4e29f1fcbc5a58ffd1432a3e0938a", "047384c51ae81add0a523adbb186c91b906ffb64c2c765802bf26dbd13bdf12c319e80c2213a136c8ee03d7874fd22b70d68e7dee469decfbbb510ee9a460cda45", ) ], firmware_sigs_needed=3, ) LEGACY_V1V2_DEV = ModelKeys( production=False, boardloader_keys=(), boardloader_sigs_needed=-1, bootloader_keys=(), bootloader_sigs_needed=-1, firmware_keys=[ bytes.fromhex(key) for key in ( "042c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991ae31a9c671a36543f46cea8fce6984608aa316aa0472a7eed08847440218cb2f", "04edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f2211452c88a66eb8ac3c19a1cc3a3fc6d72506f6fce2025f738d8b55f29f22125eb0a4", "04665f660a5052be7a95546a02179058d93d3e08a779734914594346075bb0afd45113948d72cf3dc8f2b70ee02dc1695d051bb0c6da2a914a69045e3277682d3b", "0466635d999417b65566866c65630d977a7ae723fe5f6c4cd17fa00f088ba184c103f86b11525fc876237f804b1496bbb98916dcdda18e1e3670522cb5106f410d", "04f36c7d0fb615ada43d7188580f15ebda22d6f6b9b1a92bff16c6937799dcbc667e3bd719d5c84068a94b75e43724467398892eab80a0f8283f9bad6657f146c9", ) ], firmware_sigs_needed=3, ) LEGACY_V3 = ModelKeys( production=True, boardloader_keys=(), boardloader_sigs_needed=-1, bootloader_keys=(), bootloader_sigs_needed=-1, firmware_keys=[ bytes.fromhex(key) for key in ( "032300c1bb4539fcbfca2590bda3dd2093826f4ae437bddecc1a2e72520764ff7a", "0233baeaebc94a2a3e8b11f39a7133dbf427be292fcbceb887d71ef51e85395a19", "0357091fa254b55233d0bb4c48e106c91b92fd0788ebed9d3a916719f44c76c015", ) ], firmware_sigs_needed=2, ) LEGACY_V3_DEV = ModelKeys( production=False, boardloader_keys=(), boardloader_sigs_needed=-1, bootloader_keys=(), bootloader_sigs_needed=-1, firmware_keys=[ bytes.fromhex(key) for key in ( "037308e14077161c365dea0f5c80aa6c5dba34719e825bd23ae5f7e7d2988adb0f", "039c1b2460e343712e982e0732e7ed17f60de4c933065b7170d99c6e7fe7cc7f4b", "03152b37fdf126111274c894c348dcc975b57c115ee24ceb19b5190ac7f7b65173", ) ], firmware_sigs_needed=2, ) T2T1 = ModelKeys( production=True, boardloader_keys=[ bytes.fromhex(key) for key in ( "0eb9856be9ba7e972c7f34eac1ed9b6fd0efd172ec00faf0c589759da4ddfba0", "ac8ab40b32c98655798fd5da5e192be27a22306ea05c6d277cdff4a3f4125cd8", "ce0fcd12543ef5936cf2804982136707863d17295faced72af171d6e6513ff06", ) ], boardloader_sigs_needed=2, bootloader_keys=[ bytes.fromhex(key) for key in ( "c2c87a49c5a3460977fbb2ec9dfe60f06bd694db8244bd4981fe3b7a26307f3f", "80d036b08739b846f4cb77593078deb25dc9487aedcf52e30b4fb7cd7024178a", "b8307a71f552c60a4cbb317ff48b82cdbf6b6bb5f04c920fec7badf017883751", ) ], bootloader_sigs_needed=2, firmware_keys=(), firmware_sigs_needed=-1, ) TREZOR_CORE_DEV = ModelKeys( production=False, boardloader_keys=[ bytes.fromhex(key) for key in ( "db995fe25169d141cab9bbba92baa01f9f2e1ece7df4cb2ac05190f37fcc1f9d", "2152f8d19b791d24453242e15f2eab6cb7cffa7b6a5ed30097960e069881db12", "22fc297792f0b6ffc0bfcfdb7edb0c0aa14e025a365ec0e342e86e3829cb74b6", ) ], boardloader_sigs_needed=2, bootloader_keys=[ bytes.fromhex(key) for key in ( "d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48", "6355691c178a8ff91007a7478afb955ef7352c63e7b25703984cf78b26e21a56", "ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148", ) ], bootloader_sigs_needed=2, firmware_keys=(), firmware_sigs_needed=-1, ) T2B1 = ModelKeys( production=True, boardloader_keys=[ bytes.fromhex(key) for key in ( "549a45557008d5518a9a151dc6a3568cf73830a7fe419f2626d9f30d024b2bec", "c16c7027f8a3962607bf24cdec2e3cd2344e1f6071e8260b3dda52b1a5107cb7", "87180f933178b2832bee2d7046c7f4b98300ca7d7fb2e4567169c8730a1c4020", ) ], boardloader_sigs_needed=2, bootloader_keys=[ bytes.fromhex(key) for key in ( "bf4e6f004fcb32cec683f22c88c1a86c1518c6de8ac97002d84a63bea3e375dd", "d2def691c1e9d809d8190cf7af935c10688f68983479b4ee9abac19104878ec1", "07c85134946bf89fa19bdc2c5e5ff9ce01296508ee0863d0ff6d63331d1a2516", ) ], bootloader_sigs_needed=2, firmware_keys=(), firmware_sigs_needed=-1, ) T3T1 = ModelKeys( production=True, boardloader_keys=[ bytes.fromhex(key) for key in ( "76af426e61406bad7c077b409c66fde39fb817919313ae1e4c02535c80beed96", "619751dc8d2d09d7e5dfb99e41f606debdf419f85a8143e8e5399ea67a3988c7", "abf94b6615a7dde2a871f7d62c38efc7d9d8f6010d8846bee636e4f3e658a38c", ) ], boardloader_sigs_needed=2, bootloader_keys=[ bytes.fromhex(key) for key in ( "338b949b7e3b26470d4fe3696fd6fff28757265d14cca48ebf2db97b4f5bc039", "28682027730b783201b05a8c9d11685447c17297db71b8a60dc693a44610751d", "9fbf31b4e351a4cc81c75995b2257f0a7169268da5a44e94b6a5590d434e32da", ) ], bootloader_sigs_needed=2, firmware_keys=(), firmware_sigs_needed=-1, ) LEGACY_HASH_PARAMS = FirmwareHashParameters( hash_function=hashlib.sha256, chunk_size=1024 * 64, padding_byte=b"\xff", ) T2T1_HASH_PARAMS = FirmwareHashParameters( hash_function=hashlib.blake2s, chunk_size=1024 * 128, padding_byte=None, ) T3T1_HASH_PARAMS = FirmwareHashParameters( hash_function=hashlib.sha256, chunk_size=1024 * 128, padding_byte=None, ) D002_HASH_PARAMS = FirmwareHashParameters( hash_function=hashlib.sha256, chunk_size=1024 * 256, padding_byte=None, ) MODEL_MAP = { Model.T1B1: LEGACY_V3, Model.T2T1: T2T1, Model.T2B1: T2B1, Model.T3T1: T3T1, Model.D001: TREZOR_CORE_DEV, Model.D002: TREZOR_CORE_DEV, } MODEL_MAP_DEV = { Model.T1B1: LEGACY_V3_DEV, Model.T2T1: TREZOR_CORE_DEV, Model.T2B1: TREZOR_CORE_DEV, Model.T3T1: TREZOR_CORE_DEV, Model.D001: TREZOR_CORE_DEV, Model.D002: TREZOR_CORE_DEV, } MODEL_HASH_PARAMS_MAP = { Model.T1B1: LEGACY_HASH_PARAMS, Model.T2T1: T2T1_HASH_PARAMS, Model.T2B1: T2T1_HASH_PARAMS, Model.T3T1: T3T1_HASH_PARAMS, Model.D001: T2T1_HASH_PARAMS, Model.D002: D002_HASH_PARAMS, } # aliases TREZOR_ONE_V1V2 = LEGACY_V1V2 TREZOR_ONE_V1V2_DEV = LEGACY_V1V2_DEV TREZOR_ONE_V3 = LEGACY_V3 TREZOR_ONE_V3_DEV = LEGACY_V3_DEV TREZOR_T = T2T1 TREZOR_R = T2B1 TREZOR_T3T1 = T3T1 TREZOR_T_DEV = TREZOR_CORE_DEV TREZOR_R_DEV = TREZOR_CORE_DEV DISC1 = TREZOR_CORE_DEV DISC1_DEV = TREZOR_CORE_DEV D001 = TREZOR_CORE_DEV D001_DEV = TREZOR_CORE_DEV D002 = TREZOR_CORE_DEV D002_DEV = TREZOR_CORE_DEV ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/firmware/util.py0000664000175000017500000000232414636513242021310 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import typing as t from dataclasses import dataclass from typing_extensions import Protocol class FirmwareIntegrityError(Exception): pass class InvalidSignatureError(FirmwareIntegrityError): pass class Unsigned(FirmwareIntegrityError): pass class DigestCalculator(Protocol): def update(self, __data: bytes) -> None: ... def digest(self) -> bytes: ... Hasher = t.Callable[[bytes], DigestCalculator] @dataclass class FirmwareHashParameters: hash_function: Hasher chunk_size: int padding_byte: t.Optional[bytes] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/firmware/vendor.py0000664000175000017500000001211214636513242021624 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import hashlib import typing as t from copy import copy import construct as c from construct_classes import Struct, subcon from .. import cosi from ..toif import ToifStruct from ..tools import EnumAdapter, TupleAdapter from . import util from .models import Model __all__ = [ "VendorTrust", "VendorHeader", ] 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 VendorTrust(Struct): _dont_provide_secret: bool allow_run_with_secret: bool show_vendor_string: bool require_user_click: bool red_background: bool delay: int _reserved: int = 0 SUBCON = c.Transformed( c.BitStruct( "_reserved" / c.Default(c.BitsInteger(7), 0b1111111), "_dont_provide_secret" / c.Default(c.Flag, lambda this: not this.allow_run_with_secret), "allow_run_with_secret" / c.Flag, "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, ) def is_full_trust(self) -> bool: return ( not self.show_vendor_string and not self.require_user_click and not self.red_background and self.delay == 0 ) class VendorHeader(Struct): header_len: int expiry: int version: t.Tuple[int, int] sig_m: int # sig_n: int hw_model: t.Union[Model, bytes] pubkeys: t.List[bytes] text: str image: t.Dict[str, t.Any] sigmask: int signature: bytes trust: VendorTrust = subcon(VendorTrust) # fmt: off SUBCON = c.Struct( "_start_offset" / c.Tell, "magic" / c.Const(b"TRZV"), "header_len" / c.Int32ul, "expiry" / c.Int32ul, "version" / TupleAdapter(c.Int8ul, c.Int8ul), "sig_m" / c.Int8ul, "sig_n" / c.Rebuild(c.Int8ul, c.len_(c.this.pubkeys)), "trust" / VendorTrust.SUBCON, "hw_model" / EnumAdapter(c.Bytes(4), Model), "_reserved" / c.Padding(10), "pubkeys" / c.Bytes(32)[c.this.sig_n], "text" / c.Aligned(4, c.PascalString(c.Int8ul, "utf-8")), "image" / ToifStruct, "_end_offset" / c.Tell, "_min_header_len" / c.Check(c.this.header_len > (c.this._end_offset - c.this._start_offset) + 65), "_header_len_aligned" / c.Check(c.this.header_len % 512 == 0), c.Padding(c.this.header_len - c.this._end_offset + c.this._start_offset - 65), "sigmask" / c.Byte, "signature" / c.Bytes(64), ) # fmt: on def digest(self) -> bytes: hash_function = Model.from_hw_model(self.hw_model).hash_params().hash_function cpy = copy(self) cpy.sigmask = 0 cpy.signature = b"\x00" * 64 return hash_function(cpy.build()).digest() def vhash(self) -> bytes: h = hashlib.blake2s() sig_n = len(self.pubkeys) h.update(self.sig_m.to_bytes(1, "little")) h.update(sig_n.to_bytes(1, "little")) for i in range(8): if i < sig_n: h.update(self.pubkeys[i]) else: h.update(b"\x00" * 32) return h.digest() def verify(self, dev_keys: bool = False) -> None: digest = self.digest() model_keys = Model.from_hw_model(self.hw_model).model_keys(dev_keys) try: cosi.verify( self.signature, digest, model_keys.bootloader_sigs_needed, model_keys.bootloader_keys, self.sigmask, ) except Exception: raise util.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.") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/log.py0000664000175000017500000000434314636513242017303 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import logging from typing import Optional, Set, Type from typing_extensions import Protocol, runtime_checkable from . import protobuf @runtime_checkable class HasProtobuf(Protocol): protobuf: protobuf.MessageType OMITTED_MESSAGES: Set[Type[protobuf.MessageType]] = set() DUMP_BYTES = 5 DUMP_PACKETS = 4 logging.addLevelName(DUMP_BYTES, "BYTES") logging.addLevelName(DUMP_PACKETS, "PACKETS") 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 isinstance(record, HasProtobuf): if type(record.protobuf) in OMITTED_MESSAGES: message += f" ({record.protobuf.ByteSize()} bytes)" else: message += "\n" + protobuf.format_message(record.protobuf) return message def enable_debug_output( verbosity: int = 1, handler: Optional[logging.Handler] = None ) -> None: if handler is None: handler = logging.StreamHandler() formatter = PrettyProtobufFormatter() handler.setFormatter(formatter) level = logging.NOTSET if verbosity > 0: level = logging.DEBUG if verbosity > 1: level = DUMP_BYTES if verbosity > 2: level = DUMP_PACKETS logger = logging.getLogger(__name__.rsplit(".", 1)[0]) logger.setLevel(level) logger.addHandler(handler) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/mapping.py0000664000175000017500000000675514636513242020166 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from __future__ import annotations import io from types import ModuleType from typing import Dict, Optional, Tuple, Type, TypeVar from typing_extensions import Self from . import messages, protobuf T = TypeVar("T") class ProtobufMapping: """Mapping of protobuf classes to Python classes""" def __init__(self) -> None: self.type_to_class: Dict[int, Type[protobuf.MessageType]] = {} self.class_to_type_override: Dict[Type[protobuf.MessageType], int] = {} def register( self, msg_class: Type[protobuf.MessageType], msg_wire_type: Optional[int] = None, ) -> None: """Register a Python class as a protobuf type. If `msg_wire_type` is specified, it is used instead of the internal value in `msg_class`. Any existing registrations are overwritten. """ if msg_wire_type is not None: self.class_to_type_override[msg_class] = msg_wire_type elif msg_class.MESSAGE_WIRE_TYPE is None: raise ValueError("Cannot register class without wire type") else: msg_wire_type = msg_class.MESSAGE_WIRE_TYPE self.type_to_class[msg_wire_type] = msg_class def encode(self, msg: protobuf.MessageType) -> Tuple[int, bytes]: """Serialize a Python protobuf class. Returns the message wire type and a byte representation of the protobuf message. """ wire_type = self.class_to_type_override.get(type(msg), msg.MESSAGE_WIRE_TYPE) if wire_type is None: raise ValueError("Cannot encode class without wire type") buf = io.BytesIO() protobuf.dump_message(buf, msg) return wire_type, buf.getvalue() def decode(self, msg_wire_type: int, msg_bytes: bytes) -> protobuf.MessageType: """Deserialize a protobuf message into a Python class.""" cls = self.type_to_class[msg_wire_type] buf = io.BytesIO(msg_bytes) return protobuf.load_message(buf, cls) @classmethod def from_module(cls, module: ModuleType) -> Self: """Generate a mapping from a module. The module must have a `MessageType` enum that specifies individual wire types. """ mapping = cls() message_types = getattr(module, "MessageType") for entry in message_types: msg_class = getattr(module, entry.name, None) if msg_class is None: raise ValueError( f"Implementation of protobuf message '{entry.name}' is missing" ) if msg_class.MESSAGE_WIRE_TYPE != entry.value: raise ValueError( f"Inconsistent wire type and MessageType record for '{entry.name}'" ) mapping.register(msg_class) return mapping DEFAULT_MAPPING = ProtobufMapping.from_module(messages) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/merkle_tree.py0000775000175000017500000001432414636513242021023 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import typing as t from hashlib import sha256 from typing_extensions import Protocol def leaf_hash(value: bytes) -> bytes: """Calculate a hash of a leaf node based on its value. See documentation for `MerkleTree` for details. """ return sha256(b"\x00" + value).digest() def internal_hash(left: bytes, right: bytes) -> bytes: """Calculate a hash of an internal node based on its child nodes. See documentation for `MerkleTree` for details. """ hash_a = min(left, right) hash_b = max(left, right) return sha256(b"\x01" + hash_a + hash_b).digest() class NodeType(Protocol): """Merkle tree node.""" tree_hash: bytes """Merkle root hash of the subtree rooted at this node.""" def add_to_proof_list(self, proof_entry: bytes) -> None: """Add a proof entry to the proof list of this node.""" ... class Leaf: """Leaf of a Merkle tree.""" def __init__(self, value: bytes) -> None: self.tree_hash = leaf_hash(value) self.proof: t.List[bytes] = [] def add_to_proof_list(self, proof_entry: bytes) -> None: self.proof.append(proof_entry) class Node: """Internal node of a Merkle tree. Does not have its own proof, but helps to build the proof of its children by passing the respective proof entries to them. """ def __init__(self, left: NodeType, right: NodeType) -> None: self.left = left self.right = right self.left.add_to_proof_list(self.right.tree_hash) self.right.add_to_proof_list(self.left.tree_hash) self.tree_hash = internal_hash(self.left.tree_hash, self.right.tree_hash) def add_to_proof_list(self, proof_entry: bytes) -> None: self.left.add_to_proof_list(proof_entry) self.right.add_to_proof_list(proof_entry) class MerkleTree: """Merkle tree for a list of byte values. The tree is built up as follows: 1. Order the leaves by their hash. 2. Build up the next level up by pairing the leaves in the current level from left to right. 3. Any left-over odd node at the current level gets pushed to the next level. 4. Repeat until there is only one node left. Values are not saved in the tree, only their hashes. This allows us to construct a tree with very large values without having to keep them in memory. Semantically, the tree operates as a set, but this implementation does not check for duplicates. If the same value is added multiple times, the resulting tree will be different from a tree with only one instance of the value. In addition, only one of the several possible proofs for the repeated value is retrievable. Proof hashes are constructed as follows: - Leaf node entries are hashes of b"\x00" + value. - Internal node entries are hashes of b"\x01" + min(left, right) + max(left, right). The prefixes function to distinguish leaf nodes from internal nodes. This prevents two attacks: (a) An attacker cannot misuse a proof for an internal node to claim that is a member of the tree. (b) An attacker cannot insert a leaf node in the format of that is itself an internal node of a different tree. This would allow the attacker to expand the tree with their own subtree. Ordering the internal node entry as min(left, right) + max(left, right) simplifies the proof format and verifier code: when constructing the internal entry, the verifier does not need to distinguish between left and right subtree. """ entries: t.Dict[bytes, Leaf] """Map of leaf hash -> leaf node. Use `leaf_hash` to calculate the hash of a value, or use `get_proof(value)` to access the proof directly. """ root: NodeType """Root node of the tree.""" def __init__(self, values: t.Iterable[bytes]) -> None: leaves = [Leaf(value) for value in values] leaves.sort(key=lambda leaf: leaf.tree_hash) if not leaves: raise ValueError("Merkle tree must have at least one value") self.entries = {leaf.tree_hash: leaf for leaf in leaves} # build the tree current_level = leaves while len(current_level) > 1: # build one level of the tree next_level = [] while len(current_level) >= 2: left, right, *current_level = current_level next_level.append(Node(left, right)) # add the remaining one or zero nodes to the next level next_level.extend(current_level) # switch levels and continue current_level = next_level assert len(current_level) == 1, "Tree must have exactly one root node" # save the root self.root = current_level[0] def get_root_hash(self) -> bytes: return self.root.tree_hash def get_proof(self, value: bytes) -> t.List[bytes]: """Get the proof for a given value.""" try: return self.entries[leaf_hash(value)].proof except KeyError: raise KeyError("Value not found in Merkle tree") from None def evaluate_proof(value: bytes, proof: t.List[bytes]) -> bytes: """Evaluate the provided proof of membership. Reconstructs the Merkle root hash for a tree that contains `value` as a leaf node, proving membership in a Merkle tree with the given root hash. The result can be compared to a statically known root hash, or a signature of it can be verified. """ hash = leaf_hash(value) for proof_entry in proof: hash = internal_hash(hash, proof_entry) return hash ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315321.0 trezor-0.13.9/src/trezorlib/messages.py0000664000175000017500000104420614636525571020344 0ustar00matejcikmatejcik# Automatically generated by pb2py # fmt: off # isort:skip_file from enum import IntEnum from typing import Sequence, Optional from . import protobuf class BinanceOrderType(IntEnum): OT_UNKNOWN = 0 MARKET = 1 LIMIT = 2 OT_RESERVED = 3 class BinanceOrderSide(IntEnum): SIDE_UNKNOWN = 0 BUY = 1 SELL = 2 class BinanceTimeInForce(IntEnum): TIF_UNKNOWN = 0 GTE = 1 TIF_RESERVED = 2 IOC = 3 class MessageType(IntEnum): Initialize = 0 Ping = 1 Success = 2 Failure = 3 ChangePin = 4 WipeDevice = 5 GetEntropy = 9 Entropy = 10 LoadDevice = 13 ResetDevice = 14 SetBusy = 16 Features = 17 PinMatrixRequest = 18 PinMatrixAck = 19 Cancel = 20 LockDevice = 24 ApplySettings = 25 ButtonRequest = 26 ButtonAck = 27 ApplyFlags = 28 GetNonce = 31 Nonce = 33 BackupDevice = 34 EntropyRequest = 35 EntropyAck = 36 PassphraseRequest = 41 PassphraseAck = 42 RecoveryDevice = 45 WordRequest = 46 WordAck = 47 GetFeatures = 55 SdProtect = 79 ChangeWipeCode = 82 EndSession = 83 DoPreauthorized = 84 PreauthorizedRequest = 85 CancelAuthorization = 86 RebootToBootloader = 87 GetFirmwareHash = 88 FirmwareHash = 89 UnlockPath = 93 UnlockedPathRequest = 94 ShowDeviceTutorial = 95 UnlockBootloader = 96 AuthenticateDevice = 97 AuthenticityProof = 98 ChangeLanguage = 990 TranslationDataRequest = 991 TranslationDataAck = 992 SetBrightness = 993 SetU2FCounter = 63 GetNextU2FCounter = 80 NextU2FCounter = 81 Deprecated_PassphraseStateRequest = 77 Deprecated_PassphraseStateAck = 78 FirmwareErase = 6 FirmwareUpload = 7 FirmwareRequest = 8 ProdTestT1 = 32 GetPublicKey = 11 PublicKey = 12 SignTx = 15 TxRequest = 21 TxAck = 22 GetAddress = 29 Address = 30 TxAckPaymentRequest = 37 SignMessage = 38 VerifyMessage = 39 MessageSignature = 40 GetOwnershipId = 43 OwnershipId = 44 GetOwnershipProof = 49 OwnershipProof = 50 AuthorizeCoinJoin = 51 CipherKeyValue = 23 CipheredKeyValue = 48 SignIdentity = 53 SignedIdentity = 54 GetECDHSessionKey = 61 ECDHSessionKey = 62 CosiCommit = 71 CosiCommitment = 72 CosiSign = 73 CosiSignature = 74 DebugLinkDecision = 100 DebugLinkGetState = 101 DebugLinkState = 102 DebugLinkStop = 103 DebugLinkLog = 104 DebugLinkMemoryRead = 110 DebugLinkMemory = 111 DebugLinkMemoryWrite = 112 DebugLinkFlashErase = 113 DebugLinkLayout = 9001 DebugLinkReseedRandom = 9002 DebugLinkRecordScreen = 9003 DebugLinkEraseSdCard = 9005 DebugLinkWatchLayout = 9006 DebugLinkResetDebugEvents = 9007 EthereumGetPublicKey = 450 EthereumPublicKey = 451 EthereumGetAddress = 56 EthereumAddress = 57 EthereumSignTx = 58 EthereumSignTxEIP1559 = 452 EthereumTxRequest = 59 EthereumTxAck = 60 EthereumSignMessage = 64 EthereumVerifyMessage = 65 EthereumMessageSignature = 66 EthereumSignTypedData = 464 EthereumTypedDataStructRequest = 465 EthereumTypedDataStructAck = 466 EthereumTypedDataValueRequest = 467 EthereumTypedDataValueAck = 468 EthereumTypedDataSignature = 469 EthereumSignTypedHash = 470 NEMGetAddress = 67 NEMAddress = 68 NEMSignTx = 69 NEMSignedTx = 70 NEMDecryptMessage = 75 NEMDecryptedMessage = 76 TezosGetAddress = 150 TezosAddress = 151 TezosSignTx = 152 TezosSignedTx = 153 TezosGetPublicKey = 154 TezosPublicKey = 155 StellarSignTx = 202 StellarTxOpRequest = 203 StellarGetAddress = 207 StellarAddress = 208 StellarCreateAccountOp = 210 StellarPaymentOp = 211 StellarPathPaymentStrictReceiveOp = 212 StellarManageSellOfferOp = 213 StellarCreatePassiveSellOfferOp = 214 StellarSetOptionsOp = 215 StellarChangeTrustOp = 216 StellarAllowTrustOp = 217 StellarAccountMergeOp = 218 StellarManageDataOp = 220 StellarBumpSequenceOp = 221 StellarManageBuyOfferOp = 222 StellarPathPaymentStrictSendOp = 223 StellarClaimClaimableBalanceOp = 225 StellarSignedTx = 230 CardanoGetPublicKey = 305 CardanoPublicKey = 306 CardanoGetAddress = 307 CardanoAddress = 308 CardanoTxItemAck = 313 CardanoTxAuxiliaryDataSupplement = 314 CardanoTxWitnessRequest = 315 CardanoTxWitnessResponse = 316 CardanoTxHostAck = 317 CardanoTxBodyHash = 318 CardanoSignTxFinished = 319 CardanoSignTxInit = 320 CardanoTxInput = 321 CardanoTxOutput = 322 CardanoAssetGroup = 323 CardanoToken = 324 CardanoTxCertificate = 325 CardanoTxWithdrawal = 326 CardanoTxAuxiliaryData = 327 CardanoPoolOwner = 328 CardanoPoolRelayParameters = 329 CardanoGetNativeScriptHash = 330 CardanoNativeScriptHash = 331 CardanoTxMint = 332 CardanoTxCollateralInput = 333 CardanoTxRequiredSigner = 334 CardanoTxInlineDatumChunk = 335 CardanoTxReferenceScriptChunk = 336 CardanoTxReferenceInput = 337 RippleGetAddress = 400 RippleAddress = 401 RippleSignTx = 402 RippleSignedTx = 403 MoneroTransactionInitRequest = 501 MoneroTransactionInitAck = 502 MoneroTransactionSetInputRequest = 503 MoneroTransactionSetInputAck = 504 MoneroTransactionInputViniRequest = 507 MoneroTransactionInputViniAck = 508 MoneroTransactionAllInputsSetRequest = 509 MoneroTransactionAllInputsSetAck = 510 MoneroTransactionSetOutputRequest = 511 MoneroTransactionSetOutputAck = 512 MoneroTransactionAllOutSetRequest = 513 MoneroTransactionAllOutSetAck = 514 MoneroTransactionSignInputRequest = 515 MoneroTransactionSignInputAck = 516 MoneroTransactionFinalRequest = 517 MoneroTransactionFinalAck = 518 MoneroKeyImageExportInitRequest = 530 MoneroKeyImageExportInitAck = 531 MoneroKeyImageSyncStepRequest = 532 MoneroKeyImageSyncStepAck = 533 MoneroKeyImageSyncFinalRequest = 534 MoneroKeyImageSyncFinalAck = 535 MoneroGetAddress = 540 MoneroAddress = 541 MoneroGetWatchKey = 542 MoneroWatchKey = 543 DebugMoneroDiagRequest = 546 DebugMoneroDiagAck = 547 MoneroGetTxKeyRequest = 550 MoneroGetTxKeyAck = 551 MoneroLiveRefreshStartRequest = 552 MoneroLiveRefreshStartAck = 553 MoneroLiveRefreshStepRequest = 554 MoneroLiveRefreshStepAck = 555 MoneroLiveRefreshFinalRequest = 556 MoneroLiveRefreshFinalAck = 557 EosGetPublicKey = 600 EosPublicKey = 601 EosSignTx = 602 EosTxActionRequest = 603 EosTxActionAck = 604 EosSignedTx = 605 BinanceGetAddress = 700 BinanceAddress = 701 BinanceGetPublicKey = 702 BinancePublicKey = 703 BinanceSignTx = 704 BinanceTxRequest = 705 BinanceTransferMsg = 706 BinanceOrderMsg = 707 BinanceCancelMsg = 708 BinanceSignedTx = 709 WebAuthnListResidentCredentials = 800 WebAuthnCredentials = 801 WebAuthnAddResidentCredential = 802 WebAuthnRemoveResidentCredential = 803 SolanaGetPublicKey = 900 SolanaPublicKey = 901 SolanaGetAddress = 902 SolanaAddress = 903 SolanaSignTx = 904 SolanaTxSignature = 905 class FailureType(IntEnum): UnexpectedMessage = 1 ButtonExpected = 2 DataError = 3 ActionCancelled = 4 PinExpected = 5 PinCancelled = 6 PinInvalid = 7 InvalidSignature = 8 ProcessError = 9 NotEnoughFunds = 10 NotInitialized = 11 PinMismatch = 12 WipeCodeMismatch = 13 InvalidSession = 14 FirmwareError = 99 class ButtonRequestType(IntEnum): Other = 1 FeeOverThreshold = 2 ConfirmOutput = 3 ResetDevice = 4 ConfirmWord = 5 WipeDevice = 6 ProtectCall = 7 SignTx = 8 FirmwareCheck = 9 Address = 10 PublicKey = 11 MnemonicWordCount = 12 MnemonicInput = 13 _Deprecated_ButtonRequest_PassphraseType = 14 UnknownDerivationPath = 15 RecoveryHomepage = 16 Success = 17 Warning = 18 PassphraseEntry = 19 PinEntry = 20 class PinMatrixRequestType(IntEnum): Current = 1 NewFirst = 2 NewSecond = 3 WipeCodeFirst = 4 WipeCodeSecond = 5 class InputScriptType(IntEnum): SPENDADDRESS = 0 SPENDMULTISIG = 1 EXTERNAL = 2 SPENDWITNESS = 3 SPENDP2SHWITNESS = 4 SPENDTAPROOT = 5 class OutputScriptType(IntEnum): PAYTOADDRESS = 0 PAYTOSCRIPTHASH = 1 PAYTOMULTISIG = 2 PAYTOOPRETURN = 3 PAYTOWITNESS = 4 PAYTOP2SHWITNESS = 5 PAYTOTAPROOT = 6 class DecredStakingSpendType(IntEnum): SSGen = 0 SSRTX = 1 class AmountUnit(IntEnum): BITCOIN = 0 MILLIBITCOIN = 1 MICROBITCOIN = 2 SATOSHI = 3 class RequestType(IntEnum): TXINPUT = 0 TXOUTPUT = 1 TXMETA = 2 TXFINISHED = 3 TXEXTRADATA = 4 TXORIGINPUT = 5 TXORIGOUTPUT = 6 TXPAYMENTREQ = 7 class CardanoDerivationType(IntEnum): LEDGER = 0 ICARUS = 1 ICARUS_TREZOR = 2 class CardanoAddressType(IntEnum): BASE = 0 BASE_SCRIPT_KEY = 1 BASE_KEY_SCRIPT = 2 BASE_SCRIPT_SCRIPT = 3 POINTER = 4 POINTER_SCRIPT = 5 ENTERPRISE = 6 ENTERPRISE_SCRIPT = 7 BYRON = 8 REWARD = 14 REWARD_SCRIPT = 15 class CardanoNativeScriptType(IntEnum): PUB_KEY = 0 ALL = 1 ANY = 2 N_OF_K = 3 INVALID_BEFORE = 4 INVALID_HEREAFTER = 5 class CardanoNativeScriptHashDisplayFormat(IntEnum): HIDE = 0 BECH32 = 1 POLICY_ID = 2 class CardanoTxOutputSerializationFormat(IntEnum): ARRAY_LEGACY = 0 MAP_BABBAGE = 1 class CardanoCertificateType(IntEnum): STAKE_REGISTRATION = 0 STAKE_DEREGISTRATION = 1 STAKE_DELEGATION = 2 STAKE_POOL_REGISTRATION = 3 STAKE_REGISTRATION_CONWAY = 7 STAKE_DEREGISTRATION_CONWAY = 8 VOTE_DELEGATION = 9 class CardanoDRepType(IntEnum): KEY_HASH = 0 SCRIPT_HASH = 1 ABSTAIN = 2 NO_CONFIDENCE = 3 class CardanoPoolRelayType(IntEnum): SINGLE_HOST_IP = 0 SINGLE_HOST_NAME = 1 MULTIPLE_HOST_NAME = 2 class CardanoTxAuxiliaryDataSupplementType(IntEnum): NONE = 0 CVOTE_REGISTRATION_SIGNATURE = 1 class CardanoCVoteRegistrationFormat(IntEnum): CIP15 = 0 CIP36 = 1 class CardanoTxSigningMode(IntEnum): ORDINARY_TRANSACTION = 0 POOL_REGISTRATION_AS_OWNER = 1 MULTISIG_TRANSACTION = 2 PLUTUS_TRANSACTION = 3 class CardanoTxWitnessType(IntEnum): BYRON_WITNESS = 0 SHELLEY_WITNESS = 1 class BackupType(IntEnum): Bip39 = 0 Slip39_Basic = 1 Slip39_Advanced = 2 Slip39_Single_Extendable = 3 Slip39_Basic_Extendable = 4 Slip39_Advanced_Extendable = 5 class SafetyCheckLevel(IntEnum): Strict = 0 PromptAlways = 1 PromptTemporarily = 2 class HomescreenFormat(IntEnum): Toif = 1 Jpeg = 2 ToiG = 3 class RecoveryType(IntEnum): NormalRecovery = 0 DryRun = 1 UnlockRepeatedBackup = 2 class BackupAvailability(IntEnum): NotAvailable = 0 Required = 1 Available = 2 class RecoveryStatus(IntEnum): Nothing = 0 Recovery = 1 Backup = 2 class Capability(IntEnum): Bitcoin = 1 Bitcoin_like = 2 Binance = 3 Cardano = 4 Crypto = 5 EOS = 6 Ethereum = 7 Lisk = 8 Monero = 9 NEM = 10 Ripple = 11 Stellar = 12 Tezos = 13 U2F = 14 Shamir = 15 ShamirGroups = 16 PassphraseEntry = 17 Solana = 18 Translations = 19 Brightness = 20 Haptic = 21 class SdProtectOperationType(IntEnum): DISABLE = 0 ENABLE = 1 REFRESH = 2 class RecoveryDeviceInputMethod(IntEnum): ScrambledWords = 0 Matrix = 1 class WordRequestType(IntEnum): Plain = 0 Matrix9 = 1 Matrix6 = 2 class BootCommand(IntEnum): STOP_AND_WAIT = 0 INSTALL_UPGRADE = 1 class DebugSwipeDirection(IntEnum): UP = 0 DOWN = 1 LEFT = 2 RIGHT = 3 class DebugButton(IntEnum): NO = 0 YES = 1 INFO = 2 class DebugPhysicalButton(IntEnum): LEFT_BTN = 0 MIDDLE_BTN = 1 RIGHT_BTN = 2 class EthereumDefinitionType(IntEnum): NETWORK = 0 TOKEN = 1 class EthereumDataType(IntEnum): UINT = 1 INT = 2 BYTES = 3 STRING = 4 BOOL = 5 ADDRESS = 6 ARRAY = 7 STRUCT = 8 class MoneroNetworkType(IntEnum): MAINNET = 0 TESTNET = 1 STAGENET = 2 FAKECHAIN = 3 class NEMMosaicLevy(IntEnum): MosaicLevy_Absolute = 1 MosaicLevy_Percentile = 2 class NEMSupplyChangeType(IntEnum): SupplyChange_Increase = 1 SupplyChange_Decrease = 2 class NEMModificationType(IntEnum): CosignatoryModification_Add = 1 CosignatoryModification_Delete = 2 class NEMImportanceTransferMode(IntEnum): ImportanceTransfer_Activate = 1 ImportanceTransfer_Deactivate = 2 class StellarAssetType(IntEnum): NATIVE = 0 ALPHANUM4 = 1 ALPHANUM12 = 2 class StellarMemoType(IntEnum): NONE = 0 TEXT = 1 ID = 2 HASH = 3 RETURN = 4 class StellarSignerType(IntEnum): ACCOUNT = 0 PRE_AUTH = 1 HASH = 2 class TezosContractType(IntEnum): Implicit = 0 Originated = 1 class TezosBallotType(IntEnum): Yay = 0 Nay = 1 Pass = 2 class BinanceGetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 700 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display self.chunkify = chunkify class BinanceAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 701 FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), } def __init__( self, *, address: "str", ) -> None: self.address = address class BinanceGetPublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 702 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display class BinancePublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 703 FIELDS = { 1: protobuf.Field("public_key", "bytes", repeated=False, required=True), } def __init__( self, *, public_key: "bytes", ) -> None: self.public_key = public_key class BinanceSignTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 704 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("msg_count", "uint32", repeated=False, required=True), 3: protobuf.Field("account_number", "sint64", repeated=False, required=True), 4: protobuf.Field("chain_id", "string", repeated=False, required=False, default=None), 5: protobuf.Field("memo", "string", repeated=False, required=False, default=None), 6: protobuf.Field("sequence", "sint64", repeated=False, required=True), 7: protobuf.Field("source", "sint64", repeated=False, required=True), 8: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, msg_count: "int", account_number: "int", sequence: "int", source: "int", address_n: Optional[Sequence["int"]] = None, chain_id: Optional["str"] = None, memo: Optional["str"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.msg_count = msg_count self.account_number = account_number self.sequence = sequence self.source = source self.chain_id = chain_id self.memo = memo self.chunkify = chunkify class BinanceTxRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 705 class BinanceTransferMsg(protobuf.MessageType): MESSAGE_WIRE_TYPE = 706 FIELDS = { 1: protobuf.Field("inputs", "BinanceInputOutput", repeated=True, required=False, default=None), 2: protobuf.Field("outputs", "BinanceInputOutput", repeated=True, required=False, default=None), 3: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, inputs: Optional[Sequence["BinanceInputOutput"]] = None, outputs: Optional[Sequence["BinanceInputOutput"]] = None, chunkify: Optional["bool"] = None, ) -> None: self.inputs: Sequence["BinanceInputOutput"] = inputs if inputs is not None else [] self.outputs: Sequence["BinanceInputOutput"] = outputs if outputs is not None else [] self.chunkify = chunkify class BinanceOrderMsg(protobuf.MessageType): MESSAGE_WIRE_TYPE = 707 FIELDS = { 1: protobuf.Field("id", "string", repeated=False, required=False, default=None), 2: protobuf.Field("ordertype", "BinanceOrderType", repeated=False, required=True), 3: protobuf.Field("price", "sint64", repeated=False, required=True), 4: protobuf.Field("quantity", "sint64", repeated=False, required=True), 5: protobuf.Field("sender", "string", repeated=False, required=False, default=None), 6: protobuf.Field("side", "BinanceOrderSide", repeated=False, required=True), 7: protobuf.Field("symbol", "string", repeated=False, required=False, default=None), 8: protobuf.Field("timeinforce", "BinanceTimeInForce", repeated=False, required=True), } def __init__( self, *, ordertype: "BinanceOrderType", price: "int", quantity: "int", side: "BinanceOrderSide", timeinforce: "BinanceTimeInForce", id: Optional["str"] = None, sender: Optional["str"] = None, symbol: Optional["str"] = None, ) -> None: self.ordertype = ordertype self.price = price self.quantity = quantity self.side = side self.timeinforce = timeinforce self.id = id self.sender = sender self.symbol = symbol class BinanceCancelMsg(protobuf.MessageType): MESSAGE_WIRE_TYPE = 708 FIELDS = { 1: protobuf.Field("refid", "string", repeated=False, required=False, default=None), 2: protobuf.Field("sender", "string", repeated=False, required=False, default=None), 3: protobuf.Field("symbol", "string", repeated=False, required=False, default=None), } def __init__( self, *, refid: Optional["str"] = None, sender: Optional["str"] = None, symbol: Optional["str"] = None, ) -> None: self.refid = refid self.sender = sender self.symbol = symbol class BinanceSignedTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 709 FIELDS = { 1: protobuf.Field("signature", "bytes", repeated=False, required=True), 2: protobuf.Field("public_key", "bytes", repeated=False, required=True), } def __init__( self, *, signature: "bytes", public_key: "bytes", ) -> None: self.signature = signature self.public_key = public_key class BinanceInputOutput(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), 2: protobuf.Field("coins", "BinanceCoin", repeated=True, required=False, default=None), } def __init__( self, *, address: "str", coins: Optional[Sequence["BinanceCoin"]] = None, ) -> None: self.coins: Sequence["BinanceCoin"] = coins if coins is not None else [] self.address = address class BinanceCoin(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("amount", "sint64", repeated=False, required=True), 2: protobuf.Field("denom", "string", repeated=False, required=True), } def __init__( self, *, amount: "int", denom: "str", ) -> None: self.amount = amount self.denom = denom class Success(protobuf.MessageType): MESSAGE_WIRE_TYPE = 2 FIELDS = { 1: protobuf.Field("message", "string", repeated=False, required=False, default=''), } def __init__( self, *, message: Optional["str"] = '', ) -> None: self.message = message class Failure(protobuf.MessageType): MESSAGE_WIRE_TYPE = 3 FIELDS = { 1: protobuf.Field("code", "FailureType", repeated=False, required=False, default=None), 2: protobuf.Field("message", "string", repeated=False, required=False, default=None), } def __init__( self, *, code: Optional["FailureType"] = None, message: Optional["str"] = None, ) -> None: self.code = code self.message = message class ButtonRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 26 FIELDS = { 1: protobuf.Field("code", "ButtonRequestType", repeated=False, required=False, default=None), 2: protobuf.Field("pages", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, code: Optional["ButtonRequestType"] = None, pages: Optional["int"] = None, ) -> None: self.code = code self.pages = pages class ButtonAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 27 class PinMatrixRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 18 FIELDS = { 1: protobuf.Field("type", "PinMatrixRequestType", repeated=False, required=False, default=None), } def __init__( self, *, type: Optional["PinMatrixRequestType"] = None, ) -> None: self.type = type class PinMatrixAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 19 FIELDS = { 1: protobuf.Field("pin", "string", repeated=False, required=True), } def __init__( self, *, pin: "str", ) -> None: self.pin = pin class PassphraseRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 41 FIELDS = { 1: protobuf.Field("_on_device", "bool", repeated=False, required=False, default=None), } def __init__( self, *, _on_device: Optional["bool"] = None, ) -> None: self._on_device = _on_device class PassphraseAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 42 FIELDS = { 1: protobuf.Field("passphrase", "string", repeated=False, required=False, default=None), 2: protobuf.Field("_state", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("on_device", "bool", repeated=False, required=False, default=None), } def __init__( self, *, passphrase: Optional["str"] = None, _state: Optional["bytes"] = None, on_device: Optional["bool"] = None, ) -> None: self.passphrase = passphrase self._state = _state self.on_device = on_device class Deprecated_PassphraseStateRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 77 FIELDS = { 1: protobuf.Field("state", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, state: Optional["bytes"] = None, ) -> None: self.state = state class Deprecated_PassphraseStateAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 78 class HDNodeType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("depth", "uint32", repeated=False, required=True), 2: protobuf.Field("fingerprint", "uint32", repeated=False, required=True), 3: protobuf.Field("child_num", "uint32", repeated=False, required=True), 4: protobuf.Field("chain_code", "bytes", repeated=False, required=True), 5: protobuf.Field("private_key", "bytes", repeated=False, required=False, default=None), 6: protobuf.Field("public_key", "bytes", repeated=False, required=True), } def __init__( self, *, depth: "int", fingerprint: "int", child_num: "int", chain_code: "bytes", public_key: "bytes", private_key: Optional["bytes"] = None, ) -> None: self.depth = depth self.fingerprint = fingerprint self.child_num = child_num self.chain_code = chain_code self.public_key = public_key self.private_key = private_key class MultisigRedeemScriptType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("pubkeys", "HDNodePathType", repeated=True, required=False, default=None), 2: protobuf.Field("signatures", "bytes", repeated=True, required=False, default=None), 3: protobuf.Field("m", "uint32", repeated=False, required=True), 4: protobuf.Field("nodes", "HDNodeType", repeated=True, required=False, default=None), 5: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), } def __init__( self, *, m: "int", pubkeys: Optional[Sequence["HDNodePathType"]] = None, signatures: Optional[Sequence["bytes"]] = None, nodes: Optional[Sequence["HDNodeType"]] = None, address_n: Optional[Sequence["int"]] = None, ) -> None: self.pubkeys: Sequence["HDNodePathType"] = pubkeys if pubkeys is not None else [] self.signatures: Sequence["bytes"] = signatures if signatures is not None else [] self.nodes: Sequence["HDNodeType"] = nodes if nodes is not None else [] self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.m = m class GetPublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 11 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("ecdsa_curve_name", "string", repeated=False, required=False, default=None), 3: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 4: protobuf.Field("coin_name", "string", repeated=False, required=False, default='Bitcoin'), 5: protobuf.Field("script_type", "InputScriptType", repeated=False, required=False, default=InputScriptType.SPENDADDRESS), 6: protobuf.Field("ignore_xpub_magic", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, ecdsa_curve_name: Optional["str"] = None, show_display: Optional["bool"] = None, coin_name: Optional["str"] = 'Bitcoin', script_type: Optional["InputScriptType"] = InputScriptType.SPENDADDRESS, ignore_xpub_magic: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.ecdsa_curve_name = ecdsa_curve_name self.show_display = show_display self.coin_name = coin_name self.script_type = script_type self.ignore_xpub_magic = ignore_xpub_magic class PublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 12 FIELDS = { 1: protobuf.Field("node", "HDNodeType", repeated=False, required=True), 2: protobuf.Field("xpub", "string", repeated=False, required=True), 3: protobuf.Field("root_fingerprint", "uint32", repeated=False, required=False, default=None), 4: protobuf.Field("descriptor", "string", repeated=False, required=False, default=None), } def __init__( self, *, node: "HDNodeType", xpub: "str", root_fingerprint: Optional["int"] = None, descriptor: Optional["str"] = None, ) -> None: self.node = node self.xpub = xpub self.root_fingerprint = root_fingerprint self.descriptor = descriptor class GetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 29 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("coin_name", "string", repeated=False, required=False, default='Bitcoin'), 3: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 4: protobuf.Field("multisig", "MultisigRedeemScriptType", repeated=False, required=False, default=None), 5: protobuf.Field("script_type", "InputScriptType", repeated=False, required=False, default=InputScriptType.SPENDADDRESS), 6: protobuf.Field("ignore_xpub_magic", "bool", repeated=False, required=False, default=None), 7: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, coin_name: Optional["str"] = 'Bitcoin', show_display: Optional["bool"] = None, multisig: Optional["MultisigRedeemScriptType"] = None, script_type: Optional["InputScriptType"] = InputScriptType.SPENDADDRESS, ignore_xpub_magic: Optional["bool"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.coin_name = coin_name self.show_display = show_display self.multisig = multisig self.script_type = script_type self.ignore_xpub_magic = ignore_xpub_magic self.chunkify = chunkify class Address(protobuf.MessageType): MESSAGE_WIRE_TYPE = 30 FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), 2: protobuf.Field("mac", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, address: "str", mac: Optional["bytes"] = None, ) -> None: self.address = address self.mac = mac class GetOwnershipId(protobuf.MessageType): MESSAGE_WIRE_TYPE = 43 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("coin_name", "string", repeated=False, required=False, default='Bitcoin'), 3: protobuf.Field("multisig", "MultisigRedeemScriptType", repeated=False, required=False, default=None), 4: protobuf.Field("script_type", "InputScriptType", repeated=False, required=False, default=InputScriptType.SPENDADDRESS), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, coin_name: Optional["str"] = 'Bitcoin', multisig: Optional["MultisigRedeemScriptType"] = None, script_type: Optional["InputScriptType"] = InputScriptType.SPENDADDRESS, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.coin_name = coin_name self.multisig = multisig self.script_type = script_type class OwnershipId(protobuf.MessageType): MESSAGE_WIRE_TYPE = 44 FIELDS = { 1: protobuf.Field("ownership_id", "bytes", repeated=False, required=True), } def __init__( self, *, ownership_id: "bytes", ) -> None: self.ownership_id = ownership_id class SignMessage(protobuf.MessageType): MESSAGE_WIRE_TYPE = 38 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("message", "bytes", repeated=False, required=True), 3: protobuf.Field("coin_name", "string", repeated=False, required=False, default='Bitcoin'), 4: protobuf.Field("script_type", "InputScriptType", repeated=False, required=False, default=InputScriptType.SPENDADDRESS), 5: protobuf.Field("no_script_type", "bool", repeated=False, required=False, default=None), 6: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, message: "bytes", address_n: Optional[Sequence["int"]] = None, coin_name: Optional["str"] = 'Bitcoin', script_type: Optional["InputScriptType"] = InputScriptType.SPENDADDRESS, no_script_type: Optional["bool"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.message = message self.coin_name = coin_name self.script_type = script_type self.no_script_type = no_script_type self.chunkify = chunkify class MessageSignature(protobuf.MessageType): MESSAGE_WIRE_TYPE = 40 FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), 2: protobuf.Field("signature", "bytes", repeated=False, required=True), } def __init__( self, *, address: "str", signature: "bytes", ) -> None: self.address = address self.signature = signature class VerifyMessage(protobuf.MessageType): MESSAGE_WIRE_TYPE = 39 FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), 2: protobuf.Field("signature", "bytes", repeated=False, required=True), 3: protobuf.Field("message", "bytes", repeated=False, required=True), 4: protobuf.Field("coin_name", "string", repeated=False, required=False, default='Bitcoin'), 5: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address: "str", signature: "bytes", message: "bytes", coin_name: Optional["str"] = 'Bitcoin', chunkify: Optional["bool"] = None, ) -> None: self.address = address self.signature = signature self.message = message self.coin_name = coin_name self.chunkify = chunkify class SignTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 15 FIELDS = { 1: protobuf.Field("outputs_count", "uint32", repeated=False, required=True), 2: protobuf.Field("inputs_count", "uint32", repeated=False, required=True), 3: protobuf.Field("coin_name", "string", repeated=False, required=False, default='Bitcoin'), 4: protobuf.Field("version", "uint32", repeated=False, required=False, default=1), 5: protobuf.Field("lock_time", "uint32", repeated=False, required=False, default=0), 6: protobuf.Field("expiry", "uint32", repeated=False, required=False, default=None), 7: protobuf.Field("overwintered", "bool", repeated=False, required=False, default=None), 8: protobuf.Field("version_group_id", "uint32", repeated=False, required=False, default=None), 9: protobuf.Field("timestamp", "uint32", repeated=False, required=False, default=None), 10: protobuf.Field("branch_id", "uint32", repeated=False, required=False, default=None), 11: protobuf.Field("amount_unit", "AmountUnit", repeated=False, required=False, default=AmountUnit.BITCOIN), 12: protobuf.Field("decred_staking_ticket", "bool", repeated=False, required=False, default=False), 13: protobuf.Field("serialize", "bool", repeated=False, required=False, default=True), 14: protobuf.Field("coinjoin_request", "CoinJoinRequest", repeated=False, required=False, default=None), 15: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, outputs_count: "int", inputs_count: "int", coin_name: Optional["str"] = 'Bitcoin', version: Optional["int"] = 1, lock_time: Optional["int"] = 0, expiry: Optional["int"] = None, overwintered: Optional["bool"] = None, version_group_id: Optional["int"] = None, timestamp: Optional["int"] = None, branch_id: Optional["int"] = None, amount_unit: Optional["AmountUnit"] = AmountUnit.BITCOIN, decred_staking_ticket: Optional["bool"] = False, serialize: Optional["bool"] = True, coinjoin_request: Optional["CoinJoinRequest"] = None, chunkify: Optional["bool"] = None, ) -> None: self.outputs_count = outputs_count self.inputs_count = inputs_count self.coin_name = coin_name self.version = version self.lock_time = lock_time self.expiry = expiry self.overwintered = overwintered self.version_group_id = version_group_id self.timestamp = timestamp self.branch_id = branch_id self.amount_unit = amount_unit self.decred_staking_ticket = decred_staking_ticket self.serialize = serialize self.coinjoin_request = coinjoin_request self.chunkify = chunkify class TxRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 21 FIELDS = { 1: protobuf.Field("request_type", "RequestType", repeated=False, required=False, default=None), 2: protobuf.Field("details", "TxRequestDetailsType", repeated=False, required=False, default=None), 3: protobuf.Field("serialized", "TxRequestSerializedType", repeated=False, required=False, default=None), } def __init__( self, *, request_type: Optional["RequestType"] = None, details: Optional["TxRequestDetailsType"] = None, serialized: Optional["TxRequestSerializedType"] = None, ) -> None: self.request_type = request_type self.details = details self.serialized = serialized class TxAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 22 FIELDS = { 1: protobuf.Field("tx", "TransactionType", repeated=False, required=False, default=None), } def __init__( self, *, tx: Optional["TransactionType"] = None, ) -> None: self.tx = tx class TxInput(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("prev_hash", "bytes", repeated=False, required=True), 3: protobuf.Field("prev_index", "uint32", repeated=False, required=True), 4: protobuf.Field("script_sig", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("sequence", "uint32", repeated=False, required=False, default=4294967295), 6: protobuf.Field("script_type", "InputScriptType", repeated=False, required=False, default=InputScriptType.SPENDADDRESS), 7: protobuf.Field("multisig", "MultisigRedeemScriptType", repeated=False, required=False, default=None), 8: protobuf.Field("amount", "uint64", repeated=False, required=True), 9: protobuf.Field("decred_tree", "uint32", repeated=False, required=False, default=None), 13: protobuf.Field("witness", "bytes", repeated=False, required=False, default=None), 14: protobuf.Field("ownership_proof", "bytes", repeated=False, required=False, default=None), 15: protobuf.Field("commitment_data", "bytes", repeated=False, required=False, default=None), 16: protobuf.Field("orig_hash", "bytes", repeated=False, required=False, default=None), 17: protobuf.Field("orig_index", "uint32", repeated=False, required=False, default=None), 18: protobuf.Field("decred_staking_spend", "DecredStakingSpendType", repeated=False, required=False, default=None), 19: protobuf.Field("script_pubkey", "bytes", repeated=False, required=False, default=None), 20: protobuf.Field("coinjoin_flags", "uint32", repeated=False, required=False, default=0), } def __init__( self, *, prev_hash: "bytes", prev_index: "int", amount: "int", address_n: Optional[Sequence["int"]] = None, script_sig: Optional["bytes"] = None, sequence: Optional["int"] = 4294967295, script_type: Optional["InputScriptType"] = InputScriptType.SPENDADDRESS, multisig: Optional["MultisigRedeemScriptType"] = None, decred_tree: Optional["int"] = None, witness: Optional["bytes"] = None, ownership_proof: Optional["bytes"] = None, commitment_data: Optional["bytes"] = None, orig_hash: Optional["bytes"] = None, orig_index: Optional["int"] = None, decred_staking_spend: Optional["DecredStakingSpendType"] = None, script_pubkey: Optional["bytes"] = None, coinjoin_flags: Optional["int"] = 0, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.prev_hash = prev_hash self.prev_index = prev_index self.amount = amount self.script_sig = script_sig self.sequence = sequence self.script_type = script_type self.multisig = multisig self.decred_tree = decred_tree self.witness = witness self.ownership_proof = ownership_proof self.commitment_data = commitment_data self.orig_hash = orig_hash self.orig_index = orig_index self.decred_staking_spend = decred_staking_spend self.script_pubkey = script_pubkey self.coinjoin_flags = coinjoin_flags class TxOutput(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=False, default=None), 2: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 3: protobuf.Field("amount", "uint64", repeated=False, required=True), 4: protobuf.Field("script_type", "OutputScriptType", repeated=False, required=False, default=OutputScriptType.PAYTOADDRESS), 5: protobuf.Field("multisig", "MultisigRedeemScriptType", repeated=False, required=False, default=None), 6: protobuf.Field("op_return_data", "bytes", repeated=False, required=False, default=None), 10: protobuf.Field("orig_hash", "bytes", repeated=False, required=False, default=None), 11: protobuf.Field("orig_index", "uint32", repeated=False, required=False, default=None), 12: protobuf.Field("payment_req_index", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, amount: "int", address_n: Optional[Sequence["int"]] = None, address: Optional["str"] = None, script_type: Optional["OutputScriptType"] = OutputScriptType.PAYTOADDRESS, multisig: Optional["MultisigRedeemScriptType"] = None, op_return_data: Optional["bytes"] = None, orig_hash: Optional["bytes"] = None, orig_index: Optional["int"] = None, payment_req_index: Optional["int"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.amount = amount self.address = address self.script_type = script_type self.multisig = multisig self.op_return_data = op_return_data self.orig_hash = orig_hash self.orig_index = orig_index self.payment_req_index = payment_req_index class PrevTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("version", "uint32", repeated=False, required=True), 4: protobuf.Field("lock_time", "uint32", repeated=False, required=True), 6: protobuf.Field("inputs_count", "uint32", repeated=False, required=True), 7: protobuf.Field("outputs_count", "uint32", repeated=False, required=True), 9: protobuf.Field("extra_data_len", "uint32", repeated=False, required=False, default=0), 10: protobuf.Field("expiry", "uint32", repeated=False, required=False, default=None), 12: protobuf.Field("version_group_id", "uint32", repeated=False, required=False, default=None), 13: protobuf.Field("timestamp", "uint32", repeated=False, required=False, default=None), 14: protobuf.Field("branch_id", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, version: "int", lock_time: "int", inputs_count: "int", outputs_count: "int", extra_data_len: Optional["int"] = 0, expiry: Optional["int"] = None, version_group_id: Optional["int"] = None, timestamp: Optional["int"] = None, branch_id: Optional["int"] = None, ) -> None: self.version = version self.lock_time = lock_time self.inputs_count = inputs_count self.outputs_count = outputs_count self.extra_data_len = extra_data_len self.expiry = expiry self.version_group_id = version_group_id self.timestamp = timestamp self.branch_id = branch_id class PrevInput(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 2: protobuf.Field("prev_hash", "bytes", repeated=False, required=True), 3: protobuf.Field("prev_index", "uint32", repeated=False, required=True), 4: protobuf.Field("script_sig", "bytes", repeated=False, required=True), 5: protobuf.Field("sequence", "uint32", repeated=False, required=True), 9: protobuf.Field("decred_tree", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, prev_hash: "bytes", prev_index: "int", script_sig: "bytes", sequence: "int", decred_tree: Optional["int"] = None, ) -> None: self.prev_hash = prev_hash self.prev_index = prev_index self.script_sig = script_sig self.sequence = sequence self.decred_tree = decred_tree class PrevOutput(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("amount", "uint64", repeated=False, required=True), 2: protobuf.Field("script_pubkey", "bytes", repeated=False, required=True), 3: protobuf.Field("decred_script_version", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, amount: "int", script_pubkey: "bytes", decred_script_version: Optional["int"] = None, ) -> None: self.amount = amount self.script_pubkey = script_pubkey self.decred_script_version = decred_script_version class TxAckPaymentRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 37 FIELDS = { 1: protobuf.Field("nonce", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("recipient_name", "string", repeated=False, required=True), 3: protobuf.Field("memos", "PaymentRequestMemo", repeated=True, required=False, default=None), 4: protobuf.Field("amount", "uint64", repeated=False, required=False, default=None), 5: protobuf.Field("signature", "bytes", repeated=False, required=True), } def __init__( self, *, recipient_name: "str", signature: "bytes", memos: Optional[Sequence["PaymentRequestMemo"]] = None, nonce: Optional["bytes"] = None, amount: Optional["int"] = None, ) -> None: self.memos: Sequence["PaymentRequestMemo"] = memos if memos is not None else [] self.recipient_name = recipient_name self.signature = signature self.nonce = nonce self.amount = amount class TxAckInput(protobuf.MessageType): MESSAGE_WIRE_TYPE = 22 FIELDS = { 1: protobuf.Field("tx", "TxAckInputWrapper", repeated=False, required=True), } def __init__( self, *, tx: "TxAckInputWrapper", ) -> None: self.tx = tx class TxAckOutput(protobuf.MessageType): MESSAGE_WIRE_TYPE = 22 FIELDS = { 1: protobuf.Field("tx", "TxAckOutputWrapper", repeated=False, required=True), } def __init__( self, *, tx: "TxAckOutputWrapper", ) -> None: self.tx = tx class TxAckPrevMeta(protobuf.MessageType): MESSAGE_WIRE_TYPE = 22 FIELDS = { 1: protobuf.Field("tx", "PrevTx", repeated=False, required=True), } def __init__( self, *, tx: "PrevTx", ) -> None: self.tx = tx class TxAckPrevInput(protobuf.MessageType): MESSAGE_WIRE_TYPE = 22 FIELDS = { 1: protobuf.Field("tx", "TxAckPrevInputWrapper", repeated=False, required=True), } def __init__( self, *, tx: "TxAckPrevInputWrapper", ) -> None: self.tx = tx class TxAckPrevOutput(protobuf.MessageType): MESSAGE_WIRE_TYPE = 22 FIELDS = { 1: protobuf.Field("tx", "TxAckPrevOutputWrapper", repeated=False, required=True), } def __init__( self, *, tx: "TxAckPrevOutputWrapper", ) -> None: self.tx = tx class TxAckPrevExtraData(protobuf.MessageType): MESSAGE_WIRE_TYPE = 22 FIELDS = { 1: protobuf.Field("tx", "TxAckPrevExtraDataWrapper", repeated=False, required=True), } def __init__( self, *, tx: "TxAckPrevExtraDataWrapper", ) -> None: self.tx = tx class GetOwnershipProof(protobuf.MessageType): MESSAGE_WIRE_TYPE = 49 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("coin_name", "string", repeated=False, required=False, default='Bitcoin'), 3: protobuf.Field("script_type", "InputScriptType", repeated=False, required=False, default=InputScriptType.SPENDWITNESS), 4: protobuf.Field("multisig", "MultisigRedeemScriptType", repeated=False, required=False, default=None), 5: protobuf.Field("user_confirmation", "bool", repeated=False, required=False, default=False), 6: protobuf.Field("ownership_ids", "bytes", repeated=True, required=False, default=None), 7: protobuf.Field("commitment_data", "bytes", repeated=False, required=False, default=b''), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, ownership_ids: Optional[Sequence["bytes"]] = None, coin_name: Optional["str"] = 'Bitcoin', script_type: Optional["InputScriptType"] = InputScriptType.SPENDWITNESS, multisig: Optional["MultisigRedeemScriptType"] = None, user_confirmation: Optional["bool"] = False, commitment_data: Optional["bytes"] = b'', ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.ownership_ids: Sequence["bytes"] = ownership_ids if ownership_ids is not None else [] self.coin_name = coin_name self.script_type = script_type self.multisig = multisig self.user_confirmation = user_confirmation self.commitment_data = commitment_data class OwnershipProof(protobuf.MessageType): MESSAGE_WIRE_TYPE = 50 FIELDS = { 1: protobuf.Field("ownership_proof", "bytes", repeated=False, required=True), 2: protobuf.Field("signature", "bytes", repeated=False, required=True), } def __init__( self, *, ownership_proof: "bytes", signature: "bytes", ) -> None: self.ownership_proof = ownership_proof self.signature = signature class AuthorizeCoinJoin(protobuf.MessageType): MESSAGE_WIRE_TYPE = 51 FIELDS = { 1: protobuf.Field("coordinator", "string", repeated=False, required=True), 2: protobuf.Field("max_rounds", "uint64", repeated=False, required=True), 3: protobuf.Field("max_coordinator_fee_rate", "uint32", repeated=False, required=True), 4: protobuf.Field("max_fee_per_kvbyte", "uint32", repeated=False, required=True), 5: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 6: protobuf.Field("coin_name", "string", repeated=False, required=False, default='Bitcoin'), 7: protobuf.Field("script_type", "InputScriptType", repeated=False, required=False, default=InputScriptType.SPENDADDRESS), 8: protobuf.Field("amount_unit", "AmountUnit", repeated=False, required=False, default=AmountUnit.BITCOIN), } def __init__( self, *, coordinator: "str", max_rounds: "int", max_coordinator_fee_rate: "int", max_fee_per_kvbyte: "int", address_n: Optional[Sequence["int"]] = None, coin_name: Optional["str"] = 'Bitcoin', script_type: Optional["InputScriptType"] = InputScriptType.SPENDADDRESS, amount_unit: Optional["AmountUnit"] = AmountUnit.BITCOIN, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.coordinator = coordinator self.max_rounds = max_rounds self.max_coordinator_fee_rate = max_coordinator_fee_rate self.max_fee_per_kvbyte = max_fee_per_kvbyte self.coin_name = coin_name self.script_type = script_type self.amount_unit = amount_unit class HDNodePathType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("node", "HDNodeType", repeated=False, required=True), 2: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), } def __init__( self, *, node: "HDNodeType", address_n: Optional[Sequence["int"]] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.node = node class CoinJoinRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("fee_rate", "uint32", repeated=False, required=True), 2: protobuf.Field("no_fee_threshold", "uint64", repeated=False, required=True), 3: protobuf.Field("min_registrable_amount", "uint64", repeated=False, required=True), 4: protobuf.Field("mask_public_key", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("signature", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, fee_rate: "int", no_fee_threshold: "int", min_registrable_amount: "int", mask_public_key: Optional["bytes"] = None, signature: Optional["bytes"] = None, ) -> None: self.fee_rate = fee_rate self.no_fee_threshold = no_fee_threshold self.min_registrable_amount = min_registrable_amount self.mask_public_key = mask_public_key self.signature = signature class TxRequestDetailsType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("request_index", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("tx_hash", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("extra_data_len", "uint32", repeated=False, required=False, default=None), 4: protobuf.Field("extra_data_offset", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, request_index: Optional["int"] = None, tx_hash: Optional["bytes"] = None, extra_data_len: Optional["int"] = None, extra_data_offset: Optional["int"] = None, ) -> None: self.request_index = request_index self.tx_hash = tx_hash self.extra_data_len = extra_data_len self.extra_data_offset = extra_data_offset class TxRequestSerializedType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("signature_index", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("signature", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("serialized_tx", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, signature_index: Optional["int"] = None, signature: Optional["bytes"] = None, serialized_tx: Optional["bytes"] = None, ) -> None: self.signature_index = signature_index self.signature = signature self.serialized_tx = serialized_tx class TransactionType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("version", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("inputs", "TxInputType", repeated=True, required=False, default=None), 3: protobuf.Field("bin_outputs", "TxOutputBinType", repeated=True, required=False, default=None), 4: protobuf.Field("lock_time", "uint32", repeated=False, required=False, default=None), 5: protobuf.Field("outputs", "TxOutputType", repeated=True, required=False, default=None), 6: protobuf.Field("inputs_cnt", "uint32", repeated=False, required=False, default=None), 7: protobuf.Field("outputs_cnt", "uint32", repeated=False, required=False, default=None), 8: protobuf.Field("extra_data", "bytes", repeated=False, required=False, default=None), 9: protobuf.Field("extra_data_len", "uint32", repeated=False, required=False, default=None), 10: protobuf.Field("expiry", "uint32", repeated=False, required=False, default=None), 11: protobuf.Field("overwintered", "bool", repeated=False, required=False, default=None), 12: protobuf.Field("version_group_id", "uint32", repeated=False, required=False, default=None), 13: protobuf.Field("timestamp", "uint32", repeated=False, required=False, default=None), 14: protobuf.Field("branch_id", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, inputs: Optional[Sequence["TxInputType"]] = None, bin_outputs: Optional[Sequence["TxOutputBinType"]] = None, outputs: Optional[Sequence["TxOutputType"]] = None, version: Optional["int"] = None, lock_time: Optional["int"] = None, inputs_cnt: Optional["int"] = None, outputs_cnt: Optional["int"] = None, extra_data: Optional["bytes"] = None, extra_data_len: Optional["int"] = None, expiry: Optional["int"] = None, overwintered: Optional["bool"] = None, version_group_id: Optional["int"] = None, timestamp: Optional["int"] = None, branch_id: Optional["int"] = None, ) -> None: self.inputs: Sequence["TxInputType"] = inputs if inputs is not None else [] self.bin_outputs: Sequence["TxOutputBinType"] = bin_outputs if bin_outputs is not None else [] self.outputs: Sequence["TxOutputType"] = outputs if outputs is not None else [] self.version = version self.lock_time = lock_time self.inputs_cnt = inputs_cnt self.outputs_cnt = outputs_cnt self.extra_data = extra_data self.extra_data_len = extra_data_len self.expiry = expiry self.overwintered = overwintered self.version_group_id = version_group_id self.timestamp = timestamp self.branch_id = branch_id class TxInputType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("prev_hash", "bytes", repeated=False, required=True), 3: protobuf.Field("prev_index", "uint32", repeated=False, required=True), 4: protobuf.Field("script_sig", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("sequence", "uint32", repeated=False, required=False, default=4294967295), 6: protobuf.Field("script_type", "InputScriptType", repeated=False, required=False, default=InputScriptType.SPENDADDRESS), 7: protobuf.Field("multisig", "MultisigRedeemScriptType", repeated=False, required=False, default=None), 8: protobuf.Field("amount", "uint64", repeated=False, required=False, default=None), 9: protobuf.Field("decred_tree", "uint32", repeated=False, required=False, default=None), 13: protobuf.Field("witness", "bytes", repeated=False, required=False, default=None), 14: protobuf.Field("ownership_proof", "bytes", repeated=False, required=False, default=None), 15: protobuf.Field("commitment_data", "bytes", repeated=False, required=False, default=None), 16: protobuf.Field("orig_hash", "bytes", repeated=False, required=False, default=None), 17: protobuf.Field("orig_index", "uint32", repeated=False, required=False, default=None), 18: protobuf.Field("decred_staking_spend", "DecredStakingSpendType", repeated=False, required=False, default=None), 19: protobuf.Field("script_pubkey", "bytes", repeated=False, required=False, default=None), 20: protobuf.Field("coinjoin_flags", "uint32", repeated=False, required=False, default=0), } def __init__( self, *, prev_hash: "bytes", prev_index: "int", address_n: Optional[Sequence["int"]] = None, script_sig: Optional["bytes"] = None, sequence: Optional["int"] = 4294967295, script_type: Optional["InputScriptType"] = InputScriptType.SPENDADDRESS, multisig: Optional["MultisigRedeemScriptType"] = None, amount: Optional["int"] = None, decred_tree: Optional["int"] = None, witness: Optional["bytes"] = None, ownership_proof: Optional["bytes"] = None, commitment_data: Optional["bytes"] = None, orig_hash: Optional["bytes"] = None, orig_index: Optional["int"] = None, decred_staking_spend: Optional["DecredStakingSpendType"] = None, script_pubkey: Optional["bytes"] = None, coinjoin_flags: Optional["int"] = 0, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.prev_hash = prev_hash self.prev_index = prev_index self.script_sig = script_sig self.sequence = sequence self.script_type = script_type self.multisig = multisig self.amount = amount self.decred_tree = decred_tree self.witness = witness self.ownership_proof = ownership_proof self.commitment_data = commitment_data self.orig_hash = orig_hash self.orig_index = orig_index self.decred_staking_spend = decred_staking_spend self.script_pubkey = script_pubkey self.coinjoin_flags = coinjoin_flags class TxOutputBinType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("amount", "uint64", repeated=False, required=True), 2: protobuf.Field("script_pubkey", "bytes", repeated=False, required=True), 3: protobuf.Field("decred_script_version", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, amount: "int", script_pubkey: "bytes", decred_script_version: Optional["int"] = None, ) -> None: self.amount = amount self.script_pubkey = script_pubkey self.decred_script_version = decred_script_version class TxOutputType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=False, default=None), 2: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 3: protobuf.Field("amount", "uint64", repeated=False, required=True), 4: protobuf.Field("script_type", "OutputScriptType", repeated=False, required=False, default=OutputScriptType.PAYTOADDRESS), 5: protobuf.Field("multisig", "MultisigRedeemScriptType", repeated=False, required=False, default=None), 6: protobuf.Field("op_return_data", "bytes", repeated=False, required=False, default=None), 10: protobuf.Field("orig_hash", "bytes", repeated=False, required=False, default=None), 11: protobuf.Field("orig_index", "uint32", repeated=False, required=False, default=None), 12: protobuf.Field("payment_req_index", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, amount: "int", address_n: Optional[Sequence["int"]] = None, address: Optional["str"] = None, script_type: Optional["OutputScriptType"] = OutputScriptType.PAYTOADDRESS, multisig: Optional["MultisigRedeemScriptType"] = None, op_return_data: Optional["bytes"] = None, orig_hash: Optional["bytes"] = None, orig_index: Optional["int"] = None, payment_req_index: Optional["int"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.amount = amount self.address = address self.script_type = script_type self.multisig = multisig self.op_return_data = op_return_data self.orig_hash = orig_hash self.orig_index = orig_index self.payment_req_index = payment_req_index class PaymentRequestMemo(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("text_memo", "TextMemo", repeated=False, required=False, default=None), 2: protobuf.Field("refund_memo", "RefundMemo", repeated=False, required=False, default=None), 3: protobuf.Field("coin_purchase_memo", "CoinPurchaseMemo", repeated=False, required=False, default=None), } def __init__( self, *, text_memo: Optional["TextMemo"] = None, refund_memo: Optional["RefundMemo"] = None, coin_purchase_memo: Optional["CoinPurchaseMemo"] = None, ) -> None: self.text_memo = text_memo self.refund_memo = refund_memo self.coin_purchase_memo = coin_purchase_memo class TextMemo(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("text", "string", repeated=False, required=True), } def __init__( self, *, text: "str", ) -> None: self.text = text class RefundMemo(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), 2: protobuf.Field("mac", "bytes", repeated=False, required=True), } def __init__( self, *, address: "str", mac: "bytes", ) -> None: self.address = address self.mac = mac class CoinPurchaseMemo(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("coin_type", "uint32", repeated=False, required=True), 2: protobuf.Field("amount", "string", repeated=False, required=True), 3: protobuf.Field("address", "string", repeated=False, required=True), 4: protobuf.Field("mac", "bytes", repeated=False, required=True), } def __init__( self, *, coin_type: "int", amount: "str", address: "str", mac: "bytes", ) -> None: self.coin_type = coin_type self.amount = amount self.address = address self.mac = mac class TxAckInputWrapper(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 2: protobuf.Field("input", "TxInput", repeated=False, required=True), } def __init__( self, *, input: "TxInput", ) -> None: self.input = input class TxAckOutputWrapper(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 5: protobuf.Field("output", "TxOutput", repeated=False, required=True), } def __init__( self, *, output: "TxOutput", ) -> None: self.output = output class TxAckPrevInputWrapper(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 2: protobuf.Field("input", "PrevInput", repeated=False, required=True), } def __init__( self, *, input: "PrevInput", ) -> None: self.input = input class TxAckPrevOutputWrapper(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 3: protobuf.Field("output", "PrevOutput", repeated=False, required=True), } def __init__( self, *, output: "PrevOutput", ) -> None: self.output = output class TxAckPrevExtraDataWrapper(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 8: protobuf.Field("extra_data_chunk", "bytes", repeated=False, required=True), } def __init__( self, *, extra_data_chunk: "bytes", ) -> None: self.extra_data_chunk = extra_data_chunk class FirmwareErase(protobuf.MessageType): MESSAGE_WIRE_TYPE = 6 FIELDS = { 1: protobuf.Field("length", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, length: Optional["int"] = None, ) -> None: self.length = length class FirmwareRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 8 FIELDS = { 1: protobuf.Field("offset", "uint32", repeated=False, required=True), 2: protobuf.Field("length", "uint32", repeated=False, required=True), } def __init__( self, *, offset: "int", length: "int", ) -> None: self.offset = offset self.length = length class FirmwareUpload(protobuf.MessageType): MESSAGE_WIRE_TYPE = 7 FIELDS = { 1: protobuf.Field("payload", "bytes", repeated=False, required=True), 2: protobuf.Field("hash", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, payload: "bytes", hash: Optional["bytes"] = None, ) -> None: self.payload = payload self.hash = hash class ProdTestT1(protobuf.MessageType): MESSAGE_WIRE_TYPE = 32 FIELDS = { 1: protobuf.Field("payload", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, payload: Optional["bytes"] = None, ) -> None: self.payload = payload class CardanoBlockchainPointerType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("block_index", "uint32", repeated=False, required=True), 2: protobuf.Field("tx_index", "uint32", repeated=False, required=True), 3: protobuf.Field("certificate_index", "uint32", repeated=False, required=True), } def __init__( self, *, block_index: "int", tx_index: "int", certificate_index: "int", ) -> None: self.block_index = block_index self.tx_index = tx_index self.certificate_index = certificate_index class CardanoNativeScript(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("type", "CardanoNativeScriptType", repeated=False, required=True), 2: protobuf.Field("scripts", "CardanoNativeScript", repeated=True, required=False, default=None), 3: protobuf.Field("key_hash", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("key_path", "uint32", repeated=True, required=False, default=None), 5: protobuf.Field("required_signatures_count", "uint32", repeated=False, required=False, default=None), 6: protobuf.Field("invalid_before", "uint64", repeated=False, required=False, default=None), 7: protobuf.Field("invalid_hereafter", "uint64", repeated=False, required=False, default=None), } def __init__( self, *, type: "CardanoNativeScriptType", scripts: Optional[Sequence["CardanoNativeScript"]] = None, key_path: Optional[Sequence["int"]] = None, key_hash: Optional["bytes"] = None, required_signatures_count: Optional["int"] = None, invalid_before: Optional["int"] = None, invalid_hereafter: Optional["int"] = None, ) -> None: self.scripts: Sequence["CardanoNativeScript"] = scripts if scripts is not None else [] self.key_path: Sequence["int"] = key_path if key_path is not None else [] self.type = type self.key_hash = key_hash self.required_signatures_count = required_signatures_count self.invalid_before = invalid_before self.invalid_hereafter = invalid_hereafter class CardanoGetNativeScriptHash(protobuf.MessageType): MESSAGE_WIRE_TYPE = 330 FIELDS = { 1: protobuf.Field("script", "CardanoNativeScript", repeated=False, required=True), 2: protobuf.Field("display_format", "CardanoNativeScriptHashDisplayFormat", repeated=False, required=True), 3: protobuf.Field("derivation_type", "CardanoDerivationType", repeated=False, required=True), } def __init__( self, *, script: "CardanoNativeScript", display_format: "CardanoNativeScriptHashDisplayFormat", derivation_type: "CardanoDerivationType", ) -> None: self.script = script self.display_format = display_format self.derivation_type = derivation_type class CardanoNativeScriptHash(protobuf.MessageType): MESSAGE_WIRE_TYPE = 331 FIELDS = { 1: protobuf.Field("script_hash", "bytes", repeated=False, required=True), } def __init__( self, *, script_hash: "bytes", ) -> None: self.script_hash = script_hash class CardanoAddressParametersType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("address_type", "CardanoAddressType", repeated=False, required=True), 2: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 3: protobuf.Field("address_n_staking", "uint32", repeated=True, required=False, default=None), 4: protobuf.Field("staking_key_hash", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("certificate_pointer", "CardanoBlockchainPointerType", repeated=False, required=False, default=None), 6: protobuf.Field("script_payment_hash", "bytes", repeated=False, required=False, default=None), 7: protobuf.Field("script_staking_hash", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, address_type: "CardanoAddressType", address_n: Optional[Sequence["int"]] = None, address_n_staking: Optional[Sequence["int"]] = None, staking_key_hash: Optional["bytes"] = None, certificate_pointer: Optional["CardanoBlockchainPointerType"] = None, script_payment_hash: Optional["bytes"] = None, script_staking_hash: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.address_n_staking: Sequence["int"] = address_n_staking if address_n_staking is not None else [] self.address_type = address_type self.staking_key_hash = staking_key_hash self.certificate_pointer = certificate_pointer self.script_payment_hash = script_payment_hash self.script_staking_hash = script_staking_hash class CardanoGetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 307 FIELDS = { 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=False), 3: protobuf.Field("protocol_magic", "uint32", repeated=False, required=True), 4: protobuf.Field("network_id", "uint32", repeated=False, required=True), 5: protobuf.Field("address_parameters", "CardanoAddressParametersType", repeated=False, required=True), 6: protobuf.Field("derivation_type", "CardanoDerivationType", repeated=False, required=True), 7: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, protocol_magic: "int", network_id: "int", address_parameters: "CardanoAddressParametersType", derivation_type: "CardanoDerivationType", show_display: Optional["bool"] = False, chunkify: Optional["bool"] = None, ) -> None: self.protocol_magic = protocol_magic self.network_id = network_id self.address_parameters = address_parameters self.derivation_type = derivation_type self.show_display = show_display self.chunkify = chunkify class CardanoAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 308 FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), } def __init__( self, *, address: "str", ) -> None: self.address = address class CardanoGetPublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 305 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("derivation_type", "CardanoDerivationType", repeated=False, required=True), } def __init__( self, *, derivation_type: "CardanoDerivationType", address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.derivation_type = derivation_type self.show_display = show_display class CardanoPublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 306 FIELDS = { 1: protobuf.Field("xpub", "string", repeated=False, required=True), 2: protobuf.Field("node", "HDNodeType", repeated=False, required=True), } def __init__( self, *, xpub: "str", node: "HDNodeType", ) -> None: self.xpub = xpub self.node = node class CardanoSignTxInit(protobuf.MessageType): MESSAGE_WIRE_TYPE = 320 FIELDS = { 1: protobuf.Field("signing_mode", "CardanoTxSigningMode", repeated=False, required=True), 2: protobuf.Field("protocol_magic", "uint32", repeated=False, required=True), 3: protobuf.Field("network_id", "uint32", repeated=False, required=True), 4: protobuf.Field("inputs_count", "uint32", repeated=False, required=True), 5: protobuf.Field("outputs_count", "uint32", repeated=False, required=True), 6: protobuf.Field("fee", "uint64", repeated=False, required=True), 7: protobuf.Field("ttl", "uint64", repeated=False, required=False, default=None), 8: protobuf.Field("certificates_count", "uint32", repeated=False, required=True), 9: protobuf.Field("withdrawals_count", "uint32", repeated=False, required=True), 10: protobuf.Field("has_auxiliary_data", "bool", repeated=False, required=True), 11: protobuf.Field("validity_interval_start", "uint64", repeated=False, required=False, default=None), 12: protobuf.Field("witness_requests_count", "uint32", repeated=False, required=True), 13: protobuf.Field("minting_asset_groups_count", "uint32", repeated=False, required=True), 14: protobuf.Field("derivation_type", "CardanoDerivationType", repeated=False, required=True), 15: protobuf.Field("include_network_id", "bool", repeated=False, required=False, default=False), 16: protobuf.Field("script_data_hash", "bytes", repeated=False, required=False, default=None), 17: protobuf.Field("collateral_inputs_count", "uint32", repeated=False, required=True), 18: protobuf.Field("required_signers_count", "uint32", repeated=False, required=True), 19: protobuf.Field("has_collateral_return", "bool", repeated=False, required=False, default=False), 20: protobuf.Field("total_collateral", "uint64", repeated=False, required=False, default=None), 21: protobuf.Field("reference_inputs_count", "uint32", repeated=False, required=False, default=0), 22: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), 23: protobuf.Field("tag_cbor_sets", "bool", repeated=False, required=False, default=False), } def __init__( self, *, signing_mode: "CardanoTxSigningMode", protocol_magic: "int", network_id: "int", inputs_count: "int", outputs_count: "int", fee: "int", certificates_count: "int", withdrawals_count: "int", has_auxiliary_data: "bool", witness_requests_count: "int", minting_asset_groups_count: "int", derivation_type: "CardanoDerivationType", collateral_inputs_count: "int", required_signers_count: "int", ttl: Optional["int"] = None, validity_interval_start: Optional["int"] = None, include_network_id: Optional["bool"] = False, script_data_hash: Optional["bytes"] = None, has_collateral_return: Optional["bool"] = False, total_collateral: Optional["int"] = None, reference_inputs_count: Optional["int"] = 0, chunkify: Optional["bool"] = None, tag_cbor_sets: Optional["bool"] = False, ) -> None: self.signing_mode = signing_mode self.protocol_magic = protocol_magic self.network_id = network_id self.inputs_count = inputs_count self.outputs_count = outputs_count self.fee = fee self.certificates_count = certificates_count self.withdrawals_count = withdrawals_count self.has_auxiliary_data = has_auxiliary_data self.witness_requests_count = witness_requests_count self.minting_asset_groups_count = minting_asset_groups_count self.derivation_type = derivation_type self.collateral_inputs_count = collateral_inputs_count self.required_signers_count = required_signers_count self.ttl = ttl self.validity_interval_start = validity_interval_start self.include_network_id = include_network_id self.script_data_hash = script_data_hash self.has_collateral_return = has_collateral_return self.total_collateral = total_collateral self.reference_inputs_count = reference_inputs_count self.chunkify = chunkify self.tag_cbor_sets = tag_cbor_sets class CardanoTxInput(protobuf.MessageType): MESSAGE_WIRE_TYPE = 321 FIELDS = { 1: protobuf.Field("prev_hash", "bytes", repeated=False, required=True), 2: protobuf.Field("prev_index", "uint32", repeated=False, required=True), } def __init__( self, *, prev_hash: "bytes", prev_index: "int", ) -> None: self.prev_hash = prev_hash self.prev_index = prev_index class CardanoTxOutput(protobuf.MessageType): MESSAGE_WIRE_TYPE = 322 FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=False, default=None), 2: protobuf.Field("address_parameters", "CardanoAddressParametersType", repeated=False, required=False, default=None), 3: protobuf.Field("amount", "uint64", repeated=False, required=True), 4: protobuf.Field("asset_groups_count", "uint32", repeated=False, required=True), 5: protobuf.Field("datum_hash", "bytes", repeated=False, required=False, default=None), 6: protobuf.Field("format", "CardanoTxOutputSerializationFormat", repeated=False, required=False, default=CardanoTxOutputSerializationFormat.ARRAY_LEGACY), 7: protobuf.Field("inline_datum_size", "uint32", repeated=False, required=False, default=0), 8: protobuf.Field("reference_script_size", "uint32", repeated=False, required=False, default=0), } def __init__( self, *, amount: "int", asset_groups_count: "int", address: Optional["str"] = None, address_parameters: Optional["CardanoAddressParametersType"] = None, datum_hash: Optional["bytes"] = None, format: Optional["CardanoTxOutputSerializationFormat"] = CardanoTxOutputSerializationFormat.ARRAY_LEGACY, inline_datum_size: Optional["int"] = 0, reference_script_size: Optional["int"] = 0, ) -> None: self.amount = amount self.asset_groups_count = asset_groups_count self.address = address self.address_parameters = address_parameters self.datum_hash = datum_hash self.format = format self.inline_datum_size = inline_datum_size self.reference_script_size = reference_script_size class CardanoAssetGroup(protobuf.MessageType): MESSAGE_WIRE_TYPE = 323 FIELDS = { 1: protobuf.Field("policy_id", "bytes", repeated=False, required=True), 2: protobuf.Field("tokens_count", "uint32", repeated=False, required=True), } def __init__( self, *, policy_id: "bytes", tokens_count: "int", ) -> None: self.policy_id = policy_id self.tokens_count = tokens_count class CardanoToken(protobuf.MessageType): MESSAGE_WIRE_TYPE = 324 FIELDS = { 1: protobuf.Field("asset_name_bytes", "bytes", repeated=False, required=True), 2: protobuf.Field("amount", "uint64", repeated=False, required=False, default=None), 3: protobuf.Field("mint_amount", "sint64", repeated=False, required=False, default=None), } def __init__( self, *, asset_name_bytes: "bytes", amount: Optional["int"] = None, mint_amount: Optional["int"] = None, ) -> None: self.asset_name_bytes = asset_name_bytes self.amount = amount self.mint_amount = mint_amount class CardanoTxInlineDatumChunk(protobuf.MessageType): MESSAGE_WIRE_TYPE = 335 FIELDS = { 1: protobuf.Field("data", "bytes", repeated=False, required=True), } def __init__( self, *, data: "bytes", ) -> None: self.data = data class CardanoTxReferenceScriptChunk(protobuf.MessageType): MESSAGE_WIRE_TYPE = 336 FIELDS = { 1: protobuf.Field("data", "bytes", repeated=False, required=True), } def __init__( self, *, data: "bytes", ) -> None: self.data = data class CardanoPoolOwner(protobuf.MessageType): MESSAGE_WIRE_TYPE = 328 FIELDS = { 1: protobuf.Field("staking_key_path", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("staking_key_hash", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, staking_key_path: Optional[Sequence["int"]] = None, staking_key_hash: Optional["bytes"] = None, ) -> None: self.staking_key_path: Sequence["int"] = staking_key_path if staking_key_path is not None else [] self.staking_key_hash = staking_key_hash class CardanoPoolRelayParameters(protobuf.MessageType): MESSAGE_WIRE_TYPE = 329 FIELDS = { 1: protobuf.Field("type", "CardanoPoolRelayType", repeated=False, required=True), 2: protobuf.Field("ipv4_address", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("ipv6_address", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("host_name", "string", repeated=False, required=False, default=None), 5: protobuf.Field("port", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, type: "CardanoPoolRelayType", ipv4_address: Optional["bytes"] = None, ipv6_address: Optional["bytes"] = None, host_name: Optional["str"] = None, port: Optional["int"] = None, ) -> None: self.type = type self.ipv4_address = ipv4_address self.ipv6_address = ipv6_address self.host_name = host_name self.port = port class CardanoPoolMetadataType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("url", "string", repeated=False, required=True), 2: protobuf.Field("hash", "bytes", repeated=False, required=True), } def __init__( self, *, url: "str", hash: "bytes", ) -> None: self.url = url self.hash = hash class CardanoPoolParametersType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("pool_id", "bytes", repeated=False, required=True), 2: protobuf.Field("vrf_key_hash", "bytes", repeated=False, required=True), 3: protobuf.Field("pledge", "uint64", repeated=False, required=True), 4: protobuf.Field("cost", "uint64", repeated=False, required=True), 5: protobuf.Field("margin_numerator", "uint64", repeated=False, required=True), 6: protobuf.Field("margin_denominator", "uint64", repeated=False, required=True), 7: protobuf.Field("reward_account", "string", repeated=False, required=True), 10: protobuf.Field("metadata", "CardanoPoolMetadataType", repeated=False, required=False, default=None), 11: protobuf.Field("owners_count", "uint32", repeated=False, required=True), 12: protobuf.Field("relays_count", "uint32", repeated=False, required=True), } def __init__( self, *, pool_id: "bytes", vrf_key_hash: "bytes", pledge: "int", cost: "int", margin_numerator: "int", margin_denominator: "int", reward_account: "str", owners_count: "int", relays_count: "int", metadata: Optional["CardanoPoolMetadataType"] = None, ) -> None: self.pool_id = pool_id self.vrf_key_hash = vrf_key_hash self.pledge = pledge self.cost = cost self.margin_numerator = margin_numerator self.margin_denominator = margin_denominator self.reward_account = reward_account self.owners_count = owners_count self.relays_count = relays_count self.metadata = metadata class CardanoDRep(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("type", "CardanoDRepType", repeated=False, required=True), 2: protobuf.Field("key_hash", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("script_hash", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, type: "CardanoDRepType", key_hash: Optional["bytes"] = None, script_hash: Optional["bytes"] = None, ) -> None: self.type = type self.key_hash = key_hash self.script_hash = script_hash class CardanoTxCertificate(protobuf.MessageType): MESSAGE_WIRE_TYPE = 325 FIELDS = { 1: protobuf.Field("type", "CardanoCertificateType", repeated=False, required=True), 2: protobuf.Field("path", "uint32", repeated=True, required=False, default=None), 3: protobuf.Field("pool", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("pool_parameters", "CardanoPoolParametersType", repeated=False, required=False, default=None), 5: protobuf.Field("script_hash", "bytes", repeated=False, required=False, default=None), 6: protobuf.Field("key_hash", "bytes", repeated=False, required=False, default=None), 7: protobuf.Field("deposit", "uint64", repeated=False, required=False, default=None), 8: protobuf.Field("drep", "CardanoDRep", repeated=False, required=False, default=None), } def __init__( self, *, type: "CardanoCertificateType", path: Optional[Sequence["int"]] = None, pool: Optional["bytes"] = None, pool_parameters: Optional["CardanoPoolParametersType"] = None, script_hash: Optional["bytes"] = None, key_hash: Optional["bytes"] = None, deposit: Optional["int"] = None, drep: Optional["CardanoDRep"] = None, ) -> None: self.path: Sequence["int"] = path if path is not None else [] self.type = type self.pool = pool self.pool_parameters = pool_parameters self.script_hash = script_hash self.key_hash = key_hash self.deposit = deposit self.drep = drep class CardanoTxWithdrawal(protobuf.MessageType): MESSAGE_WIRE_TYPE = 326 FIELDS = { 1: protobuf.Field("path", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("amount", "uint64", repeated=False, required=True), 3: protobuf.Field("script_hash", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("key_hash", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, amount: "int", path: Optional[Sequence["int"]] = None, script_hash: Optional["bytes"] = None, key_hash: Optional["bytes"] = None, ) -> None: self.path: Sequence["int"] = path if path is not None else [] self.amount = amount self.script_hash = script_hash self.key_hash = key_hash class CardanoCVoteRegistrationDelegation(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("vote_public_key", "bytes", repeated=False, required=True), 2: protobuf.Field("weight", "uint32", repeated=False, required=True), } def __init__( self, *, vote_public_key: "bytes", weight: "int", ) -> None: self.vote_public_key = vote_public_key self.weight = weight class CardanoCVoteRegistrationParametersType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("vote_public_key", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("staking_path", "uint32", repeated=True, required=False, default=None), 3: protobuf.Field("payment_address_parameters", "CardanoAddressParametersType", repeated=False, required=False, default=None), 4: protobuf.Field("nonce", "uint64", repeated=False, required=True), 5: protobuf.Field("format", "CardanoCVoteRegistrationFormat", repeated=False, required=False, default=CardanoCVoteRegistrationFormat.CIP15), 6: protobuf.Field("delegations", "CardanoCVoteRegistrationDelegation", repeated=True, required=False, default=None), 7: protobuf.Field("voting_purpose", "uint64", repeated=False, required=False, default=None), 8: protobuf.Field("payment_address", "string", repeated=False, required=False, default=None), } def __init__( self, *, nonce: "int", staking_path: Optional[Sequence["int"]] = None, delegations: Optional[Sequence["CardanoCVoteRegistrationDelegation"]] = None, vote_public_key: Optional["bytes"] = None, payment_address_parameters: Optional["CardanoAddressParametersType"] = None, format: Optional["CardanoCVoteRegistrationFormat"] = CardanoCVoteRegistrationFormat.CIP15, voting_purpose: Optional["int"] = None, payment_address: Optional["str"] = None, ) -> None: self.staking_path: Sequence["int"] = staking_path if staking_path is not None else [] self.delegations: Sequence["CardanoCVoteRegistrationDelegation"] = delegations if delegations is not None else [] self.nonce = nonce self.vote_public_key = vote_public_key self.payment_address_parameters = payment_address_parameters self.format = format self.voting_purpose = voting_purpose self.payment_address = payment_address class CardanoTxAuxiliaryData(protobuf.MessageType): MESSAGE_WIRE_TYPE = 327 FIELDS = { 1: protobuf.Field("cvote_registration_parameters", "CardanoCVoteRegistrationParametersType", repeated=False, required=False, default=None), 2: protobuf.Field("hash", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, cvote_registration_parameters: Optional["CardanoCVoteRegistrationParametersType"] = None, hash: Optional["bytes"] = None, ) -> None: self.cvote_registration_parameters = cvote_registration_parameters self.hash = hash class CardanoTxMint(protobuf.MessageType): MESSAGE_WIRE_TYPE = 332 FIELDS = { 1: protobuf.Field("asset_groups_count", "uint32", repeated=False, required=True), } def __init__( self, *, asset_groups_count: "int", ) -> None: self.asset_groups_count = asset_groups_count class CardanoTxCollateralInput(protobuf.MessageType): MESSAGE_WIRE_TYPE = 333 FIELDS = { 1: protobuf.Field("prev_hash", "bytes", repeated=False, required=True), 2: protobuf.Field("prev_index", "uint32", repeated=False, required=True), } def __init__( self, *, prev_hash: "bytes", prev_index: "int", ) -> None: self.prev_hash = prev_hash self.prev_index = prev_index class CardanoTxRequiredSigner(protobuf.MessageType): MESSAGE_WIRE_TYPE = 334 FIELDS = { 1: protobuf.Field("key_hash", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("key_path", "uint32", repeated=True, required=False, default=None), } def __init__( self, *, key_path: Optional[Sequence["int"]] = None, key_hash: Optional["bytes"] = None, ) -> None: self.key_path: Sequence["int"] = key_path if key_path is not None else [] self.key_hash = key_hash class CardanoTxReferenceInput(protobuf.MessageType): MESSAGE_WIRE_TYPE = 337 FIELDS = { 1: protobuf.Field("prev_hash", "bytes", repeated=False, required=True), 2: protobuf.Field("prev_index", "uint32", repeated=False, required=True), } def __init__( self, *, prev_hash: "bytes", prev_index: "int", ) -> None: self.prev_hash = prev_hash self.prev_index = prev_index class CardanoTxItemAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 313 class CardanoTxAuxiliaryDataSupplement(protobuf.MessageType): MESSAGE_WIRE_TYPE = 314 FIELDS = { 1: protobuf.Field("type", "CardanoTxAuxiliaryDataSupplementType", repeated=False, required=True), 2: protobuf.Field("auxiliary_data_hash", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("cvote_registration_signature", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, type: "CardanoTxAuxiliaryDataSupplementType", auxiliary_data_hash: Optional["bytes"] = None, cvote_registration_signature: Optional["bytes"] = None, ) -> None: self.type = type self.auxiliary_data_hash = auxiliary_data_hash self.cvote_registration_signature = cvote_registration_signature class CardanoTxWitnessRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 315 FIELDS = { 1: protobuf.Field("path", "uint32", repeated=True, required=False, default=None), } def __init__( self, *, path: Optional[Sequence["int"]] = None, ) -> None: self.path: Sequence["int"] = path if path is not None else [] class CardanoTxWitnessResponse(protobuf.MessageType): MESSAGE_WIRE_TYPE = 316 FIELDS = { 1: protobuf.Field("type", "CardanoTxWitnessType", repeated=False, required=True), 2: protobuf.Field("pub_key", "bytes", repeated=False, required=True), 3: protobuf.Field("signature", "bytes", repeated=False, required=True), 4: protobuf.Field("chain_code", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, type: "CardanoTxWitnessType", pub_key: "bytes", signature: "bytes", chain_code: Optional["bytes"] = None, ) -> None: self.type = type self.pub_key = pub_key self.signature = signature self.chain_code = chain_code class CardanoTxHostAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 317 class CardanoTxBodyHash(protobuf.MessageType): MESSAGE_WIRE_TYPE = 318 FIELDS = { 1: protobuf.Field("tx_hash", "bytes", repeated=False, required=True), } def __init__( self, *, tx_hash: "bytes", ) -> None: self.tx_hash = tx_hash class CardanoSignTxFinished(protobuf.MessageType): MESSAGE_WIRE_TYPE = 319 class CipherKeyValue(protobuf.MessageType): MESSAGE_WIRE_TYPE = 23 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("key", "string", repeated=False, required=True), 3: protobuf.Field("value", "bytes", repeated=False, required=True), 4: protobuf.Field("encrypt", "bool", repeated=False, required=False, default=None), 5: protobuf.Field("ask_on_encrypt", "bool", repeated=False, required=False, default=None), 6: protobuf.Field("ask_on_decrypt", "bool", repeated=False, required=False, default=None), 7: protobuf.Field("iv", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, key: "str", value: "bytes", address_n: Optional[Sequence["int"]] = None, encrypt: Optional["bool"] = None, ask_on_encrypt: Optional["bool"] = None, ask_on_decrypt: Optional["bool"] = None, iv: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.key = key self.value = value self.encrypt = encrypt self.ask_on_encrypt = ask_on_encrypt self.ask_on_decrypt = ask_on_decrypt self.iv = iv class CipheredKeyValue(protobuf.MessageType): MESSAGE_WIRE_TYPE = 48 FIELDS = { 1: protobuf.Field("value", "bytes", repeated=False, required=True), } def __init__( self, *, value: "bytes", ) -> None: self.value = value class IdentityType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("proto", "string", repeated=False, required=False, default=None), 2: protobuf.Field("user", "string", repeated=False, required=False, default=None), 3: protobuf.Field("host", "string", repeated=False, required=False, default=None), 4: protobuf.Field("port", "string", repeated=False, required=False, default=None), 5: protobuf.Field("path", "string", repeated=False, required=False, default=None), 6: protobuf.Field("index", "uint32", repeated=False, required=False, default=0), } def __init__( self, *, proto: Optional["str"] = None, user: Optional["str"] = None, host: Optional["str"] = None, port: Optional["str"] = None, path: Optional["str"] = None, index: Optional["int"] = 0, ) -> None: self.proto = proto self.user = user self.host = host self.port = port self.path = path self.index = index class SignIdentity(protobuf.MessageType): MESSAGE_WIRE_TYPE = 53 FIELDS = { 1: protobuf.Field("identity", "IdentityType", repeated=False, required=True), 2: protobuf.Field("challenge_hidden", "bytes", repeated=False, required=False, default=b''), 3: protobuf.Field("challenge_visual", "string", repeated=False, required=False, default=''), 4: protobuf.Field("ecdsa_curve_name", "string", repeated=False, required=False, default=None), } def __init__( self, *, identity: "IdentityType", challenge_hidden: Optional["bytes"] = b'', challenge_visual: Optional["str"] = '', ecdsa_curve_name: Optional["str"] = None, ) -> None: self.identity = identity self.challenge_hidden = challenge_hidden self.challenge_visual = challenge_visual self.ecdsa_curve_name = ecdsa_curve_name class SignedIdentity(protobuf.MessageType): MESSAGE_WIRE_TYPE = 54 FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=False, default=None), 2: protobuf.Field("public_key", "bytes", repeated=False, required=True), 3: protobuf.Field("signature", "bytes", repeated=False, required=True), } def __init__( self, *, public_key: "bytes", signature: "bytes", address: Optional["str"] = None, ) -> None: self.public_key = public_key self.signature = signature self.address = address class GetECDHSessionKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 61 FIELDS = { 1: protobuf.Field("identity", "IdentityType", repeated=False, required=True), 2: protobuf.Field("peer_public_key", "bytes", repeated=False, required=True), 3: protobuf.Field("ecdsa_curve_name", "string", repeated=False, required=False, default=None), } def __init__( self, *, identity: "IdentityType", peer_public_key: "bytes", ecdsa_curve_name: Optional["str"] = None, ) -> None: self.identity = identity self.peer_public_key = peer_public_key self.ecdsa_curve_name = ecdsa_curve_name class ECDHSessionKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 62 FIELDS = { 1: protobuf.Field("session_key", "bytes", repeated=False, required=True), 2: protobuf.Field("public_key", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, session_key: "bytes", public_key: Optional["bytes"] = None, ) -> None: self.session_key = session_key self.public_key = public_key class CosiCommit(protobuf.MessageType): MESSAGE_WIRE_TYPE = 71 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("data", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, data: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.data = data class CosiCommitment(protobuf.MessageType): MESSAGE_WIRE_TYPE = 72 FIELDS = { 1: protobuf.Field("commitment", "bytes", repeated=False, required=True), 2: protobuf.Field("pubkey", "bytes", repeated=False, required=True), } def __init__( self, *, commitment: "bytes", pubkey: "bytes", ) -> None: self.commitment = commitment self.pubkey = pubkey class CosiSign(protobuf.MessageType): MESSAGE_WIRE_TYPE = 73 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("data", "bytes", repeated=False, required=True), 3: protobuf.Field("global_commitment", "bytes", repeated=False, required=True), 4: protobuf.Field("global_pubkey", "bytes", repeated=False, required=True), } def __init__( self, *, data: "bytes", global_commitment: "bytes", global_pubkey: "bytes", address_n: Optional[Sequence["int"]] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.data = data self.global_commitment = global_commitment self.global_pubkey = global_pubkey class CosiSignature(protobuf.MessageType): MESSAGE_WIRE_TYPE = 74 FIELDS = { 1: protobuf.Field("signature", "bytes", repeated=False, required=True), } def __init__( self, *, signature: "bytes", ) -> None: self.signature = signature class Initialize(protobuf.MessageType): MESSAGE_WIRE_TYPE = 0 FIELDS = { 1: protobuf.Field("session_id", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("_skip_passphrase", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("derive_cardano", "bool", repeated=False, required=False, default=None), } def __init__( self, *, session_id: Optional["bytes"] = None, _skip_passphrase: Optional["bool"] = None, derive_cardano: Optional["bool"] = None, ) -> None: self.session_id = session_id self._skip_passphrase = _skip_passphrase self.derive_cardano = derive_cardano class GetFeatures(protobuf.MessageType): MESSAGE_WIRE_TYPE = 55 class Features(protobuf.MessageType): MESSAGE_WIRE_TYPE = 17 FIELDS = { 1: protobuf.Field("vendor", "string", repeated=False, required=False, default=None), 2: protobuf.Field("major_version", "uint32", repeated=False, required=True), 3: protobuf.Field("minor_version", "uint32", repeated=False, required=True), 4: protobuf.Field("patch_version", "uint32", repeated=False, required=True), 5: protobuf.Field("bootloader_mode", "bool", repeated=False, required=False, default=None), 6: protobuf.Field("device_id", "string", repeated=False, required=False, default=None), 7: protobuf.Field("pin_protection", "bool", repeated=False, required=False, default=None), 8: protobuf.Field("passphrase_protection", "bool", repeated=False, required=False, default=None), 9: protobuf.Field("language", "string", repeated=False, required=False, default=None), 10: protobuf.Field("label", "string", repeated=False, required=False, default=None), 12: protobuf.Field("initialized", "bool", repeated=False, required=False, default=None), 13: protobuf.Field("revision", "bytes", repeated=False, required=False, default=None), 14: protobuf.Field("bootloader_hash", "bytes", repeated=False, required=False, default=None), 15: protobuf.Field("imported", "bool", repeated=False, required=False, default=None), 16: protobuf.Field("unlocked", "bool", repeated=False, required=False, default=None), 17: protobuf.Field("_passphrase_cached", "bool", repeated=False, required=False, default=None), 18: protobuf.Field("firmware_present", "bool", repeated=False, required=False, default=None), 19: protobuf.Field("backup_availability", "BackupAvailability", repeated=False, required=False, default=None), 20: protobuf.Field("flags", "uint32", repeated=False, required=False, default=None), 21: protobuf.Field("model", "string", repeated=False, required=False, default=None), 22: protobuf.Field("fw_major", "uint32", repeated=False, required=False, default=None), 23: protobuf.Field("fw_minor", "uint32", repeated=False, required=False, default=None), 24: protobuf.Field("fw_patch", "uint32", repeated=False, required=False, default=None), 25: protobuf.Field("fw_vendor", "string", repeated=False, required=False, default=None), 27: protobuf.Field("unfinished_backup", "bool", repeated=False, required=False, default=None), 28: protobuf.Field("no_backup", "bool", repeated=False, required=False, default=None), 29: protobuf.Field("recovery_status", "RecoveryStatus", repeated=False, required=False, default=None), 30: protobuf.Field("capabilities", "Capability", repeated=True, required=False, default=None), 31: protobuf.Field("backup_type", "BackupType", repeated=False, required=False, default=None), 32: protobuf.Field("sd_card_present", "bool", repeated=False, required=False, default=None), 33: protobuf.Field("sd_protection", "bool", repeated=False, required=False, default=None), 34: protobuf.Field("wipe_code_protection", "bool", repeated=False, required=False, default=None), 35: protobuf.Field("session_id", "bytes", repeated=False, required=False, default=None), 36: protobuf.Field("passphrase_always_on_device", "bool", repeated=False, required=False, default=None), 37: protobuf.Field("safety_checks", "SafetyCheckLevel", repeated=False, required=False, default=None), 38: protobuf.Field("auto_lock_delay_ms", "uint32", repeated=False, required=False, default=None), 39: protobuf.Field("display_rotation", "uint32", repeated=False, required=False, default=None), 40: protobuf.Field("experimental_features", "bool", repeated=False, required=False, default=None), 41: protobuf.Field("busy", "bool", repeated=False, required=False, default=None), 42: protobuf.Field("homescreen_format", "HomescreenFormat", repeated=False, required=False, default=None), 43: protobuf.Field("hide_passphrase_from_host", "bool", repeated=False, required=False, default=None), 44: protobuf.Field("internal_model", "string", repeated=False, required=False, default=None), 45: protobuf.Field("unit_color", "uint32", repeated=False, required=False, default=None), 46: protobuf.Field("unit_btconly", "bool", repeated=False, required=False, default=None), 47: protobuf.Field("homescreen_width", "uint32", repeated=False, required=False, default=None), 48: protobuf.Field("homescreen_height", "uint32", repeated=False, required=False, default=None), 49: protobuf.Field("bootloader_locked", "bool", repeated=False, required=False, default=None), 50: protobuf.Field("language_version_matches", "bool", repeated=False, required=False, default=True), 51: protobuf.Field("unit_packaging", "uint32", repeated=False, required=False, default=None), 52: protobuf.Field("haptic_feedback", "bool", repeated=False, required=False, default=None), 53: protobuf.Field("recovery_type", "RecoveryType", repeated=False, required=False, default=None), 54: protobuf.Field("optiga_sec", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, major_version: "int", minor_version: "int", patch_version: "int", capabilities: Optional[Sequence["Capability"]] = None, vendor: Optional["str"] = None, bootloader_mode: Optional["bool"] = None, device_id: Optional["str"] = None, pin_protection: Optional["bool"] = None, passphrase_protection: Optional["bool"] = None, language: Optional["str"] = None, label: Optional["str"] = None, initialized: Optional["bool"] = None, revision: Optional["bytes"] = None, bootloader_hash: Optional["bytes"] = None, imported: Optional["bool"] = None, unlocked: Optional["bool"] = None, _passphrase_cached: Optional["bool"] = None, firmware_present: Optional["bool"] = None, backup_availability: Optional["BackupAvailability"] = None, flags: Optional["int"] = None, model: Optional["str"] = None, fw_major: Optional["int"] = None, fw_minor: Optional["int"] = None, fw_patch: Optional["int"] = None, fw_vendor: Optional["str"] = None, unfinished_backup: Optional["bool"] = None, no_backup: Optional["bool"] = None, recovery_status: Optional["RecoveryStatus"] = None, backup_type: Optional["BackupType"] = None, sd_card_present: Optional["bool"] = None, sd_protection: Optional["bool"] = None, wipe_code_protection: Optional["bool"] = None, session_id: Optional["bytes"] = None, passphrase_always_on_device: Optional["bool"] = None, safety_checks: Optional["SafetyCheckLevel"] = None, auto_lock_delay_ms: Optional["int"] = None, display_rotation: Optional["int"] = None, experimental_features: Optional["bool"] = None, busy: Optional["bool"] = None, homescreen_format: Optional["HomescreenFormat"] = None, hide_passphrase_from_host: Optional["bool"] = None, internal_model: Optional["str"] = None, unit_color: Optional["int"] = None, unit_btconly: Optional["bool"] = None, homescreen_width: Optional["int"] = None, homescreen_height: Optional["int"] = None, bootloader_locked: Optional["bool"] = None, language_version_matches: Optional["bool"] = True, unit_packaging: Optional["int"] = None, haptic_feedback: Optional["bool"] = None, recovery_type: Optional["RecoveryType"] = None, optiga_sec: Optional["int"] = None, ) -> None: self.capabilities: Sequence["Capability"] = capabilities if capabilities is not None else [] self.major_version = major_version self.minor_version = minor_version self.patch_version = patch_version self.vendor = vendor self.bootloader_mode = bootloader_mode self.device_id = device_id self.pin_protection = pin_protection self.passphrase_protection = passphrase_protection self.language = language self.label = label self.initialized = initialized self.revision = revision self.bootloader_hash = bootloader_hash self.imported = imported self.unlocked = unlocked self._passphrase_cached = _passphrase_cached self.firmware_present = firmware_present self.backup_availability = backup_availability self.flags = flags self.model = model self.fw_major = fw_major self.fw_minor = fw_minor self.fw_patch = fw_patch self.fw_vendor = fw_vendor self.unfinished_backup = unfinished_backup self.no_backup = no_backup self.recovery_status = recovery_status self.backup_type = backup_type self.sd_card_present = sd_card_present self.sd_protection = sd_protection self.wipe_code_protection = wipe_code_protection self.session_id = session_id self.passphrase_always_on_device = passphrase_always_on_device self.safety_checks = safety_checks self.auto_lock_delay_ms = auto_lock_delay_ms self.display_rotation = display_rotation self.experimental_features = experimental_features self.busy = busy self.homescreen_format = homescreen_format self.hide_passphrase_from_host = hide_passphrase_from_host self.internal_model = internal_model self.unit_color = unit_color self.unit_btconly = unit_btconly self.homescreen_width = homescreen_width self.homescreen_height = homescreen_height self.bootloader_locked = bootloader_locked self.language_version_matches = language_version_matches self.unit_packaging = unit_packaging self.haptic_feedback = haptic_feedback self.recovery_type = recovery_type self.optiga_sec = optiga_sec class LockDevice(protobuf.MessageType): MESSAGE_WIRE_TYPE = 24 class SetBusy(protobuf.MessageType): MESSAGE_WIRE_TYPE = 16 FIELDS = { 1: protobuf.Field("expiry_ms", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, expiry_ms: Optional["int"] = None, ) -> None: self.expiry_ms = expiry_ms class EndSession(protobuf.MessageType): MESSAGE_WIRE_TYPE = 83 class ApplySettings(protobuf.MessageType): MESSAGE_WIRE_TYPE = 25 FIELDS = { 1: protobuf.Field("language", "string", repeated=False, required=False, default=None), 2: protobuf.Field("label", "string", repeated=False, required=False, default=None), 3: protobuf.Field("use_passphrase", "bool", repeated=False, required=False, default=None), 4: protobuf.Field("homescreen", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("_passphrase_source", "uint32", repeated=False, required=False, default=None), 6: protobuf.Field("auto_lock_delay_ms", "uint32", repeated=False, required=False, default=None), 7: protobuf.Field("display_rotation", "uint32", repeated=False, required=False, default=None), 8: protobuf.Field("passphrase_always_on_device", "bool", repeated=False, required=False, default=None), 9: protobuf.Field("safety_checks", "SafetyCheckLevel", repeated=False, required=False, default=None), 10: protobuf.Field("experimental_features", "bool", repeated=False, required=False, default=None), 11: protobuf.Field("hide_passphrase_from_host", "bool", repeated=False, required=False, default=None), 13: protobuf.Field("haptic_feedback", "bool", repeated=False, required=False, default=None), } def __init__( self, *, language: Optional["str"] = None, label: Optional["str"] = None, use_passphrase: Optional["bool"] = None, homescreen: Optional["bytes"] = None, _passphrase_source: Optional["int"] = None, auto_lock_delay_ms: Optional["int"] = None, display_rotation: Optional["int"] = None, passphrase_always_on_device: Optional["bool"] = None, safety_checks: Optional["SafetyCheckLevel"] = None, experimental_features: Optional["bool"] = None, hide_passphrase_from_host: Optional["bool"] = None, haptic_feedback: Optional["bool"] = None, ) -> None: self.language = language self.label = label self.use_passphrase = use_passphrase self.homescreen = homescreen self._passphrase_source = _passphrase_source self.auto_lock_delay_ms = auto_lock_delay_ms self.display_rotation = display_rotation self.passphrase_always_on_device = passphrase_always_on_device self.safety_checks = safety_checks self.experimental_features = experimental_features self.hide_passphrase_from_host = hide_passphrase_from_host self.haptic_feedback = haptic_feedback class ChangeLanguage(protobuf.MessageType): MESSAGE_WIRE_TYPE = 990 FIELDS = { 1: protobuf.Field("data_length", "uint32", repeated=False, required=True), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), } def __init__( self, *, data_length: "int", show_display: Optional["bool"] = None, ) -> None: self.data_length = data_length self.show_display = show_display class TranslationDataRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 991 FIELDS = { 1: protobuf.Field("data_length", "uint32", repeated=False, required=True), 2: protobuf.Field("data_offset", "uint32", repeated=False, required=True), } def __init__( self, *, data_length: "int", data_offset: "int", ) -> None: self.data_length = data_length self.data_offset = data_offset class TranslationDataAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 992 FIELDS = { 1: protobuf.Field("data_chunk", "bytes", repeated=False, required=True), } def __init__( self, *, data_chunk: "bytes", ) -> None: self.data_chunk = data_chunk class ApplyFlags(protobuf.MessageType): MESSAGE_WIRE_TYPE = 28 FIELDS = { 1: protobuf.Field("flags", "uint32", repeated=False, required=True), } def __init__( self, *, flags: "int", ) -> None: self.flags = flags class ChangePin(protobuf.MessageType): MESSAGE_WIRE_TYPE = 4 FIELDS = { 1: protobuf.Field("remove", "bool", repeated=False, required=False, default=None), } def __init__( self, *, remove: Optional["bool"] = None, ) -> None: self.remove = remove class ChangeWipeCode(protobuf.MessageType): MESSAGE_WIRE_TYPE = 82 FIELDS = { 1: protobuf.Field("remove", "bool", repeated=False, required=False, default=None), } def __init__( self, *, remove: Optional["bool"] = None, ) -> None: self.remove = remove class SdProtect(protobuf.MessageType): MESSAGE_WIRE_TYPE = 79 FIELDS = { 1: protobuf.Field("operation", "SdProtectOperationType", repeated=False, required=True), } def __init__( self, *, operation: "SdProtectOperationType", ) -> None: self.operation = operation class Ping(protobuf.MessageType): MESSAGE_WIRE_TYPE = 1 FIELDS = { 1: protobuf.Field("message", "string", repeated=False, required=False, default=''), 2: protobuf.Field("button_protection", "bool", repeated=False, required=False, default=None), } def __init__( self, *, message: Optional["str"] = '', button_protection: Optional["bool"] = None, ) -> None: self.message = message self.button_protection = button_protection class Cancel(protobuf.MessageType): MESSAGE_WIRE_TYPE = 20 class GetEntropy(protobuf.MessageType): MESSAGE_WIRE_TYPE = 9 FIELDS = { 1: protobuf.Field("size", "uint32", repeated=False, required=True), } def __init__( self, *, size: "int", ) -> None: self.size = size class Entropy(protobuf.MessageType): MESSAGE_WIRE_TYPE = 10 FIELDS = { 1: protobuf.Field("entropy", "bytes", repeated=False, required=True), } def __init__( self, *, entropy: "bytes", ) -> None: self.entropy = entropy class GetFirmwareHash(protobuf.MessageType): MESSAGE_WIRE_TYPE = 88 FIELDS = { 1: protobuf.Field("challenge", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, challenge: Optional["bytes"] = None, ) -> None: self.challenge = challenge class FirmwareHash(protobuf.MessageType): MESSAGE_WIRE_TYPE = 89 FIELDS = { 1: protobuf.Field("hash", "bytes", repeated=False, required=True), } def __init__( self, *, hash: "bytes", ) -> None: self.hash = hash class AuthenticateDevice(protobuf.MessageType): MESSAGE_WIRE_TYPE = 97 FIELDS = { 1: protobuf.Field("challenge", "bytes", repeated=False, required=True), } def __init__( self, *, challenge: "bytes", ) -> None: self.challenge = challenge class AuthenticityProof(protobuf.MessageType): MESSAGE_WIRE_TYPE = 98 FIELDS = { 1: protobuf.Field("certificates", "bytes", repeated=True, required=False, default=None), 2: protobuf.Field("signature", "bytes", repeated=False, required=True), } def __init__( self, *, signature: "bytes", certificates: Optional[Sequence["bytes"]] = None, ) -> None: self.certificates: Sequence["bytes"] = certificates if certificates is not None else [] self.signature = signature class WipeDevice(protobuf.MessageType): MESSAGE_WIRE_TYPE = 5 class LoadDevice(protobuf.MessageType): MESSAGE_WIRE_TYPE = 13 FIELDS = { 1: protobuf.Field("mnemonics", "string", repeated=True, required=False, default=None), 3: protobuf.Field("pin", "string", repeated=False, required=False, default=None), 4: protobuf.Field("passphrase_protection", "bool", repeated=False, required=False, default=None), 5: protobuf.Field("language", "string", repeated=False, required=False, default=None), 6: protobuf.Field("label", "string", repeated=False, required=False, default=None), 7: protobuf.Field("skip_checksum", "bool", repeated=False, required=False, default=None), 8: protobuf.Field("u2f_counter", "uint32", repeated=False, required=False, default=None), 9: protobuf.Field("needs_backup", "bool", repeated=False, required=False, default=None), 10: protobuf.Field("no_backup", "bool", repeated=False, required=False, default=None), } def __init__( self, *, mnemonics: Optional[Sequence["str"]] = None, pin: Optional["str"] = None, passphrase_protection: Optional["bool"] = None, language: Optional["str"] = None, label: Optional["str"] = None, skip_checksum: Optional["bool"] = None, u2f_counter: Optional["int"] = None, needs_backup: Optional["bool"] = None, no_backup: Optional["bool"] = None, ) -> None: self.mnemonics: Sequence["str"] = mnemonics if mnemonics is not None else [] self.pin = pin self.passphrase_protection = passphrase_protection self.language = language self.label = label self.skip_checksum = skip_checksum self.u2f_counter = u2f_counter self.needs_backup = needs_backup self.no_backup = no_backup class ResetDevice(protobuf.MessageType): MESSAGE_WIRE_TYPE = 14 FIELDS = { 1: protobuf.Field("display_random", "bool", repeated=False, required=False, default=None), 2: protobuf.Field("strength", "uint32", repeated=False, required=False, default=256), 3: protobuf.Field("passphrase_protection", "bool", repeated=False, required=False, default=None), 4: protobuf.Field("pin_protection", "bool", repeated=False, required=False, default=None), 5: protobuf.Field("language", "string", repeated=False, required=False, default=None), 6: protobuf.Field("label", "string", repeated=False, required=False, default=None), 7: protobuf.Field("u2f_counter", "uint32", repeated=False, required=False, default=None), 8: protobuf.Field("skip_backup", "bool", repeated=False, required=False, default=None), 9: protobuf.Field("no_backup", "bool", repeated=False, required=False, default=None), 10: protobuf.Field("backup_type", "BackupType", repeated=False, required=False, default=BackupType.Bip39), } def __init__( self, *, display_random: Optional["bool"] = None, strength: Optional["int"] = 256, passphrase_protection: Optional["bool"] = None, pin_protection: Optional["bool"] = None, language: Optional["str"] = None, label: Optional["str"] = None, u2f_counter: Optional["int"] = None, skip_backup: Optional["bool"] = None, no_backup: Optional["bool"] = None, backup_type: Optional["BackupType"] = BackupType.Bip39, ) -> None: self.display_random = display_random self.strength = strength self.passphrase_protection = passphrase_protection self.pin_protection = pin_protection self.language = language self.label = label self.u2f_counter = u2f_counter self.skip_backup = skip_backup self.no_backup = no_backup self.backup_type = backup_type class BackupDevice(protobuf.MessageType): MESSAGE_WIRE_TYPE = 34 FIELDS = { 1: protobuf.Field("group_threshold", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("groups", "Slip39Group", repeated=True, required=False, default=None), } def __init__( self, *, groups: Optional[Sequence["Slip39Group"]] = None, group_threshold: Optional["int"] = None, ) -> None: self.groups: Sequence["Slip39Group"] = groups if groups is not None else [] self.group_threshold = group_threshold class EntropyRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 35 class EntropyAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 36 FIELDS = { 1: protobuf.Field("entropy", "bytes", repeated=False, required=True), } def __init__( self, *, entropy: "bytes", ) -> None: self.entropy = entropy class RecoveryDevice(protobuf.MessageType): MESSAGE_WIRE_TYPE = 45 FIELDS = { 1: protobuf.Field("word_count", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("passphrase_protection", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("pin_protection", "bool", repeated=False, required=False, default=None), 4: protobuf.Field("language", "string", repeated=False, required=False, default=None), 5: protobuf.Field("label", "string", repeated=False, required=False, default=None), 6: protobuf.Field("enforce_wordlist", "bool", repeated=False, required=False, default=None), 8: protobuf.Field("input_method", "RecoveryDeviceInputMethod", repeated=False, required=False, default=None), 9: protobuf.Field("u2f_counter", "uint32", repeated=False, required=False, default=None), 10: protobuf.Field("type", "RecoveryType", repeated=False, required=False, default=RecoveryType.NormalRecovery), } def __init__( self, *, word_count: Optional["int"] = None, passphrase_protection: Optional["bool"] = None, pin_protection: Optional["bool"] = None, language: Optional["str"] = None, label: Optional["str"] = None, enforce_wordlist: Optional["bool"] = None, input_method: Optional["RecoveryDeviceInputMethod"] = None, u2f_counter: Optional["int"] = None, type: Optional["RecoveryType"] = RecoveryType.NormalRecovery, ) -> None: self.word_count = word_count self.passphrase_protection = passphrase_protection self.pin_protection = pin_protection self.language = language self.label = label self.enforce_wordlist = enforce_wordlist self.input_method = input_method self.u2f_counter = u2f_counter self.type = type class WordRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 46 FIELDS = { 1: protobuf.Field("type", "WordRequestType", repeated=False, required=True), } def __init__( self, *, type: "WordRequestType", ) -> None: self.type = type class WordAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 47 FIELDS = { 1: protobuf.Field("word", "string", repeated=False, required=True), } def __init__( self, *, word: "str", ) -> None: self.word = word class SetU2FCounter(protobuf.MessageType): MESSAGE_WIRE_TYPE = 63 FIELDS = { 1: protobuf.Field("u2f_counter", "uint32", repeated=False, required=True), } def __init__( self, *, u2f_counter: "int", ) -> None: self.u2f_counter = u2f_counter class GetNextU2FCounter(protobuf.MessageType): MESSAGE_WIRE_TYPE = 80 class NextU2FCounter(protobuf.MessageType): MESSAGE_WIRE_TYPE = 81 FIELDS = { 1: protobuf.Field("u2f_counter", "uint32", repeated=False, required=True), } def __init__( self, *, u2f_counter: "int", ) -> None: self.u2f_counter = u2f_counter class DoPreauthorized(protobuf.MessageType): MESSAGE_WIRE_TYPE = 84 class PreauthorizedRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 85 class CancelAuthorization(protobuf.MessageType): MESSAGE_WIRE_TYPE = 86 class RebootToBootloader(protobuf.MessageType): MESSAGE_WIRE_TYPE = 87 FIELDS = { 1: protobuf.Field("boot_command", "BootCommand", repeated=False, required=False, default=BootCommand.STOP_AND_WAIT), 2: protobuf.Field("firmware_header", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("language_data_length", "uint32", repeated=False, required=False, default=0), } def __init__( self, *, boot_command: Optional["BootCommand"] = BootCommand.STOP_AND_WAIT, firmware_header: Optional["bytes"] = None, language_data_length: Optional["int"] = 0, ) -> None: self.boot_command = boot_command self.firmware_header = firmware_header self.language_data_length = language_data_length class GetNonce(protobuf.MessageType): MESSAGE_WIRE_TYPE = 31 class Nonce(protobuf.MessageType): MESSAGE_WIRE_TYPE = 33 FIELDS = { 1: protobuf.Field("nonce", "bytes", repeated=False, required=True), } def __init__( self, *, nonce: "bytes", ) -> None: self.nonce = nonce class UnlockPath(protobuf.MessageType): MESSAGE_WIRE_TYPE = 93 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("mac", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, mac: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.mac = mac class UnlockedPathRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 94 FIELDS = { 1: protobuf.Field("mac", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, mac: Optional["bytes"] = None, ) -> None: self.mac = mac class ShowDeviceTutorial(protobuf.MessageType): MESSAGE_WIRE_TYPE = 95 class UnlockBootloader(protobuf.MessageType): MESSAGE_WIRE_TYPE = 96 class SetBrightness(protobuf.MessageType): MESSAGE_WIRE_TYPE = 993 FIELDS = { 1: protobuf.Field("value", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, value: Optional["int"] = None, ) -> None: self.value = value class Slip39Group(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("member_threshold", "uint32", repeated=False, required=True), 2: protobuf.Field("member_count", "uint32", repeated=False, required=True), } def __init__( self, *, member_threshold: "int", member_count: "int", ) -> None: self.member_threshold = member_threshold self.member_count = member_count class DebugLinkDecision(protobuf.MessageType): MESSAGE_WIRE_TYPE = 100 FIELDS = { 1: protobuf.Field("button", "DebugButton", repeated=False, required=False, default=None), 2: protobuf.Field("swipe", "DebugSwipeDirection", repeated=False, required=False, default=None), 3: protobuf.Field("input", "string", repeated=False, required=False, default=None), 4: protobuf.Field("x", "uint32", repeated=False, required=False, default=None), 5: protobuf.Field("y", "uint32", repeated=False, required=False, default=None), 6: protobuf.Field("wait", "bool", repeated=False, required=False, default=None), 7: protobuf.Field("hold_ms", "uint32", repeated=False, required=False, default=None), 8: protobuf.Field("physical_button", "DebugPhysicalButton", repeated=False, required=False, default=None), } def __init__( self, *, button: Optional["DebugButton"] = None, swipe: Optional["DebugSwipeDirection"] = None, input: Optional["str"] = None, x: Optional["int"] = None, y: Optional["int"] = None, wait: Optional["bool"] = None, hold_ms: Optional["int"] = None, physical_button: Optional["DebugPhysicalButton"] = None, ) -> None: self.button = button self.swipe = swipe self.input = input self.x = x self.y = y self.wait = wait self.hold_ms = hold_ms self.physical_button = physical_button class DebugLinkLayout(protobuf.MessageType): MESSAGE_WIRE_TYPE = 9001 FIELDS = { 1: protobuf.Field("tokens", "string", repeated=True, required=False, default=None), } def __init__( self, *, tokens: Optional[Sequence["str"]] = None, ) -> None: self.tokens: Sequence["str"] = tokens if tokens is not None else [] class DebugLinkReseedRandom(protobuf.MessageType): MESSAGE_WIRE_TYPE = 9002 FIELDS = { 1: protobuf.Field("value", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, value: Optional["int"] = None, ) -> None: self.value = value class DebugLinkRecordScreen(protobuf.MessageType): MESSAGE_WIRE_TYPE = 9003 FIELDS = { 1: protobuf.Field("target_directory", "string", repeated=False, required=False, default=None), 2: protobuf.Field("refresh_index", "uint32", repeated=False, required=False, default=0), } def __init__( self, *, target_directory: Optional["str"] = None, refresh_index: Optional["int"] = 0, ) -> None: self.target_directory = target_directory self.refresh_index = refresh_index class DebugLinkGetState(protobuf.MessageType): MESSAGE_WIRE_TYPE = 101 FIELDS = { 1: protobuf.Field("wait_word_list", "bool", repeated=False, required=False, default=None), 2: protobuf.Field("wait_word_pos", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("wait_layout", "bool", repeated=False, required=False, default=None), } def __init__( self, *, wait_word_list: Optional["bool"] = None, wait_word_pos: Optional["bool"] = None, wait_layout: Optional["bool"] = None, ) -> None: self.wait_word_list = wait_word_list self.wait_word_pos = wait_word_pos self.wait_layout = wait_layout class DebugLinkState(protobuf.MessageType): MESSAGE_WIRE_TYPE = 102 FIELDS = { 1: protobuf.Field("layout", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("pin", "string", repeated=False, required=False, default=None), 3: protobuf.Field("matrix", "string", repeated=False, required=False, default=None), 4: protobuf.Field("mnemonic_secret", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("node", "HDNodeType", repeated=False, required=False, default=None), 6: protobuf.Field("passphrase_protection", "bool", repeated=False, required=False, default=None), 7: protobuf.Field("reset_word", "string", repeated=False, required=False, default=None), 8: protobuf.Field("reset_entropy", "bytes", repeated=False, required=False, default=None), 9: protobuf.Field("recovery_fake_word", "string", repeated=False, required=False, default=None), 10: protobuf.Field("recovery_word_pos", "uint32", repeated=False, required=False, default=None), 11: protobuf.Field("reset_word_pos", "uint32", repeated=False, required=False, default=None), 12: protobuf.Field("mnemonic_type", "BackupType", repeated=False, required=False, default=None), 13: protobuf.Field("tokens", "string", repeated=True, required=False, default=None), } def __init__( self, *, tokens: Optional[Sequence["str"]] = None, layout: Optional["bytes"] = None, pin: Optional["str"] = None, matrix: Optional["str"] = None, mnemonic_secret: Optional["bytes"] = None, node: Optional["HDNodeType"] = None, passphrase_protection: Optional["bool"] = None, reset_word: Optional["str"] = None, reset_entropy: Optional["bytes"] = None, recovery_fake_word: Optional["str"] = None, recovery_word_pos: Optional["int"] = None, reset_word_pos: Optional["int"] = None, mnemonic_type: Optional["BackupType"] = None, ) -> None: self.tokens: Sequence["str"] = tokens if tokens is not None else [] self.layout = layout self.pin = pin self.matrix = matrix self.mnemonic_secret = mnemonic_secret self.node = node self.passphrase_protection = passphrase_protection self.reset_word = reset_word self.reset_entropy = reset_entropy self.recovery_fake_word = recovery_fake_word self.recovery_word_pos = recovery_word_pos self.reset_word_pos = reset_word_pos self.mnemonic_type = mnemonic_type class DebugLinkStop(protobuf.MessageType): MESSAGE_WIRE_TYPE = 103 class DebugLinkLog(protobuf.MessageType): MESSAGE_WIRE_TYPE = 104 FIELDS = { 1: protobuf.Field("level", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("bucket", "string", repeated=False, required=False, default=None), 3: protobuf.Field("text", "string", repeated=False, required=False, default=None), } def __init__( self, *, level: Optional["int"] = None, bucket: Optional["str"] = None, text: Optional["str"] = None, ) -> None: self.level = level self.bucket = bucket self.text = text class DebugLinkMemoryRead(protobuf.MessageType): MESSAGE_WIRE_TYPE = 110 FIELDS = { 1: protobuf.Field("address", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("length", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, address: Optional["int"] = None, length: Optional["int"] = None, ) -> None: self.address = address self.length = length class DebugLinkMemory(protobuf.MessageType): MESSAGE_WIRE_TYPE = 111 FIELDS = { 1: protobuf.Field("memory", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, memory: Optional["bytes"] = None, ) -> None: self.memory = memory class DebugLinkMemoryWrite(protobuf.MessageType): MESSAGE_WIRE_TYPE = 112 FIELDS = { 1: protobuf.Field("address", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("memory", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("flash", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address: Optional["int"] = None, memory: Optional["bytes"] = None, flash: Optional["bool"] = None, ) -> None: self.address = address self.memory = memory self.flash = flash class DebugLinkFlashErase(protobuf.MessageType): MESSAGE_WIRE_TYPE = 113 FIELDS = { 1: protobuf.Field("sector", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, sector: Optional["int"] = None, ) -> None: self.sector = sector class DebugLinkEraseSdCard(protobuf.MessageType): MESSAGE_WIRE_TYPE = 9005 FIELDS = { 1: protobuf.Field("format", "bool", repeated=False, required=False, default=None), } def __init__( self, *, format: Optional["bool"] = None, ) -> None: self.format = format class DebugLinkWatchLayout(protobuf.MessageType): MESSAGE_WIRE_TYPE = 9006 FIELDS = { 1: protobuf.Field("watch", "bool", repeated=False, required=False, default=None), } def __init__( self, *, watch: Optional["bool"] = None, ) -> None: self.watch = watch class DebugLinkResetDebugEvents(protobuf.MessageType): MESSAGE_WIRE_TYPE = 9007 class EosGetPublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 600 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display self.chunkify = chunkify class EosPublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 601 FIELDS = { 1: protobuf.Field("wif_public_key", "string", repeated=False, required=True), 2: protobuf.Field("raw_public_key", "bytes", repeated=False, required=True), } def __init__( self, *, wif_public_key: "str", raw_public_key: "bytes", ) -> None: self.wif_public_key = wif_public_key self.raw_public_key = raw_public_key class EosSignTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 602 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("chain_id", "bytes", repeated=False, required=True), 3: protobuf.Field("header", "EosTxHeader", repeated=False, required=True), 4: protobuf.Field("num_actions", "uint32", repeated=False, required=True), 5: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, chain_id: "bytes", header: "EosTxHeader", num_actions: "int", address_n: Optional[Sequence["int"]] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.chain_id = chain_id self.header = header self.num_actions = num_actions self.chunkify = chunkify class EosTxActionRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 603 FIELDS = { 1: protobuf.Field("data_size", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, data_size: Optional["int"] = None, ) -> None: self.data_size = data_size class EosTxActionAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 604 FIELDS = { 1: protobuf.Field("common", "EosActionCommon", repeated=False, required=True), 2: protobuf.Field("transfer", "EosActionTransfer", repeated=False, required=False, default=None), 3: protobuf.Field("delegate", "EosActionDelegate", repeated=False, required=False, default=None), 4: protobuf.Field("undelegate", "EosActionUndelegate", repeated=False, required=False, default=None), 5: protobuf.Field("refund", "EosActionRefund", repeated=False, required=False, default=None), 6: protobuf.Field("buy_ram", "EosActionBuyRam", repeated=False, required=False, default=None), 7: protobuf.Field("buy_ram_bytes", "EosActionBuyRamBytes", repeated=False, required=False, default=None), 8: protobuf.Field("sell_ram", "EosActionSellRam", repeated=False, required=False, default=None), 9: protobuf.Field("vote_producer", "EosActionVoteProducer", repeated=False, required=False, default=None), 10: protobuf.Field("update_auth", "EosActionUpdateAuth", repeated=False, required=False, default=None), 11: protobuf.Field("delete_auth", "EosActionDeleteAuth", repeated=False, required=False, default=None), 12: protobuf.Field("link_auth", "EosActionLinkAuth", repeated=False, required=False, default=None), 13: protobuf.Field("unlink_auth", "EosActionUnlinkAuth", repeated=False, required=False, default=None), 14: protobuf.Field("new_account", "EosActionNewAccount", repeated=False, required=False, default=None), 15: protobuf.Field("unknown", "EosActionUnknown", repeated=False, required=False, default=None), } def __init__( self, *, common: "EosActionCommon", transfer: Optional["EosActionTransfer"] = None, delegate: Optional["EosActionDelegate"] = None, undelegate: Optional["EosActionUndelegate"] = None, refund: Optional["EosActionRefund"] = None, buy_ram: Optional["EosActionBuyRam"] = None, buy_ram_bytes: Optional["EosActionBuyRamBytes"] = None, sell_ram: Optional["EosActionSellRam"] = None, vote_producer: Optional["EosActionVoteProducer"] = None, update_auth: Optional["EosActionUpdateAuth"] = None, delete_auth: Optional["EosActionDeleteAuth"] = None, link_auth: Optional["EosActionLinkAuth"] = None, unlink_auth: Optional["EosActionUnlinkAuth"] = None, new_account: Optional["EosActionNewAccount"] = None, unknown: Optional["EosActionUnknown"] = None, ) -> None: self.common = common self.transfer = transfer self.delegate = delegate self.undelegate = undelegate self.refund = refund self.buy_ram = buy_ram self.buy_ram_bytes = buy_ram_bytes self.sell_ram = sell_ram self.vote_producer = vote_producer self.update_auth = update_auth self.delete_auth = delete_auth self.link_auth = link_auth self.unlink_auth = unlink_auth self.new_account = new_account self.unknown = unknown class EosSignedTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 605 FIELDS = { 1: protobuf.Field("signature", "string", repeated=False, required=True), } def __init__( self, *, signature: "str", ) -> None: self.signature = signature class EosTxHeader(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("expiration", "uint32", repeated=False, required=True), 2: protobuf.Field("ref_block_num", "uint32", repeated=False, required=True), 3: protobuf.Field("ref_block_prefix", "uint32", repeated=False, required=True), 4: protobuf.Field("max_net_usage_words", "uint32", repeated=False, required=True), 5: protobuf.Field("max_cpu_usage_ms", "uint32", repeated=False, required=True), 6: protobuf.Field("delay_sec", "uint32", repeated=False, required=True), } def __init__( self, *, expiration: "int", ref_block_num: "int", ref_block_prefix: "int", max_net_usage_words: "int", max_cpu_usage_ms: "int", delay_sec: "int", ) -> None: self.expiration = expiration self.ref_block_num = ref_block_num self.ref_block_prefix = ref_block_prefix self.max_net_usage_words = max_net_usage_words self.max_cpu_usage_ms = max_cpu_usage_ms self.delay_sec = delay_sec class EosAsset(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("amount", "sint64", repeated=False, required=True), 2: protobuf.Field("symbol", "uint64", repeated=False, required=True), } def __init__( self, *, amount: "int", symbol: "int", ) -> None: self.amount = amount self.symbol = symbol class EosPermissionLevel(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("actor", "uint64", repeated=False, required=True), 2: protobuf.Field("permission", "uint64", repeated=False, required=True), } def __init__( self, *, actor: "int", permission: "int", ) -> None: self.actor = actor self.permission = permission class EosAuthorizationKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("type", "uint32", repeated=False, required=True), 2: protobuf.Field("key", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 4: protobuf.Field("weight", "uint32", repeated=False, required=True), } def __init__( self, *, type: "int", weight: "int", address_n: Optional[Sequence["int"]] = None, key: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.type = type self.weight = weight self.key = key class EosAuthorizationAccount(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("account", "EosPermissionLevel", repeated=False, required=True), 2: protobuf.Field("weight", "uint32", repeated=False, required=True), } def __init__( self, *, account: "EosPermissionLevel", weight: "int", ) -> None: self.account = account self.weight = weight class EosAuthorizationWait(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("wait_sec", "uint32", repeated=False, required=True), 2: protobuf.Field("weight", "uint32", repeated=False, required=True), } def __init__( self, *, wait_sec: "int", weight: "int", ) -> None: self.wait_sec = wait_sec self.weight = weight class EosAuthorization(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("threshold", "uint32", repeated=False, required=True), 2: protobuf.Field("keys", "EosAuthorizationKey", repeated=True, required=False, default=None), 3: protobuf.Field("accounts", "EosAuthorizationAccount", repeated=True, required=False, default=None), 4: protobuf.Field("waits", "EosAuthorizationWait", repeated=True, required=False, default=None), } def __init__( self, *, threshold: "int", keys: Optional[Sequence["EosAuthorizationKey"]] = None, accounts: Optional[Sequence["EosAuthorizationAccount"]] = None, waits: Optional[Sequence["EosAuthorizationWait"]] = None, ) -> None: self.keys: Sequence["EosAuthorizationKey"] = keys if keys is not None else [] self.accounts: Sequence["EosAuthorizationAccount"] = accounts if accounts is not None else [] self.waits: Sequence["EosAuthorizationWait"] = waits if waits is not None else [] self.threshold = threshold class EosActionCommon(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("account", "uint64", repeated=False, required=True), 2: protobuf.Field("name", "uint64", repeated=False, required=True), 3: protobuf.Field("authorization", "EosPermissionLevel", repeated=True, required=False, default=None), } def __init__( self, *, account: "int", name: "int", authorization: Optional[Sequence["EosPermissionLevel"]] = None, ) -> None: self.authorization: Sequence["EosPermissionLevel"] = authorization if authorization is not None else [] self.account = account self.name = name class EosActionTransfer(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("sender", "uint64", repeated=False, required=True), 2: protobuf.Field("receiver", "uint64", repeated=False, required=True), 3: protobuf.Field("quantity", "EosAsset", repeated=False, required=True), 4: protobuf.Field("memo", "string", repeated=False, required=True), } def __init__( self, *, sender: "int", receiver: "int", quantity: "EosAsset", memo: "str", ) -> None: self.sender = sender self.receiver = receiver self.quantity = quantity self.memo = memo class EosActionDelegate(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("sender", "uint64", repeated=False, required=True), 2: protobuf.Field("receiver", "uint64", repeated=False, required=True), 3: protobuf.Field("net_quantity", "EosAsset", repeated=False, required=True), 4: protobuf.Field("cpu_quantity", "EosAsset", repeated=False, required=True), 5: protobuf.Field("transfer", "bool", repeated=False, required=True), } def __init__( self, *, sender: "int", receiver: "int", net_quantity: "EosAsset", cpu_quantity: "EosAsset", transfer: "bool", ) -> None: self.sender = sender self.receiver = receiver self.net_quantity = net_quantity self.cpu_quantity = cpu_quantity self.transfer = transfer class EosActionUndelegate(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("sender", "uint64", repeated=False, required=True), 2: protobuf.Field("receiver", "uint64", repeated=False, required=True), 3: protobuf.Field("net_quantity", "EosAsset", repeated=False, required=True), 4: protobuf.Field("cpu_quantity", "EosAsset", repeated=False, required=True), } def __init__( self, *, sender: "int", receiver: "int", net_quantity: "EosAsset", cpu_quantity: "EosAsset", ) -> None: self.sender = sender self.receiver = receiver self.net_quantity = net_quantity self.cpu_quantity = cpu_quantity class EosActionRefund(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("owner", "uint64", repeated=False, required=True), } def __init__( self, *, owner: "int", ) -> None: self.owner = owner class EosActionBuyRam(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("payer", "uint64", repeated=False, required=True), 2: protobuf.Field("receiver", "uint64", repeated=False, required=True), 3: protobuf.Field("quantity", "EosAsset", repeated=False, required=True), } def __init__( self, *, payer: "int", receiver: "int", quantity: "EosAsset", ) -> None: self.payer = payer self.receiver = receiver self.quantity = quantity class EosActionBuyRamBytes(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("payer", "uint64", repeated=False, required=True), 2: protobuf.Field("receiver", "uint64", repeated=False, required=True), 3: protobuf.Field("bytes", "uint32", repeated=False, required=True), } def __init__( self, *, payer: "int", receiver: "int", bytes: "int", ) -> None: self.payer = payer self.receiver = receiver self.bytes = bytes class EosActionSellRam(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("account", "uint64", repeated=False, required=True), 2: protobuf.Field("bytes", "uint64", repeated=False, required=True), } def __init__( self, *, account: "int", bytes: "int", ) -> None: self.account = account self.bytes = bytes class EosActionVoteProducer(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("voter", "uint64", repeated=False, required=True), 2: protobuf.Field("proxy", "uint64", repeated=False, required=True), 3: protobuf.Field("producers", "uint64", repeated=True, required=False, default=None), } def __init__( self, *, voter: "int", proxy: "int", producers: Optional[Sequence["int"]] = None, ) -> None: self.producers: Sequence["int"] = producers if producers is not None else [] self.voter = voter self.proxy = proxy class EosActionUpdateAuth(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("account", "uint64", repeated=False, required=True), 2: protobuf.Field("permission", "uint64", repeated=False, required=True), 3: protobuf.Field("parent", "uint64", repeated=False, required=True), 4: protobuf.Field("auth", "EosAuthorization", repeated=False, required=True), } def __init__( self, *, account: "int", permission: "int", parent: "int", auth: "EosAuthorization", ) -> None: self.account = account self.permission = permission self.parent = parent self.auth = auth class EosActionDeleteAuth(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("account", "uint64", repeated=False, required=True), 2: protobuf.Field("permission", "uint64", repeated=False, required=True), } def __init__( self, *, account: "int", permission: "int", ) -> None: self.account = account self.permission = permission class EosActionLinkAuth(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("account", "uint64", repeated=False, required=True), 2: protobuf.Field("code", "uint64", repeated=False, required=True), 3: protobuf.Field("type", "uint64", repeated=False, required=True), 4: protobuf.Field("requirement", "uint64", repeated=False, required=True), } def __init__( self, *, account: "int", code: "int", type: "int", requirement: "int", ) -> None: self.account = account self.code = code self.type = type self.requirement = requirement class EosActionUnlinkAuth(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("account", "uint64", repeated=False, required=True), 2: protobuf.Field("code", "uint64", repeated=False, required=True), 3: protobuf.Field("type", "uint64", repeated=False, required=True), } def __init__( self, *, account: "int", code: "int", type: "int", ) -> None: self.account = account self.code = code self.type = type class EosActionNewAccount(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("creator", "uint64", repeated=False, required=True), 2: protobuf.Field("name", "uint64", repeated=False, required=True), 3: protobuf.Field("owner", "EosAuthorization", repeated=False, required=True), 4: protobuf.Field("active", "EosAuthorization", repeated=False, required=True), } def __init__( self, *, creator: "int", name: "int", owner: "EosAuthorization", active: "EosAuthorization", ) -> None: self.creator = creator self.name = name self.owner = owner self.active = active class EosActionUnknown(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("data_size", "uint32", repeated=False, required=True), 2: protobuf.Field("data_chunk", "bytes", repeated=False, required=True), } def __init__( self, *, data_size: "int", data_chunk: "bytes", ) -> None: self.data_size = data_size self.data_chunk = data_chunk class EthereumNetworkInfo(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("chain_id", "uint64", repeated=False, required=True), 2: protobuf.Field("symbol", "string", repeated=False, required=True), 3: protobuf.Field("slip44", "uint32", repeated=False, required=True), 4: protobuf.Field("name", "string", repeated=False, required=True), } def __init__( self, *, chain_id: "int", symbol: "str", slip44: "int", name: "str", ) -> None: self.chain_id = chain_id self.symbol = symbol self.slip44 = slip44 self.name = name class EthereumTokenInfo(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("address", "bytes", repeated=False, required=True), 2: protobuf.Field("chain_id", "uint64", repeated=False, required=True), 3: protobuf.Field("symbol", "string", repeated=False, required=True), 4: protobuf.Field("decimals", "uint32", repeated=False, required=True), 5: protobuf.Field("name", "string", repeated=False, required=True), } def __init__( self, *, address: "bytes", chain_id: "int", symbol: "str", decimals: "int", name: "str", ) -> None: self.address = address self.chain_id = chain_id self.symbol = symbol self.decimals = decimals self.name = name class EthereumDefinitions(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("encoded_network", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("encoded_token", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, encoded_network: Optional["bytes"] = None, encoded_token: Optional["bytes"] = None, ) -> None: self.encoded_network = encoded_network self.encoded_token = encoded_token class EthereumSignTypedData(protobuf.MessageType): MESSAGE_WIRE_TYPE = 464 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("primary_type", "string", repeated=False, required=True), 3: protobuf.Field("metamask_v4_compat", "bool", repeated=False, required=False, default=True), 4: protobuf.Field("definitions", "EthereumDefinitions", repeated=False, required=False, default=None), } def __init__( self, *, primary_type: "str", address_n: Optional[Sequence["int"]] = None, metamask_v4_compat: Optional["bool"] = True, definitions: Optional["EthereumDefinitions"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.primary_type = primary_type self.metamask_v4_compat = metamask_v4_compat self.definitions = definitions class EthereumTypedDataStructRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 465 FIELDS = { 1: protobuf.Field("name", "string", repeated=False, required=True), } def __init__( self, *, name: "str", ) -> None: self.name = name class EthereumTypedDataStructAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 466 FIELDS = { 1: protobuf.Field("members", "EthereumStructMember", repeated=True, required=False, default=None), } def __init__( self, *, members: Optional[Sequence["EthereumStructMember"]] = None, ) -> None: self.members: Sequence["EthereumStructMember"] = members if members is not None else [] class EthereumTypedDataValueRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 467 FIELDS = { 1: protobuf.Field("member_path", "uint32", repeated=True, required=False, default=None), } def __init__( self, *, member_path: Optional[Sequence["int"]] = None, ) -> None: self.member_path: Sequence["int"] = member_path if member_path is not None else [] class EthereumTypedDataValueAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 468 FIELDS = { 1: protobuf.Field("value", "bytes", repeated=False, required=True), } def __init__( self, *, value: "bytes", ) -> None: self.value = value class EthereumStructMember(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("type", "EthereumFieldType", repeated=False, required=True), 2: protobuf.Field("name", "string", repeated=False, required=True), } def __init__( self, *, type: "EthereumFieldType", name: "str", ) -> None: self.type = type self.name = name class EthereumFieldType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("data_type", "EthereumDataType", repeated=False, required=True), 2: protobuf.Field("size", "uint32", repeated=False, required=False, default=None), 3: protobuf.Field("entry_type", "EthereumFieldType", repeated=False, required=False, default=None), 4: protobuf.Field("struct_name", "string", repeated=False, required=False, default=None), } def __init__( self, *, data_type: "EthereumDataType", size: Optional["int"] = None, entry_type: Optional["EthereumFieldType"] = None, struct_name: Optional["str"] = None, ) -> None: self.data_type = data_type self.size = size self.entry_type = entry_type self.struct_name = struct_name class EthereumGetPublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 450 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display class EthereumPublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 451 FIELDS = { 1: protobuf.Field("node", "HDNodeType", repeated=False, required=True), 2: protobuf.Field("xpub", "string", repeated=False, required=True), } def __init__( self, *, node: "HDNodeType", xpub: "str", ) -> None: self.node = node self.xpub = xpub class EthereumGetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 56 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("encoded_network", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, encoded_network: Optional["bytes"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display self.encoded_network = encoded_network self.chunkify = chunkify class EthereumAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 57 FIELDS = { 1: protobuf.Field("_old_address", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("address", "string", repeated=False, required=False, default=None), } def __init__( self, *, _old_address: Optional["bytes"] = None, address: Optional["str"] = None, ) -> None: self._old_address = _old_address self.address = address class EthereumSignTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 58 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("nonce", "bytes", repeated=False, required=False, default=b''), 3: protobuf.Field("gas_price", "bytes", repeated=False, required=True), 4: protobuf.Field("gas_limit", "bytes", repeated=False, required=True), 11: protobuf.Field("to", "string", repeated=False, required=False, default=''), 6: protobuf.Field("value", "bytes", repeated=False, required=False, default=b''), 7: protobuf.Field("data_initial_chunk", "bytes", repeated=False, required=False, default=b''), 8: protobuf.Field("data_length", "uint32", repeated=False, required=False, default=0), 9: protobuf.Field("chain_id", "uint64", repeated=False, required=True), 10: protobuf.Field("tx_type", "uint32", repeated=False, required=False, default=None), 12: protobuf.Field("definitions", "EthereumDefinitions", repeated=False, required=False, default=None), 13: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, gas_price: "bytes", gas_limit: "bytes", chain_id: "int", address_n: Optional[Sequence["int"]] = None, nonce: Optional["bytes"] = b'', to: Optional["str"] = '', value: Optional["bytes"] = b'', data_initial_chunk: Optional["bytes"] = b'', data_length: Optional["int"] = 0, tx_type: Optional["int"] = None, definitions: Optional["EthereumDefinitions"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.gas_price = gas_price self.gas_limit = gas_limit self.chain_id = chain_id self.nonce = nonce self.to = to self.value = value self.data_initial_chunk = data_initial_chunk self.data_length = data_length self.tx_type = tx_type self.definitions = definitions self.chunkify = chunkify class EthereumSignTxEIP1559(protobuf.MessageType): MESSAGE_WIRE_TYPE = 452 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("nonce", "bytes", repeated=False, required=True), 3: protobuf.Field("max_gas_fee", "bytes", repeated=False, required=True), 4: protobuf.Field("max_priority_fee", "bytes", repeated=False, required=True), 5: protobuf.Field("gas_limit", "bytes", repeated=False, required=True), 6: protobuf.Field("to", "string", repeated=False, required=False, default=''), 7: protobuf.Field("value", "bytes", repeated=False, required=True), 8: protobuf.Field("data_initial_chunk", "bytes", repeated=False, required=False, default=b''), 9: protobuf.Field("data_length", "uint32", repeated=False, required=True), 10: protobuf.Field("chain_id", "uint64", repeated=False, required=True), 11: protobuf.Field("access_list", "EthereumAccessList", repeated=True, required=False, default=None), 12: protobuf.Field("definitions", "EthereumDefinitions", repeated=False, required=False, default=None), 13: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, nonce: "bytes", max_gas_fee: "bytes", max_priority_fee: "bytes", gas_limit: "bytes", value: "bytes", data_length: "int", chain_id: "int", address_n: Optional[Sequence["int"]] = None, access_list: Optional[Sequence["EthereumAccessList"]] = None, to: Optional["str"] = '', data_initial_chunk: Optional["bytes"] = b'', definitions: Optional["EthereumDefinitions"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.access_list: Sequence["EthereumAccessList"] = access_list if access_list is not None else [] self.nonce = nonce self.max_gas_fee = max_gas_fee self.max_priority_fee = max_priority_fee self.gas_limit = gas_limit self.value = value self.data_length = data_length self.chain_id = chain_id self.to = to self.data_initial_chunk = data_initial_chunk self.definitions = definitions self.chunkify = chunkify class EthereumTxRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 59 FIELDS = { 1: protobuf.Field("data_length", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("signature_v", "uint32", repeated=False, required=False, default=None), 3: protobuf.Field("signature_r", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("signature_s", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, data_length: Optional["int"] = None, signature_v: Optional["int"] = None, signature_r: Optional["bytes"] = None, signature_s: Optional["bytes"] = None, ) -> None: self.data_length = data_length self.signature_v = signature_v self.signature_r = signature_r self.signature_s = signature_s class EthereumTxAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 60 FIELDS = { 1: protobuf.Field("data_chunk", "bytes", repeated=False, required=True), } def __init__( self, *, data_chunk: "bytes", ) -> None: self.data_chunk = data_chunk class EthereumSignMessage(protobuf.MessageType): MESSAGE_WIRE_TYPE = 64 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("message", "bytes", repeated=False, required=True), 3: protobuf.Field("encoded_network", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, message: "bytes", address_n: Optional[Sequence["int"]] = None, encoded_network: Optional["bytes"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.message = message self.encoded_network = encoded_network self.chunkify = chunkify class EthereumMessageSignature(protobuf.MessageType): MESSAGE_WIRE_TYPE = 66 FIELDS = { 2: protobuf.Field("signature", "bytes", repeated=False, required=True), 3: protobuf.Field("address", "string", repeated=False, required=True), } def __init__( self, *, signature: "bytes", address: "str", ) -> None: self.signature = signature self.address = address class EthereumVerifyMessage(protobuf.MessageType): MESSAGE_WIRE_TYPE = 65 FIELDS = { 2: protobuf.Field("signature", "bytes", repeated=False, required=True), 3: protobuf.Field("message", "bytes", repeated=False, required=True), 4: protobuf.Field("address", "string", repeated=False, required=True), 5: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, signature: "bytes", message: "bytes", address: "str", chunkify: Optional["bool"] = None, ) -> None: self.signature = signature self.message = message self.address = address self.chunkify = chunkify class EthereumSignTypedHash(protobuf.MessageType): MESSAGE_WIRE_TYPE = 470 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("domain_separator_hash", "bytes", repeated=False, required=True), 3: protobuf.Field("message_hash", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("encoded_network", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, domain_separator_hash: "bytes", address_n: Optional[Sequence["int"]] = None, message_hash: Optional["bytes"] = None, encoded_network: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.domain_separator_hash = domain_separator_hash self.message_hash = message_hash self.encoded_network = encoded_network class EthereumTypedDataSignature(protobuf.MessageType): MESSAGE_WIRE_TYPE = 469 FIELDS = { 1: protobuf.Field("signature", "bytes", repeated=False, required=True), 2: protobuf.Field("address", "string", repeated=False, required=True), } def __init__( self, *, signature: "bytes", address: "str", ) -> None: self.signature = signature self.address = address class EthereumAccessList(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), 2: protobuf.Field("storage_keys", "bytes", repeated=True, required=False, default=None), } def __init__( self, *, address: "str", storage_keys: Optional[Sequence["bytes"]] = None, ) -> None: self.storage_keys: Sequence["bytes"] = storage_keys if storage_keys is not None else [] self.address = address class MoneroTransactionSourceEntry(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("outputs", "MoneroOutputEntry", repeated=True, required=False, default=None), 2: protobuf.Field("real_output", "uint64", repeated=False, required=False, default=None), 3: protobuf.Field("real_out_tx_key", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("real_out_additional_tx_keys", "bytes", repeated=True, required=False, default=None), 5: protobuf.Field("real_output_in_tx_index", "uint64", repeated=False, required=False, default=None), 6: protobuf.Field("amount", "uint64", repeated=False, required=False, default=None), 7: protobuf.Field("rct", "bool", repeated=False, required=False, default=None), 8: protobuf.Field("mask", "bytes", repeated=False, required=False, default=None), 9: protobuf.Field("multisig_kLRki", "MoneroMultisigKLRki", repeated=False, required=False, default=None), 10: protobuf.Field("subaddr_minor", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, outputs: Optional[Sequence["MoneroOutputEntry"]] = None, real_out_additional_tx_keys: Optional[Sequence["bytes"]] = None, real_output: Optional["int"] = None, real_out_tx_key: Optional["bytes"] = None, real_output_in_tx_index: Optional["int"] = None, amount: Optional["int"] = None, rct: Optional["bool"] = None, mask: Optional["bytes"] = None, multisig_kLRki: Optional["MoneroMultisigKLRki"] = None, subaddr_minor: Optional["int"] = None, ) -> None: self.outputs: Sequence["MoneroOutputEntry"] = outputs if outputs is not None else [] self.real_out_additional_tx_keys: Sequence["bytes"] = real_out_additional_tx_keys if real_out_additional_tx_keys is not None else [] self.real_output = real_output self.real_out_tx_key = real_out_tx_key self.real_output_in_tx_index = real_output_in_tx_index self.amount = amount self.rct = rct self.mask = mask self.multisig_kLRki = multisig_kLRki self.subaddr_minor = subaddr_minor class MoneroTransactionDestinationEntry(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("amount", "uint64", repeated=False, required=False, default=None), 2: protobuf.Field("addr", "MoneroAccountPublicAddress", repeated=False, required=False, default=None), 3: protobuf.Field("is_subaddress", "bool", repeated=False, required=False, default=None), 4: protobuf.Field("original", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("is_integrated", "bool", repeated=False, required=False, default=None), } def __init__( self, *, amount: Optional["int"] = None, addr: Optional["MoneroAccountPublicAddress"] = None, is_subaddress: Optional["bool"] = None, original: Optional["bytes"] = None, is_integrated: Optional["bool"] = None, ) -> None: self.amount = amount self.addr = addr self.is_subaddress = is_subaddress self.original = original self.is_integrated = is_integrated class MoneroTransactionRsigData(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("rsig_type", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("offload_type", "uint32", repeated=False, required=False, default=None), 3: protobuf.Field("grouping", "uint64", repeated=True, required=False, default=None), 4: protobuf.Field("mask", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("rsig", "bytes", repeated=False, required=False, default=None), 6: protobuf.Field("rsig_parts", "bytes", repeated=True, required=False, default=None), 7: protobuf.Field("bp_version", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, grouping: Optional[Sequence["int"]] = None, rsig_parts: Optional[Sequence["bytes"]] = None, rsig_type: Optional["int"] = None, offload_type: Optional["int"] = None, mask: Optional["bytes"] = None, rsig: Optional["bytes"] = None, bp_version: Optional["int"] = None, ) -> None: self.grouping: Sequence["int"] = grouping if grouping is not None else [] self.rsig_parts: Sequence["bytes"] = rsig_parts if rsig_parts is not None else [] self.rsig_type = rsig_type self.offload_type = offload_type self.mask = mask self.rsig = rsig self.bp_version = bp_version class MoneroGetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 540 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("network_type", "MoneroNetworkType", repeated=False, required=False, default=MoneroNetworkType.MAINNET), 4: protobuf.Field("account", "uint32", repeated=False, required=False, default=None), 5: protobuf.Field("minor", "uint32", repeated=False, required=False, default=None), 6: protobuf.Field("payment_id", "bytes", repeated=False, required=False, default=None), 7: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, network_type: Optional["MoneroNetworkType"] = MoneroNetworkType.MAINNET, account: Optional["int"] = None, minor: Optional["int"] = None, payment_id: Optional["bytes"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display self.network_type = network_type self.account = account self.minor = minor self.payment_id = payment_id self.chunkify = chunkify class MoneroAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 541 FIELDS = { 1: protobuf.Field("address", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, address: Optional["bytes"] = None, ) -> None: self.address = address class MoneroGetWatchKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 542 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("network_type", "MoneroNetworkType", repeated=False, required=False, default=MoneroNetworkType.MAINNET), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, network_type: Optional["MoneroNetworkType"] = MoneroNetworkType.MAINNET, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.network_type = network_type class MoneroWatchKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 543 FIELDS = { 1: protobuf.Field("watch_key", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("address", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, watch_key: Optional["bytes"] = None, address: Optional["bytes"] = None, ) -> None: self.watch_key = watch_key self.address = address class MoneroTransactionInitRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 501 FIELDS = { 1: protobuf.Field("version", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 3: protobuf.Field("network_type", "MoneroNetworkType", repeated=False, required=False, default=MoneroNetworkType.MAINNET), 4: protobuf.Field("tsx_data", "MoneroTransactionData", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, version: Optional["int"] = None, network_type: Optional["MoneroNetworkType"] = MoneroNetworkType.MAINNET, tsx_data: Optional["MoneroTransactionData"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.version = version self.network_type = network_type self.tsx_data = tsx_data class MoneroTransactionInitAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 502 FIELDS = { 1: protobuf.Field("hmacs", "bytes", repeated=True, required=False, default=None), 2: protobuf.Field("rsig_data", "MoneroTransactionRsigData", repeated=False, required=False, default=None), } def __init__( self, *, hmacs: Optional[Sequence["bytes"]] = None, rsig_data: Optional["MoneroTransactionRsigData"] = None, ) -> None: self.hmacs: Sequence["bytes"] = hmacs if hmacs is not None else [] self.rsig_data = rsig_data class MoneroTransactionSetInputRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 503 FIELDS = { 1: protobuf.Field("src_entr", "MoneroTransactionSourceEntry", repeated=False, required=False, default=None), } def __init__( self, *, src_entr: Optional["MoneroTransactionSourceEntry"] = None, ) -> None: self.src_entr = src_entr class MoneroTransactionSetInputAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 504 FIELDS = { 1: protobuf.Field("vini", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("vini_hmac", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("pseudo_out", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("pseudo_out_hmac", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("pseudo_out_alpha", "bytes", repeated=False, required=False, default=None), 6: protobuf.Field("spend_key", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, vini: Optional["bytes"] = None, vini_hmac: Optional["bytes"] = None, pseudo_out: Optional["bytes"] = None, pseudo_out_hmac: Optional["bytes"] = None, pseudo_out_alpha: Optional["bytes"] = None, spend_key: Optional["bytes"] = None, ) -> None: self.vini = vini self.vini_hmac = vini_hmac self.pseudo_out = pseudo_out self.pseudo_out_hmac = pseudo_out_hmac self.pseudo_out_alpha = pseudo_out_alpha self.spend_key = spend_key class MoneroTransactionInputViniRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 507 FIELDS = { 1: protobuf.Field("src_entr", "MoneroTransactionSourceEntry", repeated=False, required=False, default=None), 2: protobuf.Field("vini", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("vini_hmac", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("pseudo_out", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("pseudo_out_hmac", "bytes", repeated=False, required=False, default=None), 6: protobuf.Field("orig_idx", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, src_entr: Optional["MoneroTransactionSourceEntry"] = None, vini: Optional["bytes"] = None, vini_hmac: Optional["bytes"] = None, pseudo_out: Optional["bytes"] = None, pseudo_out_hmac: Optional["bytes"] = None, orig_idx: Optional["int"] = None, ) -> None: self.src_entr = src_entr self.vini = vini self.vini_hmac = vini_hmac self.pseudo_out = pseudo_out self.pseudo_out_hmac = pseudo_out_hmac self.orig_idx = orig_idx class MoneroTransactionInputViniAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 508 class MoneroTransactionAllInputsSetRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 509 class MoneroTransactionAllInputsSetAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 510 FIELDS = { 1: protobuf.Field("rsig_data", "MoneroTransactionRsigData", repeated=False, required=False, default=None), } def __init__( self, *, rsig_data: Optional["MoneroTransactionRsigData"] = None, ) -> None: self.rsig_data = rsig_data class MoneroTransactionSetOutputRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 511 FIELDS = { 1: protobuf.Field("dst_entr", "MoneroTransactionDestinationEntry", repeated=False, required=False, default=None), 2: protobuf.Field("dst_entr_hmac", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("rsig_data", "MoneroTransactionRsigData", repeated=False, required=False, default=None), 4: protobuf.Field("is_offloaded_bp", "bool", repeated=False, required=False, default=None), } def __init__( self, *, dst_entr: Optional["MoneroTransactionDestinationEntry"] = None, dst_entr_hmac: Optional["bytes"] = None, rsig_data: Optional["MoneroTransactionRsigData"] = None, is_offloaded_bp: Optional["bool"] = None, ) -> None: self.dst_entr = dst_entr self.dst_entr_hmac = dst_entr_hmac self.rsig_data = rsig_data self.is_offloaded_bp = is_offloaded_bp class MoneroTransactionSetOutputAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 512 FIELDS = { 1: protobuf.Field("tx_out", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("vouti_hmac", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("rsig_data", "MoneroTransactionRsigData", repeated=False, required=False, default=None), 4: protobuf.Field("out_pk", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("ecdh_info", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, tx_out: Optional["bytes"] = None, vouti_hmac: Optional["bytes"] = None, rsig_data: Optional["MoneroTransactionRsigData"] = None, out_pk: Optional["bytes"] = None, ecdh_info: Optional["bytes"] = None, ) -> None: self.tx_out = tx_out self.vouti_hmac = vouti_hmac self.rsig_data = rsig_data self.out_pk = out_pk self.ecdh_info = ecdh_info class MoneroTransactionAllOutSetRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 513 FIELDS = { 1: protobuf.Field("rsig_data", "MoneroTransactionRsigData", repeated=False, required=False, default=None), } def __init__( self, *, rsig_data: Optional["MoneroTransactionRsigData"] = None, ) -> None: self.rsig_data = rsig_data class MoneroTransactionAllOutSetAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 514 FIELDS = { 1: protobuf.Field("extra", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("tx_prefix_hash", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("rv", "MoneroRingCtSig", repeated=False, required=False, default=None), 5: protobuf.Field("full_message_hash", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, extra: Optional["bytes"] = None, tx_prefix_hash: Optional["bytes"] = None, rv: Optional["MoneroRingCtSig"] = None, full_message_hash: Optional["bytes"] = None, ) -> None: self.extra = extra self.tx_prefix_hash = tx_prefix_hash self.rv = rv self.full_message_hash = full_message_hash class MoneroTransactionSignInputRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 515 FIELDS = { 1: protobuf.Field("src_entr", "MoneroTransactionSourceEntry", repeated=False, required=False, default=None), 2: protobuf.Field("vini", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("vini_hmac", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("pseudo_out", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("pseudo_out_hmac", "bytes", repeated=False, required=False, default=None), 6: protobuf.Field("pseudo_out_alpha", "bytes", repeated=False, required=False, default=None), 7: protobuf.Field("spend_key", "bytes", repeated=False, required=False, default=None), 8: protobuf.Field("orig_idx", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, src_entr: Optional["MoneroTransactionSourceEntry"] = None, vini: Optional["bytes"] = None, vini_hmac: Optional["bytes"] = None, pseudo_out: Optional["bytes"] = None, pseudo_out_hmac: Optional["bytes"] = None, pseudo_out_alpha: Optional["bytes"] = None, spend_key: Optional["bytes"] = None, orig_idx: Optional["int"] = None, ) -> None: self.src_entr = src_entr self.vini = vini self.vini_hmac = vini_hmac self.pseudo_out = pseudo_out self.pseudo_out_hmac = pseudo_out_hmac self.pseudo_out_alpha = pseudo_out_alpha self.spend_key = spend_key self.orig_idx = orig_idx class MoneroTransactionSignInputAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 516 FIELDS = { 1: protobuf.Field("signature", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("pseudo_out", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, signature: Optional["bytes"] = None, pseudo_out: Optional["bytes"] = None, ) -> None: self.signature = signature self.pseudo_out = pseudo_out class MoneroTransactionFinalRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 517 class MoneroTransactionFinalAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 518 FIELDS = { 1: protobuf.Field("cout_key", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("salt", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("rand_mult", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("tx_enc_keys", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("opening_key", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, cout_key: Optional["bytes"] = None, salt: Optional["bytes"] = None, rand_mult: Optional["bytes"] = None, tx_enc_keys: Optional["bytes"] = None, opening_key: Optional["bytes"] = None, ) -> None: self.cout_key = cout_key self.salt = salt self.rand_mult = rand_mult self.tx_enc_keys = tx_enc_keys self.opening_key = opening_key class MoneroKeyImageExportInitRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 530 FIELDS = { 1: protobuf.Field("num", "uint64", repeated=False, required=True), 2: protobuf.Field("hash", "bytes", repeated=False, required=True), 3: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 4: protobuf.Field("network_type", "MoneroNetworkType", repeated=False, required=False, default=MoneroNetworkType.MAINNET), 5: protobuf.Field("subs", "MoneroSubAddressIndicesList", repeated=True, required=False, default=None), } def __init__( self, *, num: "int", hash: "bytes", address_n: Optional[Sequence["int"]] = None, subs: Optional[Sequence["MoneroSubAddressIndicesList"]] = None, network_type: Optional["MoneroNetworkType"] = MoneroNetworkType.MAINNET, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.subs: Sequence["MoneroSubAddressIndicesList"] = subs if subs is not None else [] self.num = num self.hash = hash self.network_type = network_type class MoneroKeyImageExportInitAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 531 class MoneroKeyImageSyncStepRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 532 FIELDS = { 1: protobuf.Field("tdis", "MoneroTransferDetails", repeated=True, required=False, default=None), } def __init__( self, *, tdis: Optional[Sequence["MoneroTransferDetails"]] = None, ) -> None: self.tdis: Sequence["MoneroTransferDetails"] = tdis if tdis is not None else [] class MoneroKeyImageSyncStepAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 533 FIELDS = { 1: protobuf.Field("kis", "MoneroExportedKeyImage", repeated=True, required=False, default=None), } def __init__( self, *, kis: Optional[Sequence["MoneroExportedKeyImage"]] = None, ) -> None: self.kis: Sequence["MoneroExportedKeyImage"] = kis if kis is not None else [] class MoneroKeyImageSyncFinalRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 534 class MoneroKeyImageSyncFinalAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 535 FIELDS = { 1: protobuf.Field("enc_key", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, enc_key: Optional["bytes"] = None, ) -> None: self.enc_key = enc_key class MoneroGetTxKeyRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 550 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("network_type", "MoneroNetworkType", repeated=False, required=False, default=MoneroNetworkType.MAINNET), 3: protobuf.Field("salt1", "bytes", repeated=False, required=True), 4: protobuf.Field("salt2", "bytes", repeated=False, required=True), 5: protobuf.Field("tx_enc_keys", "bytes", repeated=False, required=True), 6: protobuf.Field("tx_prefix_hash", "bytes", repeated=False, required=True), 7: protobuf.Field("reason", "uint32", repeated=False, required=False, default=None), 8: protobuf.Field("view_public_key", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, salt1: "bytes", salt2: "bytes", tx_enc_keys: "bytes", tx_prefix_hash: "bytes", address_n: Optional[Sequence["int"]] = None, network_type: Optional["MoneroNetworkType"] = MoneroNetworkType.MAINNET, reason: Optional["int"] = None, view_public_key: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.salt1 = salt1 self.salt2 = salt2 self.tx_enc_keys = tx_enc_keys self.tx_prefix_hash = tx_prefix_hash self.network_type = network_type self.reason = reason self.view_public_key = view_public_key class MoneroGetTxKeyAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 551 FIELDS = { 1: protobuf.Field("salt", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("tx_keys", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("tx_derivations", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, salt: Optional["bytes"] = None, tx_keys: Optional["bytes"] = None, tx_derivations: Optional["bytes"] = None, ) -> None: self.salt = salt self.tx_keys = tx_keys self.tx_derivations = tx_derivations class MoneroLiveRefreshStartRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 552 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("network_type", "MoneroNetworkType", repeated=False, required=False, default=MoneroNetworkType.MAINNET), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, network_type: Optional["MoneroNetworkType"] = MoneroNetworkType.MAINNET, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.network_type = network_type class MoneroLiveRefreshStartAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 553 class MoneroLiveRefreshStepRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 554 FIELDS = { 1: protobuf.Field("out_key", "bytes", repeated=False, required=True), 2: protobuf.Field("recv_deriv", "bytes", repeated=False, required=True), 3: protobuf.Field("real_out_idx", "uint64", repeated=False, required=True), 4: protobuf.Field("sub_addr_major", "uint32", repeated=False, required=True), 5: protobuf.Field("sub_addr_minor", "uint32", repeated=False, required=True), } def __init__( self, *, out_key: "bytes", recv_deriv: "bytes", real_out_idx: "int", sub_addr_major: "int", sub_addr_minor: "int", ) -> None: self.out_key = out_key self.recv_deriv = recv_deriv self.real_out_idx = real_out_idx self.sub_addr_major = sub_addr_major self.sub_addr_minor = sub_addr_minor class MoneroLiveRefreshStepAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 555 FIELDS = { 1: protobuf.Field("salt", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("key_image", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, salt: Optional["bytes"] = None, key_image: Optional["bytes"] = None, ) -> None: self.salt = salt self.key_image = key_image class MoneroLiveRefreshFinalRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 556 class MoneroLiveRefreshFinalAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 557 class DebugMoneroDiagRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 546 FIELDS = { 1: protobuf.Field("ins", "uint64", repeated=False, required=False, default=None), 2: protobuf.Field("p1", "uint64", repeated=False, required=False, default=None), 3: protobuf.Field("p2", "uint64", repeated=False, required=False, default=None), 4: protobuf.Field("pd", "uint64", repeated=True, required=False, default=None), 5: protobuf.Field("data1", "bytes", repeated=False, required=False, default=None), 6: protobuf.Field("data2", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, pd: Optional[Sequence["int"]] = None, ins: Optional["int"] = None, p1: Optional["int"] = None, p2: Optional["int"] = None, data1: Optional["bytes"] = None, data2: Optional["bytes"] = None, ) -> None: self.pd: Sequence["int"] = pd if pd is not None else [] self.ins = ins self.p1 = p1 self.p2 = p2 self.data1 = data1 self.data2 = data2 class DebugMoneroDiagAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 547 FIELDS = { 1: protobuf.Field("ins", "uint64", repeated=False, required=False, default=None), 2: protobuf.Field("p1", "uint64", repeated=False, required=False, default=None), 3: protobuf.Field("p2", "uint64", repeated=False, required=False, default=None), 4: protobuf.Field("pd", "uint64", repeated=True, required=False, default=None), 5: protobuf.Field("data1", "bytes", repeated=False, required=False, default=None), 6: protobuf.Field("data2", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, pd: Optional[Sequence["int"]] = None, ins: Optional["int"] = None, p1: Optional["int"] = None, p2: Optional["int"] = None, data1: Optional["bytes"] = None, data2: Optional["bytes"] = None, ) -> None: self.pd: Sequence["int"] = pd if pd is not None else [] self.ins = ins self.p1 = p1 self.p2 = p2 self.data1 = data1 self.data2 = data2 class MoneroOutputEntry(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("idx", "uint64", repeated=False, required=False, default=None), 2: protobuf.Field("key", "MoneroRctKeyPublic", repeated=False, required=False, default=None), } def __init__( self, *, idx: Optional["int"] = None, key: Optional["MoneroRctKeyPublic"] = None, ) -> None: self.idx = idx self.key = key class MoneroMultisigKLRki(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("K", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("L", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("R", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("ki", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, K: Optional["bytes"] = None, L: Optional["bytes"] = None, R: Optional["bytes"] = None, ki: Optional["bytes"] = None, ) -> None: self.K = K self.L = L self.R = R self.ki = ki class MoneroRctKeyPublic(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("dest", "bytes", repeated=False, required=True), 2: protobuf.Field("commitment", "bytes", repeated=False, required=True), } def __init__( self, *, dest: "bytes", commitment: "bytes", ) -> None: self.dest = dest self.commitment = commitment class MoneroAccountPublicAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("spend_public_key", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("view_public_key", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, spend_public_key: Optional["bytes"] = None, view_public_key: Optional["bytes"] = None, ) -> None: self.spend_public_key = spend_public_key self.view_public_key = view_public_key class MoneroTransactionData(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("version", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("payment_id", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("unlock_time", "uint64", repeated=False, required=False, default=None), 4: protobuf.Field("outputs", "MoneroTransactionDestinationEntry", repeated=True, required=False, default=None), 5: protobuf.Field("change_dts", "MoneroTransactionDestinationEntry", repeated=False, required=False, default=None), 6: protobuf.Field("num_inputs", "uint32", repeated=False, required=False, default=None), 7: protobuf.Field("mixin", "uint32", repeated=False, required=False, default=None), 8: protobuf.Field("fee", "uint64", repeated=False, required=False, default=None), 9: protobuf.Field("account", "uint32", repeated=False, required=False, default=None), 10: protobuf.Field("minor_indices", "uint32", repeated=True, required=False, default=None), 11: protobuf.Field("rsig_data", "MoneroTransactionRsigData", repeated=False, required=False, default=None), 12: protobuf.Field("integrated_indices", "uint32", repeated=True, required=False, default=None), 13: protobuf.Field("client_version", "uint32", repeated=False, required=False, default=None), 14: protobuf.Field("hard_fork", "uint32", repeated=False, required=False, default=None), 15: protobuf.Field("monero_version", "bytes", repeated=False, required=False, default=None), 16: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, outputs: Optional[Sequence["MoneroTransactionDestinationEntry"]] = None, minor_indices: Optional[Sequence["int"]] = None, integrated_indices: Optional[Sequence["int"]] = None, version: Optional["int"] = None, payment_id: Optional["bytes"] = None, unlock_time: Optional["int"] = None, change_dts: Optional["MoneroTransactionDestinationEntry"] = None, num_inputs: Optional["int"] = None, mixin: Optional["int"] = None, fee: Optional["int"] = None, account: Optional["int"] = None, rsig_data: Optional["MoneroTransactionRsigData"] = None, client_version: Optional["int"] = None, hard_fork: Optional["int"] = None, monero_version: Optional["bytes"] = None, chunkify: Optional["bool"] = None, ) -> None: self.outputs: Sequence["MoneroTransactionDestinationEntry"] = outputs if outputs is not None else [] self.minor_indices: Sequence["int"] = minor_indices if minor_indices is not None else [] self.integrated_indices: Sequence["int"] = integrated_indices if integrated_indices is not None else [] self.version = version self.payment_id = payment_id self.unlock_time = unlock_time self.change_dts = change_dts self.num_inputs = num_inputs self.mixin = mixin self.fee = fee self.account = account self.rsig_data = rsig_data self.client_version = client_version self.hard_fork = hard_fork self.monero_version = monero_version self.chunkify = chunkify class MoneroRingCtSig(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("txn_fee", "uint64", repeated=False, required=False, default=None), 2: protobuf.Field("message", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("rv_type", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, txn_fee: Optional["int"] = None, message: Optional["bytes"] = None, rv_type: Optional["int"] = None, ) -> None: self.txn_fee = txn_fee self.message = message self.rv_type = rv_type class MoneroSubAddressIndicesList(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("account", "uint32", repeated=False, required=True), 2: protobuf.Field("minor_indices", "uint32", repeated=True, required=False, default=None), } def __init__( self, *, account: "int", minor_indices: Optional[Sequence["int"]] = None, ) -> None: self.minor_indices: Sequence["int"] = minor_indices if minor_indices is not None else [] self.account = account class MoneroTransferDetails(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("out_key", "bytes", repeated=False, required=True), 2: protobuf.Field("tx_pub_key", "bytes", repeated=False, required=True), 3: protobuf.Field("additional_tx_pub_keys", "bytes", repeated=True, required=False, default=None), 4: protobuf.Field("internal_output_index", "uint64", repeated=False, required=True), 5: protobuf.Field("sub_addr_major", "uint32", repeated=False, required=False, default=None), 6: protobuf.Field("sub_addr_minor", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, out_key: "bytes", tx_pub_key: "bytes", internal_output_index: "int", additional_tx_pub_keys: Optional[Sequence["bytes"]] = None, sub_addr_major: Optional["int"] = None, sub_addr_minor: Optional["int"] = None, ) -> None: self.additional_tx_pub_keys: Sequence["bytes"] = additional_tx_pub_keys if additional_tx_pub_keys is not None else [] self.out_key = out_key self.tx_pub_key = tx_pub_key self.internal_output_index = internal_output_index self.sub_addr_major = sub_addr_major self.sub_addr_minor = sub_addr_minor class MoneroExportedKeyImage(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("iv", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("blob", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, iv: Optional["bytes"] = None, blob: Optional["bytes"] = None, ) -> None: self.iv = iv self.blob = blob class NEMGetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 67 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("network", "uint32", repeated=False, required=False, default=104), 3: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 4: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, network: Optional["int"] = 104, show_display: Optional["bool"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.network = network self.show_display = show_display self.chunkify = chunkify class NEMAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 68 FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), } def __init__( self, *, address: "str", ) -> None: self.address = address class NEMSignTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 69 FIELDS = { 1: protobuf.Field("transaction", "NEMTransactionCommon", repeated=False, required=True), 2: protobuf.Field("multisig", "NEMTransactionCommon", repeated=False, required=False, default=None), 3: protobuf.Field("transfer", "NEMTransfer", repeated=False, required=False, default=None), 4: protobuf.Field("cosigning", "bool", repeated=False, required=False, default=None), 5: protobuf.Field("provision_namespace", "NEMProvisionNamespace", repeated=False, required=False, default=None), 6: protobuf.Field("mosaic_creation", "NEMMosaicCreation", repeated=False, required=False, default=None), 7: protobuf.Field("supply_change", "NEMMosaicSupplyChange", repeated=False, required=False, default=None), 8: protobuf.Field("aggregate_modification", "NEMAggregateModification", repeated=False, required=False, default=None), 9: protobuf.Field("importance_transfer", "NEMImportanceTransfer", repeated=False, required=False, default=None), 10: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, transaction: "NEMTransactionCommon", multisig: Optional["NEMTransactionCommon"] = None, transfer: Optional["NEMTransfer"] = None, cosigning: Optional["bool"] = None, provision_namespace: Optional["NEMProvisionNamespace"] = None, mosaic_creation: Optional["NEMMosaicCreation"] = None, supply_change: Optional["NEMMosaicSupplyChange"] = None, aggregate_modification: Optional["NEMAggregateModification"] = None, importance_transfer: Optional["NEMImportanceTransfer"] = None, chunkify: Optional["bool"] = None, ) -> None: self.transaction = transaction self.multisig = multisig self.transfer = transfer self.cosigning = cosigning self.provision_namespace = provision_namespace self.mosaic_creation = mosaic_creation self.supply_change = supply_change self.aggregate_modification = aggregate_modification self.importance_transfer = importance_transfer self.chunkify = chunkify class NEMSignedTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 70 FIELDS = { 1: protobuf.Field("data", "bytes", repeated=False, required=True), 2: protobuf.Field("signature", "bytes", repeated=False, required=True), } def __init__( self, *, data: "bytes", signature: "bytes", ) -> None: self.data = data self.signature = signature class NEMDecryptMessage(protobuf.MessageType): MESSAGE_WIRE_TYPE = 75 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("network", "uint32", repeated=False, required=False, default=None), 3: protobuf.Field("public_key", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("payload", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, network: Optional["int"] = None, public_key: Optional["bytes"] = None, payload: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.network = network self.public_key = public_key self.payload = payload class NEMDecryptedMessage(protobuf.MessageType): MESSAGE_WIRE_TYPE = 76 FIELDS = { 1: protobuf.Field("payload", "bytes", repeated=False, required=True), } def __init__( self, *, payload: "bytes", ) -> None: self.payload = payload class NEMTransactionCommon(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("network", "uint32", repeated=False, required=False, default=104), 3: protobuf.Field("timestamp", "uint32", repeated=False, required=True), 4: protobuf.Field("fee", "uint64", repeated=False, required=True), 5: protobuf.Field("deadline", "uint32", repeated=False, required=True), 6: protobuf.Field("signer", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, timestamp: "int", fee: "int", deadline: "int", address_n: Optional[Sequence["int"]] = None, network: Optional["int"] = 104, signer: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.timestamp = timestamp self.fee = fee self.deadline = deadline self.network = network self.signer = signer class NEMTransfer(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("recipient", "string", repeated=False, required=True), 2: protobuf.Field("amount", "uint64", repeated=False, required=True), 3: protobuf.Field("payload", "bytes", repeated=False, required=False, default=None), 4: protobuf.Field("public_key", "bytes", repeated=False, required=False, default=None), 5: protobuf.Field("mosaics", "NEMMosaic", repeated=True, required=False, default=None), } def __init__( self, *, recipient: "str", amount: "int", mosaics: Optional[Sequence["NEMMosaic"]] = None, payload: Optional["bytes"] = None, public_key: Optional["bytes"] = None, ) -> None: self.mosaics: Sequence["NEMMosaic"] = mosaics if mosaics is not None else [] self.recipient = recipient self.amount = amount self.payload = payload self.public_key = public_key class NEMProvisionNamespace(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("namespace", "string", repeated=False, required=True), 2: protobuf.Field("parent", "string", repeated=False, required=False, default=None), 3: protobuf.Field("sink", "string", repeated=False, required=True), 4: protobuf.Field("fee", "uint64", repeated=False, required=True), } def __init__( self, *, namespace: "str", sink: "str", fee: "int", parent: Optional["str"] = None, ) -> None: self.namespace = namespace self.sink = sink self.fee = fee self.parent = parent class NEMMosaicCreation(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("definition", "NEMMosaicDefinition", repeated=False, required=True), 2: protobuf.Field("sink", "string", repeated=False, required=True), 3: protobuf.Field("fee", "uint64", repeated=False, required=True), } def __init__( self, *, definition: "NEMMosaicDefinition", sink: "str", fee: "int", ) -> None: self.definition = definition self.sink = sink self.fee = fee class NEMMosaicSupplyChange(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("namespace", "string", repeated=False, required=True), 2: protobuf.Field("mosaic", "string", repeated=False, required=True), 3: protobuf.Field("type", "NEMSupplyChangeType", repeated=False, required=True), 4: protobuf.Field("delta", "uint64", repeated=False, required=True), } def __init__( self, *, namespace: "str", mosaic: "str", type: "NEMSupplyChangeType", delta: "int", ) -> None: self.namespace = namespace self.mosaic = mosaic self.type = type self.delta = delta class NEMAggregateModification(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("modifications", "NEMCosignatoryModification", repeated=True, required=False, default=None), 2: protobuf.Field("relative_change", "sint32", repeated=False, required=False, default=None), } def __init__( self, *, modifications: Optional[Sequence["NEMCosignatoryModification"]] = None, relative_change: Optional["int"] = None, ) -> None: self.modifications: Sequence["NEMCosignatoryModification"] = modifications if modifications is not None else [] self.relative_change = relative_change class NEMImportanceTransfer(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("mode", "NEMImportanceTransferMode", repeated=False, required=True), 2: protobuf.Field("public_key", "bytes", repeated=False, required=True), } def __init__( self, *, mode: "NEMImportanceTransferMode", public_key: "bytes", ) -> None: self.mode = mode self.public_key = public_key class NEMMosaic(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("namespace", "string", repeated=False, required=True), 2: protobuf.Field("mosaic", "string", repeated=False, required=True), 3: protobuf.Field("quantity", "uint64", repeated=False, required=True), } def __init__( self, *, namespace: "str", mosaic: "str", quantity: "int", ) -> None: self.namespace = namespace self.mosaic = mosaic self.quantity = quantity class NEMMosaicDefinition(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("name", "string", repeated=False, required=False, default=None), 2: protobuf.Field("ticker", "string", repeated=False, required=False, default=None), 3: protobuf.Field("namespace", "string", repeated=False, required=True), 4: protobuf.Field("mosaic", "string", repeated=False, required=True), 5: protobuf.Field("divisibility", "uint32", repeated=False, required=False, default=None), 6: protobuf.Field("levy", "NEMMosaicLevy", repeated=False, required=False, default=None), 7: protobuf.Field("fee", "uint64", repeated=False, required=False, default=None), 8: protobuf.Field("levy_address", "string", repeated=False, required=False, default=None), 9: protobuf.Field("levy_namespace", "string", repeated=False, required=False, default=None), 10: protobuf.Field("levy_mosaic", "string", repeated=False, required=False, default=None), 11: protobuf.Field("supply", "uint64", repeated=False, required=False, default=None), 12: protobuf.Field("mutable_supply", "bool", repeated=False, required=False, default=None), 13: protobuf.Field("transferable", "bool", repeated=False, required=False, default=None), 14: protobuf.Field("description", "string", repeated=False, required=True), 15: protobuf.Field("networks", "uint32", repeated=True, required=False, default=None), } def __init__( self, *, namespace: "str", mosaic: "str", description: "str", networks: Optional[Sequence["int"]] = None, name: Optional["str"] = None, ticker: Optional["str"] = None, divisibility: Optional["int"] = None, levy: Optional["NEMMosaicLevy"] = None, fee: Optional["int"] = None, levy_address: Optional["str"] = None, levy_namespace: Optional["str"] = None, levy_mosaic: Optional["str"] = None, supply: Optional["int"] = None, mutable_supply: Optional["bool"] = None, transferable: Optional["bool"] = None, ) -> None: self.networks: Sequence["int"] = networks if networks is not None else [] self.namespace = namespace self.mosaic = mosaic self.description = description self.name = name self.ticker = ticker self.divisibility = divisibility self.levy = levy self.fee = fee self.levy_address = levy_address self.levy_namespace = levy_namespace self.levy_mosaic = levy_mosaic self.supply = supply self.mutable_supply = mutable_supply self.transferable = transferable class NEMCosignatoryModification(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("type", "NEMModificationType", repeated=False, required=True), 2: protobuf.Field("public_key", "bytes", repeated=False, required=True), } def __init__( self, *, type: "NEMModificationType", public_key: "bytes", ) -> None: self.type = type self.public_key = public_key class RippleGetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 400 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display self.chunkify = chunkify class RippleAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 401 FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), } def __init__( self, *, address: "str", ) -> None: self.address = address class RippleSignTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 402 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("fee", "uint64", repeated=False, required=True), 3: protobuf.Field("flags", "uint32", repeated=False, required=False, default=0), 4: protobuf.Field("sequence", "uint32", repeated=False, required=True), 5: protobuf.Field("last_ledger_sequence", "uint32", repeated=False, required=False, default=None), 6: protobuf.Field("payment", "RipplePayment", repeated=False, required=True), 7: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, fee: "int", sequence: "int", payment: "RipplePayment", address_n: Optional[Sequence["int"]] = None, flags: Optional["int"] = 0, last_ledger_sequence: Optional["int"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.fee = fee self.sequence = sequence self.payment = payment self.flags = flags self.last_ledger_sequence = last_ledger_sequence self.chunkify = chunkify class RippleSignedTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 403 FIELDS = { 1: protobuf.Field("signature", "bytes", repeated=False, required=True), 2: protobuf.Field("serialized_tx", "bytes", repeated=False, required=True), } def __init__( self, *, signature: "bytes", serialized_tx: "bytes", ) -> None: self.signature = signature self.serialized_tx = serialized_tx class RipplePayment(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("amount", "uint64", repeated=False, required=True), 2: protobuf.Field("destination", "string", repeated=False, required=True), 3: protobuf.Field("destination_tag", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, amount: "int", destination: "str", destination_tag: Optional["int"] = None, ) -> None: self.amount = amount self.destination = destination self.destination_tag = destination_tag class SolanaGetPublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 900 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display class SolanaPublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 901 FIELDS = { 1: protobuf.Field("public_key", "bytes", repeated=False, required=True), } def __init__( self, *, public_key: "bytes", ) -> None: self.public_key = public_key class SolanaGetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 902 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display self.chunkify = chunkify class SolanaAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 903 FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), } def __init__( self, *, address: "str", ) -> None: self.address = address class SolanaTxTokenAccountInfo(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("base_address", "string", repeated=False, required=True), 2: protobuf.Field("token_program", "string", repeated=False, required=True), 3: protobuf.Field("token_mint", "string", repeated=False, required=True), 4: protobuf.Field("token_account", "string", repeated=False, required=True), } def __init__( self, *, base_address: "str", token_program: "str", token_mint: "str", token_account: "str", ) -> None: self.base_address = base_address self.token_program = token_program self.token_mint = token_mint self.token_account = token_account class SolanaTxAdditionalInfo(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("token_accounts_infos", "SolanaTxTokenAccountInfo", repeated=True, required=False, default=None), } def __init__( self, *, token_accounts_infos: Optional[Sequence["SolanaTxTokenAccountInfo"]] = None, ) -> None: self.token_accounts_infos: Sequence["SolanaTxTokenAccountInfo"] = token_accounts_infos if token_accounts_infos is not None else [] class SolanaSignTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 904 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("serialized_tx", "bytes", repeated=False, required=True), 3: protobuf.Field("additional_info", "SolanaTxAdditionalInfo", repeated=False, required=False, default=None), } def __init__( self, *, serialized_tx: "bytes", address_n: Optional[Sequence["int"]] = None, additional_info: Optional["SolanaTxAdditionalInfo"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.serialized_tx = serialized_tx self.additional_info = additional_info class SolanaTxSignature(protobuf.MessageType): MESSAGE_WIRE_TYPE = 905 FIELDS = { 1: protobuf.Field("signature", "bytes", repeated=False, required=True), } def __init__( self, *, signature: "bytes", ) -> None: self.signature = signature class StellarAsset(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("type", "StellarAssetType", repeated=False, required=True), 2: protobuf.Field("code", "string", repeated=False, required=False, default=None), 3: protobuf.Field("issuer", "string", repeated=False, required=False, default=None), } def __init__( self, *, type: "StellarAssetType", code: Optional["str"] = None, issuer: Optional["str"] = None, ) -> None: self.type = type self.code = code self.issuer = issuer class StellarGetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 207 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display self.chunkify = chunkify class StellarAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 208 FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), } def __init__( self, *, address: "str", ) -> None: self.address = address class StellarSignTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 202 FIELDS = { 2: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 3: protobuf.Field("network_passphrase", "string", repeated=False, required=True), 4: protobuf.Field("source_account", "string", repeated=False, required=True), 5: protobuf.Field("fee", "uint32", repeated=False, required=True), 6: protobuf.Field("sequence_number", "uint64", repeated=False, required=True), 8: protobuf.Field("timebounds_start", "uint32", repeated=False, required=True), 9: protobuf.Field("timebounds_end", "uint32", repeated=False, required=True), 10: protobuf.Field("memo_type", "StellarMemoType", repeated=False, required=True), 11: protobuf.Field("memo_text", "string", repeated=False, required=False, default=None), 12: protobuf.Field("memo_id", "uint64", repeated=False, required=False, default=None), 13: protobuf.Field("memo_hash", "bytes", repeated=False, required=False, default=None), 14: protobuf.Field("num_operations", "uint32", repeated=False, required=True), } def __init__( self, *, network_passphrase: "str", source_account: "str", fee: "int", sequence_number: "int", timebounds_start: "int", timebounds_end: "int", memo_type: "StellarMemoType", num_operations: "int", address_n: Optional[Sequence["int"]] = None, memo_text: Optional["str"] = None, memo_id: Optional["int"] = None, memo_hash: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.network_passphrase = network_passphrase self.source_account = source_account self.fee = fee self.sequence_number = sequence_number self.timebounds_start = timebounds_start self.timebounds_end = timebounds_end self.memo_type = memo_type self.num_operations = num_operations self.memo_text = memo_text self.memo_id = memo_id self.memo_hash = memo_hash class StellarTxOpRequest(protobuf.MessageType): MESSAGE_WIRE_TYPE = 203 class StellarPaymentOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 211 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("destination_account", "string", repeated=False, required=True), 3: protobuf.Field("asset", "StellarAsset", repeated=False, required=True), 4: protobuf.Field("amount", "sint64", repeated=False, required=True), } def __init__( self, *, destination_account: "str", asset: "StellarAsset", amount: "int", source_account: Optional["str"] = None, ) -> None: self.destination_account = destination_account self.asset = asset self.amount = amount self.source_account = source_account class StellarCreateAccountOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 210 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("new_account", "string", repeated=False, required=True), 3: protobuf.Field("starting_balance", "sint64", repeated=False, required=True), } def __init__( self, *, new_account: "str", starting_balance: "int", source_account: Optional["str"] = None, ) -> None: self.new_account = new_account self.starting_balance = starting_balance self.source_account = source_account class StellarPathPaymentStrictReceiveOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 212 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("send_asset", "StellarAsset", repeated=False, required=True), 3: protobuf.Field("send_max", "sint64", repeated=False, required=True), 4: protobuf.Field("destination_account", "string", repeated=False, required=True), 5: protobuf.Field("destination_asset", "StellarAsset", repeated=False, required=True), 6: protobuf.Field("destination_amount", "sint64", repeated=False, required=True), 7: protobuf.Field("paths", "StellarAsset", repeated=True, required=False, default=None), } def __init__( self, *, send_asset: "StellarAsset", send_max: "int", destination_account: "str", destination_asset: "StellarAsset", destination_amount: "int", paths: Optional[Sequence["StellarAsset"]] = None, source_account: Optional["str"] = None, ) -> None: self.paths: Sequence["StellarAsset"] = paths if paths is not None else [] self.send_asset = send_asset self.send_max = send_max self.destination_account = destination_account self.destination_asset = destination_asset self.destination_amount = destination_amount self.source_account = source_account class StellarPathPaymentStrictSendOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 223 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("send_asset", "StellarAsset", repeated=False, required=True), 3: protobuf.Field("send_amount", "sint64", repeated=False, required=True), 4: protobuf.Field("destination_account", "string", repeated=False, required=True), 5: protobuf.Field("destination_asset", "StellarAsset", repeated=False, required=True), 6: protobuf.Field("destination_min", "sint64", repeated=False, required=True), 7: protobuf.Field("paths", "StellarAsset", repeated=True, required=False, default=None), } def __init__( self, *, send_asset: "StellarAsset", send_amount: "int", destination_account: "str", destination_asset: "StellarAsset", destination_min: "int", paths: Optional[Sequence["StellarAsset"]] = None, source_account: Optional["str"] = None, ) -> None: self.paths: Sequence["StellarAsset"] = paths if paths is not None else [] self.send_asset = send_asset self.send_amount = send_amount self.destination_account = destination_account self.destination_asset = destination_asset self.destination_min = destination_min self.source_account = source_account class StellarManageSellOfferOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 213 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("selling_asset", "StellarAsset", repeated=False, required=True), 3: protobuf.Field("buying_asset", "StellarAsset", repeated=False, required=True), 4: protobuf.Field("amount", "sint64", repeated=False, required=True), 5: protobuf.Field("price_n", "uint32", repeated=False, required=True), 6: protobuf.Field("price_d", "uint32", repeated=False, required=True), 7: protobuf.Field("offer_id", "uint64", repeated=False, required=True), } def __init__( self, *, selling_asset: "StellarAsset", buying_asset: "StellarAsset", amount: "int", price_n: "int", price_d: "int", offer_id: "int", source_account: Optional["str"] = None, ) -> None: self.selling_asset = selling_asset self.buying_asset = buying_asset self.amount = amount self.price_n = price_n self.price_d = price_d self.offer_id = offer_id self.source_account = source_account class StellarManageBuyOfferOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 222 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("selling_asset", "StellarAsset", repeated=False, required=True), 3: protobuf.Field("buying_asset", "StellarAsset", repeated=False, required=True), 4: protobuf.Field("amount", "sint64", repeated=False, required=True), 5: protobuf.Field("price_n", "uint32", repeated=False, required=True), 6: protobuf.Field("price_d", "uint32", repeated=False, required=True), 7: protobuf.Field("offer_id", "uint64", repeated=False, required=True), } def __init__( self, *, selling_asset: "StellarAsset", buying_asset: "StellarAsset", amount: "int", price_n: "int", price_d: "int", offer_id: "int", source_account: Optional["str"] = None, ) -> None: self.selling_asset = selling_asset self.buying_asset = buying_asset self.amount = amount self.price_n = price_n self.price_d = price_d self.offer_id = offer_id self.source_account = source_account class StellarCreatePassiveSellOfferOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 214 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("selling_asset", "StellarAsset", repeated=False, required=True), 3: protobuf.Field("buying_asset", "StellarAsset", repeated=False, required=True), 4: protobuf.Field("amount", "sint64", repeated=False, required=True), 5: protobuf.Field("price_n", "uint32", repeated=False, required=True), 6: protobuf.Field("price_d", "uint32", repeated=False, required=True), } def __init__( self, *, selling_asset: "StellarAsset", buying_asset: "StellarAsset", amount: "int", price_n: "int", price_d: "int", source_account: Optional["str"] = None, ) -> None: self.selling_asset = selling_asset self.buying_asset = buying_asset self.amount = amount self.price_n = price_n self.price_d = price_d self.source_account = source_account class StellarSetOptionsOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 215 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("inflation_destination_account", "string", repeated=False, required=False, default=None), 3: protobuf.Field("clear_flags", "uint32", repeated=False, required=False, default=None), 4: protobuf.Field("set_flags", "uint32", repeated=False, required=False, default=None), 5: protobuf.Field("master_weight", "uint32", repeated=False, required=False, default=None), 6: protobuf.Field("low_threshold", "uint32", repeated=False, required=False, default=None), 7: protobuf.Field("medium_threshold", "uint32", repeated=False, required=False, default=None), 8: protobuf.Field("high_threshold", "uint32", repeated=False, required=False, default=None), 9: protobuf.Field("home_domain", "string", repeated=False, required=False, default=None), 10: protobuf.Field("signer_type", "StellarSignerType", repeated=False, required=False, default=None), 11: protobuf.Field("signer_key", "bytes", repeated=False, required=False, default=None), 12: protobuf.Field("signer_weight", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, source_account: Optional["str"] = None, inflation_destination_account: Optional["str"] = None, clear_flags: Optional["int"] = None, set_flags: Optional["int"] = None, master_weight: Optional["int"] = None, low_threshold: Optional["int"] = None, medium_threshold: Optional["int"] = None, high_threshold: Optional["int"] = None, home_domain: Optional["str"] = None, signer_type: Optional["StellarSignerType"] = None, signer_key: Optional["bytes"] = None, signer_weight: Optional["int"] = None, ) -> None: self.source_account = source_account self.inflation_destination_account = inflation_destination_account self.clear_flags = clear_flags self.set_flags = set_flags self.master_weight = master_weight self.low_threshold = low_threshold self.medium_threshold = medium_threshold self.high_threshold = high_threshold self.home_domain = home_domain self.signer_type = signer_type self.signer_key = signer_key self.signer_weight = signer_weight class StellarChangeTrustOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 216 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("asset", "StellarAsset", repeated=False, required=True), 3: protobuf.Field("limit", "uint64", repeated=False, required=True), } def __init__( self, *, asset: "StellarAsset", limit: "int", source_account: Optional["str"] = None, ) -> None: self.asset = asset self.limit = limit self.source_account = source_account class StellarAllowTrustOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 217 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("trusted_account", "string", repeated=False, required=True), 3: protobuf.Field("asset_type", "StellarAssetType", repeated=False, required=True), 4: protobuf.Field("asset_code", "string", repeated=False, required=False, default=None), 5: protobuf.Field("is_authorized", "bool", repeated=False, required=True), } def __init__( self, *, trusted_account: "str", asset_type: "StellarAssetType", is_authorized: "bool", source_account: Optional["str"] = None, asset_code: Optional["str"] = None, ) -> None: self.trusted_account = trusted_account self.asset_type = asset_type self.is_authorized = is_authorized self.source_account = source_account self.asset_code = asset_code class StellarAccountMergeOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 218 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("destination_account", "string", repeated=False, required=True), } def __init__( self, *, destination_account: "str", source_account: Optional["str"] = None, ) -> None: self.destination_account = destination_account self.source_account = source_account class StellarManageDataOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 220 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("key", "string", repeated=False, required=True), 3: protobuf.Field("value", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, key: "str", source_account: Optional["str"] = None, value: Optional["bytes"] = None, ) -> None: self.key = key self.source_account = source_account self.value = value class StellarBumpSequenceOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 221 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("bump_to", "uint64", repeated=False, required=True), } def __init__( self, *, bump_to: "int", source_account: Optional["str"] = None, ) -> None: self.bump_to = bump_to self.source_account = source_account class StellarClaimClaimableBalanceOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = 225 FIELDS = { 1: protobuf.Field("source_account", "string", repeated=False, required=False, default=None), 2: protobuf.Field("balance_id", "bytes", repeated=False, required=True), } def __init__( self, *, balance_id: "bytes", source_account: Optional["str"] = None, ) -> None: self.balance_id = balance_id self.source_account = source_account class StellarSignedTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 230 FIELDS = { 1: protobuf.Field("public_key", "bytes", repeated=False, required=True), 2: protobuf.Field("signature", "bytes", repeated=False, required=True), } def __init__( self, *, public_key: "bytes", signature: "bytes", ) -> None: self.public_key = public_key self.signature = signature class TezosGetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 150 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display self.chunkify = chunkify class TezosAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 151 FIELDS = { 1: protobuf.Field("address", "string", repeated=False, required=True), } def __init__( self, *, address: "str", ) -> None: self.address = address class TezosGetPublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 154 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display self.chunkify = chunkify class TezosPublicKey(protobuf.MessageType): MESSAGE_WIRE_TYPE = 155 FIELDS = { 1: protobuf.Field("public_key", "string", repeated=False, required=True), } def __init__( self, *, public_key: "str", ) -> None: self.public_key = public_key class TezosSignTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 152 FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("branch", "bytes", repeated=False, required=True), 3: protobuf.Field("reveal", "TezosRevealOp", repeated=False, required=False, default=None), 4: protobuf.Field("transaction", "TezosTransactionOp", repeated=False, required=False, default=None), 5: protobuf.Field("origination", "TezosOriginationOp", repeated=False, required=False, default=None), 6: protobuf.Field("delegation", "TezosDelegationOp", repeated=False, required=False, default=None), 7: protobuf.Field("proposal", "TezosProposalOp", repeated=False, required=False, default=None), 8: protobuf.Field("ballot", "TezosBallotOp", repeated=False, required=False, default=None), 9: protobuf.Field("chunkify", "bool", repeated=False, required=False, default=None), } def __init__( self, *, branch: "bytes", address_n: Optional[Sequence["int"]] = None, reveal: Optional["TezosRevealOp"] = None, transaction: Optional["TezosTransactionOp"] = None, origination: Optional["TezosOriginationOp"] = None, delegation: Optional["TezosDelegationOp"] = None, proposal: Optional["TezosProposalOp"] = None, ballot: Optional["TezosBallotOp"] = None, chunkify: Optional["bool"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.branch = branch self.reveal = reveal self.transaction = transaction self.origination = origination self.delegation = delegation self.proposal = proposal self.ballot = ballot self.chunkify = chunkify class TezosSignedTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 153 FIELDS = { 1: protobuf.Field("signature", "string", repeated=False, required=True), 2: protobuf.Field("sig_op_contents", "bytes", repeated=False, required=True), 3: protobuf.Field("operation_hash", "string", repeated=False, required=True), } def __init__( self, *, signature: "str", sig_op_contents: "bytes", operation_hash: "str", ) -> None: self.signature = signature self.sig_op_contents = sig_op_contents self.operation_hash = operation_hash class TezosContractID(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("tag", "TezosContractType", repeated=False, required=True), 2: protobuf.Field("hash", "bytes", repeated=False, required=True), } def __init__( self, *, tag: "TezosContractType", hash: "bytes", ) -> None: self.tag = tag self.hash = hash class TezosRevealOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 7: protobuf.Field("source", "bytes", repeated=False, required=True), 2: protobuf.Field("fee", "uint64", repeated=False, required=True), 3: protobuf.Field("counter", "uint64", repeated=False, required=True), 4: protobuf.Field("gas_limit", "uint64", repeated=False, required=True), 5: protobuf.Field("storage_limit", "uint64", repeated=False, required=True), 6: protobuf.Field("public_key", "bytes", repeated=False, required=True), } def __init__( self, *, source: "bytes", fee: "int", counter: "int", gas_limit: "int", storage_limit: "int", public_key: "bytes", ) -> None: self.source = source self.fee = fee self.counter = counter self.gas_limit = gas_limit self.storage_limit = storage_limit self.public_key = public_key class TezosTransactionOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 9: protobuf.Field("source", "bytes", repeated=False, required=True), 2: protobuf.Field("fee", "uint64", repeated=False, required=True), 3: protobuf.Field("counter", "uint64", repeated=False, required=True), 4: protobuf.Field("gas_limit", "uint64", repeated=False, required=True), 5: protobuf.Field("storage_limit", "uint64", repeated=False, required=True), 6: protobuf.Field("amount", "uint64", repeated=False, required=True), 7: protobuf.Field("destination", "TezosContractID", repeated=False, required=True), 8: protobuf.Field("parameters", "bytes", repeated=False, required=False, default=None), 10: protobuf.Field("parameters_manager", "TezosParametersManager", repeated=False, required=False, default=None), } def __init__( self, *, source: "bytes", fee: "int", counter: "int", gas_limit: "int", storage_limit: "int", amount: "int", destination: "TezosContractID", parameters: Optional["bytes"] = None, parameters_manager: Optional["TezosParametersManager"] = None, ) -> None: self.source = source self.fee = fee self.counter = counter self.gas_limit = gas_limit self.storage_limit = storage_limit self.amount = amount self.destination = destination self.parameters = parameters self.parameters_manager = parameters_manager class TezosOriginationOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 12: protobuf.Field("source", "bytes", repeated=False, required=True), 2: protobuf.Field("fee", "uint64", repeated=False, required=True), 3: protobuf.Field("counter", "uint64", repeated=False, required=True), 4: protobuf.Field("gas_limit", "uint64", repeated=False, required=True), 5: protobuf.Field("storage_limit", "uint64", repeated=False, required=True), 6: protobuf.Field("manager_pubkey", "bytes", repeated=False, required=False, default=None), 7: protobuf.Field("balance", "uint64", repeated=False, required=True), 8: protobuf.Field("spendable", "bool", repeated=False, required=False, default=None), 9: protobuf.Field("delegatable", "bool", repeated=False, required=False, default=None), 10: protobuf.Field("delegate", "bytes", repeated=False, required=False, default=None), 11: protobuf.Field("script", "bytes", repeated=False, required=True), } def __init__( self, *, source: "bytes", fee: "int", counter: "int", gas_limit: "int", storage_limit: "int", balance: "int", script: "bytes", manager_pubkey: Optional["bytes"] = None, spendable: Optional["bool"] = None, delegatable: Optional["bool"] = None, delegate: Optional["bytes"] = None, ) -> None: self.source = source self.fee = fee self.counter = counter self.gas_limit = gas_limit self.storage_limit = storage_limit self.balance = balance self.script = script self.manager_pubkey = manager_pubkey self.spendable = spendable self.delegatable = delegatable self.delegate = delegate class TezosDelegationOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 7: protobuf.Field("source", "bytes", repeated=False, required=True), 2: protobuf.Field("fee", "uint64", repeated=False, required=True), 3: protobuf.Field("counter", "uint64", repeated=False, required=True), 4: protobuf.Field("gas_limit", "uint64", repeated=False, required=True), 5: protobuf.Field("storage_limit", "uint64", repeated=False, required=True), 6: protobuf.Field("delegate", "bytes", repeated=False, required=True), } def __init__( self, *, source: "bytes", fee: "int", counter: "int", gas_limit: "int", storage_limit: "int", delegate: "bytes", ) -> None: self.source = source self.fee = fee self.counter = counter self.gas_limit = gas_limit self.storage_limit = storage_limit self.delegate = delegate class TezosProposalOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("source", "bytes", repeated=False, required=True), 2: protobuf.Field("period", "uint64", repeated=False, required=True), 4: protobuf.Field("proposals", "bytes", repeated=True, required=False, default=None), } def __init__( self, *, source: "bytes", period: "int", proposals: Optional[Sequence["bytes"]] = None, ) -> None: self.proposals: Sequence["bytes"] = proposals if proposals is not None else [] self.source = source self.period = period class TezosBallotOp(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("source", "bytes", repeated=False, required=True), 2: protobuf.Field("period", "uint64", repeated=False, required=True), 3: protobuf.Field("proposal", "bytes", repeated=False, required=True), 4: protobuf.Field("ballot", "TezosBallotType", repeated=False, required=True), } def __init__( self, *, source: "bytes", period: "int", proposal: "bytes", ballot: "TezosBallotType", ) -> None: self.source = source self.period = period self.proposal = proposal self.ballot = ballot class TezosParametersManager(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("set_delegate", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("cancel_delegate", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("transfer", "TezosManagerTransfer", repeated=False, required=False, default=None), } def __init__( self, *, set_delegate: Optional["bytes"] = None, cancel_delegate: Optional["bool"] = None, transfer: Optional["TezosManagerTransfer"] = None, ) -> None: self.set_delegate = set_delegate self.cancel_delegate = cancel_delegate self.transfer = transfer class TezosManagerTransfer(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("destination", "TezosContractID", repeated=False, required=True), 2: protobuf.Field("amount", "uint64", repeated=False, required=True), } def __init__( self, *, destination: "TezosContractID", amount: "int", ) -> None: self.destination = destination self.amount = amount class WebAuthnListResidentCredentials(protobuf.MessageType): MESSAGE_WIRE_TYPE = 800 class WebAuthnAddResidentCredential(protobuf.MessageType): MESSAGE_WIRE_TYPE = 802 FIELDS = { 1: protobuf.Field("credential_id", "bytes", repeated=False, required=False, default=None), } def __init__( self, *, credential_id: Optional["bytes"] = None, ) -> None: self.credential_id = credential_id class WebAuthnRemoveResidentCredential(protobuf.MessageType): MESSAGE_WIRE_TYPE = 803 FIELDS = { 1: protobuf.Field("index", "uint32", repeated=False, required=False, default=None), } def __init__( self, *, index: Optional["int"] = None, ) -> None: self.index = index class WebAuthnCredentials(protobuf.MessageType): MESSAGE_WIRE_TYPE = 801 FIELDS = { 1: protobuf.Field("credentials", "WebAuthnCredential", repeated=True, required=False, default=None), } def __init__( self, *, credentials: Optional[Sequence["WebAuthnCredential"]] = None, ) -> None: self.credentials: Sequence["WebAuthnCredential"] = credentials if credentials is not None else [] class WebAuthnCredential(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { 1: protobuf.Field("index", "uint32", repeated=False, required=False, default=None), 2: protobuf.Field("id", "bytes", repeated=False, required=False, default=None), 3: protobuf.Field("rp_id", "string", repeated=False, required=False, default=None), 4: protobuf.Field("rp_name", "string", repeated=False, required=False, default=None), 5: protobuf.Field("user_id", "bytes", repeated=False, required=False, default=None), 6: protobuf.Field("user_name", "string", repeated=False, required=False, default=None), 7: protobuf.Field("user_display_name", "string", repeated=False, required=False, default=None), 8: protobuf.Field("creation_time", "uint32", repeated=False, required=False, default=None), 9: protobuf.Field("hmac_secret", "bool", repeated=False, required=False, default=None), 10: protobuf.Field("use_sign_count", "bool", repeated=False, required=False, default=None), 11: protobuf.Field("algorithm", "sint32", repeated=False, required=False, default=None), 12: protobuf.Field("curve", "sint32", repeated=False, required=False, default=None), } def __init__( self, *, index: Optional["int"] = None, id: Optional["bytes"] = None, rp_id: Optional["str"] = None, rp_name: Optional["str"] = None, user_id: Optional["bytes"] = None, user_name: Optional["str"] = None, user_display_name: Optional["str"] = None, creation_time: Optional["int"] = None, hmac_secret: Optional["bool"] = None, use_sign_count: Optional["bool"] = None, algorithm: Optional["int"] = None, curve: Optional["int"] = None, ) -> None: self.index = index self.id = id self.rp_id = rp_id self.rp_name = rp_name self.user_id = user_id self.user_name = user_name self.user_display_name = user_display_name self.creation_time = creation_time self.hmac_secret = hmac_secret self.use_sign_count = use_sign_count self.algorithm = algorithm self.curve = curve ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/misc.py0000664000175000017500000000623014636513242017452 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from typing import TYPE_CHECKING, Optional from . import messages from .tools import expect if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType from .tools import Address @expect(messages.Entropy, field="entropy", ret_type=bytes) def get_entropy(client: "TrezorClient", size: int) -> "MessageType": return client.call(messages.GetEntropy(size=size)) @expect(messages.SignedIdentity) def sign_identity( client: "TrezorClient", identity: messages.IdentityType, challenge_hidden: bytes, challenge_visual: str, ecdsa_curve_name: Optional[str] = None, ) -> "MessageType": return client.call( messages.SignIdentity( identity=identity, challenge_hidden=challenge_hidden, challenge_visual=challenge_visual, ecdsa_curve_name=ecdsa_curve_name, ) ) @expect(messages.ECDHSessionKey) def get_ecdh_session_key( client: "TrezorClient", identity: messages.IdentityType, peer_public_key: bytes, ecdsa_curve_name: Optional[str] = None, ) -> "MessageType": return client.call( messages.GetECDHSessionKey( identity=identity, peer_public_key=peer_public_key, ecdsa_curve_name=ecdsa_curve_name, ) ) @expect(messages.CipheredKeyValue, field="value", ret_type=bytes) def encrypt_keyvalue( client: "TrezorClient", n: "Address", key: str, value: bytes, ask_on_encrypt: bool = True, ask_on_decrypt: bool = True, iv: bytes = b"", ) -> "MessageType": return client.call( messages.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(messages.CipheredKeyValue, field="value", ret_type=bytes) def decrypt_keyvalue( client: "TrezorClient", n: "Address", key: str, value: bytes, ask_on_encrypt: bool = True, ask_on_decrypt: bool = True, iv: bytes = b"", ) -> "MessageType": return client.call( messages.CipherKeyValue( address_n=n, key=key, value=value, encrypt=False, ask_on_encrypt=ask_on_encrypt, ask_on_decrypt=ask_on_decrypt, iv=iv, ) ) @expect(messages.Nonce, field="nonce", ret_type=bytes) def get_nonce(client: "TrezorClient"): return client.call(messages.GetNonce()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/models.py0000664000175000017500000000571214636513242020006 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from dataclasses import dataclass from typing import Collection, Optional, Tuple from . import mapping UsbId = Tuple[int, int] VENDORS = ("bitcointrezor.com", "trezor.io") @dataclass(eq=True, frozen=True) class TrezorModel: name: str internal_name: str minimum_version: Tuple[int, int, int] vendors: Collection[str] usb_ids: Collection[UsbId] default_mapping: mapping.ProtobufMapping # ==== internal names ==== T1B1 = TrezorModel( name="1", internal_name="T1B1", minimum_version=(1, 8, 0), vendors=VENDORS, usb_ids=((0x534C, 0x0001),), default_mapping=mapping.DEFAULT_MAPPING, ) T2T1 = TrezorModel( name="T", internal_name="T2T1", minimum_version=(2, 1, 0), vendors=VENDORS, usb_ids=((0x1209, 0x53C1), (0x1209, 0x53C0)), default_mapping=mapping.DEFAULT_MAPPING, ) T2B1 = TrezorModel( name="Safe 3", internal_name="T2B1", minimum_version=(2, 1, 0), vendors=VENDORS, usb_ids=((0x1209, 0x53C1), (0x1209, 0x53C0)), default_mapping=mapping.DEFAULT_MAPPING, ) T3T1 = TrezorModel( name="Safe 5", internal_name="T3T1", minimum_version=(2, 1, 0), vendors=VENDORS, usb_ids=((0x1209, 0x53C1), (0x1209, 0x53C0)), default_mapping=mapping.DEFAULT_MAPPING, ) DISC1 = TrezorModel( name="DISC1", internal_name="D001", minimum_version=(2, 1, 0), vendors=VENDORS, usb_ids=((0x1209, 0x53C1), (0x1209, 0x53C0)), default_mapping=mapping.DEFAULT_MAPPING, ) DISC2 = TrezorModel( name="DISC2", internal_name="D002", minimum_version=(2, 1, 0), vendors=VENDORS, usb_ids=((0x1209, 0x53C1), (0x1209, 0x53C0)), default_mapping=mapping.DEFAULT_MAPPING, ) # ==== model based names ==== TREZOR_ONE = T1B1 TREZOR_T = T2T1 TREZOR_R = T2B1 TREZOR_SAFE3 = T2B1 TREZOR_SAFE5 = T3T1 TREZOR_DISC1 = DISC1 TREZOR_DISC2 = DISC2 TREZORS = {T1B1, T2T1, T2B1, T3T1, DISC1, DISC2} def by_name(name: Optional[str]) -> Optional[TrezorModel]: if name is None: return T1B1 for model in TREZORS: if model.name == name: return model return None def by_internal_name(name: Optional[str]) -> Optional[TrezorModel]: if name is None: return None for model in TREZORS: if model.internal_name == name: return model return None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/monero.py0000664000175000017500000000334014636513242020015 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from typing import TYPE_CHECKING from . import messages from .tools import expect if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType from .tools import Address # MAINNET = 0 # TESTNET = 1 # STAGENET = 2 # FAKECHAIN = 3 @expect(messages.MoneroAddress, field="address", ret_type=bytes) def get_address( client: "TrezorClient", n: "Address", show_display: bool = False, network_type: messages.MoneroNetworkType = messages.MoneroNetworkType.MAINNET, chunkify: bool = False, ) -> "MessageType": return client.call( messages.MoneroGetAddress( address_n=n, show_display=show_display, network_type=network_type, chunkify=chunkify, ) ) @expect(messages.MoneroWatchKey) def get_watch_key( client: "TrezorClient", n: "Address", network_type: messages.MoneroNetworkType = messages.MoneroNetworkType.MAINNET, ) -> "MessageType": return client.call( messages.MoneroGetWatchKey(address_n=n, network_type=network_type) ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/nem.py0000664000175000017500000001674114636513242017306 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import json from typing import TYPE_CHECKING from . import exceptions, messages from .tools import expect if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType from .tools import Address 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: dict) -> messages.NEMTransactionCommon: msg = messages.NEMTransactionCommon( network=(transaction["version"] >> 24) & 0xFF, timestamp=transaction["timeStamp"], fee=transaction["fee"], deadline=transaction["deadline"], ) if "signer" in transaction: msg.signer = bytes.fromhex(transaction["signer"]) return msg def create_transfer(transaction: dict) -> messages.NEMTransfer: msg = messages.NEMTransfer( recipient=transaction["recipient"], 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 = [ messages.NEMMosaic( namespace=mosaic["mosaicId"]["namespaceId"], mosaic=mosaic["mosaicId"]["name"], quantity=mosaic["quantity"], ) for mosaic in transaction["mosaics"] ] return msg def create_aggregate_modification( transaction: dict, ) -> messages.NEMAggregateModification: msg = messages.NEMAggregateModification( modifications=[ messages.NEMCosignatoryModification( type=modification["modificationType"], public_key=bytes.fromhex(modification["cosignatoryAccount"]), ) for modification in transaction["modifications"] ] ) if "minCosignatories" in transaction: msg.relative_change = transaction["minCosignatories"]["relativeChange"] return msg def create_provision_namespace(transaction: dict) -> messages.NEMProvisionNamespace: msg = messages.NEMProvisionNamespace( sink=transaction["rentalFeeSink"], fee=transaction["rentalFee"], namespace=transaction["newPart"], ) if transaction["parent"]: msg.parent = transaction["parent"] return msg def create_mosaic_creation(transaction: dict) -> messages.NEMMosaicCreation: definition_dict = transaction["mosaicDefinition"] definition = messages.NEMMosaicDefinition( namespace=definition_dict["id"]["namespaceId"], mosaic=definition_dict["id"]["name"], description=definition_dict["description"], ) if definition_dict["levy"]: definition.levy = definition_dict["levy"]["type"] definition.fee = definition_dict["levy"]["fee"] definition.levy_address = definition_dict["levy"]["recipient"] definition.levy_namespace = definition_dict["levy"]["mosaicId"]["namespaceId"] definition.levy_mosaic = definition_dict["levy"]["mosaicId"]["name"] for property in definition_dict["properties"]: name = property["name"] value = json.loads(property["value"]) if name == "divisibility": definition.divisibility = value elif name == "initialSupply": definition.supply = value elif name == "supplyMutable": definition.mutable_supply = value elif name == "transferable": definition.transferable = value return messages.NEMMosaicCreation( definition=definition, sink=transaction["creationFeeSink"], fee=transaction["creationFee"], ) def create_supply_change(transaction: dict) -> messages.NEMMosaicSupplyChange: return messages.NEMMosaicSupplyChange( namespace=transaction["mosaicId"]["namespaceId"], mosaic=transaction["mosaicId"]["name"], type=transaction["supplyType"], delta=transaction["delta"], ) def create_importance_transfer(transaction: dict) -> messages.NEMImportanceTransfer: return messages.NEMImportanceTransfer( mode=transaction["importanceTransfer"]["mode"], public_key=bytes.fromhex(transaction["importanceTransfer"]["publicKey"]), ) def fill_transaction_by_type(msg: messages.NEMSignTx, transaction: dict) -> None: 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: dict, chunkify: bool = False) -> messages.NEMSignTx: msg = messages.NEMSignTx( transaction=create_transaction_common(transaction), cosigning=transaction["type"] == TYPE_MULTISIG_SIGNATURE, chunkify=chunkify, ) 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(messages.NEMAddress, field="address", ret_type=str) def get_address( client: "TrezorClient", n: "Address", network: int, show_display: bool = False, chunkify: bool = False, ) -> "MessageType": return client.call( messages.NEMGetAddress( address_n=n, network=network, show_display=show_display, chunkify=chunkify ) ) @expect(messages.NEMSignedTx) def sign_tx( client: "TrezorClient", n: "Address", transaction: dict, chunkify: bool = False ) -> "MessageType": try: msg = create_sign_tx(transaction, chunkify=chunkify) except ValueError as e: raise exceptions.TrezorException("Failed to encode transaction") from e assert msg.transaction is not None msg.transaction.address_n = n return client.call(msg) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315793.0 trezor-0.13.9/src/trezorlib/protobuf.py0000664000175000017500000004772714636526521020403 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . """ Extremely minimal streaming codec for a subset of protobuf. Supports uint32, bytes, string, embedded message and repeated fields. For de-serializing (loading) protobuf types, object with `Reader` interface is required. For serializing (dumping) protobuf types, object with `Writer` interface is required. """ from __future__ import annotations import logging import sys import typing as t import warnings from dataclasses import dataclass from enum import IntEnum from io import BytesIO from itertools import zip_longest import typing_extensions as tx T = t.TypeVar("T", bound=type) MT = t.TypeVar("MT", bound="MessageType") MAX_FIELD_SIZE = 1024 * 1024 # 1 MB class Reader(tx.Protocol): def readinto(self, __buf: bytearray) -> int: """ Reads exactly `len(buffer)` bytes into `buffer`. Returns number of bytes read, or 0 if it cannot read that much. """ ... class Writer(tx.Protocol): def write(self, __buf: bytes) -> int: """ Writes all bytes from `buffer`, or raises `EOFError` """ ... _UVARINT_BUFFER = bytearray(1) LOG = logging.getLogger(__name__) def load_uvarint(reader: Reader) -> int: buffer = _UVARINT_BUFFER result = 0 shift = 0 byte = 0x80 bytes_read = 0 while byte & 0x80: if reader.readinto(buffer) == 0: if bytes_read > 0: raise IOError("Interrupted UVarint") else: raise EOFError bytes_read += 1 byte = buffer[0] result += (byte & 0x7F) << shift shift += 7 return result def dump_uvarint(writer: Writer, n: int) -> None: if n < 0: raise ValueError("Cannot dump signed value, convert it to unsigned first.") buffer = _UVARINT_BUFFER shifted = 1 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: int) -> int: res = sint << 1 if sint < 0: res = ~res return res def uint_to_sint(uint: int) -> int: sign = uint & 1 res = uint >> 1 if sign: res = ~res return res WIRE_TYPE_INT = 0 WIRE_TYPE_LENGTH = 2 PROTO_TYPES = { "uint32": int, "uint64": int, "sint32": int, "sint64": int, "bool": bool, "bytes": bytes, "string": str, } REQUIRED_FIELD_PLACEHOLDER = object() @dataclass class Field: name: str proto_type: str repeated: bool = False required: bool = False default: object = None _py_type: type | None = None _owner: type[MessageType] | None = None @property def py_type(self) -> type: if self._py_type is None: self._py_type = self._resolve_type() # pyright issue https://github.com/microsoft/pyright/issues/8136 return self._py_type # type: ignore [Type ["Unknown | None"]] def _resolve_type(self) -> type: # look for a type in the builtins py_type = PROTO_TYPES.get(self.proto_type) if py_type is not None: return py_type # look for a type in the class locals assert self._owner is not None, "Field is not owned by a MessageType" py_type = self._owner.__dict__.get(self.proto_type) if py_type is not None: return py_type # look for a type in the class globals cls_module = sys.modules.get(self._owner.__module__, None) cls_globals = getattr(cls_module, "__dict__", {}) py_type = cls_globals.get(self.proto_type) if py_type is not None: return py_type raise TypeError(f"Could not resolve field type {self.proto_type}") @property def wire_type(self) -> int: if issubclass(self.py_type, (MessageType, bytes, str)): return WIRE_TYPE_LENGTH if issubclass(self.py_type, int): return WIRE_TYPE_INT raise ValueError(f"Unrecognized type for field {self.name}") def value_fits(self, value: int) -> bool: if self.proto_type == "uint32": return 0 <= value < 2**32 if self.proto_type == "uint64": return 0 <= value < 2**64 if self.proto_type == "sint32": return -(2**31) <= value < 2**31 if self.proto_type == "sint64": return -(2**63) <= value < 2**63 raise ValueError(f"Cannot check range bounds for {self.proto_type}") class MessageType: MESSAGE_WIRE_TYPE: t.ClassVar[int | None] = None FIELDS: t.ClassVar[dict[int, Field]] = {} def __init_subclass__(cls) -> None: super().__init_subclass__() # override the generated __init__ methods by the parent method cls.__init__ = MessageType.__init__ for field in cls.FIELDS.values(): field._owner = cls @classmethod def get_field(cls, name: str) -> Field | None: return next((f for f in cls.FIELDS.values() if f.name == name), None) def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: if args: warnings.warn( "Positional arguments for MessageType are deprecated", DeprecationWarning, stacklevel=2, ) # process fields one by one MISSING = object() for field, val in zip_longest(self.FIELDS.values(), args, fillvalue=MISSING): if field is MISSING: raise TypeError("too many positional arguments") assert isinstance(field, Field) if field.name in kwargs and val is not MISSING: # both *args and **kwargs specify the same thing raise TypeError(f"got multiple values for argument '{field.name}'") elif field.name in kwargs: # set in kwargs but not in args setattr(self, field.name, kwargs[field.name]) elif val is not MISSING: # set in args but not in kwargs setattr(self, field.name, val) else: default: t.Any # not set at all, pick a default if field.repeated: default = [] elif field.required: warnings.warn( f"Value of required field '{field.name}' must be provided in constructor", DeprecationWarning, stacklevel=2, ) default = REQUIRED_FIELD_PLACEHOLDER else: default = field.default setattr(self, field.name, default) def __eq__(self, rhs: t.Any) -> bool: return self.__class__ is rhs.__class__ and self.__dict__ == rhs.__dict__ def __repr__(self) -> str: d = {} for key, value in self.__dict__.items(): if value is None or value == []: continue d[key] = value return f"<{self.__class__.__name__}: {d}>" def ByteSize(self) -> int: data = BytesIO() dump_message(data, self) return len(data.getvalue()) class LimitedReader: def __init__(self, reader: Reader, limit: int) -> None: self.reader = reader self.limit = limit def readinto(self, buf: bytearray) -> int: if self.limit < len(buf): return 0 else: nread = self.reader.readinto(buf) self.limit -= nread return nread class CountingWriter: def __init__(self) -> None: self.size = 0 def write(self, buf: bytes) -> int: nwritten = len(buf) self.size += nwritten return nwritten def decode_packed_array_field(field: Field, reader: Reader) -> list[t.Any]: assert field.repeated, "Not decoding packed array into non-repeated field" length = load_uvarint(reader) packed_reader = LimitedReader(reader, length) values = [] try: while True: values.append(decode_varint_field(field, packed_reader)) except EOFError: pass return values def decode_varint_field(field: Field, reader: Reader) -> int | bool | IntEnum: assert field.wire_type == WIRE_TYPE_INT, f"Field {field.name} is not varint-encoded" value = load_uvarint(reader) if issubclass(field.py_type, IntEnum): try: return field.py_type(value) except ValueError as e: # treat enum errors as warnings LOG.info(f"On field {field.name}: {e}") return value if issubclass(field.py_type, bool): return bool(value) if issubclass(field.py_type, int): if field.proto_type.startswith("sint"): value = uint_to_sint(value) if not field.value_fits(value): LOG.info( f"On field {field.name}: value {value} out of range for {field.proto_type}" ) return value raise TypeError # not a varint field or unknown type def decode_length_delimited_field( field: Field, reader: Reader ) -> bytes | str | MessageType: value = load_uvarint(reader) if value > MAX_FIELD_SIZE: raise ValueError(f"Field {field.name} contents too large ({value} bytes)") if issubclass(field.py_type, bytes): buf = bytearray(value) reader.readinto(buf) return bytes(buf) if issubclass(field.py_type, str): buf = bytearray(value) reader.readinto(buf) return buf.decode() if issubclass(field.py_type, MessageType): return load_message(LimitedReader(reader, value), field.py_type) raise TypeError # field type is unknown def load_message(reader: Reader, msg_type: type[MT]) -> MT: msg_dict: dict[str, t.Any] = {} # pre-seed the dict for field in msg_type.FIELDS.values(): if field.repeated: msg_dict[field.name] = [] elif not field.required: msg_dict[field.name] = field.default while True: try: fkey = load_uvarint(reader) except EOFError: break # no more fields to load ftag = fkey >> 3 wtype = fkey & 7 if ftag not in msg_type.FIELDS: # unknown field, skip it if wtype == WIRE_TYPE_INT: load_uvarint(reader) elif wtype == WIRE_TYPE_LENGTH: ivalue = load_uvarint(reader) if ivalue > MAX_FIELD_SIZE: raise ValueError(f"Unknown field {ftag} too large ({ivalue} bytes)") reader.readinto(bytearray(ivalue)) else: raise ValueError continue field = msg_type.FIELDS[ftag] if ( wtype == WIRE_TYPE_LENGTH and field.wire_type == WIRE_TYPE_INT and field.repeated ): # packed array fvalues = decode_packed_array_field(field, reader) elif wtype != field.wire_type: raise ValueError(f"Field {field.name} received value does not match schema") elif wtype == WIRE_TYPE_LENGTH: fvalues = [decode_length_delimited_field(field, reader)] elif wtype == WIRE_TYPE_INT: fvalues = [decode_varint_field(field, reader)] else: raise TypeError # unknown wire type if field.repeated: msg_dict[field.name].extend(fvalues) elif len(fvalues) != 1: raise ValueError("Unexpected multiple values in non-repeating field") else: msg_dict[field.name] = fvalues[0] for field in msg_type.FIELDS.values(): if field.required and field.name not in msg_dict: raise ValueError(f"Did not receive value for field {field.name}") return msg_type(**msg_dict) def dump_message(writer: Writer, msg: "MessageType") -> None: repvalue = [0] mtype = msg.__class__ for ftag, field in mtype.FIELDS.items(): fvalue = getattr(msg, field.name, None) if fvalue is REQUIRED_FIELD_PLACEHOLDER: raise ValueError(f"Required value of field {field.name} was not provided") if fvalue is None: # not sending empty values continue fkey = (ftag << 3) | field.wire_type if not field.repeated: repvalue[0] = fvalue fvalue = repvalue for svalue in fvalue: dump_uvarint(writer, fkey) if issubclass(field.py_type, MessageType): if not isinstance(svalue, field.py_type): raise ValueError( f"Value {svalue} in field {field.name} is not {field.py_type.__name__}" ) counter = CountingWriter() dump_message(counter, svalue) dump_uvarint(writer, counter.size) dump_message(writer, svalue) elif issubclass(field.py_type, IntEnum): if svalue not in field.py_type.__members__.values(): raise ValueError( f"Value {svalue} in field {field.name} unknown for {field.proto_type}" ) dump_uvarint(writer, svalue) elif issubclass(field.py_type, bool): dump_uvarint(writer, int(svalue)) elif issubclass(field.py_type, int): if not field.value_fits(svalue): raise ValueError( f"Value {svalue} in field {field.name} does not fit into {field.proto_type}" ) if field.proto_type.startswith("sint"): svalue = sint_to_uint(svalue) dump_uvarint(writer, svalue) elif issubclass(field.py_type, bytes): assert isinstance(svalue, (bytes, bytearray)) dump_uvarint(writer, len(svalue)) writer.write(svalue) elif issubclass(field.py_type, str): assert isinstance(svalue, str) svalue_bytes = svalue.encode() dump_uvarint(writer, len(svalue_bytes)) writer.write(svalue_bytes) else: raise TypeError def format_message( pb: "MessageType", indent: int = 0, sep: str = " " * 4, truncate_after: int | None = 256, truncate_to: int | None = 64, ) -> str: def mostly_printable(bytes: bytes) -> bool: if not bytes: return True printable = sum(1 for byte in bytes if 0x20 <= byte <= 0x7E) return printable / len(bytes) > 0.8 def pformat(name: str, value: t.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 all(isinstance(x, int) for x in value): return repr(value) # long list, one line per entry lines = ["[", level + "]"] lines[1:1] = [leadin + pformat(name, 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(key, 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 f"{length} bytes {output}{suffix}" field = pb.get_field(name) if field is not None: if isinstance(value, int) and issubclass(field.py_type, IntEnum): try: return f"{field.py_type(value).name} ({value})" except ValueError: return str(value) return repr(value) try: byte_size = str(pb.ByteSize()) + " bytes" except Exception: byte_size = "encoding failed" return "{name} ({size}) {content}".format( name=pb.__class__.__name__, size=byte_size, content=pformat("", pb.__dict__, indent), ) def value_to_proto(field: Field, value: t.Any) -> t.Any: if issubclass(field.py_type, MessageType): raise TypeError("value_to_proto only converts simple values") if issubclass(field.py_type, IntEnum): if isinstance(value, str): return field.py_type.__members__[value] else: try: return field.py_type(value) except ValueError as e: LOG.info(f"On field {field.name}: {e}") return int(value) if issubclass(field.py_type, bytes): if isinstance(value, str): return bytes.fromhex(value) elif isinstance(value, bytes): return value else: raise TypeError(f"can't convert {type(value)} value to bytes") return field.py_type(value) def dict_to_proto(message_type: type[MT], d: dict[str, t.Any]) -> MT: params = {} for field in message_type.FIELDS.values(): value = d.get(field.name) if value is None: continue if not field.repeated: value = [value] if issubclass(field.py_type, MessageType): newvalue = [dict_to_proto(field.py_type, v) for v in value] else: newvalue = [value_to_proto(field, v) for v in value] if not field.repeated: newvalue = newvalue[0] params[field.name] = newvalue return message_type(**params) def to_dict(msg: "MessageType", hexlify_bytes: bool = True) -> dict[str, t.Any]: def convert_value(value: t.Any) -> t.Any: 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] elif isinstance(value, IntEnum): return value.name 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/py.typed0000664000175000017500000000000014636513242017631 0ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1719315826.1321068 trezor-0.13.9/src/trezorlib/qt/0000775000175000017500000000000014636526562016601 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/qt/__init__.py0000664000175000017500000000000014636513242020667 0ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/qt/pinmatrix.py0000664000175000017500000001253114636513242021157 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import math import sys from typing import Any try: from PyQt5.QtCore import QT_VERSION_STR, QRegExp, Qt from PyQt5.QtGui import QRegExpValidator from PyQt5.QtWidgets import ( QApplication, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QSizePolicy, QVBoxLayout, QWidget, ) except Exception: from PyQt4.QtCore import QT_VERSION_STR, SIGNAL, QObject, QRegExp, Qt # noqa: I from PyQt4.QtGui import ( # noqa: I QApplication, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QRegExpValidator, QSizePolicy, QVBoxLayout, QWidget, ) class PinButton(QPushButton): def __init__(self, password: QLineEdit, encoded_value: int) -> None: 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) -> None: 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: bool = True, parent: Any = None) -> 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: float) -> None: 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: Any) -> None: self._set_strength(self.get_strength()) def get_strength(self) -> float: digits = len(set(str(self.password.text()))) strength = math.factorial(9) / math.factorial(9 - digits) return strength def get_value(self) -> str: return self.password.text() if __name__ == "__main__": """ Demo application showing PinMatrix widget in action """ app = QApplication(sys.argv) matrix = PinMatrixWidget() def clicked() -> None: 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_() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/ripple.py0000664000175000017500000000435314636513242020016 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from typing import TYPE_CHECKING from . import messages from .protobuf import dict_to_proto from .tools import dict_from_camelcase, expect if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType from .tools import Address REQUIRED_FIELDS = ("Fee", "Sequence", "TransactionType", "Payment") REQUIRED_PAYMENT_FIELDS = ("Amount", "Destination") @expect(messages.RippleAddress, field="address", ret_type=str) def get_address( client: "TrezorClient", address_n: "Address", show_display: bool = False, chunkify: bool = False, ) -> "MessageType": return client.call( messages.RippleGetAddress( address_n=address_n, show_display=show_display, chunkify=chunkify ) ) @expect(messages.RippleSignedTx) def sign_tx( client: "TrezorClient", address_n: "Address", msg: messages.RippleSignTx, chunkify: bool = False, ) -> "MessageType": msg.address_n = address_n msg.chunkify = chunkify return client.call(msg) def create_sign_tx_msg(transaction: dict) -> 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) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/solana.py0000664000175000017500000000230514636513242017773 0ustar00matejcikmatejcikfrom typing import TYPE_CHECKING, List, Optional from . import messages from .tools import expect if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType @expect(messages.SolanaPublicKey) def get_public_key( client: "TrezorClient", address_n: List[int], show_display: bool, ) -> "MessageType": return client.call( messages.SolanaGetPublicKey(address_n=address_n, show_display=show_display) ) @expect(messages.SolanaAddress) def get_address( client: "TrezorClient", address_n: List[int], show_display: bool, chunkify: bool = False, ) -> "MessageType": return client.call( messages.SolanaGetAddress( address_n=address_n, show_display=show_display, chunkify=chunkify, ) ) @expect(messages.SolanaTxSignature) def sign_tx( client: "TrezorClient", address_n: List[int], serialized_tx: bytes, additional_info: Optional[messages.SolanaTxAdditionalInfo], ) -> "MessageType": return client.call( messages.SolanaSignTx( address_n=address_n, serialized_tx=serialized_tx, additional_info=additional_info, ) ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/stellar.py0000664000175000017500000003311214636513242020164 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from decimal import Decimal from typing import TYPE_CHECKING, List, Tuple, Union from . import exceptions, messages from .tools import expect if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType from .tools import Address StellarMessageType = Union[ messages.StellarAccountMergeOp, messages.StellarAllowTrustOp, messages.StellarBumpSequenceOp, messages.StellarChangeTrustOp, messages.StellarCreateAccountOp, messages.StellarCreatePassiveSellOfferOp, messages.StellarManageDataOp, messages.StellarManageBuyOfferOp, messages.StellarManageSellOfferOp, messages.StellarPathPaymentStrictReceiveOp, messages.StellarPathPaymentStrictSendOp, messages.StellarPaymentOp, messages.StellarSetOptionsOp, messages.StellarClaimClaimableBalanceOp, ] try: from stellar_sdk import ( AccountMerge, AllowTrust, Asset, BumpSequence, ChangeTrust, ClaimClaimableBalance, CreateAccount, CreatePassiveSellOffer, HashMemo, IdMemo, LiquidityPoolAsset, ManageBuyOffer, ManageData, ManageSellOffer, MuxedAccount, Network, NoneMemo, Operation, PathPaymentStrictReceive, PathPaymentStrictSend, Payment, Price, ReturnHashMemo, SetOptions, TextMemo, TransactionEnvelope, TrustLineEntryFlag, ) HAVE_STELLAR_SDK = True DEFAULT_NETWORK_PASSPHRASE = Network.PUBLIC_NETWORK_PASSPHRASE except ImportError: HAVE_STELLAR_SDK = False DEFAULT_NETWORK_PASSPHRASE = "Public Global Stellar Network ; September 2015" DEFAULT_BIP32_PATH = "m/44h/148h/0h" def from_envelope( envelope: "TransactionEnvelope", ) -> Tuple[messages.StellarSignTx, List["StellarMessageType"]]: """Parses transaction envelope into a map with the following keys: tx - a StellarSignTx describing the transaction header operations - an array of protobuf message objects for each operation """ if not HAVE_STELLAR_SDK: raise RuntimeError("Stellar SDK not available") parsed_tx = envelope.transaction if parsed_tx.preconditions is None or parsed_tx.preconditions.time_bounds is None: raise ValueError("Timebounds are mandatory") memo_type = messages.StellarMemoType.NONE memo_text = None memo_id = None memo_hash = None if isinstance(parsed_tx.memo, NoneMemo): pass elif isinstance(parsed_tx.memo, TextMemo): # memo_text is specified as UTF-8 string, but returned as bytes from the XDR parser memo_type = messages.StellarMemoType.TEXT memo_text = parsed_tx.memo.memo_text.decode("utf-8") elif isinstance(parsed_tx.memo, IdMemo): memo_type = messages.StellarMemoType.ID memo_id = parsed_tx.memo.memo_id elif isinstance(parsed_tx.memo, HashMemo): memo_type = messages.StellarMemoType.HASH memo_hash = parsed_tx.memo.memo_hash elif isinstance(parsed_tx.memo, ReturnHashMemo): memo_type = messages.StellarMemoType.RETURN memo_hash = parsed_tx.memo.memo_return else: raise ValueError("Unsupported memo type") _raise_if_account_muxed_id_exists(parsed_tx.source) tx = messages.StellarSignTx( source_account=parsed_tx.source.account_id, fee=parsed_tx.fee, sequence_number=parsed_tx.sequence, timebounds_start=parsed_tx.preconditions.time_bounds.min_time, timebounds_end=parsed_tx.preconditions.time_bounds.max_time, memo_type=memo_type, memo_text=memo_text, memo_id=memo_id, memo_hash=memo_hash, num_operations=len(parsed_tx.operations), network_passphrase=envelope.network_passphrase, ) operations = [_read_operation(op) for op in parsed_tx.operations] return tx, operations def _read_operation(op: "Operation") -> "StellarMessageType": # TODO: Let's add muxed account support later. if op.source: _raise_if_account_muxed_id_exists(op.source) source_account = op.source.account_id else: source_account = None if isinstance(op, CreateAccount): return messages.StellarCreateAccountOp( source_account=source_account, new_account=op.destination, starting_balance=_read_amount(op.starting_balance), ) if isinstance(op, Payment): _raise_if_account_muxed_id_exists(op.destination) return messages.StellarPaymentOp( source_account=source_account, destination_account=op.destination.account_id, asset=_read_asset(op.asset), amount=_read_amount(op.amount), ) if isinstance(op, PathPaymentStrictReceive): _raise_if_account_muxed_id_exists(op.destination) return messages.StellarPathPaymentStrictReceiveOp( source_account=source_account, send_asset=_read_asset(op.send_asset), send_max=_read_amount(op.send_max), destination_account=op.destination.account_id, destination_asset=_read_asset(op.dest_asset), destination_amount=_read_amount(op.dest_amount), paths=[_read_asset(asset) for asset in op.path], ) if isinstance(op, ManageSellOffer): price = _read_price(op.price) return messages.StellarManageSellOfferOp( source_account=source_account, selling_asset=_read_asset(op.selling), buying_asset=_read_asset(op.buying), amount=_read_amount(op.amount), price_n=price.n, price_d=price.d, offer_id=op.offer_id, ) if isinstance(op, CreatePassiveSellOffer): price = _read_price(op.price) return messages.StellarCreatePassiveSellOfferOp( source_account=source_account, selling_asset=_read_asset(op.selling), buying_asset=_read_asset(op.buying), amount=_read_amount(op.amount), price_n=price.n, price_d=price.d, ) if isinstance(op, SetOptions): operation = messages.StellarSetOptionsOp( source_account=source_account, inflation_destination_account=op.inflation_dest, clear_flags=op.clear_flags, set_flags=op.set_flags, master_weight=op.master_weight, low_threshold=op.low_threshold, medium_threshold=op.med_threshold, high_threshold=op.high_threshold, home_domain=op.home_domain, ) if op.signer: signer_type = op.signer.signer_key.signer_key_type operation.signer_type = messages.StellarSignerType(signer_type.value) operation.signer_key = op.signer.signer_key.signer_key operation.signer_weight = op.signer.weight return operation if isinstance(op, ChangeTrust): if isinstance(op.asset, LiquidityPoolAsset): raise ValueError("Liquidity pool assets are not supported") return messages.StellarChangeTrustOp( source_account=source_account, asset=_read_asset(op.asset), limit=_read_amount(op.limit), ) if isinstance(op, AllowTrust): if op.authorize not in ( TrustLineEntryFlag.UNAUTHORIZED_FLAG, TrustLineEntryFlag.AUTHORIZED_FLAG, ): raise ValueError("Unsupported trust line flag") asset_type = ( messages.StellarAssetType.ALPHANUM4 if len(op.asset_code) <= 4 else messages.StellarAssetType.ALPHANUM12 ) return messages.StellarAllowTrustOp( source_account=source_account, trusted_account=op.trustor, asset_type=asset_type, asset_code=op.asset_code, is_authorized=bool(op.authorize.value), ) if isinstance(op, AccountMerge): _raise_if_account_muxed_id_exists(op.destination) return messages.StellarAccountMergeOp( source_account=source_account, destination_account=op.destination.account_id, ) # Inflation is not implemented since anyone can submit this operation to the network if isinstance(op, ManageData): return messages.StellarManageDataOp( source_account=source_account, key=op.data_name, value=op.data_value, ) if isinstance(op, BumpSequence): return messages.StellarBumpSequenceOp( source_account=source_account, bump_to=op.bump_to ) if isinstance(op, ManageBuyOffer): price = _read_price(op.price) return messages.StellarManageBuyOfferOp( source_account=source_account, selling_asset=_read_asset(op.selling), buying_asset=_read_asset(op.buying), amount=_read_amount(op.amount), price_n=price.n, price_d=price.d, offer_id=op.offer_id, ) if isinstance(op, PathPaymentStrictSend): _raise_if_account_muxed_id_exists(op.destination) return messages.StellarPathPaymentStrictSendOp( source_account=source_account, send_asset=_read_asset(op.send_asset), send_amount=_read_amount(op.send_amount), destination_account=op.destination.account_id, destination_asset=_read_asset(op.dest_asset), destination_min=_read_amount(op.dest_min), paths=[_read_asset(asset) for asset in op.path], ) if isinstance(op, ClaimClaimableBalance): return messages.StellarClaimClaimableBalanceOp( source_account=source_account, balance_id=bytes.fromhex(op.balance_id), ) raise ValueError(f"Unknown operation type: {op.__class__.__name__}") def _raise_if_account_muxed_id_exists(account: "MuxedAccount"): # Currently Trezor firmware does not support MuxedAccount, # so we throw an exception here. if account.account_muxed_id is not None: raise ValueError("MuxedAccount is not supported") def _read_amount(amount: str) -> int: return Operation.to_xdr_amount(amount) def _read_price(price: Union["Price", str, Decimal]) -> "Price": # In the coming stellar-sdk 6.x, the type of price must be Price, # at that time we can remove this function if isinstance(price, Price): return price return Price.from_raw_price(price) def _read_asset(asset: "Asset") -> messages.StellarAsset: """Reads a stellar Asset from unpacker""" if asset.is_native(): return messages.StellarAsset(type=messages.StellarAssetType.NATIVE) if asset.guess_asset_type() == "credit_alphanum4": return messages.StellarAsset( type=messages.StellarAssetType.ALPHANUM4, code=asset.code, issuer=asset.issuer, ) if asset.guess_asset_type() == "credit_alphanum12": return messages.StellarAsset( type=messages.StellarAssetType.ALPHANUM12, code=asset.code, issuer=asset.issuer, ) raise ValueError("Unsupported asset type") # ====== Client functions ====== # @expect(messages.StellarAddress, field="address", ret_type=str) def get_address( client: "TrezorClient", address_n: "Address", show_display: bool = False, chunkify: bool = False, ) -> "MessageType": return client.call( messages.StellarGetAddress( address_n=address_n, show_display=show_display, chunkify=chunkify ) ) def sign_tx( client: "TrezorClient", tx: messages.StellarSignTx, operations: List["StellarMessageType"], address_n: "Address", network_passphrase: str = DEFAULT_NETWORK_PASSPHRASE, ) -> messages.StellarSignedTx: 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 exceptions.TrezorException( "Reached end of operations without a signature." ) from None if not isinstance(resp, messages.StellarSignedTx): raise exceptions.TrezorException( f"Unexpected message: {resp.__class__.__name__}" ) if operations: raise exceptions.TrezorException( "Received a signature before processing all operations." ) return resp ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/tezos.py0000664000175000017500000000361314636513242017665 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from typing import TYPE_CHECKING from . import messages from .tools import expect if TYPE_CHECKING: from .client import TrezorClient from .protobuf import MessageType from .tools import Address @expect(messages.TezosAddress, field="address", ret_type=str) def get_address( client: "TrezorClient", address_n: "Address", show_display: bool = False, chunkify: bool = False, ) -> "MessageType": return client.call( messages.TezosGetAddress( address_n=address_n, show_display=show_display, chunkify=chunkify ) ) @expect(messages.TezosPublicKey, field="public_key", ret_type=str) def get_public_key( client: "TrezorClient", address_n: "Address", show_display: bool = False, chunkify: bool = False, ) -> "MessageType": return client.call( messages.TezosGetPublicKey( address_n=address_n, show_display=show_display, chunkify=chunkify ) ) @expect(messages.TezosSignedTx) def sign_tx( client: "TrezorClient", address_n: "Address", sign_tx_msg: messages.TezosSignTx, chunkify: bool = False, ) -> "MessageType": sign_tx_msg.address_n = address_n sign_tx_msg.chunkify = chunkify return client.call(sign_tx_msg) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/toif.py0000664000175000017500000001736114636513242017467 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import struct import zlib from dataclasses import dataclass from enum import Enum from typing import Sequence, Tuple import construct as c from typing_extensions import Literal from .tools import EnumAdapter try: # Explanation of having to use "Image.Image" in typing: # https://stackoverflow.com/questions/58236138/pil-and-python-static-typing/58236618#58236618 from PIL import Image PIL_AVAILABLE = True except ImportError: PIL_AVAILABLE = False RGBPixel = Tuple[int, int, int] class ToifMode(Enum): full_color = b"f" # big endian grayscale = b"g" # odd hi full_color_le = b"F" # little endian grayscale_eh = b"G" # even hi ToifStruct = c.Struct( "magic" / c.Const(b"TOI"), "format" / EnumAdapter(c.Bytes(1), ToifMode), "width" / c.Int16ul, "height" / c.Int16ul, "data" / c.Prefixed(c.Int32ul, c.GreedyBytes), ) def _compress(data: bytes) -> bytes: z = zlib.compressobj(level=9, wbits=-10) return z.compress(data) + z.flush() def _decompress(data: bytes) -> bytes: return zlib.decompress(data, wbits=-10) def _from_pil_rgb(pixels: Sequence[RGBPixel], little_endian: bool) -> bytes: data = bytearray() for r, g, b in pixels: c = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3) if little_endian: data += struct.pack("H", c) return bytes(data) def _to_rgb(data: bytes, little_endian: bool) -> bytes: res = bytearray() for i in range(0, len(data), 2): if little_endian: (c,) = struct.unpack("H", data[i : i + 2]) r = (c & 0xF800) >> 8 g = (c & 0x07E0) >> 3 b = (c & 0x001F) << 3 res += bytes((r, g, b)) return bytes(res) def _from_pil_grayscale(pixels: Sequence[int], right_hi: bool) -> bytes: data = bytearray() for i in range(0, len(pixels), 2): left, right = pixels[i], pixels[i + 1] if right_hi: c = (right & 0xF0) | ((left & 0xF0) >> 4) else: c = (left & 0xF0) | ((right & 0xF0) >> 4) data += struct.pack(">B", c) return bytes(data) def _from_pil_grayscale_alpha( pixels: Sequence[Tuple[int, int]], right_hi: bool ) -> bytes: data = bytearray() for i in range(0, len(pixels), 2): left_w_alpha, right_w_alpha = pixels[i], pixels[i + 1] left = int((left_w_alpha[0] * left_w_alpha[1]) / 255) right = int((right_w_alpha[0] * right_w_alpha[1]) / 255) if right_hi: c = (right & 0xF0) | ((left & 0xF0) >> 4) else: c = (left & 0xF0) | ((right & 0xF0) >> 4) data += struct.pack(">B", c) return bytes(data) def _to_grayscale(data: bytes, right_hi: bool) -> bytes: res = bytearray() for pixel in data: if right_hi: right = pixel & 0xF0 left = (pixel & 0x0F) << 4 else: left = pixel & 0xF0 right = (pixel & 0x0F) << 4 res += bytes((left, right)) return bytes(res) @dataclass class Toif: mode: ToifMode size: Tuple[int, int] data: bytes def __post_init__(self) -> None: # checking the data size width, height = self.size if self.mode is ToifMode.grayscale or self.mode is ToifMode.grayscale_eh: expected_size = width * height // 2 else: expected_size = width * height * 2 uncompressed = _decompress(self.data) if len(uncompressed) != expected_size: raise ValueError( f"Uncompressed data is {len(uncompressed)} bytes, expected {expected_size}" ) def to_image(self) -> "Image.Image": if not PIL_AVAILABLE: raise RuntimeError( "PIL is not available. Please install via 'pip install Pillow'" ) uncompressed = _decompress(self.data) pil_mode: Literal["L", "RGB"] if self.mode is ToifMode.grayscale: pil_mode = "L" raw_data = _to_grayscale(uncompressed, right_hi=False) elif self.mode is ToifMode.grayscale_eh: pil_mode = "L" raw_data = _to_grayscale(uncompressed, right_hi=True) elif self.mode is ToifMode.full_color: pil_mode = "RGB" raw_data = _to_rgb(uncompressed, little_endian=False) else: # self.mode is ToifMode.full_color_le: pil_mode = "RGB" raw_data = _to_rgb(uncompressed, little_endian=True) return Image.frombuffer(pil_mode, self.size, raw_data, "raw", pil_mode, 0, 1) def to_bytes(self) -> bytes: width, height = self.size return ToifStruct.build( dict(format=self.mode, width=width, height=height, data=self.data) ) def save(self, filename: str) -> None: with open(filename, "wb") as out: out.write(self.to_bytes()) def from_bytes(data: bytes) -> Toif: return from_struct(ToifStruct.parse(data)) def from_struct(parsed: c.Container) -> Toif: return Toif(parsed.format, (parsed.width, parsed.height), parsed.data) def load(filename: str) -> Toif: with open(filename, "rb") as f: return from_bytes(f.read()) def from_image( image: "Image.Image", background: Tuple[int, int, int, int] = (0, 0, 0, 255), legacy_format: bool = False, ) -> Toif: if not PIL_AVAILABLE: raise RuntimeError( "PIL is not available. Please install via 'pip install Pillow'" ) if image.mode == "RGBA": img_background = Image.new("RGBA", image.size, background) blend = Image.alpha_composite(img_background, image) image = blend.convert("RGB") if image.mode == "1": image = image.convert("L") if image.mode == "L": # if image.size[0] % 2 != 0: # raise ValueError("Only even-width grayscale images are supported") if not legacy_format: toif_mode = ToifMode.grayscale_eh toif_data = _from_pil_grayscale(image.getdata(), right_hi=True) else: toif_mode = ToifMode.grayscale toif_data = _from_pil_grayscale(image.getdata(), right_hi=False) elif image.mode == "LA": toif_mode = ToifMode.grayscale if image.size[0] % 2 != 0: raise ValueError("Only even-width grayscale images are supported") if not legacy_format: toif_mode = ToifMode.grayscale_eh toif_data = _from_pil_grayscale_alpha(image.getdata(), right_hi=True) else: toif_mode = ToifMode.grayscale toif_data = _from_pil_grayscale_alpha(image.getdata(), right_hi=False) elif image.mode == "RGB": if not legacy_format: toif_mode = ToifMode.full_color_le toif_data = _from_pil_rgb(image.getdata(), little_endian=True) else: toif_mode = ToifMode.full_color toif_data = _from_pil_rgb(image.getdata(), little_endian=False) else: raise ValueError(f"Unsupported image mode: {image.mode}") data = _compress(toif_data) return Toif(toif_mode, image.size, data) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/tools.py0000664000175000017500000002546514636513242017672 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import functools import hashlib import re import struct import unicodedata from typing import ( TYPE_CHECKING, Any, AnyStr, Callable, Dict, List, NewType, Optional, Type, Union, overload, ) import construct if TYPE_CHECKING: # Needed to enforce a return value from decorators # More details: https://www.python.org/dev/peps/pep-0612/ from typing import TypeVar from typing_extensions import Concatenate, ParamSpec from . import client from .protobuf import MessageType MT = TypeVar("MT", bound=MessageType) P = ParamSpec("P") R = TypeVar("R") TrezorClient = TypeVar("TrezorClient", bound=client.TrezorClient) HARDENED_FLAG = 1 << 31 Address = NewType("Address", List[int]) def H_(x: int) -> int: """ Shortcut function that "hardens" a number in a BIP44 path. """ return x | HARDENED_FLAG def is_hardened(x: int) -> bool: """ Determines if a number in a BIP44 path is hardened. """ return x & HARDENED_FLAG != 0 def unharden(x: int) -> int: """ Unhardens a number in a BIP44 path. """ if not is_hardened(x): raise ValueError("Unhardened path component") return x ^ HARDENED_FLAG def btc_hash(data: bytes) -> bytes: """ Double-SHA256 hash as used in BTC """ return hashlib.sha256(hashlib.sha256(data).digest()).digest() def tx_hash(data: bytes) -> bytes: """Calculate and return double-SHA256 hash in reverse order. This is what Bitcoin uses as txids. """ return btc_hash(data)[::-1] def hash_160(public_key: bytes) -> bytes: md = hashlib.new("ripemd160") md.update(hashlib.sha256(public_key).digest()) return md.digest() def hash_160_to_bc_address(h160: bytes, address_type: int) -> str: vh160 = struct.pack(" bytes: if public_key[0] == 4: return bytes((public_key[64] & 1) + 2) + public_key[1:33] raise ValueError("Pubkey is already compressed") def public_key_to_bc_address( public_key: bytes, address_type: int, compress: bool = True ) -> str: if public_key[0] == "\x04" and compress: public_key = compress_pubkey(public_key) h160 = hash_160(public_key) return hash_160_to_bc_address(h160, address_type) __b58chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" __b58base = len(__b58chars) def b58encode_int(i: int) -> str: """Encode an integer using Base58""" digits = [] while i: i, idx = divmod(i, __b58base) digits.append(__b58chars[idx]) return "".join(reversed(digits)) def b58encode(v: bytes) -> str: """encode v, which is a string of bytes, to base58.""" origlen = len(v) v = v.lstrip(b"\0") newlen = len(v) acc = int.from_bytes(v, byteorder="big") # first byte is most significant result = b58encode_int(acc) return __b58chars[0] * (origlen - newlen) + result def b58decode_int(v: str) -> int: """Decode a Base58 encoded string as an integer""" decimal = 0 try: for char in v: decimal = decimal * __b58base + __b58chars.index(char) except KeyError: raise ValueError(f"Invalid character {char!r}") from None return decimal def b58decode(v: AnyStr, length: Optional[int] = None) -> bytes: """decode v into a string of len bytes.""" v_str = v if isinstance(v, str) else v.decode() origlen = len(v_str) v_str = v_str.lstrip(__b58chars[0]) newlen = len(v_str) acc = b58decode_int(v_str) result = acc.to_bytes(origlen - newlen + (acc.bit_length() + 7) // 8, "big") if length is not None and len(result) != length: raise ValueError("Result length does not match expected_length") return result def b58check_encode(v: bytes) -> str: checksum = btc_hash(v)[:4] return b58encode(v + checksum) def b58check_decode(v: AnyStr, length: Optional[int] = None) -> bytes: dec = b58decode(v, length) data, checksum = dec[:-4], dec[-4:] if btc_hash(data)[:4] != checksum: raise ValueError("invalid checksum") return data def parse_path(nstr: str) -> Address: """ Convert BIP32 path string to list of uint32 integers with hardened flags. Several conventions are supported to set the hardened flag: -1, 1', 1h e.g.: "0/1h/1" -> [0, 0x80000001, 1] :param nstr: path string :return: list of integers """ if not nstr: return Address([]) n = nstr.split("/") # m/a/b/c => a/b/c if n[0] == "m": n = n[1:] def str_to_harden(x: str) -> int: if x.startswith("-"): return H_(abs(int(x))) elif x.endswith(("h", "'")): return H_(int(x[:-1])) else: return int(x) try: return Address([str_to_harden(x) for x in n]) except Exception as e: raise ValueError("Invalid BIP32 path", nstr) from e def prepare_message_bytes(txt: AnyStr) -> bytes: """ Make message suitable for protobuf. If the message is a Unicode string, normalize it. If it's bytes, return the raw bytes. """ if isinstance(txt, bytes): return txt return unicodedata.normalize("NFC", txt).encode() # NOTE for type tests (mypy/pyright): # Overloads below have a goal of enforcing the return value # that should be returned from the original function being decorated # while still preserving the function signature (the inputted arguments # are going to be type-checked). # Currently (November 2021) mypy does not support "ParamSpec" typing # construct, so it will not understand it and will complain about # definitions below. @overload def expect( expected: "Type[MT]", ) -> "Callable[[Callable[P, MessageType]], Callable[P, MT]]": ... @overload def expect( expected: "Type[MT]", *, field: str, ret_type: "Type[R]" ) -> "Callable[[Callable[P, MessageType]], Callable[P, R]]": ... def expect( expected: "Type[MT]", *, field: Optional[str] = None, ret_type: "Optional[Type[R]]" = None, ) -> "Callable[[Callable[P, MessageType]], Callable[P, Union[MT, R]]]": """ Decorator checks if the method returned one of expected protobuf messages or raises an exception """ def decorator(f: "Callable[P, MessageType]") -> "Callable[P, Union[MT, R]]": @functools.wraps(f) def wrapped_f(*args: "P.args", **kwargs: "P.kwargs") -> "Union[MT, R]": __tracebackhide__ = True # for pytest # pylint: disable=W0612 ret = f(*args, **kwargs) if not isinstance(ret, expected): raise RuntimeError(f"Got {ret.__class__}, expected {expected}") if field is not None: return getattr(ret, field) else: return ret return wrapped_f return decorator def session( f: "Callable[Concatenate[TrezorClient, P], R]", ) -> "Callable[Concatenate[TrezorClient, P], R]": # Decorator wraps a BaseClient method # with session activation / deactivation @functools.wraps(f) def wrapped_f(client: "TrezorClient", *args: "P.args", **kwargs: "P.kwargs") -> "R": __tracebackhide__ = True # for pytest # pylint: disable=W0612 client.open() try: return f(client, *args, **kwargs) finally: client.close() return wrapped_f # de-camelcasifier # https://stackoverflow.com/a/1176023/222189 FIRST_CAP_RE = re.compile("(.)([A-Z][a-z]+)") ALL_CAP_RE = re.compile("([a-z0-9])([A-Z])") def from_camelcase(s: str) -> str: s = FIRST_CAP_RE.sub(r"\1_\2", s) return ALL_CAP_RE.sub(r"\1_\2", s).lower() def dict_from_camelcase(d: Any, renames: Optional[dict] = None) -> dict: if not isinstance(d, dict): return d if renames is None: renames = {} res: Dict[str, Any] = {} for key, value in d.items(): newkey = from_camelcase(key) renamed_key = renames.get(newkey) or renames.get(key) if renamed_key: newkey = renamed_key if isinstance(value, list): res[newkey] = [dict_from_camelcase(v, renames) for v in value] else: res[newkey] = dict_from_camelcase(value, renames) return res # adapted from https://github.com/bitcoin-core/HWI/blob/master/hwilib/descriptor.py def descriptor_checksum(desc: str) -> str: def _polymod(c: int, val: int) -> int: c0 = c >> 35 c = ((c & 0x7FFFFFFFF) << 5) ^ val if c0 & 1: c ^= 0xF5DEE51989 if c0 & 2: c ^= 0xA9FDCA3312 if c0 & 4: c ^= 0x1BAB10E32D if c0 & 8: c ^= 0x3706B1677A if c0 & 16: c ^= 0x644D626FFD return c INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ " CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" c = 1 cls = 0 clscount = 0 for ch in desc: pos = INPUT_CHARSET.find(ch) if pos == -1: return "" c = _polymod(c, pos & 31) cls = cls * 3 + (pos >> 5) clscount += 1 if clscount == 3: c = _polymod(c, cls) cls = 0 clscount = 0 if clscount > 0: c = _polymod(c, cls) for j in range(0, 8): c = _polymod(c, 0) c ^= 1 ret = [""] * 8 for j in range(0, 8): ret[j] = CHECKSUM_CHARSET[(c >> (5 * (7 - j))) & 31] return "".join(ret) class EnumAdapter(construct.Adapter): def __init__(self, subcon: Any, enum: Any) -> None: self.enum = enum super().__init__(subcon) def _encode(self, obj: Any, ctx: Any, path: Any): if isinstance(obj, self.enum): return obj.value return obj def _decode(self, obj: Any, ctx: Any, path: Any): try: return self.enum(obj) except ValueError: return obj class TupleAdapter(construct.Adapter): def __init__(self, *subcons: Any) -> None: super().__init__(construct.Sequence(*subcons)) def _encode(self, obj: Any, ctx: Any, path: Any): return obj def _decode(self, obj: Any, ctx: Any, path: Any): return tuple(obj) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1719315826.133107 trezor-0.13.9/src/trezorlib/transport/0000775000175000017500000000000014636526562020211 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/transport/__init__.py0000664000175000017500000001205714636513242022316 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import logging from typing import ( TYPE_CHECKING, Iterable, List, Optional, Sequence, Tuple, Type, TypeVar, ) from ..exceptions import TrezorException if TYPE_CHECKING: from ..models import TrezorModel T = TypeVar("T", bound="Transport") LOG = logging.getLogger(__name__) UDEV_RULES_STR = """ Do you have udev rules installed? https://github.com/trezor/trezor-common/blob/master/udev/51-trezor.rules """.strip() MessagePayload = Tuple[int, bytes] class TransportException(TrezorException): pass class DeviceIsBusy(TransportException): pass class Transport: """Raw connection to a Trezor device. Transport subclass represents a kind of communication link: Trezor Bridge, WebUSB or USB-HID connection, or UDP socket of listening emulator(s). It can also enumerate devices available over this communication link, and return them as instances. Transport instance is a thing that: - can be identified and requested by a string URI-like path - can open and close sessions, which enclose related operations - can read and write protobuf messages You need to implement a new Transport subclass if you invent a new way to connect a Trezor device to a computer. """ PATH_PREFIX: str ENABLED = False def __str__(self) -> str: return self.get_path() def get_path(self) -> str: raise NotImplementedError def begin_session(self) -> None: raise NotImplementedError def end_session(self) -> None: raise NotImplementedError def read(self) -> MessagePayload: raise NotImplementedError def write(self, message_type: int, message_data: bytes) -> None: raise NotImplementedError def find_debug(self: "T") -> "T": raise NotImplementedError @classmethod def enumerate( cls: Type["T"], models: Optional[Iterable["TrezorModel"]] = None ) -> Iterable["T"]: raise NotImplementedError @classmethod def find_by_path(cls: Type["T"], path: str, prefix_search: bool = False) -> "T": for device in cls.enumerate(): if ( path is None or device.get_path() == path or (prefix_search and device.get_path().startswith(path)) ): return device raise TransportException(f"{cls.PATH_PREFIX} device not found: {path}") def all_transports() -> Iterable[Type["Transport"]]: from .bridge import BridgeTransport from .hid import HidTransport from .udp import UdpTransport from .webusb import WebUsbTransport transports: Tuple[Type["Transport"], ...] = ( BridgeTransport, HidTransport, UdpTransport, WebUsbTransport, ) return set(t for t in transports if t.ENABLED) def enumerate_devices( models: Optional[Iterable["TrezorModel"]] = None, ) -> Sequence["Transport"]: devices: List["Transport"] = [] for transport in all_transports(): name = transport.__name__ try: found = list(transport.enumerate(models)) LOG.info(f"Enumerating {name}: found {len(found)} devices") devices.extend(found) except NotImplementedError: LOG.error(f"{name} does not implement device enumeration") except Exception as e: excname = e.__class__.__name__ LOG.error(f"Failed to enumerate {name}. {excname}: {e}") return devices def get_transport( path: Optional[str] = None, prefix_search: bool = False ) -> "Transport": if path is None: try: return next(iter(enumerate_devices())) except StopIteration: raise TransportException("No Trezor device found") from None # Find whether B is prefix of A (transport name is part of the path) # or A is prefix of B (path is a prefix, or a name, of transport). # This naively expects that no two transports have a common prefix. def match_prefix(a: str, b: str) -> bool: return a.startswith(b) or b.startswith(a) LOG.info( "looking for device by {}: {}".format( "prefix" if prefix_search else "full path", path ) ) transports = [t for t in all_transports() if match_prefix(path, t.PATH_PREFIX)] if transports: return transports[0].find_by_path(path, prefix_search=prefix_search) raise TransportException(f"Could not find device by path: {path}") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/transport/bridge.py0000664000175000017500000001354214636513242022013 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import logging import struct from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional import requests from ..log import DUMP_PACKETS from . import DeviceIsBusy, MessagePayload, Transport, TransportException if TYPE_CHECKING: from ..models import TrezorModel LOG = logging.getLogger(__name__) TREZORD_HOST = "http://127.0.0.1:21325" TREZORD_ORIGIN_HEADER = {"Origin": "https://python.trezor.io"} TREZORD_VERSION_MODERN = (2, 0, 25) CONNECTION = requests.Session() CONNECTION.headers.update(TREZORD_ORIGIN_HEADER) class BridgeException(TransportException): def __init__(self, path: str, status: int, message: str) -> None: self.path = path self.status = status self.message = message super().__init__(f"trezord: {path} failed with code {status}: {message}") def call_bridge(path: str, data: Optional[str] = None) -> requests.Response: url = TREZORD_HOST + "/" + path r = CONNECTION.post(url, data=data) if r.status_code != 200: raise BridgeException(path, r.status_code, r.json()["error"]) return r def is_legacy_bridge() -> bool: config = call_bridge("configure").json() version_tuple = tuple(map(int, config["version"].split("."))) return version_tuple < TREZORD_VERSION_MODERN class BridgeHandle: def __init__(self, transport: "BridgeTransport") -> None: self.transport = transport def read_buf(self) -> bytes: raise NotImplementedError def write_buf(self, buf: bytes) -> None: raise NotImplementedError class BridgeHandleModern(BridgeHandle): def write_buf(self, buf: bytes) -> None: LOG.log(DUMP_PACKETS, f"sending message: {buf.hex()}") self.transport._call("post", data=buf.hex()) def read_buf(self) -> bytes: data = self.transport._call("read") LOG.log(DUMP_PACKETS, f"received message: {data.text}") return bytes.fromhex(data.text) class BridgeHandleLegacy(BridgeHandle): def __init__(self, transport: "BridgeTransport") -> None: super().__init__(transport) self.request: Optional[str] = None def write_buf(self, buf: bytes) -> None: if self.request is not None: raise TransportException("Can't write twice on legacy Bridge") self.request = buf.hex() def read_buf(self) -> bytes: if self.request is None: raise TransportException("Can't read without write on legacy Bridge") try: LOG.log(DUMP_PACKETS, f"calling with message: {self.request}") data = self.transport._call("call", data=self.request) LOG.log(DUMP_PACKETS, f"received response: {data.text}") return bytes.fromhex(data.text) finally: self.request = None class BridgeTransport(Transport): """ BridgeTransport implements transport through Trezor Bridge (aka trezord). """ PATH_PREFIX = "bridge" ENABLED: bool = True def __init__( self, device: Dict[str, Any], legacy: bool, debug: bool = False ) -> None: if legacy and debug: raise TransportException("Debugging not supported on legacy Bridge") self.device = device self.session: Optional[str] = None self.debug = debug self.legacy = legacy if legacy: self.handle: BridgeHandle = BridgeHandleLegacy(self) else: self.handle = BridgeHandleModern(self) def get_path(self) -> str: return f"{self.PATH_PREFIX}:{self.device['path']}" def find_debug(self) -> "BridgeTransport": if not self.device.get("debug"): raise TransportException("Debug device not available") return BridgeTransport(self.device, self.legacy, debug=True) def _call(self, action: str, data: Optional[str] = None) -> requests.Response: session = self.session or "null" uri = action + "/" + str(session) if self.debug: uri = "debug/" + uri return call_bridge(uri, data=data) @classmethod def enumerate( cls, _models: Optional[Iterable["TrezorModel"]] = None ) -> Iterable["BridgeTransport"]: try: legacy = is_legacy_bridge() return [ BridgeTransport(dev, legacy) for dev in call_bridge("enumerate").json() ] except Exception: return [] def begin_session(self) -> None: try: data = self._call("acquire/" + self.device["path"]) except BridgeException as e: if e.message == "wrong previous session": raise DeviceIsBusy(self.device["path"]) from e raise self.session = data.json()["session"] def end_session(self) -> None: if not self.session: return self._call("release") self.session = None def write(self, message_type: int, message_data: bytes) -> None: header = struct.pack(">HL", message_type, len(message_data)) self.handle.write_buf(header + message_data) def read(self) -> MessagePayload: data = self.handle.read_buf() headerlen = struct.calcsize(">HL") msg_type, datalen = struct.unpack(">HL", data[:headerlen]) return msg_type, data[headerlen : headerlen + datalen] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/transport/hid.py0000664000175000017500000001266414636513242021327 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import logging import sys import time from typing import Any, Dict, Iterable, List, Optional from ..log import DUMP_PACKETS from ..models import TREZOR_ONE, TrezorModel from . import UDEV_RULES_STR, TransportException from .protocol import ProtocolBasedTransport, ProtocolV1 LOG = logging.getLogger(__name__) try: import hid HID_IMPORTED = True except Exception as e: LOG.info(f"HID transport is disabled: {e}") HID_IMPORTED = False HidDevice = Dict[str, Any] HidDeviceHandle = Any class HidHandle: def __init__( self, path: bytes, serial: str, probe_hid_version: bool = False ) -> None: self.path = path self.serial = serial self.handle: HidDeviceHandle = None self.hid_version = None if probe_hid_version else 2 def open(self) -> None: self.handle = hid.device() try: self.handle.open_path(self.path) except (IOError, OSError) as e: if sys.platform.startswith("linux"): e.args = e.args + (UDEV_RULES_STR,) raise e # On some platforms, HID path stays the same over device reconnects. # That means that someone could unplug a Trezor, plug a different one # and we wouldn't even know. # So we check that the serial matches what we expect. serial = self.handle.get_serial_number_string() if serial != self.serial: self.handle.close() self.handle = None raise TransportException( f"Unexpected device {serial} on path {self.path.decode()}" ) self.handle.set_nonblocking(True) if self.hid_version is None: self.hid_version = self.probe_hid_version() def close(self) -> None: if self.handle is not None: # reload serial, because device.wipe() can reset it self.serial = self.handle.get_serial_number_string() self.handle.close() self.handle = None def write_chunk(self, chunk: bytes) -> None: if len(chunk) != 64: raise TransportException(f"Unexpected chunk size: {len(chunk)}") if self.hid_version == 2: chunk = b"\x00" + chunk LOG.log(DUMP_PACKETS, f"writing packet: {chunk.hex()}") self.handle.write(chunk) def read_chunk(self) -> bytes: while True: # hidapi seems to return lists of ints instead of bytes chunk = bytes(self.handle.read(64)) if chunk: break else: time.sleep(0.001) LOG.log(DUMP_PACKETS, f"read packet: {chunk.hex()}") if len(chunk) != 64: raise TransportException(f"Unexpected chunk size: {len(chunk)}") return bytes(chunk) def probe_hid_version(self) -> int: n = self.handle.write([0, 63] + [0xFF] * 63) if n == 65: return 2 n = self.handle.write([63] + [0xFF] * 63) if n == 64: return 1 raise TransportException("Unknown HID version") class HidTransport(ProtocolBasedTransport): """ HidTransport implements transport over USB HID interface. """ PATH_PREFIX = "hid" ENABLED = HID_IMPORTED def __init__(self, device: HidDevice) -> None: self.device = device self.handle = HidHandle(device["path"], device["serial_number"]) super().__init__(protocol=ProtocolV1(self.handle)) def get_path(self) -> str: return f"{self.PATH_PREFIX}:{self.device['path'].decode()}" @classmethod def enumerate( cls, models: Optional[Iterable["TrezorModel"]] = None, debug: bool = False ) -> Iterable["HidTransport"]: if models is None: models = {TREZOR_ONE} usb_ids = [id for model in models for id in model.usb_ids] devices: List["HidTransport"] = [] for dev in hid.enumerate(0, 0): usb_id = (dev["vendor_id"], dev["product_id"]) if usb_id not in usb_ids: continue if debug: if not is_debuglink(dev): continue else: if not is_wirelink(dev): continue devices.append(HidTransport(dev)) return devices def find_debug(self) -> "HidTransport": # For v1 protocol, find debug USB interface for the same serial number for debug in HidTransport.enumerate(debug=True): if debug.device["serial_number"] == self.device["serial_number"]: return debug raise TransportException("Debug HID device not found") def is_wirelink(dev: HidDevice) -> bool: return dev["usage_page"] == 0xFF00 or dev["interface_number"] == 0 def is_debuglink(dev: HidDevice) -> bool: return dev["usage_page"] == 0xFF01 or dev["interface_number"] == 1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/transport/protocol.py0000664000175000017500000001233414636513242022416 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import logging import struct from typing import Tuple from typing_extensions import Protocol as StructuralType from . import MessagePayload, Transport REPLEN = 64 V2_FIRST_CHUNK = 0x01 V2_NEXT_CHUNK = 0x02 V2_BEGIN_SESSION = 0x03 V2_END_SESSION = 0x04 LOG = logging.getLogger(__name__) class Handle(StructuralType): """PEP 544 structural type for Handle functionality. (called a "Protocol" in the proposed PEP, name which is impractical here) Handle is a "physical" layer for a protocol. It can open/close a connection and read/write bare data in 64-byte chunks. Functionally we gain nothing from making this an (abstract) base class for handle implementations, so this definition is for type hinting purposes only. You can, but don't have to, inherit from it. """ def open(self) -> None: ... def close(self) -> None: ... def read_chunk(self) -> bytes: ... def write_chunk(self, chunk: bytes) -> None: ... class Protocol: """Wire protocol that can communicate with a Trezor device, given a Handle. A Protocol implements the part of the Transport API that relates to communicating logical messages over a physical layer. It is a thing that can: - open and close sessions, - send and receive protobuf messages, given the ability to: - open and close physical connections, - and send and receive binary chunks. For now, the class also handles session counting and opening the underlying Handle. This will probably be removed in the future. We will need a new Protocol class if we change the way a Trezor device encapsulates its messages. """ def __init__(self, handle: Handle) -> None: self.handle = handle self.session_counter = 0 # XXX we might be able to remove this now that TrezorClient does session handling def begin_session(self) -> None: if self.session_counter == 0: self.handle.open() self.session_counter += 1 def end_session(self) -> None: self.session_counter = max(self.session_counter - 1, 0) if self.session_counter == 0: self.handle.close() def read(self) -> MessagePayload: raise NotImplementedError def write(self, message_type: int, message_data: bytes) -> None: raise NotImplementedError class ProtocolBasedTransport(Transport): """Transport that implements its communications through a Protocol. Intended as a base class for implementations that proxy their communication operations to a Protocol. """ def __init__(self, protocol: Protocol) -> None: self.protocol = protocol def write(self, message_type: int, message_data: bytes) -> None: self.protocol.write(message_type, message_data) def read(self) -> MessagePayload: return self.protocol.read() def begin_session(self) -> None: self.protocol.begin_session() def end_session(self) -> None: self.protocol.end_session() class ProtocolV1(Protocol): """Protocol version 1. Currently (11/2018) in use on all Trezors. Does not understand sessions. """ HEADER_LEN = struct.calcsize(">HL") def write(self, message_type: int, message_data: bytes) -> None: header = struct.pack(">HL", message_type, len(message_data)) buffer = bytearray(b"##" + header + message_data) while buffer: # Report ID, data padded to 63 bytes chunk = b"?" + buffer[: REPLEN - 1] chunk = chunk.ljust(REPLEN, b"\x00") self.handle.write_chunk(chunk) buffer = buffer[63:] def read(self) -> MessagePayload: buffer = bytearray() # Read header with first part of message data msg_type, datalen, first_chunk = self.read_first() buffer.extend(first_chunk) # Read the rest of the message while len(buffer) < datalen: buffer.extend(self.read_next()) return msg_type, buffer[:datalen] def read_first(self) -> Tuple[int, int, bytes]: chunk = self.handle.read_chunk() if chunk[:3] != b"?##": raise RuntimeError("Unexpected magic characters") try: msg_type, datalen = struct.unpack(">HL", chunk[3 : 3 + self.HEADER_LEN]) except Exception: raise RuntimeError("Cannot parse header") data = chunk[3 + self.HEADER_LEN :] return msg_type, datalen, data def read_next(self) -> bytes: chunk = self.handle.read_chunk() if chunk[:1] != b"?": raise RuntimeError("Unexpected magic characters") return chunk[1:] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/transport/udp.py0000664000175000017500000001154114636513242021344 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import logging import socket import time from typing import TYPE_CHECKING, Iterable, Optional from ..log import DUMP_PACKETS from . import TransportException from .protocol import ProtocolBasedTransport, ProtocolV1 if TYPE_CHECKING: from ..models import TrezorModel SOCKET_TIMEOUT = 10 LOG = logging.getLogger(__name__) class UdpTransport(ProtocolBasedTransport): DEFAULT_HOST = "127.0.0.1" DEFAULT_PORT = 21324 PATH_PREFIX = "udp" ENABLED: bool = True def __init__(self, device: Optional[str] = None) -> None: if not device: host = UdpTransport.DEFAULT_HOST port = UdpTransport.DEFAULT_PORT else: devparts = device.split(":") host = devparts[0] port = int(devparts[1]) if len(devparts) > 1 else UdpTransport.DEFAULT_PORT self.device = (host, port) self.socket: Optional[socket.socket] = None super().__init__(protocol=ProtocolV1(self)) def get_path(self) -> str: return "{}:{}:{}".format(self.PATH_PREFIX, *self.device) def find_debug(self) -> "UdpTransport": host, port = self.device return UdpTransport(f"{host}:{port + 1}") @classmethod def _try_path(cls, path: str) -> "UdpTransport": d = cls(path) try: d.open() if d._ping(): return d else: raise TransportException( f"No Trezor device found at address {d.get_path()}" ) except Exception as e: raise TransportException(f"Error opening {d.get_path()}") from e finally: d.close() @classmethod def enumerate( cls, _models: Optional[Iterable["TrezorModel"]] = None ) -> Iterable["UdpTransport"]: default_path = f"{cls.DEFAULT_HOST}:{cls.DEFAULT_PORT}" try: return [cls._try_path(default_path)] except TransportException: return [] @classmethod def find_by_path(cls, path: str, prefix_search: bool = False) -> "UdpTransport": try: address = path.replace(f"{cls.PATH_PREFIX}:", "") return cls._try_path(address) except TransportException: if not prefix_search: raise if prefix_search: return super().find_by_path(path, prefix_search) else: raise TransportException(f"No UDP device at {path}") def wait_until_ready(self, timeout: float = 10) -> None: try: self.open() start = time.monotonic() while True: if self._ping(): break elapsed = time.monotonic() - start if elapsed >= timeout: raise TransportException("Timed out waiting for connection.") time.sleep(0.05) finally: self.close() def open(self) -> None: self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket.connect(self.device) self.socket.settimeout(SOCKET_TIMEOUT) def close(self) -> None: if self.socket is not None: self.socket.close() self.socket = None def _ping(self) -> bool: """Test if the device is listening.""" assert self.socket is not None resp = None try: self.socket.sendall(b"PINGPING") resp = self.socket.recv(8) except Exception: pass return resp == b"PONGPONG" def write_chunk(self, chunk: bytes) -> None: assert self.socket is not None if len(chunk) != 64: raise TransportException("Unexpected data length") LOG.log(DUMP_PACKETS, f"sending packet: {chunk.hex()}") self.socket.sendall(chunk) def read_chunk(self) -> bytes: assert self.socket is not None while True: try: chunk = self.socket.recv(64) break except socket.timeout: continue LOG.log(DUMP_PACKETS, f"received packet: {chunk.hex()}") if len(chunk) != 64: raise TransportException(f"Unexpected chunk size: {len(chunk)}") return bytearray(chunk) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/transport/webusb.py0000664000175000017500000001307314636513242022045 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import atexit import logging import sys import time from typing import Iterable, List, Optional from ..log import DUMP_PACKETS from ..models import TREZORS, TrezorModel from . import UDEV_RULES_STR, DeviceIsBusy, TransportException from .protocol import ProtocolBasedTransport, ProtocolV1 LOG = logging.getLogger(__name__) try: import usb1 USB_IMPORTED = True except Exception as e: LOG.warning(f"WebUSB transport is disabled: {e}") USB_IMPORTED = False INTERFACE = 0 ENDPOINT = 1 DEBUG_INTERFACE = 1 DEBUG_ENDPOINT = 2 class WebUsbHandle: def __init__(self, device: "usb1.USBDevice", debug: bool = False) -> None: self.device = device self.interface = DEBUG_INTERFACE if debug else INTERFACE self.endpoint = DEBUG_ENDPOINT if debug else ENDPOINT self.count = 0 self.handle: Optional["usb1.USBDeviceHandle"] = None def open(self) -> None: self.handle = self.device.open() if self.handle is None: if sys.platform.startswith("linux"): args = (UDEV_RULES_STR,) else: args = () raise IOError("Cannot open device", *args) try: self.handle.claimInterface(self.interface) except usb1.USBErrorAccess as e: raise DeviceIsBusy(self.device) from e def close(self) -> None: if self.handle is not None: self.handle.releaseInterface(self.interface) self.handle.close() self.handle = None def write_chunk(self, chunk: bytes) -> None: assert self.handle is not None if len(chunk) != 64: raise TransportException(f"Unexpected chunk size: {len(chunk)}") LOG.log(DUMP_PACKETS, f"writing packet: {chunk.hex()}") self.handle.interruptWrite(self.endpoint, chunk) def read_chunk(self) -> bytes: assert self.handle is not None endpoint = 0x80 | self.endpoint while True: chunk = self.handle.interruptRead(endpoint, 64) if chunk: break else: time.sleep(0.001) LOG.log(DUMP_PACKETS, f"read packet: {chunk.hex()}") if len(chunk) != 64: raise TransportException(f"Unexpected chunk size: {len(chunk)}") return chunk class WebUsbTransport(ProtocolBasedTransport): """ WebUsbTransport implements transport over WebUSB interface. """ PATH_PREFIX = "webusb" ENABLED = USB_IMPORTED context = None def __init__( self, device: "usb1.USBDevice", handle: Optional[WebUsbHandle] = None, debug: bool = False, ) -> None: if handle is None: handle = WebUsbHandle(device, debug) self.device = device self.handle = handle self.debug = debug super().__init__(protocol=ProtocolV1(handle)) def get_path(self) -> str: return f"{self.PATH_PREFIX}:{dev_to_str(self.device)}" @classmethod def enumerate( cls, models: Optional[Iterable["TrezorModel"]] = None, usb_reset: bool = False ) -> Iterable["WebUsbTransport"]: if cls.context is None: cls.context = usb1.USBContext() cls.context.open() atexit.register(cls.context.close) if models is None: models = TREZORS usb_ids = [id for model in models for id in model.usb_ids] devices: List["WebUsbTransport"] = [] for dev in cls.context.getDeviceIterator(skip_on_error=True): usb_id = (dev.getVendorID(), dev.getProductID()) if usb_id not in usb_ids: continue if not is_vendor_class(dev): continue try: # workaround for issue #223: # on certain combinations of Windows USB drivers and libusb versions, # Trezor is returned twice (possibly because Windows know it as both # a HID and a WebUSB device), and one of the returned devices is # non-functional. dev.getProduct() devices.append(WebUsbTransport(dev)) except usb1.USBErrorNotSupported: pass except usb1.USBErrorPipe: if usb_reset: handle = dev.open() handle.resetDevice() handle.close() return devices def find_debug(self) -> "WebUsbTransport": # For v1 protocol, find debug USB interface for the same serial number return WebUsbTransport(self.device, debug=True) def is_vendor_class(dev: "usb1.USBDevice") -> bool: configurationId = 0 altSettingId = 0 return ( dev[configurationId][INTERFACE][altSettingId].getClass() == usb1.libusb1.LIBUSB_CLASS_VENDOR_SPEC ) def dev_to_str(dev: "usb1.USBDevice") -> str: return ":".join( str(x) for x in ["%03i" % (dev.getBusNumber(),)] + dev.getPortNumberList() ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/src/trezorlib/ui.py0000664000175000017500000002231014636513242017131 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import os import sys from typing import Any, Callable, Optional, Union import click from mnemonic import Mnemonic from typing_extensions import Protocol from . import device, messages from .client import MAX_PIN_LENGTH, PASSPHRASE_ON_DEVICE from .exceptions import Cancelled from .messages import PinMatrixRequestType, WordRequestType PIN_MATRIX_DESCRIPTION = """ Use the numeric keypad or lowercase letters to describe number positions. The layout is: 7 8 9 e r t 4 5 6 -or- d f g 1 2 3 c v b """.strip() RECOVERY_MATRIX_DESCRIPTION = """ Use the numeric keypad to describe positions. For the word list use only left and right keys. Use backspace to correct an entry. The keypad layout is: 7 8 9 7 | 9 4 5 6 4 | 6 1 2 3 1 | 3 """.strip() PIN_GENERIC = None PIN_CURRENT = PinMatrixRequestType.Current PIN_NEW = PinMatrixRequestType.NewFirst PIN_CONFIRM = PinMatrixRequestType.NewSecond WIPE_CODE_NEW = PinMatrixRequestType.WipeCodeFirst WIPE_CODE_CONFIRM = PinMatrixRequestType.WipeCodeSecond # Workaround for limitation of Git Bash # getpass function does not work correctly on Windows when not using a real terminal # (the hidden input is not allowed and it also freezes the script completely) # Details: https://bugs.python.org/issue44762 CAN_HANDLE_HIDDEN_INPUT = sys.stdin and sys.stdin.isatty() class TrezorClientUI(Protocol): def button_request(self, br: messages.ButtonRequest) -> None: ... def get_pin(self, code: Optional[PinMatrixRequestType]) -> str: ... def get_passphrase(self, available_on_device: bool) -> Union[str, object]: ... def echo(*args: Any, **kwargs: Any) -> None: return click.echo(*args, err=True, **kwargs) def prompt(text: str, *, hide_input: bool = False, **kwargs: Any) -> Any: # Disallowing hidden input and warning user when it would cause issues if not CAN_HANDLE_HIDDEN_INPUT and hide_input: hide_input = False text += " (WARNING: will be displayed!)" return click.prompt(text, hide_input=hide_input, err=True, **kwargs) class ClickUI: def __init__( self, always_prompt: bool = False, passphrase_on_host: bool = False ) -> None: self.pinmatrix_shown = False self.last_prompt_shown = "" self.always_prompt = always_prompt self.passphrase_on_host = passphrase_on_host def _prompt_for_button(self, br: messages.ButtonRequest) -> str: if br.code == messages.ButtonRequestType.PassphraseEntry: return "Please enter passphrase on your Trezor device." if br.code == messages.ButtonRequestType.PinEntry: return "Please enter PIN on your Trezor device." return "Please confirm action on your Trezor device." def button_request(self, br: messages.ButtonRequest) -> None: prompt = self._prompt_for_button(br) if prompt != self.last_prompt_shown: echo(prompt) if not self.always_prompt: self.last_prompt_shown = prompt def get_pin(self, code: Optional[PinMatrixRequestType] = None) -> str: if code == PIN_CURRENT: desc = "current PIN" elif code == PIN_NEW: desc = "new PIN" elif code == PIN_CONFIRM: desc = "new PIN again" elif code == WIPE_CODE_NEW: desc = "new wipe code" elif code == WIPE_CODE_CONFIRM: desc = "new wipe code again" else: desc = "PIN" if not self.pinmatrix_shown: echo(PIN_MATRIX_DESCRIPTION) if not self.always_prompt: self.pinmatrix_shown = True while True: try: pin = prompt(f"Please enter {desc}", hide_input=True) except click.Abort: raise Cancelled from None # translate letters to numbers if letters were used if all(d in "cvbdfgert" for d in pin): pin = pin.translate(str.maketrans("cvbdfgert", "123456789")) if any(d not in "123456789" for d in pin): echo( "The value may only consist of digits 1 to 9 or letters cvbdfgert." ) elif len(pin) > MAX_PIN_LENGTH: echo(f"The value must be at most {MAX_PIN_LENGTH} digits in length.") else: return pin def get_passphrase(self, available_on_device: bool) -> Union[str, object]: if available_on_device and not self.passphrase_on_host: return PASSPHRASE_ON_DEVICE env_passphrase = os.getenv("PASSPHRASE") if env_passphrase is not None: echo("Passphrase required. Using PASSPHRASE environment variable.") return env_passphrase while True: try: passphrase = prompt( "Passphrase required", hide_input=True, default="", show_default=False, ) # In case user sees the input on the screen, we do not need confirmation if not CAN_HANDLE_HIDDEN_INPUT: return passphrase second = prompt( "Confirm your passphrase", hide_input=True, default="", show_default=False, ) if passphrase == second: return passphrase else: echo("Passphrase did not match. Please try again.") except click.Abort: raise Cancelled from None class ScriptUI: """Interface to be used by scripts, not directly by user. Communicates with a client application using print() and input(). Lot of `ClickUI` logic is outsourced to the client application, which is responsible for supplying the PIN and passphrase. Reference client implementation can be found under `tools/trezorctl_script_client.py`. """ @staticmethod def button_request(br: messages.ButtonRequest) -> None: # TODO: send name={br.name} when it will be supported code = br.code.name if br.code else None print(f"?BUTTON code={code} pages={br.pages}") @staticmethod def get_pin(code: Optional[PinMatrixRequestType] = None) -> str: if code is None: print("?PIN") else: print(f"?PIN code={code.name}") pin = input() if pin == "CANCEL": raise Cancelled from None elif not pin.startswith(":"): raise RuntimeError("Sent PIN must start with ':'") else: return pin[1:] @staticmethod def get_passphrase(available_on_device: bool) -> Union[str, object]: if available_on_device: print("?PASSPHRASE available_on_device") else: print("?PASSPHRASE") passphrase = input() if passphrase == "CANCEL": raise Cancelled from None elif passphrase == "ON_DEVICE": return PASSPHRASE_ON_DEVICE elif not passphrase.startswith(":"): raise RuntimeError("Sent passphrase must start with ':'") else: return passphrase[1:] def mnemonic_words( expand: bool = False, language: str = "english" ) -> Callable[[WordRequestType], str]: if expand: wordlist = Mnemonic(language).wordlist else: wordlist = [] def expand_word(word: str) -> str: if not expand: return word if word in wordlist: return word matches = [w for w in wordlist if w.startswith(word)] if len(matches) == 1: return matches[0] echo("Choose one of: " + ", ".join(matches)) raise KeyError(word) def get_word(type: WordRequestType) -> str: assert type == WordRequestType.Plain while True: try: word = prompt("Enter one word of mnemonic") return expand_word(word) except KeyError: pass except click.Abort: raise Cancelled from None return get_word def matrix_words(type: WordRequestType) -> str: while True: try: ch = click.getchar() except (KeyboardInterrupt, EOFError): raise Cancelled from None if ch in "\x04\x1b": # Ctrl+D, Esc raise Cancelled if ch in "\x08\x7f": # Backspace, Del return device.RECOVERY_BACK if type == WordRequestType.Matrix6 and ch in "147369": return ch if type == WordRequestType.Matrix9 and ch in "123456789": return ch ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1719315826.1201067 trezor-0.13.9/stubs/0000775000175000017500000000000014636526562014512 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1719315826.1341069 trezor-0.13.9/stubs/PIL/0000775000175000017500000000000014636526562015136 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/stubs/PIL/Image.pyi0000664000175000017500000015267514636513242016712 0ustar00matejcikmatejcik""" This type stub file was generated by pyright. """ import abc from collections.abc import Callable, MutableMapping from enum import IntEnum from typing import Any, IO, TYPE_CHECKING from PIL import ImageFile class Transpose(IntEnum): FLIP_LEFT_RIGHT = ... FLIP_TOP_BOTTOM = ... ROTATE_90 = ... ROTATE_180 = ... ROTATE_270 = ... TRANSPOSE = ... TRANSVERSE = ... class Transform(IntEnum): AFFINE = ... EXTENT = ... PERSPECTIVE = ... QUAD = ... MESH = ... class Resampling(IntEnum): NEAREST = ... BOX = ... BILINEAR = ... HAMMING = ... BICUBIC = ... LANCZOS = ... _filters_support = ... class Dither(IntEnum): NONE = ... ORDERED = ... RASTERIZE = ... FLOYDSTEINBERG = ... class Palette(IntEnum): WEB = ... ADAPTIVE = ... class Quantize(IntEnum): MEDIANCUT = ... MAXCOVERAGE = ... FASTOCTREE = ... LIBIMAGEQUANT = ... class Image: """ This class represents an image object. To create :py:class:`~PIL.Image.Image` objects, use the appropriate factory functions. There's hardly ever any reason to call the Image constructor directly. * :py:func:`~PIL.Image.open` * :py:func:`~PIL.Image.new` * :py:func:`~PIL.Image.frombytes` """ format: str | None = ... format_description: str | None = ... _close_exclusive_fp_after_loading = ... def __init__(self) -> None: ... @property def width(self) -> int: ... @property def height(self) -> int: ... @property def size(self) -> tuple[int, int]: ... @property def mode(self): # -> str | Any: ... def __enter__(self): # -> Self: ... def __exit__(self, *args): # -> None: ... def close(self) -> None: """ Closes the file pointer, if possible. This operation will destroy the image core and release its memory. The image data will be unusable afterward. This function is required to close images that have multiple frames or have not had their file read and closed by the :py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for more information. """ ... def __eq__(self, other) -> bool: ... def __repr__(self) -> str: ... @property def __array_interface__(self): # -> dict[str, int]: ... def __getstate__(self): # -> list[Any]: ... def __setstate__(self, state) -> None: ... def tobytes(self, encoder_name: str = ..., *args) -> bytes: """ Return image as a bytes object. .. warning:: This method returns the raw image data from the internal storage. For compressed image data (e.g. PNG, JPEG) use :meth:`~.save`, with a BytesIO parameter for in-memory data. :param encoder_name: What encoder to use. The default is to use the standard "raw" encoder. A list of C encoders can be seen under codecs section of the function array in :file:`_imaging.c`. Python encoders are registered within the relevant plugins. :param args: Extra arguments to the encoder. :returns: A :py:class:`bytes` object. """ ... def tobitmap(self, name: str = ...) -> bytes: """ Returns the image converted to an X11 bitmap. .. note:: This method only works for mode "1" images. :param name: The name prefix to use for the bitmap variables. :returns: A string containing an X11 bitmap. :raises ValueError: If the mode is not "1" """ ... def frombytes(self, data: bytes, decoder_name: str = ..., *args) -> None: """ Loads this image with pixel data from a bytes object. This method is similar to the :py:func:`~PIL.Image.frombytes` function, but loads data into this image instead of creating a new image object. """ ... def load(self): # -> Any | None: """ Allocates storage for the image and loads the pixel data. In normal cases, you don't need to call this method, since the Image class automatically loads an opened image when it is accessed for the first time. If the file associated with the image was opened by Pillow, then this method will close it. The exception to this is if the image has multiple frames, in which case the file will be left open for seek operations. See :ref:`file-handling` for more information. :returns: An image access object. :rtype: :ref:`PixelAccess` or :py:class:`PIL.PyAccess` """ ... def verify(self): # -> None: """ Verifies the contents of a file. For data read from a file, this method attempts to determine if the file is broken, without actually decoding the image data. If this method finds any problems, it raises suitable exceptions. If you need to load the image after using this method, you must reopen the image file. """ ... def convert( self, mode: str | None = ..., matrix: tuple[float, ...] | None = ..., dither: Dither | None = ..., palette: Palette = ..., colors: int = ..., ) -> Image: """ Returns a converted copy of this image. For the "P" mode, this method translates pixels through the palette. If mode is omitted, a mode is chosen so that all information in the image and the palette can be represented without a palette. This supports all possible conversions between "L", "RGB" and "CMYK". The ``matrix`` argument only supports "L" and "RGB". When translating a color image to grayscale (mode "L"), the library uses the ITU-R 601-2 luma transform:: L = R * 299/1000 + G * 587/1000 + B * 114/1000 The default method of converting a grayscale ("L") or "RGB" image into a bilevel (mode "1") image uses Floyd-Steinberg dither to approximate the original image luminosity levels. If dither is ``None``, all values larger than 127 are set to 255 (white), all other values to 0 (black). To use other thresholds, use the :py:meth:`~PIL.Image.Image.point` method. When converting from "RGBA" to "P" without a ``matrix`` argument, this passes the operation to :py:meth:`~PIL.Image.Image.quantize`, and ``dither`` and ``palette`` are ignored. When converting from "PA", if an "RGBA" palette is present, the alpha channel from the image will be used instead of the values from the palette. :param mode: The requested mode. See: :ref:`concept-modes`. :param matrix: An optional conversion matrix. If given, this should be 4- or 12-tuple containing floating point values. :param dither: Dithering method, used when converting from mode "RGB" to "P" or from "RGB" or "L" to "1". Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG` (default). Note that this is not used when ``matrix`` is supplied. :param palette: Palette to use when converting from mode "RGB" to "P". Available palettes are :data:`Palette.WEB` or :data:`Palette.ADAPTIVE`. :param colors: Number of colors to use for the :data:`Palette.ADAPTIVE` palette. Defaults to 256. :rtype: :py:class:`~PIL.Image.Image` :returns: An :py:class:`~PIL.Image.Image` object. """ ... def quantize( self, colors: int = ..., method: Quantize | None = ..., kmeans: int = ..., palette=..., dither: Dither = ..., ) -> Image: """ Convert the image to 'P' mode with the specified number of colors. :param colors: The desired number of colors, <= 256 :param method: :data:`Quantize.MEDIANCUT` (median cut), :data:`Quantize.MAXCOVERAGE` (maximum coverage), :data:`Quantize.FASTOCTREE` (fast octree), :data:`Quantize.LIBIMAGEQUANT` (libimagequant; check support using :py:func:`PIL.features.check_feature` with ``feature="libimagequant"``). By default, :data:`Quantize.MEDIANCUT` will be used. The exception to this is RGBA images. :data:`Quantize.MEDIANCUT` and :data:`Quantize.MAXCOVERAGE` do not support RGBA images, so :data:`Quantize.FASTOCTREE` is used by default instead. :param kmeans: Integer greater than or equal to zero. :param palette: Quantize to the palette of given :py:class:`PIL.Image.Image`. :param dither: Dithering method, used when converting from mode "RGB" to "P" or from "RGB" or "L" to "1". Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG` (default). :returns: A new image """ ... def copy(self) -> Image: """ Copies this image. Use this method if you wish to paste things into an image, but still retain the original. :rtype: :py:class:`~PIL.Image.Image` :returns: An :py:class:`~PIL.Image.Image` object. """ ... __copy__ = ... def crop(self, box: tuple[int, int, int, int] | None = ...) -> Image: """ Returns a rectangular region from this image. The box is a 4-tuple defining the left, upper, right, and lower pixel coordinate. See :ref:`coordinate-system`. Note: Prior to Pillow 3.4.0, this was a lazy operation. :param box: The crop rectangle, as a (left, upper, right, lower)-tuple. :rtype: :py:class:`~PIL.Image.Image` :returns: An :py:class:`~PIL.Image.Image` object. """ ... def draft(self, mode, size): # -> None: """ Configures the image file loader so it returns a version of the image that as closely as possible matches the given mode and size. For example, you can use this method to convert a color JPEG to grayscale while loading it. If any changes are made, returns a tuple with the chosen ``mode`` and ``box`` with coordinates of the original image within the altered one. Note that this method modifies the :py:class:`~PIL.Image.Image` object in place. If the image has already been loaded, this method has no effect. Note: This method is not implemented for most images. It is currently implemented only for JPEG and MPO images. :param mode: The requested mode. :param size: The requested size in pixels, as a 2-tuple: (width, height). """ ... def filter(self, filter): # -> Image: """ Filters this image using the given filter. For a list of available filters, see the :py:mod:`~PIL.ImageFilter` module. :param filter: Filter kernel. :returns: An :py:class:`~PIL.Image.Image` object.""" ... def getbands(self) -> tuple[str, ...]: """ Returns a tuple containing the name of each band in this image. For example, ``getbands`` on an RGB image returns ("R", "G", "B"). :returns: A tuple containing band names. :rtype: tuple """ ... def getbbox(self, *, alpha_only: bool = ...) -> tuple[int, int, int, int]: """ Calculates the bounding box of the non-zero regions in the image. :param alpha_only: Optional flag, defaulting to ``True``. If ``True`` and the image has an alpha channel, trim transparent pixels. Otherwise, trim pixels when all channels are zero. Keyword-only argument. :returns: The bounding box is returned as a 4-tuple defining the left, upper, right, and lower pixel coordinate. See :ref:`coordinate-system`. If the image is completely empty, this method returns None. """ ... def getcolors(self, maxcolors: int = ...): # -> list[tuple[Any, int]] | Any | None: """ Returns a list of colors used in this image. The colors will be in the image's mode. For example, an RGB image will return a tuple of (red, green, blue) color values, and a P image will return the index of the color in the palette. :param maxcolors: Maximum number of colors. If this number is exceeded, this method returns None. The default limit is 256 colors. :returns: An unsorted list of (count, pixel) values. """ ... def getdata(self, band: int | None = ...): # -> Any | DeferredError | None: """ Returns the contents of this image as a sequence object containing pixel values. The sequence object is flattened, so that values for line one follow directly after the values of line zero, and so on. Note that the sequence object returned by this method is an internal PIL data type, which only supports certain sequence operations. To convert it to an ordinary sequence (e.g. for printing), use ``list(im.getdata())``. :param band: What band to return. The default is to return all bands. To return a single band, pass in the index value (e.g. 0 to get the "R" band from an "RGB" image). :returns: A sequence-like object. """ ... def getextrema(self) -> tuple[float, float] | tuple[tuple[int, int], ...]: """ Gets the minimum and maximum pixel values for each band in the image. :returns: For a single-band image, a 2-tuple containing the minimum and maximum pixel value. For a multi-band image, a tuple containing one 2-tuple for each band. """ ... def getexif(self) -> Exif: """ Gets EXIF data from the image. :returns: an :py:class:`~PIL.Image.Exif` object. """ ... def get_child_images(self): # -> list[Any]: ... def getim(self): # -> Any: """ Returns a capsule that points to the internal image memory. :returns: A capsule object. """ ... def getpalette(self, rawmode: str | None = ...) -> list[int] | None: """ Returns the image palette as a list. :param rawmode: The mode in which to return the palette. ``None`` will return the palette in its current mode. .. versionadded:: 9.1.0 :returns: A list of color values [r, g, b, ...], or None if the image has no palette. """ ... @property def has_transparency_data(self) -> bool: """ Determine if an image has transparency data, whether in the form of an alpha channel, a palette with an alpha channel, or a "transparency" key in the info dictionary. Note the image might still appear solid, if all of the values shown within are opaque. :returns: A boolean. """ ... def apply_transparency(self): # -> None: """ If a P mode image has a "transparency" key in the info dictionary, remove the key and instead apply the transparency to the palette. Otherwise, the image is unchanged. """ ... def getpixel(self, xy): # -> Any: """ Returns the pixel value at a given position. :param xy: The coordinate, given as (x, y). See :ref:`coordinate-system`. :returns: The pixel value. If the image is a multi-layer image, this method returns a tuple. """ ... def getprojection(self) -> tuple[list[int], list[int]]: """ Get projection to x and y axes :returns: Two sequences, indicating where there are non-zero pixels along the X-axis and the Y-axis, respectively. """ ... def histogram(self, mask: Image | None = ..., extrema=...) -> list[int]: """ Returns a histogram for the image. The histogram is returned as a list of pixel counts, one for each pixel value in the source image. Counts are grouped into 256 bins for each band, even if the image has more than 8 bits per band. If the image has more than one band, the histograms for all bands are concatenated (for example, the histogram for an "RGB" image contains 768 values). A bilevel image (mode "1") is treated as a grayscale ("L") image by this method. If a mask is provided, the method returns a histogram for those parts of the image where the mask image is non-zero. The mask image must have the same size as the image, and be either a bi-level image (mode "1") or a grayscale image ("L"). :param mask: An optional mask. :param extrema: An optional tuple of manually-specified extrema. :returns: A list containing pixel counts. """ ... def entropy(self, mask=..., extrema=...): # -> Any: """ Calculates and returns the entropy for the image. A bilevel image (mode "1") is treated as a grayscale ("L") image by this method. If a mask is provided, the method employs the histogram for those parts of the image where the mask image is non-zero. The mask image must have the same size as the image, and be either a bi-level image (mode "1") or a grayscale image ("L"). :param mask: An optional mask. :param extrema: An optional tuple of manually-specified extrema. :returns: A float value representing the image entropy """ ... def paste(self, im, box=..., mask=...) -> None: """ Pastes another image into this image. The box argument is either a 2-tuple giving the upper left corner, a 4-tuple defining the left, upper, right, and lower pixel coordinate, or None (same as (0, 0)). See :ref:`coordinate-system`. If a 4-tuple is given, the size of the pasted image must match the size of the region. If the modes don't match, the pasted image is converted to the mode of this image (see the :py:meth:`~PIL.Image.Image.convert` method for details). Instead of an image, the source can be a integer or tuple containing pixel values. The method then fills the region with the given color. When creating RGB images, you can also use color strings as supported by the ImageColor module. If a mask is given, this method updates only the regions indicated by the mask. You can use either "1", "L", "LA", "RGBA" or "RGBa" images (if present, the alpha band is used as mask). Where the mask is 255, the given image is copied as is. Where the mask is 0, the current value is preserved. Intermediate values will mix the two images together, including their alpha channels if they have them. See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to combine images with respect to their alpha channels. :param im: Source image or pixel value (integer or tuple). :param box: An optional 4-tuple giving the region to paste into. If a 2-tuple is used instead, it's treated as the upper left corner. If omitted or None, the source is pasted into the upper left corner. If an image is given as the second argument and there is no third, the box defaults to (0, 0), and the second argument is interpreted as a mask image. :param mask: An optional mask image. """ ... def alpha_composite(self, im, dest=..., source=...): # -> None: """'In-place' analog of Image.alpha_composite. Composites an image onto this image. :param im: image to composite over this one :param dest: Optional 2 tuple (left, top) specifying the upper left corner in this (destination) image. :param source: Optional 2 (left, top) tuple for the upper left corner in the overlay source image, or 4 tuple (left, top, right, bottom) for the bounds of the source rectangle Performance Note: Not currently implemented in-place in the core layer. """ ... def point(self, lut, mode: str | None = ...) -> Image: """ Maps this image through a lookup table or function. :param lut: A lookup table, containing 256 (or 65536 if self.mode=="I" and mode == "L") values per band in the image. A function can be used instead, it should take a single argument. The function is called once for each possible pixel value, and the resulting table is applied to all bands of the image. It may also be an :py:class:`~PIL.Image.ImagePointHandler` object:: class Example(Image.ImagePointHandler): def point(self, data): # Return result :param mode: Output mode (default is same as input). This can only be used if the source image has mode "L" or "P", and the output has mode "1" or the source image mode is "I" and the output mode is "L". :returns: An :py:class:`~PIL.Image.Image` object. """ ... def putalpha(self, alpha): # -> None: """ Adds or replaces the alpha layer in this image. If the image does not have an alpha layer, it's converted to "LA" or "RGBA". The new layer must be either "L" or "1". :param alpha: The new alpha layer. This can either be an "L" or "1" image having the same size as this image, or an integer or other color value. """ ... def putdata(self, data, scale=..., offset=...): # -> None: """ Copies pixel data from a flattened sequence object into the image. The values should start at the upper left corner (0, 0), continue to the end of the line, followed directly by the first value of the second line, and so on. Data will be read until either the image or the sequence ends. The scale and offset values are used to adjust the sequence values: **pixel = value*scale + offset**. :param data: A flattened sequence object. :param scale: An optional scale value. The default is 1.0. :param offset: An optional offset value. The default is 0.0. """ ... def putpalette(self, data, rawmode=...) -> None: """ Attaches a palette to this image. The image must be a "P", "PA", "L" or "LA" image. The palette sequence must contain at most 256 colors, made up of one integer value for each channel in the raw mode. For example, if the raw mode is "RGB", then it can contain at most 768 values, made up of red, green and blue values for the corresponding pixel index in the 256 colors. If the raw mode is "RGBA", then it can contain at most 1024 values, containing red, green, blue and alpha values. Alternatively, an 8-bit string may be used instead of an integer sequence. :param data: A palette sequence (either a list or a string). :param rawmode: The raw mode of the palette. Either "RGB", "RGBA", or a mode that can be transformed to "RGB" or "RGBA" (e.g. "R", "BGR;15", "RGBA;L"). """ ... def putpixel(self, xy, value): # -> Any: """ Modifies the pixel at the given position. The color is given as a single numerical value for single-band images, and a tuple for multi-band images. In addition to this, RGB and RGBA tuples are accepted for P and PA images. Note that this method is relatively slow. For more extensive changes, use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw` module instead. See: * :py:meth:`~PIL.Image.Image.paste` * :py:meth:`~PIL.Image.Image.putdata` * :py:mod:`~PIL.ImageDraw` :param xy: The pixel coordinate, given as (x, y). See :ref:`coordinate-system`. :param value: The pixel value. """ ... def remap_palette(self, dest_map, source_palette=...): # -> Image: """ Rewrites the image to reorder the palette. :param dest_map: A list of indexes into the original palette. e.g. ``[1,0]`` would swap a two item palette, and ``list(range(256))`` is the identity transform. :param source_palette: Bytes or None. :returns: An :py:class:`~PIL.Image.Image` object. """ ... def resize(self, size, resample=..., box=..., reducing_gap=...) -> Image: """ Returns a resized copy of this image. :param size: The requested size in pixels, as a 2-tuple: (width, height). :param resample: An optional resampling filter. This can be one of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`, :py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`, :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`. If the image has mode "1" or "P", it is always set to :py:data:`Resampling.NEAREST`. If the image mode specifies a number of bits, such as "I;16", then the default filter is :py:data:`Resampling.NEAREST`. Otherwise, the default filter is :py:data:`Resampling.BICUBIC`. See: :ref:`concept-filters`. :param box: An optional 4-tuple of floats providing the source image region to be scaled. The values must be within (0, 0, width, height) rectangle. If omitted or None, the entire source is used. :param reducing_gap: Apply optimization by resizing the image in two steps. First, reducing the image by integer times using :py:meth:`~PIL.Image.Image.reduce`. Second, resizing using regular resampling. The last step changes size no less than by ``reducing_gap`` times. ``reducing_gap`` may be None (no first step is performed) or should be greater than 1.0. The bigger ``reducing_gap``, the closer the result to the fair resampling. The smaller ``reducing_gap``, the faster resizing. With ``reducing_gap`` greater or equal to 3.0, the result is indistinguishable from fair resampling in most cases. The default value is None (no optimization). :returns: An :py:class:`~PIL.Image.Image` object. """ ... def reduce(self, factor, box=...): # -> Image: """ Returns a copy of the image reduced ``factor`` times. If the size of the image is not dividable by ``factor``, the resulting size will be rounded up. :param factor: A greater than 0 integer or tuple of two integers for width and height separately. :param box: An optional 4-tuple of ints providing the source image region to be reduced. The values must be within ``(0, 0, width, height)`` rectangle. If omitted or ``None``, the entire source is used. """ ... def rotate( self, angle, resample=..., expand=..., center=..., translate=..., fillcolor=... ): # -> Image: """ Returns a rotated copy of this image. This method returns a copy of this image, rotated the given number of degrees counter clockwise around its centre. :param angle: In degrees counter clockwise. :param resample: An optional resampling filter. This can be one of :py:data:`Resampling.NEAREST` (use nearest neighbour), :py:data:`Resampling.BILINEAR` (linear interpolation in a 2x2 environment), or :py:data:`Resampling.BICUBIC` (cubic spline interpolation in a 4x4 environment). If omitted, or if the image has mode "1" or "P", it is set to :py:data:`Resampling.NEAREST`. See :ref:`concept-filters`. :param expand: Optional expansion flag. If true, expands the output image to make it large enough to hold the entire rotated image. If false or omitted, make the output image the same size as the input image. Note that the expand flag assumes rotation around the center and no translation. :param center: Optional center of rotation (a 2-tuple). Origin is the upper left corner. Default is the center of the image. :param translate: An optional post-rotate translation (a 2-tuple). :param fillcolor: An optional color for area outside the rotated image. :returns: An :py:class:`~PIL.Image.Image` object. """ ... def save(self, fp, format=..., **params) -> None: """ Saves this image under the given filename. If no format is specified, the format to use is determined from the filename extension, if possible. Keyword options can be used to provide additional instructions to the writer. If a writer doesn't recognise an option, it is silently ignored. The available options are described in the :doc:`image format documentation <../handbook/image-file-formats>` for each writer. You can use a file object instead of a filename. In this case, you must always specify the format. The file object must implement the ``seek``, ``tell``, and ``write`` methods, and be opened in binary mode. :param fp: A filename (string), os.PathLike object or file object. :param format: Optional format override. If omitted, the format to use is determined from the filename extension. If a file object was used instead of a filename, this parameter should always be used. :param params: Extra parameters to the image writer. :returns: None :exception ValueError: If the output format could not be determined from the file name. Use the format option to solve this. :exception OSError: If the file could not be written. The file may have been created, and may contain partial data. """ ... def seek(self, frame: int) -> None: """ Seeks to the given frame in this sequence file. If you seek beyond the end of the sequence, the method raises an ``EOFError`` exception. When a sequence file is opened, the library automatically seeks to frame 0. See :py:meth:`~PIL.Image.Image.tell`. If defined, :attr:`~PIL.Image.Image.n_frames` refers to the number of available frames. :param frame: Frame number, starting at 0. :exception EOFError: If the call attempts to seek beyond the end of the sequence. """ ... def show(self, title: str | None = ...) -> None: """ Displays this image. This method is mainly intended for debugging purposes. This method calls :py:func:`PIL.ImageShow.show` internally. You can use :py:func:`PIL.ImageShow.register` to override its default behaviour. The image is first saved to a temporary file. By default, it will be in PNG format. On Unix, the image is then opened using the **xdg-open**, **display**, **gm**, **eog** or **xv** utility, depending on which one can be found. On macOS, the image is opened with the native Preview application. On Windows, the image is opened with the standard PNG display utility. :param title: Optional title to use for the image window, where possible. """ ... def split(self) -> tuple[Image, ...]: """ Split this image into individual bands. This method returns a tuple of individual image bands from an image. For example, splitting an "RGB" image creates three new images each containing a copy of one of the original bands (red, green, blue). If you need only one band, :py:meth:`~PIL.Image.Image.getchannel` method can be more convenient and faster. :returns: A tuple containing bands. """ ... def getchannel(self, channel: int | str) -> Image: """ Returns an image containing a single channel of the source image. :param channel: What channel to return. Could be index (0 for "R" channel of "RGB") or channel name ("A" for alpha channel of "RGBA"). :returns: An image in "L" mode. .. versionadded:: 4.3.0 """ ... def tell(self) -> int: """ Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`. If defined, :attr:`~PIL.Image.Image.n_frames` refers to the number of available frames. :returns: Frame number, starting with 0. """ ... def thumbnail(self, size, resample=..., reducing_gap=...): # -> None: """ Make this image into a thumbnail. This method modifies the image to contain a thumbnail version of itself, no larger than the given size. This method calculates an appropriate thumbnail size to preserve the aspect of the image, calls the :py:meth:`~PIL.Image.Image.draft` method to configure the file reader (where applicable), and finally resizes the image. Note that this function modifies the :py:class:`~PIL.Image.Image` object in place. If you need to use the full resolution image as well, apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original image. :param size: The requested size in pixels, as a 2-tuple: (width, height). :param resample: Optional resampling filter. This can be one of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`, :py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`, :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`. If omitted, it defaults to :py:data:`Resampling.BICUBIC`. (was :py:data:`Resampling.NEAREST` prior to version 2.5.0). See: :ref:`concept-filters`. :param reducing_gap: Apply optimization by resizing the image in two steps. First, reducing the image by integer times using :py:meth:`~PIL.Image.Image.reduce` or :py:meth:`~PIL.Image.Image.draft` for JPEG images. Second, resizing using regular resampling. The last step changes size no less than by ``reducing_gap`` times. ``reducing_gap`` may be None (no first step is performed) or should be greater than 1.0. The bigger ``reducing_gap``, the closer the result to the fair resampling. The smaller ``reducing_gap``, the faster resizing. With ``reducing_gap`` greater or equal to 3.0, the result is indistinguishable from fair resampling in most cases. The default value is 2.0 (very close to fair resampling while still being faster in many cases). :returns: None """ ... def transform( self, size, method, data=..., resample=..., fill=..., fillcolor=... ) -> Image: """ Transforms this image. This method creates a new image with the given size, and the same mode as the original, and copies data to the new image using the given transform. :param size: The output size in pixels, as a 2-tuple: (width, height). :param method: The transformation method. This is one of :py:data:`Transform.EXTENT` (cut out a rectangular subregion), :py:data:`Transform.AFFINE` (affine transform), :py:data:`Transform.PERSPECTIVE` (perspective transform), :py:data:`Transform.QUAD` (map a quadrilateral to a rectangle), or :py:data:`Transform.MESH` (map a number of source quadrilaterals in one operation). It may also be an :py:class:`~PIL.Image.ImageTransformHandler` object:: class Example(Image.ImageTransformHandler): def transform(self, size, data, resample, fill=1): # Return result Implementations of :py:class:`~PIL.Image.ImageTransformHandler` for some of the :py:class:`Transform` methods are provided in :py:mod:`~PIL.ImageTransform`. It may also be an object with a ``method.getdata`` method that returns a tuple supplying new ``method`` and ``data`` values:: class Example: def getdata(self): method = Image.Transform.EXTENT data = (0, 0, 100, 100) return method, data :param data: Extra data to the transformation method. :param resample: Optional resampling filter. It can be one of :py:data:`Resampling.NEAREST` (use nearest neighbour), :py:data:`Resampling.BILINEAR` (linear interpolation in a 2x2 environment), or :py:data:`Resampling.BICUBIC` (cubic spline interpolation in a 4x4 environment). If omitted, or if the image has mode "1" or "P", it is set to :py:data:`Resampling.NEAREST`. See: :ref:`concept-filters`. :param fill: If ``method`` is an :py:class:`~PIL.Image.ImageTransformHandler` object, this is one of the arguments passed to it. Otherwise, it is unused. :param fillcolor: Optional fill color for the area outside the transform in the output image. :returns: An :py:class:`~PIL.Image.Image` object. """ ... def transpose(self, method: Transpose) -> Image: """ Transpose image (flip or rotate in 90 degree steps) :param method: One of :py:data:`Transpose.FLIP_LEFT_RIGHT`, :py:data:`Transpose.FLIP_TOP_BOTTOM`, :py:data:`Transpose.ROTATE_90`, :py:data:`Transpose.ROTATE_180`, :py:data:`Transpose.ROTATE_270`, :py:data:`Transpose.TRANSPOSE` or :py:data:`Transpose.TRANSVERSE`. :returns: Returns a flipped or rotated copy of this image. """ ... def effect_spread(self, distance): # -> Image: """ Randomly spread pixels in an image. :param distance: Distance to spread pixels. """ ... def toqimage(self): # -> ImageQt: """Returns a QImage copy of this image""" ... def toqpixmap(self): """Returns a QPixmap copy of this image""" ... class ImagePointHandler: """ Used as a mixin by point transforms (for use with :py:meth:`~PIL.Image.Image.point`) """ @abc.abstractmethod def point(self, im: Image) -> Image: ... class ImageTransformHandler: """ Used as a mixin by geometry transforms (for use with :py:meth:`~PIL.Image.Image.transform`) """ @abc.abstractmethod def transform( self, size: tuple[int, int], image: Image, **options: dict[str, str | int | tuple[int, ...] | list[int]] ) -> Image: ... def new( mode: str, size: tuple[int, int], color: float | tuple[float, ...] | str | None = ..., ) -> Image: """ Creates a new image with the given mode and size. :param mode: The mode to use for the new image. See: :ref:`concept-modes`. :param size: A 2-tuple, containing (width, height) in pixels. :param color: What color to use for the image. Default is black. If given, this should be a single integer or floating point value for single-band modes, and a tuple for multi-band modes (one value per band). When creating RGB or HSV images, you can also use color strings as supported by the ImageColor module. If the color is None, the image is not initialised. :returns: An :py:class:`~PIL.Image.Image` object. """ ... def frombytes(mode, size, data, decoder_name=..., *args) -> Image: """ Creates a copy of an image memory from pixel data in a buffer. In its simplest form, this function takes three arguments (mode, size, and unpacked pixel data). You can also use any pixel decoder supported by PIL. For more information on available decoders, see the section :ref:`Writing Your Own File Codec `. Note that this function decodes pixel data only, not entire images. If you have an entire image in a string, wrap it in a :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load it. :param mode: The image mode. See: :ref:`concept-modes`. :param size: The image size. :param data: A byte buffer containing raw data for the given mode. :param decoder_name: What decoder to use. :param args: Additional parameters for the given decoder. :returns: An :py:class:`~PIL.Image.Image` object. """ ... def frombuffer(mode, size, data, decoder_name=..., *args): # -> Image: """ Creates an image memory referencing pixel data in a byte buffer. This function is similar to :py:func:`~PIL.Image.frombytes`, but uses data in the byte buffer, where possible. This means that changes to the original buffer object are reflected in this image). Not all modes can share memory; supported modes include "L", "RGBX", "RGBA", and "CMYK". Note that this function decodes pixel data only, not entire images. If you have an entire image file in a string, wrap it in a :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load it. The default parameters used for the "raw" decoder differs from that used for :py:func:`~PIL.Image.frombytes`. This is a bug, and will probably be fixed in a future release. The current release issues a warning if you do this; to disable the warning, you should provide the full set of parameters. See below for details. :param mode: The image mode. See: :ref:`concept-modes`. :param size: The image size. :param data: A bytes or other buffer object containing raw data for the given mode. :param decoder_name: What decoder to use. :param args: Additional parameters for the given decoder. For the default encoder ("raw"), it's recommended that you provide the full set of parameters:: frombuffer(mode, size, data, "raw", mode, 0, 1) :returns: An :py:class:`~PIL.Image.Image` object. .. versionadded:: 1.1.4 """ ... def fromarray(obj, mode=...): # -> Image: """ Creates an image memory from an object exporting the array interface (using the buffer protocol):: from PIL import Image import numpy as np a = np.zeros((5, 5)) im = Image.fromarray(a) If ``obj`` is not contiguous, then the ``tobytes`` method is called and :py:func:`~PIL.Image.frombuffer` is used. In the case of NumPy, be aware that Pillow modes do not always correspond to NumPy dtypes. Pillow modes only offer 1-bit pixels, 8-bit pixels, 32-bit signed integer pixels, and 32-bit floating point pixels. Pillow images can also be converted to arrays:: from PIL import Image import numpy as np im = Image.open("hopper.jpg") a = np.asarray(im) When converting Pillow images to arrays however, only pixel values are transferred. This means that P and PA mode images will lose their palette. :param obj: Object with array interface :param mode: Optional mode to use when reading ``obj``. Will be determined from type if ``None``. This will not be used to convert the data after reading, but will be used to change how the data is read:: from PIL import Image import numpy as np a = np.full((1, 1), 300) im = Image.fromarray(a, mode="L") im.getpixel((0, 0)) # 44 im = Image.fromarray(a, mode="RGB") im.getpixel((0, 0)) # (44, 1, 0) See: :ref:`concept-modes` for general information about modes. :returns: An image object. .. versionadded:: 1.1.6 """ ... def fromqimage(im): # -> Image: """Creates an image instance from a QImage image""" ... def fromqpixmap(im): # -> Image: """Creates an image instance from a QPixmap image""" ... _fromarray_typemap = ... def open(fp, mode=..., formats=...) -> Image: """ Opens and identifies the given image file. This is a lazy operation; this function identifies the file, but the file remains open and the actual image data is not read from the file until you try to process the data (or call the :py:meth:`~PIL.Image.Image.load` method). See :py:func:`~PIL.Image.new`. See :ref:`file-handling`. :param fp: A filename (string), os.PathLike object or a file object. The file object must implement ``file.read``, ``file.seek``, and ``file.tell`` methods, and be opened in binary mode. The file object will also seek to zero before reading. :param mode: The mode. If given, this argument must be "r". :param formats: A list or tuple of formats to attempt to load the file in. This can be used to restrict the set of formats checked. Pass ``None`` to try all supported formats. You can print the set of available formats by running ``python3 -m PIL`` or using the :py:func:`PIL.features.pilinfo` function. :returns: An :py:class:`~PIL.Image.Image` object. :exception FileNotFoundError: If the file cannot be found. :exception PIL.UnidentifiedImageError: If the image cannot be opened and identified. :exception ValueError: If the ``mode`` is not "r", or if a ``StringIO`` instance is used for ``fp``. :exception TypeError: If ``formats`` is not ``None``, a list or a tuple. """ ... def alpha_composite(im1: Image, im2: Image) -> Image: """ Alpha composite im2 over im1. :param im1: The first image. Must have mode RGBA. :param im2: The second image. Must have mode RGBA, and the same size as the first image. :returns: An :py:class:`~PIL.Image.Image` object. """ ... def blend(im1: Image, im2: Image, alpha: float) -> Image: """ Creates a new image by interpolating between two input images, using a constant alpha:: out = image1 * (1.0 - alpha) + image2 * alpha :param im1: The first image. :param im2: The second image. Must have the same mode and size as the first image. :param alpha: The interpolation alpha factor. If alpha is 0.0, a copy of the first image is returned. If alpha is 1.0, a copy of the second image is returned. There are no restrictions on the alpha value. If necessary, the result is clipped to fit into the allowed output range. :returns: An :py:class:`~PIL.Image.Image` object. """ ... def composite(image1: Image, image2: Image, mask: Image) -> Image: """ Create composite image by blending images using a transparency mask. :param image1: The first image. :param image2: The second image. Must have the same mode and size as the first image. :param mask: A mask image. This image can have mode "1", "L", or "RGBA", and must have the same size as the other two images. """ ... def eval(image, *args): """ Applies the function (which should take one argument) to each pixel in the given image. If the image has more than one band, the same function is applied to each band. Note that the function is evaluated once for each possible pixel value, so you cannot use random components or other generators. :param image: The input image. :param function: A function object, taking one integer argument. :returns: An :py:class:`~PIL.Image.Image` object. """ ... def merge(mode, bands): """ Merge a set of single band images into a new multiband image. :param mode: The mode to use for the output image. See: :ref:`concept-modes`. :param bands: A sequence containing one single-band image for each band in the output image. All bands must have the same size. :returns: An :py:class:`~PIL.Image.Image` object. """ ... def register_open( id, factory: Callable[[IO[bytes], str | bytes], ImageFile.ImageFile], accept: Callable[[bytes], bool] | None = ..., ) -> None: """ Register an image file plugin. This function should not be used in application code. :param id: An image format identifier. :param factory: An image file factory method. :param accept: An optional function that can be used to quickly reject images having another format. """ ... def register_mime(id: str, mimetype: str) -> None: """ Registers an image MIME type by populating ``Image.MIME``. This function should not be used in application code. ``Image.MIME`` provides a mapping from image format identifiers to mime formats, but :py:meth:`~PIL.ImageFile.ImageFile.get_format_mimetype` can provide a different result for specific images. :param id: An image format identifier. :param mimetype: The image MIME type for this format. """ ... def register_save(id: str, driver) -> None: """ Registers an image save function. This function should not be used in application code. :param id: An image format identifier. :param driver: A function to save images in this format. """ ... def register_save_all(id, driver) -> None: """ Registers an image function to save all the frames of a multiframe format. This function should not be used in application code. :param id: An image format identifier. :param driver: A function to save images in this format. """ ... def register_extension(id, extension) -> None: """ Registers an image extension. This function should not be used in application code. :param id: An image format identifier. :param extension: An extension used for this format. """ ... def register_extensions(id, extensions) -> None: """ Registers image extensions. This function should not be used in application code. :param id: An image format identifier. :param extensions: A list of extensions used for this format. """ ... def registered_extensions(): # -> dict[str, str]: """ Returns a dictionary containing all file extensions belonging to registered plugins """ ... def register_decoder(name: str, decoder: type[ImageFile.PyDecoder]) -> None: """ Registers an image decoder. This function should not be used in application code. :param name: The name of the decoder :param decoder: An ImageFile.PyDecoder object .. versionadded:: 4.1.0 """ ... def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None: """ Registers an image encoder. This function should not be used in application code. :param name: The name of the encoder :param encoder: An ImageFile.PyEncoder object .. versionadded:: 4.1.0 """ ... def effect_mandelbrot(size, extent, quality): # -> Image: """ Generate a Mandelbrot set covering the given extent. :param size: The requested size in pixels, as a 2-tuple: (width, height). :param extent: The extent to cover, as a 4-tuple: (x0, y0, x1, y1). :param quality: Quality. """ ... def effect_noise(size, sigma): # -> Image: """ Generate Gaussian noise centered around 128. :param size: The requested size in pixels, as a 2-tuple: (width, height). :param sigma: Standard deviation of noise. """ ... def linear_gradient(mode): # -> Image: """ Generate 256x256 linear gradient from black to white, top to bottom. :param mode: Input mode. """ ... def radial_gradient(mode): # -> Image: """ Generate 256x256 radial gradient from black to white, centre to edge. :param mode: Input mode. """ ... if TYPE_CHECKING: _ExifBase = MutableMapping[int, Any] else: ... class Exif(_ExifBase): """ This class provides read and write access to EXIF image data:: from PIL import Image im = Image.open("exif.png") exif = im.getexif() # Returns an instance of this class Information can be read and written, iterated over or deleted:: print(exif[274]) # 1 exif[274] = 2 for k, v in exif.items(): print("Tag", k, "Value", v) # Tag 274 Value 2 del exif[274] To access information beyond IFD0, :py:meth:`~PIL.Image.Exif.get_ifd` returns a dictionary:: from PIL import ExifTags im = Image.open("exif_gps.jpg") exif = im.getexif() gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo) print(gps_ifd) Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.Makernote``, ``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``. :py:mod:`~PIL.ExifTags` also has enum classes to provide names for data:: print(exif[ExifTags.Base.Software]) # PIL print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99 """ endian = ... bigtiff = ... _loaded = ... def __init__(self) -> None: ... def load(self, data): # -> None: ... def load_from_fp(self, fp, offset=...): # -> None: ... def tobytes(self, offset: int = ...) -> bytes: ... def get_ifd(self, tag): # -> dict[Any, Any]: ... def hide_offsets(self) -> None: ... def __str__(self) -> str: ... def __len__(self) -> int: ... def __getitem__(self, tag): ... def __contains__(self, tag) -> bool: ... def __setitem__(self, tag, value) -> None: ... def __delitem__(self, tag: int) -> None: ... def __iter__(self): # -> Iterator[Any]: ... ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1719315826.136107 trezor-0.13.9/tests/0000775000175000017500000000000014636526562014514 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tests/test_btc.py0000664000175000017500000002471414636513242016674 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import json from decimal import Decimal from trezorlib import btc # https://btc1.trezor.io/api/tx-specific/f5e735549daeb480d4348f2574b8967a4f149715edb220a742d8bb654d668348 TX_JSON_BIG = """ { "txid": "f5e735549daeb480d4348f2574b8967a4f149715edb220a742d8bb654d668348", "hash": "6cc9b72db1440ce2de78a46c1fe6f8979807eddb14ab6e532947c83937520569", "version": 2, "size": 931, "vsize": 529, "weight": 2113, "locktime": 620109, "vin": [ { "txid": "a1291df0b8ef7cce44d5ae7f31e50ef85aedc129cfcb5e1a82ab5ac917c39733", "vout": 4, "scriptSig": { "asm": "001476e4db8a1d5c4c238775df63899f71dfda0197f4", "hex": "16001476e4db8a1d5c4c238775df63899f71dfda0197f4" }, "txinwitness": [ "30440220395aeb2327c8860f42c671dec6807c962f73a67db8f7106691bc1d02095f075e0220471812cf1830b769246ddbeb9d99d034b2119415291fa6fe8fb33d6f39858e4f01", "030c9daf8f58ccd1733de05574964eaf2810c5d6e2375dee0f49603151cf589e1d" ], "sequence": 4294967294 }, { "txid": "05a049179ee576e3c3d6ae71011e08066aa6a588af31d6d61332215c65e8eebd", "vout": 3, "scriptSig": { "asm": "0014004bebeb0b79c94754f5458a3ed8a293370df7fb", "hex": "160014004bebeb0b79c94754f5458a3ed8a293370df7fb" }, "txinwitness": [ "3044022029283ef96d3def47843716e64606604044125c1642732102533076c9b1958ce9022071d0fb9504fe47b215ebc320113ec77936f143659b9fcdf4f5c9e872fdfec9b601", "025389a45a00c6d57e6a61258e95d6f7413f2c23274f7f82a7164ec040f7bf73ab" ], "sequence": 4294967294 }, { "txid": "05a049179ee576e3c3d6ae71011e08066aa6a588af31d6d61332215c65e8eebd", "vout": 7, "scriptSig": { "asm": "0014d07cba9645f2814af5ed30aef767234ddda9ac13", "hex": "160014d07cba9645f2814af5ed30aef767234ddda9ac13" }, "txinwitness": [ "3044022068dc9e81b98036014adfc6d6235a66caaf12a9060243fb768abd161024275443022075e89fa03d6a5f5d21ce778e97de521c5bb51e6eaa0f9eccd92f487d22410ed801", "03d79e1063b8b5bd169ac5b2791b57e19f43160c7e553baa0b243cb666d30c19f4" ], "sequence": 4294967294 }, { "txid": "05a049179ee576e3c3d6ae71011e08066aa6a588af31d6d61332215c65e8eebd", "vout": 8, "scriptSig": { "asm": "0014ae471e13840e2b5dcdec9c4a9b54ba11fe7b665c", "hex": "160014ae471e13840e2b5dcdec9c4a9b54ba11fe7b665c" }, "txinwitness": [ "304402206d2ddc00b36896e6cbd50dfae25e8ae81f0fbf902325f34a2c831a04503cd58202202fe96b43daa1281c96a5c9507a01956cfb0d4b94b5406a94d6de96c3b82bdf7d01", "03a261fa4d379512d74d8eb14a7f797f694bbbda5ff3dcc05a08a55be7dd2b2e00" ], "sequence": 4294967294 }, { "txid": "dc27d76fa566e14e97709c59e0b1c5e1b7628c96f6dc8cbca5816a381a4234cb", "vout": 14, "scriptSig": { "asm": "0014995cd732c74a3446f1ad35bafbd8d6f79828ef04", "hex": "160014995cd732c74a3446f1ad35bafbd8d6f79828ef04" }, "txinwitness": [ "304402202659160053eee86b6f27be4e82cb7f390597e923ae0f9d17b8e3864f6ae17a2602200468b59da0b6c1b1edd848dbbba802be74228b43dda9de5e9ad36b1e6558950101", "0393c79d23ae89f461f039b895dfd6f365ef5cc89f1a3ef030382f80c2cbd84caa" ], "sequence": 4294967294 } ], "vout": [ { "value": 0.42651353, "n": 0, "scriptPubKey": { "asm": "OP_HASH160 3a530afe02afe8dd7bc1b7d731550eec4f442666 OP_EQUAL", "hex": "a9143a530afe02afe8dd7bc1b7d731550eec4f44266687", "reqSigs": 1, "type": "scripthash", "addresses": [ "371QbbRvKoqgSxfUm9Ly6qHoBJzuaBHDCT" ] } }, { "value": 22.63125, "n": 1, "scriptPubKey": { "asm": "OP_HASH160 01a331e12b1d6789c8109d86f27e6fdd6105b194 OP_EQUAL", "hex": "a91401a331e12b1d6789c8109d86f27e6fdd6105b19487", "reqSigs": 1, "type": "scripthash", "addresses": [ "31qg6iFQUxSdxyTk1RJyKmVPNvz7XV5s5c" ] } } ], "hex": "020000000001053397c317c95aab821a5ecbcf29c1ed5af80ee5317faed544ce7cefb8f01d29a1040000001716001476e4db8a1d5c4c238775df63899f71dfda0197f4feffffffbdeee8655c213213d6d631af88a5a66a06081e0171aed6c3e376e59e1749a0050300000017160014004bebeb0b79c94754f5458a3ed8a293370df7fbfeffffffbdeee8655c213213d6d631af88a5a66a06081e0171aed6c3e376e59e1749a0050700000017160014d07cba9645f2814af5ed30aef767234ddda9ac13feffffffbdeee8655c213213d6d631af88a5a66a06081e0171aed6c3e376e59e1749a0050800000017160014ae471e13840e2b5dcdec9c4a9b54ba11fe7b665cfeffffffcb34421a386a81a5bc8cdcf6968c62b7e1c5b1e0599c70974ee166a56fd727dc0e00000017160014995cd732c74a3446f1ad35bafbd8d6f79828ef04feffffff02d9ce8a020000000017a9143a530afe02afe8dd7bc1b7d731550eec4f44266687088ce4860000000017a91401a331e12b1d6789c8109d86f27e6fdd6105b19487024730440220395aeb2327c8860f42c671dec6807c962f73a67db8f7106691bc1d02095f075e0220471812cf1830b769246ddbeb9d99d034b2119415291fa6fe8fb33d6f39858e4f0121030c9daf8f58ccd1733de05574964eaf2810c5d6e2375dee0f49603151cf589e1d02473044022029283ef96d3def47843716e64606604044125c1642732102533076c9b1958ce9022071d0fb9504fe47b215ebc320113ec77936f143659b9fcdf4f5c9e872fdfec9b60121025389a45a00c6d57e6a61258e95d6f7413f2c23274f7f82a7164ec040f7bf73ab02473044022068dc9e81b98036014adfc6d6235a66caaf12a9060243fb768abd161024275443022075e89fa03d6a5f5d21ce778e97de521c5bb51e6eaa0f9eccd92f487d22410ed8012103d79e1063b8b5bd169ac5b2791b57e19f43160c7e553baa0b243cb666d30c19f40247304402206d2ddc00b36896e6cbd50dfae25e8ae81f0fbf902325f34a2c831a04503cd58202202fe96b43daa1281c96a5c9507a01956cfb0d4b94b5406a94d6de96c3b82bdf7d012103a261fa4d379512d74d8eb14a7f797f694bbbda5ff3dcc05a08a55be7dd2b2e000247304402202659160053eee86b6f27be4e82cb7f390597e923ae0f9d17b8e3864f6ae17a2602200468b59da0b6c1b1edd848dbbba802be74228b43dda9de5e9ad36b1e6558950101210393c79d23ae89f461f039b895dfd6f365ef5cc89f1a3ef030382f80c2cbd84caa4d760900", "blockhash": "000000000000000000031d097eeb2fe33a4c6fbf2dcebcdbd49051a4d7e37390", "confirmations": 13, "time": 1583317141, "blocktime": 1583317141 } """ # https://btc1.trezor.io/api/tx-specific/317f8a6e343384bd7d1a06ca25407d991ad3fc956e4ebedc66e1ec3b2ed9ccc6 TX_JSON_COINBASE = """ { "txid": "317f8a6e343384bd7d1a06ca25407d991ad3fc956e4ebedc66e1ec3b2ed9ccc6", "hash": "a55b6b60791108e5bd6b014a3527be285867744d58baba4ca7bcc9ea713533a3", "version": 2, "size": 342, "vsize": 315, "weight": 1260, "locktime": 0, "vin": [ { "coinbase": "03737609040a995f5e626a30332f4254432e434f4d2ffabe6d6db1d4bc579e23d79b7a494282f8189a5e99567ce755a1dd25e1964fcda592ac21080000007296cd102f0461a3aee6070000000000", "sequence": 4294967295 } ], "vout": [ { "value": 12.56294353, "n": 0, "scriptPubKey": { "asm": "0 97cfc76442fe717f2a3f0cc9c175f7561b661997", "hex": "001497cfc76442fe717f2a3f0cc9c175f7561b661997", "reqSigs": 1, "type": "witness_v0_keyhash", "addresses": [ "bc1qjl8uwezzlech723lpnyuza0h2cdkvxvh54v3dn" ] } }, { "value": 0, "n": 1, "scriptPubKey": { "asm": "OP_RETURN aa21a9edcc94907af04544c15ac0bd15520012b98edbb04c604b20f43306382831850da9", "hex": "6a24aa21a9edcc94907af04544c15ac0bd15520012b98edbb04c604b20f43306382831850da9", "type": "nulldata" } }, { "value": 0, "n": 2, "scriptPubKey": { "asm": "OP_RETURN 52534b424c4f434b3a738dd5336a7d4869904cb27f7d5b5afd534c360b6d0b9c45e2fafc2a00210406", "hex": "6a2952534b424c4f434b3a738dd5336a7d4869904cb27f7d5b5afd534c360b6d0b9c45e2fafc2a00210406", "type": "nulldata" } }, { "value": 0, "n": 3, "scriptPubKey": { "asm": "OP_RETURN b9e11b6df9e2d9e640ea4dc449b522003347a2c0aa421b4128f4644560c508e78a433684", "hex": "6a24b9e11b6df9e2d9e640ea4dc449b522003347a2c0aa421b4128f4644560c508e78a433684", "type": "nulldata" } } ], "hex": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff4e03737609040a995f5e626a30332f4254432e434f4d2ffabe6d6db1d4bc579e23d79b7a494282f8189a5e99567ce755a1dd25e1964fcda592ac21080000007296cd102f0461a3aee6070000000000ffffffff04d187e14a0000000016001497cfc76442fe717f2a3f0cc9c175f7561b6619970000000000000000266a24aa21a9edcc94907af04544c15ac0bd15520012b98edbb04c604b20f43306382831850da900000000000000002b6a2952534b424c4f434b3a738dd5336a7d4869904cb27f7d5b5afd534c360b6d0b9c45e2fafc2a002104060000000000000000266a24b9e11b6df9e2d9e640ea4dc449b522003347a2c0aa421b4128f4644560c508e78a4336840120000000000000000000000000000000000000000000000000000000000000000000000000", "blockhash": "00000000000000000008f5eb1ce2bac08ecd50041fa162e45470dda519fc159d", "confirmations": 1, "time": 1583323402, "blocktime": 1583323402 } """ def test_from_json(): tx_dict = json.loads(TX_JSON_BIG, parse_float=Decimal) tx = btc.from_json(tx_dict) assert tx.version == 2 assert tx.lock_time == 620109 assert len(tx.inputs) == 5 assert len(tx.bin_outputs) == 2 assert sum(o.amount for o in tx.bin_outputs) == 2305776353 for v, i in zip(tx_dict["vin"], tx.inputs): assert i.prev_hash.hex() == v["txid"] assert i.prev_index == v["vout"] assert i.script_sig.hex() == v["scriptSig"]["hex"] assert i.sequence == v["sequence"] for v, o in zip(tx_dict["vout"], tx.bin_outputs): assert o.amount == int(Decimal(v["value"]) * (10**8)) assert o.script_pubkey.hex() == v["scriptPubKey"]["hex"] def test_coinbase_from_json(): tx_dict = json.loads(TX_JSON_COINBASE, parse_float=Decimal) tx = btc.from_json(tx_dict) assert tx.version == 2 assert tx.lock_time == 0 assert len(tx.inputs) == 1 assert len(tx.bin_outputs) == 4 assert sum(o.amount for o in tx.bin_outputs) == 1256294353 coinbase = tx.inputs[0] assert coinbase.prev_hash == b"\x00" * 32 assert coinbase.prev_index == 2**32 - 1 assert coinbase.script_sig.hex() == tx_dict["vin"][0]["coinbase"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tests/test_cosi.py0000664000175000017500000001542514636513242017060 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import hashlib import pytest from trezorlib import _ed25519, cosi RFC8032_VECTORS = ( ( # test 1 # privkey bytes.fromhex( "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" ), # pubkey bytes.fromhex( "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" ), # message bytes.fromhex(""), # signature bytes.fromhex( "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155" "5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b" ), ), ( # test 2 # privkey bytes.fromhex( "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb" ), # pubkey bytes.fromhex( "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c" ), # message bytes.fromhex("72"), # signature bytes.fromhex( "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da" "085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00" ), ), ( # test 3 # privkey bytes.fromhex( "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7" ), # pubkey bytes.fromhex( "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025" ), # message bytes.fromhex("af82"), # signature bytes.fromhex( "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac" "18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a" ), ), ( # test SHA(abc) # privkey bytes.fromhex( "833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42" ), # pubkey bytes.fromhex( "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf" ), # message hashlib.sha512(b"abc").digest(), # signature bytes.fromhex( "dc2a4459e7369633a52b1bf277839a00201009a3efbf3ecb69bea2186c26b589" "09351fc9ac90b3ecfdfbc7c66431e0303dca179c138ac17ad9bef1177331a704" ), ), ) COMBINED_KEY = bytes.fromhex( "283967b1c19ff93d2924cdcba95e586547cafef509ea402963ceefe96ccb44f2" ) GLOBAL_COMMIT = bytes.fromhex( "75bd5806c6366e0374a1c6e020c53feb0791d6cc07560d27d8c158f886ecf389" ) @pytest.mark.parametrize("privkey, pubkey, message, signature", RFC8032_VECTORS) def test_single_eddsa_vector(privkey, pubkey, message, signature): my_pubkey = cosi.pubkey_from_privkey(privkey) assert my_pubkey == pubkey try: cosi.verify_combined(signature, message, pubkey) except ValueError: pytest.fail("Signature does not verify.") fake_signature = signature[:37] + b"\xf0" + signature[38:] with pytest.raises(_ed25519.SignatureMismatch): cosi.verify_combined(fake_signature, message, pubkey) def test_combine_keys(): pubkeys = [pubkey for _, pubkey, _, _ in RFC8032_VECTORS] assert cosi.combine_keys(pubkeys) == COMBINED_KEY Rs = [ cosi.get_nonce(privkey, message)[1] for privkey, _, message, _ in RFC8032_VECTORS ] assert cosi.combine_keys(Rs) == GLOBAL_COMMIT @pytest.mark.parametrize("keyset", [(0,), (0, 1), (0, 1, 2), (0, 1, 2, 3), (1, 3)]) def test_cosi_combination(keyset): message = hashlib.sha512(b"You all have to sign this.").digest() selection = [RFC8032_VECTORS[i] for i in keyset] # zip(*iterable) turns a list of tuples to a tuple of lists privkeys, pubkeys, _, _ = zip(*selection) nonce_pairs = [cosi.get_nonce(pk, message) for pk in privkeys] nonces, commits = zip(*nonce_pairs) # calculate global pubkey and commitment global_pk = cosi.combine_keys(pubkeys) global_commit = cosi.combine_keys(commits) # generate individual signatures signatures = [ cosi.sign_with_privkey(message, privkey, global_pk, nonce, global_commit) for privkey, nonce in zip(privkeys, nonces) ] # combine signatures global_sig = cosi.combine_sig(global_commit, signatures) try: cosi.verify_combined(global_sig, message, global_pk) except Exception: pytest.fail("Failed to validate global signature") def test_m_of_n(): privkeys, pubkeys, _, _ = zip(*RFC8032_VECTORS) message = hashlib.sha512(b"My hovercraft is full of eels!").digest() signer_ids = 0, 2, 3 signers = [privkeys[i] for i in signer_ids] signer_pubkeys = [pubkeys[i] for i in signer_ids] sigmask = sum(1 << i for i in signer_ids) # generate multisignature nonce_pairs = [cosi.get_nonce(pk, message) for pk in signers] nonces, commits = zip(*nonce_pairs) global_pk = cosi.combine_keys(signer_pubkeys) global_commit = cosi.combine_keys(commits) signatures = [ cosi.sign_with_privkey(message, privkey, global_pk, nonce, global_commit) for privkey, nonce in zip(signers, nonces) ] global_sig = cosi.combine_sig(global_commit, signatures) try: # this is what we are actually doing cosi.verify(global_sig, message, 3, pubkeys, sigmask) # we can require less signers too cosi.verify(global_sig, message, 1, pubkeys, sigmask) except Exception: pytest.fail("Failed to validate by sigmask") # and now for various ways that should fail with pytest.raises(ValueError) as e: cosi.verify(global_sig, message, 3, pubkeys[:2], sigmask) assert "more public keys than provided" in e.value.args[0] with pytest.raises(ValueError) as e: cosi.verify(global_sig, message, 0, pubkeys, 0) assert "At least one signer" in e.value.args[0] with pytest.raises(_ed25519.SignatureMismatch) as e: # at least 5 signatures required cosi.verify(global_sig, message, 5, pubkeys, sigmask) assert "Insufficient number of signatures" in e.value.args[0] with pytest.raises(_ed25519.SignatureMismatch) as e: # wrong sigmask cosi.verify(global_sig, message, 3, pubkeys, 7) assert "signature does not pass verification" in e.value.args[0] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tests/test_firmware.py0000664000175000017500000001022414636513242017727 0ustar00matejcikmatejcikfrom pathlib import Path import construct import pytest import requests from trezorlib import firmware from trezorlib.firmware import ( LegacyFirmware, LegacyV2Firmware, VendorFirmware, VendorHeader, ) CORE_FW_VERSION = "2.4.2" CORE_FW_FINGERPRINT = "54ccf155510b5292bd17ed748409d0d135112e24e62eb74184639460beecb213" LEGACY_FW_VERSION = "1.10.3" LEGACY_FW_FINGERPRINT = ( "bf0cc936a9afbf0a4ae7b727a2817fb69fba432d7230a0ff7b79b4a73b845197" ) CORE_FW = f"https://data.trezor.io/firmware/2/trezor-{CORE_FW_VERSION}.bin" LEGACY_FW = f"https://data.trezor.io/firmware/1/trezor-{LEGACY_FW_VERSION}.bin" HERE = Path(__file__).parent VENDOR_HEADER = ( HERE.parent.parent / "core" / "embed" / "vendorheader" / "T2T1" / "vendorheader_satoshilabs_signed_prod.bin" ) def _fetch(url: str, version: str) -> bytes: path = HERE / f"trezor-{version}.bin" if not path.exists(): r = requests.get(url) r.raise_for_status() path.write_bytes(r.content) return path.read_bytes() @pytest.fixture() def legacy_fw() -> bytes: return _fetch(LEGACY_FW, LEGACY_FW_VERSION) @pytest.fixture() def core_fw() -> bytes: return _fetch(CORE_FW, CORE_FW_VERSION) def test_core_basic(core_fw: bytes) -> None: fw = VendorFirmware.parse(core_fw) fw.verify() assert fw.digest().hex() == CORE_FW_FINGERPRINT version_str = ".".join(str(x) for x in fw.firmware.header.version) assert version_str.startswith(CORE_FW_VERSION) assert fw.vendor_header.text == "SatoshiLabs" assert fw.build() == core_fw def test_vendor_header(core_fw: bytes) -> None: fw = VendorFirmware.parse(core_fw) vh_data = fw.vendor_header.build() assert vh_data in core_fw assert vh_data == VENDOR_HEADER.read_bytes() vh = VendorHeader.parse(vh_data) assert vh == fw.vendor_header vh.verify() with pytest.raises(construct.ConstructError): VendorFirmware.parse(vh_data) def test_core_code_hashes(core_fw: bytes) -> None: fw = VendorFirmware.parse(core_fw) fw.firmware.header.hashes = [] assert fw.digest().hex() == CORE_FW_FINGERPRINT def test_legacy_basic(legacy_fw: bytes) -> None: fw = LegacyFirmware.parse(legacy_fw) fw.verify() assert fw.digest().hex() == LEGACY_FW_FINGERPRINT assert fw.build() == legacy_fw def test_unsigned(legacy_fw: bytes) -> None: legacy = LegacyFirmware.parse(legacy_fw) legacy.verify() legacy.key_indexes = [0, 0, 0] legacy.signatures = [b"", b"", b""] with pytest.raises(firmware.Unsigned): legacy.verify() assert legacy.embedded_v2 is not None legacy.embedded_v2.verify() legacy.embedded_v2.header.v1_key_indexes = [0, 0, 0] legacy.embedded_v2.header.v1_signatures = [b"", b"", b""] with pytest.raises(firmware.Unsigned): legacy.embedded_v2.verify() def test_disallow_unsigned(core_fw: bytes) -> None: core = VendorFirmware.parse(core_fw) core.firmware.header.sigmask = 0 core.firmware.header.signature = b"" with pytest.raises(firmware.InvalidSignatureError): core.verify() def test_embedded_v2(legacy_fw: bytes) -> None: legacy = LegacyFirmware.parse(legacy_fw) assert legacy.embedded_v2 is not None legacy.embedded_v2.verify() embedded_data = legacy.embedded_v2.build() cutoff_data = legacy_fw[256:] assert cutoff_data == embedded_data embedded = LegacyV2Firmware.parse(cutoff_data) assert embedded == legacy.embedded_v2 def test_integrity_legacy(legacy_fw: bytes) -> None: legacy = LegacyFirmware.parse(legacy_fw) legacy.verify() modified_data = bytearray(legacy_fw) modified_data[-1] ^= 0x01 modified = LegacyFirmware.parse(modified_data) assert modified.digest() != legacy.digest() with pytest.raises(firmware.InvalidSignatureError): modified.verify() def test_integrity_core(core_fw: bytes) -> None: core = VendorFirmware.parse(core_fw) core.verify() modified_data = bytearray(core_fw) modified_data[-1] ^= 0x01 modified = VendorFirmware.parse(modified_data) assert modified.digest() != core.digest() with pytest.raises(firmware.FirmwareIntegrityError): modified.verify() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tests/test_merkle_tree.py0000664000175000017500000000621014636513242020411 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import typing as t import pytest from trezorlib.merkle_tree import ( Leaf, MerkleTree, Node, evaluate_proof, internal_hash, leaf_hash, ) NODE_VECTORS = ( # node, expected_hash ( # leaf node Leaf(b"hello"), "8a2a5c9b768827de5a9552c38a044c66959c68f6d2f21b5260af54d2f87db827", ), ( # node with leaf nodes Node(left=Leaf(b"hello"), right=Leaf(b"world")), "24233339aadcedf287d262413f03c028eb8db397edd32a2878091151b99bf20f", ), ( # asymmetric node with leaf hanging on second level Node(left=Node(left=Leaf(b"hello"), right=Leaf(b"world")), right=Leaf(b"!")), "c3727420dc97c0dbd89678ee195957e44cfa69f5759b395a07bc171b21468633", ), ) MERKLE_TREE_VECTORS = ( ( # one value # values [b"Merkle"], # expected root hash leaf_hash(b"Merkle"), # expected dict of proof lists { b"Merkle": [], }, ), ( # two values # values [b"Haber", b"Stornetta"], # expected root hash internal_hash( leaf_hash(b"Haber"), leaf_hash(b"Stornetta"), ), # expected dict of proof lists { b"Haber": [leaf_hash(b"Stornetta")], b"Stornetta": [leaf_hash(b"Haber")], }, ), ( # three values # values [b"Andersen", b"Wuille", b"Maxwell"], # expected root hash internal_hash( internal_hash( leaf_hash(b"Maxwell"), leaf_hash(b"Wuille"), ), leaf_hash(b"Andersen"), ), # expected dict of proof lists { b"Andersen": [internal_hash(leaf_hash(b"Maxwell"), leaf_hash(b"Wuille"))], b"Maxwell": [leaf_hash(b"Wuille"), leaf_hash(b"Andersen")], b"Wuille": [leaf_hash(b"Maxwell"), leaf_hash(b"Andersen")], }, ), ) @pytest.mark.parametrize("node, expected_hash", NODE_VECTORS) def test_node(node: t.Union[Node, Leaf], expected_hash: str) -> None: assert node.tree_hash.hex() == expected_hash @pytest.mark.parametrize("values, root_hash, proofs", MERKLE_TREE_VECTORS) def test_tree( values: t.List[bytes], root_hash: bytes, proofs: t.Dict[bytes, t.List[bytes]], ) -> None: mt = MerkleTree(values) assert mt.get_root_hash() == root_hash for value, proof in proofs.items(): assert mt.get_proof(value) == proof assert evaluate_proof(value, proof) == root_hash ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tests/test_nem.py0000664000175000017500000000304314636513242016673 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from trezorlib import nem def test_nem_basic(): transaction = { "timeStamp": 76809215, "amount": 1000000, "fee": 1000000, "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", "type": nem.TYPE_TRANSACTION_TRANSFER, "deadline": 76895615, "version": (0x98 << 24), "message": {"payload": b"hello world".hex(), "type": 1}, "mosaics": [ {"mosaicId": {"namespaceId": "nem", "name": "xem"}, "quantity": 1000000} ], } msg = nem.create_sign_tx(transaction) # this is basically just a random sampling of expected properties assert msg.transaction is not None assert msg.transfer is not None assert len(msg.transfer.mosaics) == 1 assert msg.transfer.mosaics[0].namespace == "nem" assert msg.aggregate_modification is None assert msg.provision_namespace is None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315793.0 trezor-0.13.9/tests/test_protobuf_encoding.py0000664000175000017500000002154214636526521021632 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import logging from enum import IntEnum from io import BytesIO import pytest from trezorlib import protobuf class SomeEnum(IntEnum): Zero = 0 Five = 5 TwentyFive = 25 class WiderEnum(IntEnum): One = 1 Two = 2 Three = 3 Four = 4 Five = 5 class NarrowerEnum(IntEnum): One = 1 Five = 5 class PrimitiveMessage(protobuf.MessageType): FIELDS = { 1: protobuf.Field("uvarint", "uint64"), 2: protobuf.Field("svarint", "sint64"), 3: protobuf.Field("bool", "bool"), 4: protobuf.Field("bytes", "bytes"), 5: protobuf.Field("unicode", "string"), 6: protobuf.Field("enum", "SomeEnum"), } class EnumMessageMoreValues(protobuf.MessageType): FIELDS = {1: protobuf.Field("enum", "WiderEnum")} class EnumMessageLessValues(protobuf.MessageType): FIELDS = {1: protobuf.Field("enum", "NarrowerEnum")} class RepeatedFields(protobuf.MessageType): FIELDS = { 1: protobuf.Field("uintlist", "uint64", repeated=True), 2: protobuf.Field("enumlist", "SomeEnum", repeated=True), 3: protobuf.Field("strlist", "string", repeated=True), } class RequiredFields(protobuf.MessageType): FIELDS = { 1: protobuf.Field("uvarint", "uint64", required=True), 2: protobuf.Field("nested", "PrimitiveMessage", required=True), } class DefaultFields(protobuf.MessageType): FIELDS = { 1: protobuf.Field("uvarint", "uint32", default=42), 2: protobuf.Field("svarint", "sint32", default=-42), 3: protobuf.Field("bool", "bool", default=True), 4: protobuf.Field("bytes", "bytes", default=b"hello"), 5: protobuf.Field("unicode", "string", default="hello"), 6: protobuf.Field("enum", "SomeEnum", default=SomeEnum.Five), } class RecursiveMessage(protobuf.MessageType): FIELDS = { 1: protobuf.Field("uvarint", "uint64"), 2: protobuf.Field("recursivefield", "RecursiveMessage", required=False), } def load_uvarint(buffer): reader = BytesIO(buffer) return protobuf.load_uvarint(reader) def dump_uvarint(value): writer = BytesIO() protobuf.dump_uvarint(writer, value) return writer.getvalue() def load_message(buffer, msg_type): reader = BytesIO(buffer) return protobuf.load_message(reader, msg_type) def dump_message(msg): writer = BytesIO() protobuf.dump_message(writer, msg) return writer.getvalue() def test_dump_uvarint(): assert dump_uvarint(0) == b"\x00" assert dump_uvarint(1) == b"\x01" assert dump_uvarint(0xFF) == b"\xff\x01" assert dump_uvarint(123456) == b"\xc0\xc4\x07" with pytest.raises(ValueError): dump_uvarint(-1) def test_load_uvarint(): assert load_uvarint(b"\x00") == 0 assert load_uvarint(b"\x01") == 1 assert load_uvarint(b"\xff\x01") == 0xFF assert load_uvarint(b"\xc0\xc4\x07") == 123456 assert load_uvarint(b"\x80\x80\x80\x80\x00") == 0 def test_broken_uvarint(): with pytest.raises(IOError): load_uvarint(b"\x80\x80") def test_sint_uint(): """ Protobuf interleaved signed encoding https://developers.google.com/protocol-buffers/docs/encoding#structure LSbit is sign, rest is shifted absolute value. Or, by example, you count like so: 0, -1, 1, -2, 2, -3 ... """ assert protobuf.sint_to_uint(0) == 0 assert protobuf.uint_to_sint(0) == 0 assert protobuf.sint_to_uint(-1) == 1 assert protobuf.sint_to_uint(1) == 2 assert protobuf.uint_to_sint(1) == -1 assert protobuf.uint_to_sint(2) == 1 # roundtrip: assert protobuf.uint_to_sint(protobuf.sint_to_uint(1234567891011)) == 1234567891011 assert protobuf.uint_to_sint(protobuf.sint_to_uint(-(2**32))) == -(2**32) def test_simple_message(): msg = PrimitiveMessage( uvarint=12345678910, svarint=-12345678910, bool=True, bytes=b"\xDE\xAD\xCA\xFE", unicode="Příliš žluťoučký kůň úpěl ďábelské ódy 😊", enum=SomeEnum.Five, ) buf = dump_message(msg) retr = load_message(buf, PrimitiveMessage) assert msg == retr assert retr.uvarint == 12345678910 assert retr.svarint == -12345678910 assert retr.bool is True assert retr.bytes == b"\xDE\xAD\xCA\xFE" assert retr.unicode == "Příliš žluťoučký kůň úpěl ďábelské ódy 😊" assert retr.enum == SomeEnum.Five assert retr.enum == 5 def test_validate_enum(caplog): caplog.set_level(logging.INFO) # round-trip of a valid value msg = EnumMessageMoreValues(enum=WiderEnum.Five) buf = dump_message(msg) retr = load_message(buf, EnumMessageLessValues) assert retr.enum == msg.enum assert not caplog.records # dumping an invalid enum value fails msg.enum = 19 with pytest.raises( ValueError, match="Value 19 in field enum unknown for WiderEnum" ): dump_message(msg) msg.enum = WiderEnum.Three buf = dump_message(msg) retr = load_message(buf, EnumMessageLessValues) assert len(caplog.records) == 1 record = caplog.records.pop(0) assert record.levelname == "INFO" assert record.getMessage() == "On field enum: 3 is not a valid NarrowerEnum" assert retr.enum == 3 def test_repeated(): msg = RepeatedFields( uintlist=[1, 2, 3], enumlist=[0, 5, 0, 5], strlist=["hello", "world"] ) buf = dump_message(msg) retr = load_message(buf, RepeatedFields) assert retr == msg def test_packed(): values = [4, 44, 444] packed_values = b"".join(dump_uvarint(v) for v in values) field_id = 1 << 3 | 2 # field number 1, wire type 2 field_len = len(packed_values) message_bytes = dump_uvarint(field_id) + dump_uvarint(field_len) + packed_values msg = load_message(message_bytes, RepeatedFields) assert msg assert msg.uintlist == values assert not msg.enumlist assert not msg.strlist def test_packed_enum(): values = [0, 0, 0, 0] packed_values = b"".join(dump_uvarint(v) for v in values) field_id = 2 << 3 | 2 # field number 2, wire type 2 field_len = len(packed_values) message_bytes = dump_uvarint(field_id) + dump_uvarint(field_len) + packed_values msg = load_message(message_bytes, RepeatedFields) assert msg assert msg.enumlist == values assert not msg.uintlist assert not msg.strlist def test_required(): msg = RequiredFields(uvarint=3, nested=PrimitiveMessage()) buf = dump_message(msg) msg_ok = load_message(buf, RequiredFields) assert msg_ok == msg with pytest.deprecated_call(): msg = RequiredFields(uvarint=3) with pytest.raises(ValueError): # cannot encode instance without the required fields dump_message(msg) msg = RequiredFields(uvarint=3, nested=None) # we can always encode an invalid message buf = dump_message(msg) with pytest.raises(ValueError): # required field `nested` is also not sent load_message(buf, RequiredFields) msg = RequiredFields(uvarint=None, nested=PrimitiveMessage()) buf = dump_message(msg) with pytest.raises(ValueError): # required field `uvarint` is not sent load_message(buf, RequiredFields) def test_default(): # load empty message retr = load_message(b"", DefaultFields) assert retr.uvarint == 42 assert retr.svarint == -42 assert retr.bool is True assert retr.bytes == b"hello" assert retr.unicode == "hello" assert retr.enum == SomeEnum.Five msg = DefaultFields(uvarint=0) buf = dump_message(msg) retr = load_message(buf, DefaultFields) assert retr.uvarint == 0 msg = DefaultFields(uvarint=None) buf = dump_message(msg) retr = load_message(buf, DefaultFields) assert retr.uvarint == 42 def test_recursive(): msg = RecursiveMessage( uvarint=1, recursivefield=RecursiveMessage( uvarint=2, recursivefield=RecursiveMessage(uvarint=3) ), ) buf = dump_message(msg) retr = load_message(buf, RecursiveMessage) assert msg == retr assert retr.uvarint == 1 assert type(retr.recursivefield) is RecursiveMessage assert retr.recursivefield.uvarint == 2 assert type(retr.recursivefield.recursivefield) is RecursiveMessage assert retr.recursivefield.recursivefield.uvarint == 3 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719315793.0 trezor-0.13.9/tests/test_protobuf_misc.py0000664000175000017500000001637514636526521021007 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from enum import IntEnum import pytest from trezorlib import protobuf class SimpleEnum(IntEnum): FOO = 0 BAR = 5 QUUX = 13 class SimpleMessage(protobuf.MessageType): FIELDS = { 1: protobuf.Field("uvarint", "uint64"), 2: protobuf.Field("svarint", "sint64"), 3: protobuf.Field("bool", "bool"), 4: protobuf.Field("bytes", "bytes"), 5: protobuf.Field("unicode", "string"), 6: protobuf.Field("enum", "SimpleEnum"), 7: protobuf.Field("rep_int", "uint64", repeated=True), 8: protobuf.Field("rep_str", "string", repeated=True), 9: protobuf.Field("rep_enum", "SimpleEnum", repeated=True), } class NestedMessage(protobuf.MessageType): FIELDS = { 1: protobuf.Field("scalar", "uint64"), 2: protobuf.Field("nested", "SimpleMessage"), 3: protobuf.Field("repeated", "SimpleMessage", repeated=True), } class RequiredFields(protobuf.MessageType): FIELDS = { 1: protobuf.Field("scalar", "uint64", required=True), } def test_get_field(): # smoke test field = SimpleMessage.get_field("bool") assert field.name == "bool" assert field.proto_type == "bool" assert field.repeated is False assert field.required is False assert field.default is None def test_dict_roundtrip(): msg = SimpleMessage( uvarint=5, svarint=-13, bool=False, bytes=b"\xca\xfe\x00\xfe", unicode="žluťoučký kůň", enum=SimpleEnum.BAR, rep_int=[1, 2, 3], rep_str=["a", "b", "c"], rep_enum=[SimpleEnum.FOO, SimpleEnum.BAR, SimpleEnum.QUUX], ) converted = protobuf.to_dict(msg) recovered = protobuf.dict_to_proto(SimpleMessage, converted) assert recovered == msg def test_dict_to_proto_fresh(): class FreshMessage(protobuf.MessageType): FIELDS = { 1: protobuf.Field("scalar", "uint64"), 2: protobuf.Field("nested", "SimpleMessage"), } dictdata = {"scalar": 5, "nested": {"uvarint": 5}} recovered = protobuf.dict_to_proto(FreshMessage, dictdata) assert recovered.scalar == 5 assert recovered.nested.uvarint == 5 def test_to_dict(): msg = SimpleMessage( uvarint=5, svarint=-13, bool=False, bytes=b"\xca\xfe\x00\xfe", unicode="žluťoučký kůň", enum=SimpleEnum.BAR, rep_int=[1, 2, 3], rep_str=["a", "b", "c"], rep_enum=[SimpleEnum.FOO, SimpleEnum.BAR, SimpleEnum.QUUX], ) converted = protobuf.to_dict(msg) fields = [field.name for field in msg.FIELDS.values()] assert list(sorted(converted.keys())) == list(sorted(fields)) assert converted["uvarint"] == 5 assert converted["svarint"] == -13 assert converted["bool"] is False assert converted["bytes"] == "cafe00fe" assert converted["unicode"] == "žluťoučký kůň" assert converted["enum"] == "BAR" assert converted["rep_int"] == [1, 2, 3] assert converted["rep_str"] == ["a", "b", "c"] assert converted["rep_enum"] == ["FOO", "BAR", "QUUX"] def test_recover_mismatch(): dictdata = { "bool": True, "enum": "FOO", "another_field": "hello", "rep_enum": ["FOO", 5, 5], } recovered = protobuf.dict_to_proto(SimpleMessage, dictdata) assert recovered.bool is True assert recovered.enum is SimpleEnum.FOO assert not hasattr(recovered, "another_field") assert recovered.rep_enum == [SimpleEnum.FOO, SimpleEnum.BAR, SimpleEnum.BAR] for field in SimpleMessage.FIELDS.values(): if field.name not in dictdata: if field.repeated: assert getattr(recovered, field.name) == [] else: assert getattr(recovered, field.name) is None def test_hexlify(): msg = SimpleMessage(bytes=b"\xca\xfe\x00\x12\x34", unicode="žluťoučký kůň") converted_nohex = protobuf.to_dict(msg, hexlify_bytes=False) converted_hex = protobuf.to_dict(msg, hexlify_bytes=True) assert converted_nohex["bytes"] == b"\xca\xfe\x00\x12\x34" assert converted_nohex["unicode"] == "žluťoučký kůň" assert converted_hex["bytes"] == "cafe001234" assert converted_hex["unicode"] == "žluťoučký kůň" recovered_nohex = protobuf.dict_to_proto(SimpleMessage, converted_nohex) recovered_hex = protobuf.dict_to_proto(SimpleMessage, converted_hex) assert recovered_nohex.bytes == msg.bytes assert recovered_hex.bytes == msg.bytes def test_nested_round_trip(): msg = NestedMessage( scalar=9, nested=SimpleMessage(uvarint=4, enum=SimpleEnum.FOO), repeated=[ SimpleMessage(), SimpleMessage(rep_enum=[SimpleEnum.BAR, SimpleEnum.BAR]), SimpleMessage(bytes=b"\xca\xfe"), ], ) converted = protobuf.to_dict(msg) recovered = protobuf.dict_to_proto(NestedMessage, converted) assert msg == recovered def test_nested_to_dict(): msg = NestedMessage( scalar=9, nested=SimpleMessage(uvarint=4, enum=SimpleEnum.FOO), repeated=[ SimpleMessage(), SimpleMessage(rep_enum=[SimpleEnum.BAR, SimpleEnum.BAR]), SimpleMessage(bytes=b"\xca\xfe"), ], ) converted = protobuf.to_dict(msg) assert converted["scalar"] == 9 assert isinstance(converted["nested"], dict) assert isinstance(converted["repeated"], list) rep = converted["repeated"] assert rep[0] == {} assert rep[1] == {"rep_enum": ["BAR", "BAR"]} assert rep[2] == {"bytes": "cafe"} def test_nested_recover(): dictdata = {"nested": {}} recovered = protobuf.dict_to_proto(NestedMessage, dictdata) assert isinstance(recovered.nested, SimpleMessage) def test_unknown_enum_to_str(): simple = SimpleMessage(enum=SimpleEnum.QUUX) string = protobuf.format_message(simple) assert "enum: QUUX (13)" in string simple = SimpleMessage(enum=6000) string = protobuf.format_message(simple) assert "enum: 6000" in string def test_unknown_enum_to_dict(): simple = SimpleMessage(enum=6000) converted = protobuf.to_dict(simple) assert converted["enum"] == 6000 def test_constructor_deprecations(): # ok: RequiredFields(scalar=0) # positional argument with pytest.deprecated_call(): RequiredFields(0) # missing required value with pytest.deprecated_call(): RequiredFields() # more args than fields with pytest.deprecated_call(), pytest.raises(TypeError): RequiredFields(0, 0) # colliding arg and kwarg with pytest.deprecated_call(), pytest.raises(TypeError): RequiredFields(0, scalar=0) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tests/test_rlp.py0000664000175000017500000000212214636513242016706 0ustar00matejcikmatejcikimport pytest from trezorlib import _rlp VECTORS = ( # data, expected (b"\x10", b"\x10"), (b"dog", b"\x83dog"), (b"A" * 55, b"\xb7" + b"A" * 55), (b"A" * 56, b"\xb8\x38" + b"A" * 56), (b"A" * 1024, b"\xb9\x04\x00" + b"A" * 1024), ([b"dog", b"cat", [b"spy"]], b"\xcd\x83dog\x83cat\xc4\x83spy"), ([b"A" * 1024], b"\xf9\x04\x03\xb9\x04\x00" + b"A" * 1024), ([], b"\xc0"), ([b"A"] * 55, b"\xf7" + b"A" * 55), ([b"A"] * 56, b"\xf8\x38" + b"A" * 56), ([b"A"] * 1024, b"\xf9\x04\x00" + b"A" * 1024), ([b"dog"] * 1024, b"\xf9\x10\x00" + b"\x83dog" * 1024), (b"", b"\x80"), (1, b"\x01"), (0x7F, b"\x7f"), (0x80, b"\x81\x80"), (0x1_0000_0001, b"\x85\x01\x00\x00\x00\x01"), (2 ** (54 * 8), b"\xb7\x01" + b"\x00" * 54), (2 ** (55 * 8), b"\xb8\x38\x01" + b"\x00" * 55), ([0x1234, 0x5678], b"\xc6\x82\x12\x34\x82\x56\x78"), ) @pytest.mark.parametrize("data, expected", VECTORS) def test_encode(data: "_rlp.RLPItem", expected: bytes): actual = _rlp.encode(data) assert len(actual) == len(expected) assert actual == expected ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tests/test_stellar.py0000664000175000017500000010441414636513242017566 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import warnings import pytest try: from stellar_sdk import ( Account, Asset, AuthorizationFlag, MuxedAccount, Network, TransactionBuilder, TrustLineEntryFlag, ) from stellar_sdk.strkey import StrKey except ImportError: pytest.skip("stellar_sdk not installed", allow_module_level=True) from trezorlib import messages, stellar TX_SOURCE = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" SEQUENCE = 123456 TIMEBOUNDS_START = 461535181 TIMEBOUNDS_END = 1575234180 BASE_FEE = 200 def make_default_tx(default_op: bool = False, **kwargs) -> TransactionBuilder: source_account = Account(account=TX_SOURCE, sequence=SEQUENCE) default_params = { "source_account": source_account, "network_passphrase": Network.TESTNET_NETWORK_PASSPHRASE, "base_fee": BASE_FEE, } default_params.update(kwargs) builder = TransactionBuilder(**default_params) builder.add_time_bounds(TIMEBOUNDS_START, TIMEBOUNDS_END) if default_op: builder.append_manage_data_op(data_name="Trezor", data_value=b"Hello, Stellar") return builder def test_simple(): envelope = make_default_tx(default_op=True).build() tx, operations = stellar.from_envelope(envelope) assert tx.source_account == TX_SOURCE assert tx.fee == envelope.transaction.fee assert tx.sequence_number == SEQUENCE + 1 assert tx.timebounds_start is TIMEBOUNDS_START assert tx.timebounds_end is TIMEBOUNDS_END assert tx.memo_type == messages.StellarMemoType.NONE assert tx.memo_text is None assert tx.memo_id is None assert tx.memo_hash is None assert len(operations) == 1 def test_memo_text(): memo_text = "Have a nice day!" envelope = ( make_default_tx(default_op=True).add_text_memo(memo_text.encode()).build() ) tx, operations = stellar.from_envelope(envelope) assert tx.memo_type == messages.StellarMemoType.TEXT assert tx.memo_text == memo_text assert tx.memo_id is None assert tx.memo_hash is None def test_memo_id(): memo_id = 123456789 envelope = make_default_tx(default_op=True).add_id_memo(memo_id).build() tx, operations = stellar.from_envelope(envelope) assert tx.memo_type == messages.StellarMemoType.ID assert tx.memo_text is None assert tx.memo_id == memo_id assert tx.memo_hash is None def test_memo_hash(): memo_hash = "b77cd735095e1b58da2d7415c1f51f423a722b34d7d5002d8896608a9130a74b" envelope = ( make_default_tx(v1=False, default_op=True).add_hash_memo(memo_hash).build() ) tx, operations = stellar.from_envelope(envelope) assert tx.memo_type == messages.StellarMemoType.HASH assert tx.memo_text is None assert tx.memo_id is None assert tx.memo_hash.hex() == memo_hash def test_memo_return_hash(): memo_return = "b77cd735095e1b58da2d7415c1f51f423a722b34d7d5002d8896608a9130a74b" envelope = ( make_default_tx(v1=False, default_op=True) .add_return_hash_memo(memo_return) .build() ) tx, operations = stellar.from_envelope(envelope) assert tx.memo_type == messages.StellarMemoType.RETURN assert tx.memo_text is None assert tx.memo_id is None assert tx.memo_hash.hex() == memo_return def test_time_bounds_missing(): tx = make_default_tx(default_op=True) tx.time_bounds = None with warnings.catch_warnings(): # ignore warning about missing time bounds warnings.filterwarnings("ignore", message=r".*TimeBounds.*") envelope = tx.build() with pytest.raises(ValueError): stellar.from_envelope(envelope) def test_multiple_operations(): tx = make_default_tx() data_name = "Trezor" data_value = b"Hello, Stellar" operation1_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" destination = "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6" amount = "50.0111" asset_code = "XLM" asset_issuer = None operation2_source = "GBHWKBPP3O4H2BUUKSFXE4PK5WHLQYVZIZUNUJ4AU5VUZZEVBDMXISAS" envelope = ( tx.append_manage_data_op( data_name=data_name, data_value=data_value, source=operation1_source ) .append_payment_op( destination=destination, amount=amount, asset=Asset(asset_code, asset_issuer), source=operation2_source, ) .build() ) tx, operations = stellar.from_envelope(envelope) assert tx.source_account == TX_SOURCE assert tx.fee == envelope.transaction.fee assert tx.sequence_number == SEQUENCE + 1 assert tx.timebounds_start is TIMEBOUNDS_START assert tx.timebounds_end is TIMEBOUNDS_END assert tx.memo_type == messages.StellarMemoType.NONE assert tx.memo_text is None assert tx.memo_id is None assert tx.memo_hash is None assert len(operations) == 2 assert isinstance(operations[0], messages.StellarManageDataOp) assert operations[0].source_account == operation1_source assert operations[0].key == data_name assert operations[0].value == data_value assert isinstance(operations[1], messages.StellarPaymentOp) assert operations[1].source_account == operation2_source assert operations[1].destination_account == destination assert operations[1].asset.type == messages.StellarAssetType.NATIVE assert operations[1].asset.code is None assert operations[1].asset.issuer is None assert operations[1].amount == 500111000 def test_create_account(): tx = make_default_tx() destination = "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6" starting_balance = "100.0333" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_create_account_op( destination=destination, starting_balance=starting_balance, source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarCreateAccountOp) assert operations[0].source_account == operation_source assert operations[0].new_account == destination assert operations[0].starting_balance == 1000333000 def test_payment_native_asset(): tx = make_default_tx() destination = "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6" amount = "50.0111" asset_code = "XLM" asset_issuer = None operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_payment_op( destination=destination, amount=amount, asset=Asset(asset_code, asset_issuer), source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarPaymentOp) assert operations[0].source_account == operation_source assert operations[0].destination_account == destination assert operations[0].asset.type == messages.StellarAssetType.NATIVE assert operations[0].asset.code is None assert operations[0].asset.issuer is None assert operations[0].amount == 500111000 def test_payment_alpha4_asset(): tx = make_default_tx() destination = "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6" amount = "50.0111" asset_code = "USD" asset_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_payment_op( destination=destination, amount=amount, asset=Asset(asset_code, asset_issuer), source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarPaymentOp) assert operations[0].source_account == operation_source assert operations[0].destination_account == destination assert operations[0].asset.type == messages.StellarAssetType.ALPHANUM4 assert operations[0].asset.code == asset_code assert operations[0].asset.issuer == asset_issuer assert operations[0].amount == 500111000 def test_payment_alpha12_asset(): tx = make_default_tx() destination = "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6" amount = "50.0111" asset_code = "BANANA" asset_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_payment_op( destination=destination, amount=amount, asset=Asset(asset_code, asset_issuer), source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarPaymentOp) assert operations[0].source_account == operation_source assert operations[0].destination_account == destination assert operations[0].asset.type == messages.StellarAssetType.ALPHANUM12 assert operations[0].asset.code == asset_code assert operations[0].asset.issuer == asset_issuer assert operations[0].amount == 500111000 def test_path_payment_strict_receive(): tx = make_default_tx() destination = "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6" send_max = "50.0111" dest_amount = "100" send_code = "XLM" send_issuer = None dest_code = "USD" dest_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" path_asset1 = Asset( "JPY", "GD6PV7DXQJX7AGVXFQ2MTCLTCH6LR3E6IO2EO2YDZD7F7IOZZCCB5DSQ" ) path_asset2 = Asset( "BANANA", "GC7EKO37HNSKQ3V6RZ274EO7SFOWASQRHLX3OR5FIZK6UMV6LIEDXHGZ" ) envelope = tx.append_path_payment_strict_receive_op( destination=destination, send_asset=Asset(send_code, send_issuer), send_max=send_max, dest_asset=Asset(dest_code, dest_issuer), dest_amount=dest_amount, path=[path_asset1, path_asset2], source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarPathPaymentStrictReceiveOp) assert operations[0].source_account == operation_source assert operations[0].destination_account == destination assert operations[0].send_asset.type == messages.StellarAssetType.NATIVE assert operations[0].send_max == 500111000 assert operations[0].destination_amount == 1000000000 assert operations[0].destination_asset.type == messages.StellarAssetType.ALPHANUM4 assert operations[0].destination_asset.code == dest_code assert operations[0].destination_asset.issuer == dest_issuer assert len(operations[0].paths) == 2 assert operations[0].paths[0].type == messages.StellarAssetType.ALPHANUM4 assert operations[0].paths[0].code == path_asset1.code assert operations[0].paths[0].issuer == path_asset1.issuer assert operations[0].paths[1].type == messages.StellarAssetType.ALPHANUM12 assert operations[0].paths[1].code == path_asset2.code assert operations[0].paths[1].issuer == path_asset2.issuer def test_manage_sell_offer_new_offer(): tx = make_default_tx() price = "0.5" amount = "50.0111" selling_code = "XLM" selling_issuer = None buying_code = "USD" buying_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_manage_sell_offer_op( selling=Asset(selling_code, selling_issuer), buying=Asset(buying_code, buying_issuer), amount=amount, price=price, source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarManageSellOfferOp) assert operations[0].source_account == operation_source assert operations[0].selling_asset.type == messages.StellarAssetType.NATIVE assert operations[0].buying_asset.type == messages.StellarAssetType.ALPHANUM4 assert operations[0].buying_asset.code == buying_code assert operations[0].buying_asset.issuer == buying_issuer assert operations[0].amount == 500111000 assert operations[0].price_n == 1 assert operations[0].price_d == 2 assert operations[0].offer_id == 0 # indicates a new offer def test_manage_sell_offer_update_offer(): tx = make_default_tx() price = "0.5" amount = "50.0111" selling_code = "XLM" selling_issuer = None buying_code = "USD" buying_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" offer_id = 12345 operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_manage_sell_offer_op( selling=Asset(selling_code, selling_issuer), buying=Asset(buying_code, buying_issuer), amount=amount, price=price, offer_id=offer_id, source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarManageSellOfferOp) assert operations[0].source_account == operation_source assert operations[0].selling_asset.type == messages.StellarAssetType.NATIVE assert operations[0].buying_asset.type == messages.StellarAssetType.ALPHANUM4 assert operations[0].buying_asset.code == buying_code assert operations[0].buying_asset.issuer == buying_issuer assert operations[0].amount == 500111000 assert operations[0].price_n == 1 assert operations[0].price_d == 2 assert operations[0].offer_id == offer_id def test_create_passive_sell_offer(): tx = make_default_tx() price = "0.5" amount = "50.0111" selling_code = "XLM" selling_issuer = None buying_code = "USD" buying_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_create_passive_sell_offer_op( selling=Asset(selling_code, selling_issuer), buying=Asset(buying_code, buying_issuer), amount=amount, price=price, source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarCreatePassiveSellOfferOp) assert operations[0].source_account == operation_source assert operations[0].selling_asset.type == messages.StellarAssetType.NATIVE assert operations[0].buying_asset.type == messages.StellarAssetType.ALPHANUM4 assert operations[0].buying_asset.code == buying_code assert operations[0].buying_asset.issuer == buying_issuer assert operations[0].amount == 500111000 assert operations[0].price_n == 1 assert operations[0].price_d == 2 def test_set_options(): tx = make_default_tx() operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" inflation_dest = "GAXN7HZQTHIPW7N2HGPAXMR42LPJ5VLYXMCCOX4D3JC4CQZGID3UYUPF" clear_flags = AuthorizationFlag.AUTHORIZATION_REQUIRED set_flags = ( AuthorizationFlag.AUTHORIZATION_IMMUTABLE | AuthorizationFlag.AUTHORIZATION_REVOCABLE ) master_weight = 255 low_threshold = 10 med_threshold = 20 high_threshold = 30 home_domain = "example.com" envelope = tx.append_set_options_op( inflation_dest=inflation_dest, clear_flags=clear_flags, set_flags=set_flags, master_weight=master_weight, low_threshold=low_threshold, med_threshold=med_threshold, high_threshold=high_threshold, home_domain=home_domain, source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarSetOptionsOp) assert operations[0].source_account == operation_source assert operations[0].inflation_destination_account == inflation_dest assert operations[0].clear_flags == clear_flags assert operations[0].set_flags == set_flags assert operations[0].master_weight == master_weight assert operations[0].low_threshold == low_threshold assert operations[0].medium_threshold == med_threshold assert operations[0].high_threshold == high_threshold assert operations[0].home_domain == home_domain assert operations[0].signer_type is None assert operations[0].signer_key is None assert operations[0].signer_weight is None def test_set_options_ed25519_signer(): tx = make_default_tx() signer = "GAXN7HZQTHIPW7N2HGPAXMR42LPJ5VLYXMCCOX4D3JC4CQZGID3UYUPF" weight = 10 operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_ed25519_public_key_signer( account_id=signer, weight=weight, source=operation_source ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarSetOptionsOp) assert operations[0].source_account == operation_source assert operations[0].inflation_destination_account is None assert operations[0].clear_flags is None assert operations[0].set_flags is None assert operations[0].master_weight is None assert operations[0].low_threshold is None assert operations[0].medium_threshold is None assert operations[0].high_threshold is None assert operations[0].home_domain is None assert operations[0].signer_type == messages.StellarSignerType.ACCOUNT assert operations[0].signer_key == StrKey.decode_ed25519_public_key(signer) assert operations[0].signer_weight == weight def test_set_options_pre_auth_tx_signer(): tx = make_default_tx() signer = bytes.fromhex( "2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cfa4fc5a000" ) weight = 30 operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_pre_auth_tx_signer( pre_auth_tx_hash=signer, weight=weight, source=operation_source ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarSetOptionsOp) assert operations[0].signer_type == messages.StellarSignerType.PRE_AUTH assert operations[0].signer_key == signer assert operations[0].signer_weight == weight def test_set_options_hashx_signer(): tx = make_default_tx() signer = bytes.fromhex( "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8000" ) weight = 20 operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_hashx_signer( sha256_hash=signer, weight=weight, source=operation_source ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarSetOptionsOp) assert operations[0].signer_type == messages.StellarSignerType.HASH assert operations[0].signer_key == signer assert operations[0].signer_weight == weight def test_change_trust(): tx = make_default_tx() asset_code = "USD" asset_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" limit = "1000" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_change_trust_op( asset=Asset(asset_code, asset_issuer), limit=limit, source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarChangeTrustOp) assert operations[0].source_account == operation_source assert operations[0].asset.type == messages.StellarAssetType.ALPHANUM4 assert operations[0].asset.code == asset_code assert operations[0].asset.issuer == asset_issuer assert operations[0].limit == 10000000000 def test_allow_trust(): tx = make_default_tx() asset_code = "USD" trustor = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" with warnings.catch_warnings(): # ignore warnings about append_trust_line_flags being a deprecated op, # Trezor doesn't currently support the alternative warnings.filterwarnings("ignore", message=r".*append_set_trust_line_flags_op.*") warnings.filterwarnings("ignore", message=r".*SetTrustLineFlags.*") envelope = tx.append_allow_trust_op( trustor=trustor, asset_code=asset_code, authorize=TrustLineEntryFlag.AUTHORIZED_FLAG, source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarAllowTrustOp) assert operations[0].source_account == operation_source assert operations[0].asset_type == messages.StellarAssetType.ALPHANUM4 assert operations[0].asset_code == asset_code assert operations[0].trusted_account == trustor assert operations[0].is_authorized is True def test_account_merge(): tx = make_default_tx() destination = "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_account_merge_op( destination=destination, source=operation_source ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarAccountMergeOp) assert operations[0].source_account == operation_source assert operations[0].destination_account == destination def test_manage_data(): tx = make_default_tx() data_name = "Trezor" data_value = b"Hello, Stellar" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_manage_data_op( data_name=data_name, data_value=data_value, source=operation_source ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarManageDataOp) assert operations[0].source_account == operation_source assert operations[0].key == data_name assert operations[0].value == data_value def test_manage_data_remove_data_entity(): tx = make_default_tx() data_name = "Trezor" data_value = None # remove data entity operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_manage_data_op( data_name=data_name, data_value=data_value, source=operation_source ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarManageDataOp) assert operations[0].source_account == operation_source assert operations[0].key == data_name assert operations[0].value is None def test_bump_sequence(): tx = make_default_tx() bump_to = 143487250972278900 operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_bump_sequence_op( bump_to=bump_to, source=operation_source ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarBumpSequenceOp) assert operations[0].source_account == operation_source assert operations[0].bump_to == bump_to def test_manage_buy_offer_new_offer(): tx = make_default_tx() price = "0.5" amount = "50.0111" selling_code = "XLM" selling_issuer = None buying_code = "USD" buying_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_manage_buy_offer_op( selling=Asset(selling_code, selling_issuer), buying=Asset(buying_code, buying_issuer), amount=amount, price=price, source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarManageBuyOfferOp) assert operations[0].source_account == operation_source assert operations[0].selling_asset.type == messages.StellarAssetType.NATIVE assert operations[0].buying_asset.type == messages.StellarAssetType.ALPHANUM4 assert operations[0].buying_asset.code == buying_code assert operations[0].buying_asset.issuer == buying_issuer assert operations[0].amount == 500111000 assert operations[0].price_n == 1 assert operations[0].price_d == 2 assert operations[0].offer_id == 0 # indicates a new offer def test_manage_buy_offer_update_offer(): tx = make_default_tx() price = "0.5" amount = "50.0111" selling_code = "XLM" selling_issuer = None buying_code = "USD" buying_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" offer_id = 12345 operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_manage_buy_offer_op( selling=Asset(selling_code, selling_issuer), buying=Asset(buying_code, buying_issuer), amount=amount, price=price, offer_id=offer_id, source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarManageBuyOfferOp) assert operations[0].source_account == operation_source assert operations[0].selling_asset.type == messages.StellarAssetType.NATIVE assert operations[0].buying_asset.type == messages.StellarAssetType.ALPHANUM4 assert operations[0].buying_asset.code == buying_code assert operations[0].buying_asset.issuer == buying_issuer assert operations[0].amount == 500111000 assert operations[0].price_n == 1 assert operations[0].price_d == 2 assert operations[0].offer_id == offer_id def test_path_payment_strict_send(): tx = make_default_tx() destination = "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6" send_amount = "50.0112" dest_min = "120" send_code = "XLM" send_issuer = None dest_code = "USD" dest_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" path_asset1 = Asset( "JPY", "GD6PV7DXQJX7AGVXFQ2MTCLTCH6LR3E6IO2EO2YDZD7F7IOZZCCB5DSQ" ) path_asset2 = Asset( "BANANA", "GC7EKO37HNSKQ3V6RZ274EO7SFOWASQRHLX3OR5FIZK6UMV6LIEDXHGZ" ) envelope = tx.append_path_payment_strict_send_op( destination=destination, send_asset=Asset(send_code, send_issuer), send_amount=send_amount, dest_asset=Asset(dest_code, dest_issuer), dest_min=dest_min, path=[path_asset1, path_asset2], source=operation_source, ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarPathPaymentStrictSendOp) assert operations[0].source_account == operation_source assert operations[0].destination_account == destination assert operations[0].send_asset.type == messages.StellarAssetType.NATIVE assert operations[0].send_amount == 500112000 assert operations[0].destination_min == 1200000000 assert operations[0].destination_asset.type == messages.StellarAssetType.ALPHANUM4 assert operations[0].destination_asset.code == dest_code assert operations[0].destination_asset.issuer == dest_issuer assert len(operations[0].paths) == 2 assert operations[0].paths[0].type == messages.StellarAssetType.ALPHANUM4 assert operations[0].paths[0].code == path_asset1.code assert operations[0].paths[0].issuer == path_asset1.issuer assert operations[0].paths[1].type == messages.StellarAssetType.ALPHANUM12 assert operations[0].paths[1].code == path_asset2.code assert operations[0].paths[1].issuer == path_asset2.issuer def test_payment_muxed_account_not_support_raise(): tx = make_default_tx() destination = MuxedAccount( "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6", 1 ) amount = "50.0111" asset_code = "XLM" asset_issuer = None operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_payment_op( destination=destination, amount=amount, asset=Asset(asset_code, asset_issuer), source=operation_source, ).build() with pytest.raises(ValueError, match="MuxedAccount is not supported"): stellar.from_envelope(envelope) def test_path_payment_strict_send_muxed_account_not_support_raise(): tx = make_default_tx() destination = MuxedAccount( "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6", 1 ) send_amount = "50.0112" dest_min = "120" send_code = "XLM" send_issuer = None dest_code = "USD" dest_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" path_asset1 = Asset( "JPY", "GD6PV7DXQJX7AGVXFQ2MTCLTCH6LR3E6IO2EO2YDZD7F7IOZZCCB5DSQ" ) path_asset2 = Asset( "BANANA", "GC7EKO37HNSKQ3V6RZ274EO7SFOWASQRHLX3OR5FIZK6UMV6LIEDXHGZ" ) envelope = tx.append_path_payment_strict_send_op( destination=destination, send_asset=Asset(send_code, send_issuer), send_amount=send_amount, dest_asset=Asset(dest_code, dest_issuer), dest_min=dest_min, path=[path_asset1, path_asset2], source=operation_source, ).build() with pytest.raises(ValueError, match="MuxedAccount is not supported"): stellar.from_envelope(envelope) def test_path_payment_strict_receive_muxed_account_not_support_raise(): tx = make_default_tx() destination = MuxedAccount( "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6", 1 ) send_max = "50.0111" dest_amount = "100" send_code = "XLM" send_issuer = None dest_code = "USD" dest_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF" operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" path_asset1 = Asset( "JPY", "GD6PV7DXQJX7AGVXFQ2MTCLTCH6LR3E6IO2EO2YDZD7F7IOZZCCB5DSQ" ) path_asset2 = Asset( "BANANA", "GC7EKO37HNSKQ3V6RZ274EO7SFOWASQRHLX3OR5FIZK6UMV6LIEDXHGZ" ) envelope = tx.append_path_payment_strict_receive_op( destination=destination, send_asset=Asset(send_code, send_issuer), send_max=send_max, dest_asset=Asset(dest_code, dest_issuer), dest_amount=dest_amount, path=[path_asset1, path_asset2], source=operation_source, ).build() with pytest.raises(ValueError, match="MuxedAccount is not supported"): stellar.from_envelope(envelope) def test_account_merge_muxed_account_not_support_raise(): tx = make_default_tx() destination = MuxedAccount( "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6", 1 ) operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_account_merge_op( destination=destination, source=operation_source ).build() with pytest.raises(ValueError, match="MuxedAccount is not supported"): stellar.from_envelope(envelope) def test_op_source_muxed_account_not_support_raise(): tx = make_default_tx() destination = "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6" amount = "50.0111" asset_code = "XLM" asset_issuer = None operation_source = MuxedAccount( "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V", 2 ) envelope = tx.append_payment_op( destination=destination, amount=amount, asset=Asset(asset_code, asset_issuer), source=operation_source, ).build() with pytest.raises(ValueError, match="MuxedAccount is not supported"): stellar.from_envelope(envelope) def test_tx_source_muxed_account_not_support_raise(): source_account = Account(account=MuxedAccount(TX_SOURCE, 123456), sequence=SEQUENCE) destination = "GDNSSYSCSSJ76FER5WEEXME5G4MTCUBKDRQSKOYP36KUKVDB2VCMERS6" amount = "50.0111" asset_code = "XLM" asset_issuer = None operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = ( TransactionBuilder( source_account=source_account, network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE, base_fee=BASE_FEE, ) .add_time_bounds(TIMEBOUNDS_START, TIMEBOUNDS_END) .append_payment_op( destination=destination, amount=amount, asset=Asset(asset_code, asset_issuer), source=operation_source, ) .build() ) with pytest.raises(ValueError, match="MuxedAccount is not supported"): stellar.from_envelope(envelope) def test_claim_claimable_balance(): tx = make_default_tx() balance_id = ( "00000000178826fbfe339e1f5c53417c6fedfe2c05e8bec14303143ec46b38981b09c3f9" ) operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V" envelope = tx.append_claim_claimable_balance_op( balance_id=balance_id, source=operation_source ).build() tx, operations = stellar.from_envelope(envelope) assert len(operations) == 1 assert isinstance(operations[0], messages.StellarClaimClaimableBalanceOp) assert operations[0].source_account == operation_source assert operations[0].balance_id == bytes.fromhex(balance_id) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tests/test_tools.py0000664000175000017500000000777714636513242017276 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import pytest from trezorlib import tools VECTORS = ( # descriptor, checksum ( "pkh([5c9e228d/44h/0h/0h]xpub6BiVtCpG9fQPxnPmHXG8PhtzQdWC2Su4qWu6XW9tpWFYhxydCLJGrWBJZ5H6qTAHdPQ7pQhtpjiYZVZARo14qHiay2fvrX996oEP42u8wZy/0/*)", "lnrxp640", ), ( "pkh([5c9e228d/44h/0h/0h]xpub6BiVtCpG9fQPxnPmHXG8PhtzQdWC2Su4qWu6XW9tpWFYhxydCLJGrWBJZ5H6qTAHdPQ7pQhtpjiYZVZARo14qHiay2fvrX996oEP42u8wZy/1/*)", "w8x8u09h", ), ( "sh(wpkh([5c9e228d/49h/0h/0h]xpub6CVKsQYXc9awxgV1tWbG4foDvdcnieK2JkbpPEBKB5WwAPKBZ1mstLbKVB4ov7QzxzjaxNK6EfmNY5Jsk2cG26EVcEkycGW4tchT2dyUhrx/0/*))", "kx6mu23v", ), ( "sh(wpkh([5c9e228d/49h/0h/0h]xpub6CVKsQYXc9awxgV1tWbG4foDvdcnieK2JkbpPEBKB5WwAPKBZ1mstLbKVB4ov7QzxzjaxNK6EfmNY5Jsk2cG26EVcEkycGW4tchT2dyUhrx/1/*))", "r85dy4yn", ), ( "wpkh([5c9e228d/84h/0h/0h]xpub6DDUPHpUo4pcy43iJeZjbSVWGav1SMMmuWdMHiGtkK8rhKmfbomtkwW6GKs1GGAKehT6QRocrmda3WWxXawpjmwaUHfFRXuKrXSapdckEYF/0/*)", "vyj8qz0q", ), ( "wpkh([5c9e228d/84h/0h/0h]xpub6DDUPHpUo4pcy43iJeZjbSVWGav1SMMmuWdMHiGtkK8rhKmfbomtkwW6GKs1GGAKehT6QRocrmda3WWxXawpjmwaUHfFRXuKrXSapdckEYF/1/*)", "ashxahlc", ), ) @pytest.mark.parametrize("descriptor, checksum", VECTORS) def test_descriptor_checksum(descriptor, checksum): assert tools.descriptor_checksum(descriptor) == checksum BASE58_VECTORS = ( # data_hex, encoding_b58 ("", ""), ("61", "2g"), ("626262", "a3gV"), ("636363", "aPEr"), ("73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"), ( "00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L", ), ("516b6fcd0f", "ABnLTmg"), ("bf4f89001e670274dd", "3SEo3LWLoPntC"), ("572e4794", "3EFU7m"), ("ecac89cad93923c02321", "EJDM8drfXA6uyA"), ("10c8511e", "Rt5zm"), ("00000000000000000000", "1111111111"), ("00" * 32, "11111111111111111111111111111111"), ( "000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252ada93ff480d6dd43dc62a641155a5", "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", ), ( "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY", ), ) @pytest.mark.parametrize("data_hex,encoding_b58", BASE58_VECTORS) def test_b58encode(data_hex, encoding_b58): assert tools.b58encode(bytes.fromhex(data_hex)) == encoding_b58 @pytest.mark.parametrize("data_hex,encoding_b58", BASE58_VECTORS) def test_b58decode(data_hex, encoding_b58): assert tools.b58decode(encoding_b58).hex() == data_hex ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tests/test_transport.py0000664000175000017500000000345614636513242020160 0ustar00matejcikmatejcik# This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import importlib from unittest import mock from trezorlib.transport import all_transports from trezorlib.transport.bridge import BridgeTransport def test_disabled_transport(): assert BridgeTransport.ENABLED assert BridgeTransport in all_transports() BridgeTransport.ENABLED = False assert BridgeTransport not in all_transports() # re-enable BridgeTransport.ENABLED = True def test_import_all_transports(): from trezorlib.transport.bridge import BridgeTransport from trezorlib.transport.hid import HidTransport from trezorlib.transport.udp import UdpTransport from trezorlib.transport.webusb import WebUsbTransport assert BridgeTransport assert HidTransport assert WebUsbTransport assert UdpTransport def test_transport_dependencies(): import trezorlib.transport.hid as hid_transport with mock.patch.dict("sys.modules", {"hid": None}): importlib.reload(hid_transport) assert not hid_transport.HidTransport.ENABLED with mock.patch.dict("sys.modules", {"hid": mock.Mock()}): importlib.reload(hid_transport) assert hid_transport.HidTransport.ENABLED ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1719315826.138107 trezor-0.13.9/tools/0000775000175000017500000000000014636526562014512 5ustar00matejcikmatejcik././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/build_tx.py0000775000175000017500000001504414636513242016674 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import decimal import json from typing import Any, Dict, List, Optional, Tuple import click import requests from trezorlib import btc, messages, tools from trezorlib.cli import ChoiceType from trezorlib.cli.btc import INPUT_SCRIPTS, OUTPUT_SCRIPTS from trezorlib.protobuf import to_dict SESSION = requests.Session() SESSION.headers.update({"User-Agent": "trezorlib"}) # the following script type mapping is only valid for single-sig Trezor-generated utxos BITCOIN_CORE_INPUT_TYPES = { "pubkeyhash": messages.InputScriptType.SPENDADDRESS, "scripthash": messages.InputScriptType.SPENDP2SHWITNESS, "witness_v0_keyhash": messages.InputScriptType.SPENDWITNESS, "witness_v1_taproot": messages.InputScriptType.SPENDTAPROOT, } def echo(*args: Any, **kwargs: Any): return click.echo(*args, err=True, **kwargs) def prompt(*args: Any, **kwargs: Any): return click.prompt(*args, err=True, **kwargs) def _default_script_type(address_n: Optional[List[int]], script_types: Any) -> str: script_type = "address" if address_n is None: pass elif address_n[0] == tools.H_(49): script_type = "p2shsegwit" elif address_n[0] == tools.H_(84): script_type = "segwit" return script_type # return script_types[script_type] def parse_vin(s: str) -> Tuple[bytes, int]: txid, vout = s.split(":") return bytes.fromhex(txid), int(vout) def _get_inputs_interactive( blockbook_url: str, ) -> Tuple[List[messages.TxInputType], Dict[str, messages.TransactionType]]: inputs: List[messages.TxInputType] = [] txes: Dict[str, messages.TransactionType] = {} while True: echo() prev = prompt( "Previous output to spend (txid:vout)", type=parse_vin, default="" ) if not prev: break prev_hash, prev_index = prev txhash = prev_hash.hex() tx_url = blockbook_url + txhash r = SESSION.get(tx_url) if not r.ok: raise click.ClickException(f"Failed to fetch URL: {tx_url}") tx_json = r.json(parse_float=decimal.Decimal) if "error" in tx_json: raise click.ClickException(f"Transaction not found: {txhash}") tx = btc.from_json(tx_json) txes[txhash] = tx try: from_address = tx_json["vout"][prev_index]["scriptPubKey"]["address"] echo(f"From address: {from_address}") except Exception: pass amount = tx.bin_outputs[prev_index].amount echo(f"Input amount: {amount}") address_n = prompt("BIP-32 path to derive the key", type=tools.parse_path) reported_type = tx_json["vout"][prev_index]["scriptPubKey"].get("type") if reported_type in BITCOIN_CORE_INPUT_TYPES: script_type = BITCOIN_CORE_INPUT_TYPES[reported_type] click.echo(f"Script type: {script_type.name}") else: script_type = prompt( "Input type", type=ChoiceType(INPUT_SCRIPTS), default=_default_script_type(address_n, INPUT_SCRIPTS), ) if isinstance(script_type, str): script_type = INPUT_SCRIPTS[script_type] sequence = prompt( "Sequence Number to use (RBF opt-in enabled by default)", type=int, default=0xFFFFFFFD, ) new_input = messages.TxInputType( address_n=address_n, prev_hash=prev_hash, prev_index=prev_index, amount=amount, script_type=script_type, sequence=sequence, ) inputs.append(new_input) return inputs, txes def _get_outputs_interactive() -> List[messages.TxOutputType]: outputs: List[messages.TxOutputType] = [] while True: echo() address = prompt("Output address (for non-change output)", default="") if address: address_n = None script_type = messages.OutputScriptType.PAYTOADDRESS else: address = None address_n = prompt( "BIP-32 path (for change output)", type=tools.parse_path, default="" ) if not address_n: break script_type = prompt( "Output type", type=ChoiceType(OUTPUT_SCRIPTS), default=_default_script_type(address_n, OUTPUT_SCRIPTS), ) if isinstance(script_type, str): script_type = OUTPUT_SCRIPTS[script_type] amount = prompt("Amount to spend (satoshis)", type=int) outputs.append( messages.TxOutputType( address_n=address_n, address=address, amount=amount, script_type=script_type, ) ) return outputs @click.command() def sign_interactive() -> None: coin = prompt("Coin name", default="Bitcoin") blockbook_host = prompt("Blockbook server", default="btc1.trezor.io") if not SESSION.get(f"https://{blockbook_host}/api/block/1").ok: raise click.ClickException("Could not connect to blockbook") blockbook_url = f"https://{blockbook_host}/api/tx-specific/" inputs, txes = _get_inputs_interactive(blockbook_url) outputs = _get_outputs_interactive() version = prompt("Transaction version", type=int, default=2) lock_time = prompt("Transaction locktime", type=int, default=0) result = { "coin_name": coin, "inputs": [to_dict(i, hexlify_bytes=True) for i in inputs], "outputs": [to_dict(o, hexlify_bytes=True) for o in outputs], "details": { "version": version, "lock_time": lock_time, }, "prev_txes": { txhash: to_dict(txdata, hexlify_bytes=True) for txhash, txdata in txes.items() }, } print(json.dumps(result, sort_keys=True, indent=2)) if __name__ == "__main__": sign_interactive() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/deserialize_tx.py0000775000175000017500000000566214636513242020102 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import os import sys from typing import Any, Optional try: import construct as c from construct import len_, this except ImportError: sys.stderr.write( "This tool requires Construct. Install it with 'pip install Construct'.\n" ) sys.exit(1) 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: int, context: Any, path: Any) -> dict: 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: dict, context: Any, path: Any): return obj["ext"] or obj["base"] class ConstFlag(c.Adapter): def __init__(self, const: bytes) -> None: self.const = const super().__init__(c.Optional(c.Const(const))) def _encode(self, obj: Any, context: Any, path: Any) -> Optional[bytes]: return self.const if obj else None def _decode(self, obj: Any, context: Any, path: Any) -> bool: 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)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/encfs_aes_getpass.py0000775000175000017500000001076114636513242020537 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . """ Use Trezor as a hardware key for opening EncFS filesystem! Usage: encfs --standard --extpass=./encfs_aes_getpass.py ~/.crypt ~/crypt """ import hashlib import json import os import sys from typing import TYPE_CHECKING, Sequence import trezorlib import trezorlib.misc from trezorlib.client import TrezorClient from trezorlib.tools import Address from trezorlib.transport import enumerate_devices from trezorlib.ui import ClickUI version_tuple = tuple(map(int, trezorlib.__version__.split("."))) if not (0, 11) <= version_tuple < (0, 14): raise RuntimeError("trezorlib version mismatch (required: 0.13, 0.12, or 0.11)") if TYPE_CHECKING: from trezorlib.transport import Transport def wait_for_devices() -> Sequence["Transport"]: 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: Sequence["Transport"]) -> "Transport": 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("[-] \n") continue if client.features.label: sys.stderr.write(f"[{i}] {client.features.label}\n") else: sys.stderr.write(f"[{i}] \n") 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() -> None: 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 = Address([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() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/firmware-fingerprint.py0000775000175000017500000000246014636513242021221 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import sys from typing import BinaryIO, TextIO import click from trezorlib._internal import firmware_headers @click.command() @click.argument("filename", type=click.File("rb")) @click.option("-o", "--output", type=click.File("w"), default="-") def firmware_fingerprint(filename: BinaryIO, output: TextIO) -> None: """Display fingerprint of a firmware file.""" data = filename.read() try: click.echo(firmware_headers.parse_image(data).digest().hex(), file=output) except Exception as e: click.echo(e, err=True) sys.exit(2) if __name__ == "__main__": firmware_fingerprint() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/helloworld.py0000775000175000017500000000231114636513242017226 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . from trezorlib import btc from trezorlib.client import get_default_client from trezorlib.tools import parse_path def main() -> None: # 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 bip32_path = parse_path("44h/0h/0h/0/0") address = btc.get_address(client, "Bitcoin", bip32_path, True) print("Bitcoin address:", address) if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/mem_flashblock.py0000775000175000017500000000400114636513242020017 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import sys from trezorlib.debuglink import DebugLink from trezorlib.transport import enumerate_devices # 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() -> DebugLink: 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() -> None: 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() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/mem_read.py0000775000175000017500000000360314636513242016631 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import sys from trezorlib.debuglink import DebugLink from trezorlib.transport import enumerate_devices # 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() -> DebugLink: 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() -> None: 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() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/mem_write.py0000775000175000017500000000252014636513242017045 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import sys from trezorlib.debuglink import DebugLink from trezorlib.transport import enumerate_devices def find_debug() -> DebugLink: 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() -> None: debug = find_debug() debug.memory_write(int(sys.argv[1], 16), bytes.fromhex(sys.argv[2]), flash=True) if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/mnemonic_check.py0000775000175000017500000000560514636513242020026 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . 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: int, internal_entropy: bytes, external_entropy: bytes ) -> bytes: """ 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() -> None: 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() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/pwd_reader.py0000775000175000017500000001324614636513242017200 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . import hashlib import hmac import json import os from typing import Tuple from urllib.parse import urlparse from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from trezorlib import misc, ui from trezorlib.client import TrezorClient from trezorlib.tools import parse_path from trezorlib.transport import get_transport # Return path by BIP-32 BIP32_PATH = parse_path("10016h/0") # Deriving master key def getMasterKey(client: TrezorClient) -> str: 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: str) -> Tuple[str, str, str]: 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: str, key: str) -> dict: 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: str = "" 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: str, val: bytes) -> dict: 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: str = "" 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: TrezorClient, entry: dict) -> str: 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 = f"Unlock {item} for user {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: dict) -> None: print("Password entries") print("================") print() for k, v in entries.items(): print(f"Entry id: #{k}") print("-------------") for kk, vv in v.items(): if kk in ["nonce", "safe_note", "password"]: continue # skip these fields print("*", kk, ": ", vv) print() def main() -> None: 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))) if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/pybridge.py0000664000175000017500000002311614636513242016663 0ustar00matejcikmatejcik#!/usr/bin/env python3 # ### INSTRUCTIONS FOR USE #### # # 1. install Python 3.7 and up # 2. make sure you have `pip3` command available # 3. from command line, run the following: # pip3 install trezor[hidapi] gevent bottle # 4. (ONLY for TT or T1 >= 1.8.0) Make sure you have libusb available. # 4a. on Windows, download: # https://github.com/libusb/libusb/releases/download/v1.0.26/libusb-1.0.26-binaries.7z # Extract file VS2015-x64/dll/libusb-1.0.dll and place it in your working directory. # 4b. on MacOS, assuming you have Homebrew, run `brew install libusb` # Otherwise download the above, extract the file macos_/lib/libusb.1.0.0.dylib # and place it in your working directory. # 4c. on Linux, use your package manager to install `libusb` or `libusb-1.0` package. # (but on Linux you most likely already have it) # 4. Shut down Trezor Suite (and bridge if you are running it separately # 5. Disconnect and then reconnect your Trezor. # 6. Run the following command from the command line: # python3 pybridge.py # 7. Start Suite again, or use any other Trezor-compatible software. # 8. Output of pybridge goes to console and also to file `pybridge.log` from __future__ import annotations from gevent import monkey monkey.patch_all() import json import logging import struct import time import typing as t import click from bottle import post, request, response, run import trezorlib.mapping import trezorlib.models import trezorlib.transport from trezorlib.client import TrezorClient from trezorlib.protobuf import format_message from trezorlib.transport.bridge import BridgeTransport from trezorlib.ui import TrezorClientUI # ignore bridge. we are the bridge BridgeTransport.ENABLED = False logging.basicConfig( level=logging.DEBUG, format="%(asctime)s %(levelname)s %(message)s", handlers=[ logging.FileHandler("pybridge.log"), logging.StreamHandler(), ], ) LOG = logging.getLogger() class SilentUI(TrezorClientUI): def get_pin(self, _code: t.Any) -> str: return "" def get_passphrase(self) -> str: return "" def button_request(self, _br: t.Any) -> None: pass class Session: SESSION_COUNTER = 0 SESSIONS: dict[str, Session] = {} def __init__(self, transport: Transport) -> None: self.id = self._new_id() self.transport = transport self.SESSIONS[self.id] = self def release(self) -> None: self.SESSIONS.pop(self.id, None) self.transport.release() @classmethod def find(cls, sid: str) -> Session | None: return cls.SESSIONS.get(sid) @classmethod def _new_id(cls) -> str: id = str(cls.SESSION_COUNTER) cls.SESSION_COUNTER += 1 return id class Transport: TRANSPORT_COUNTER = 0 TRANSPORTS: dict[str, Transport] = {} def __init__(self, transport: trezorlib.transport.Transport) -> None: self.path = transport.get_path() self.session: Session | None = None self.transport = transport client = TrezorClient(transport, ui=SilentUI()) self.model = ( trezorlib.models.by_name(client.features.model) or trezorlib.models.TREZOR_T ) client.end_session() def acquire(self, sid: str) -> str: if self.session_id() != sid: raise Exception("Session mismatch") if self.session is not None: self.session.release() self.session = Session(self) self.transport.begin_session() return self.session.id def release(self) -> None: self.transport.end_session() self.session = None def session_id(self) -> str | None: if self.session is not None: return self.session.id else: return None def to_json(self) -> dict: vid, pid = next(iter(self.model.usb_ids), (0, 0)) return { "debug": False, "debugSession": None, "path": self.path, "product": pid, "vendor": vid, "session": self.session_id(), } def write(self, msg_id: int, data: bytes) -> None: self.transport.write(msg_id, data) def read(self) -> tuple[int, bytes]: return self.transport.read() @classmethod def find(cls, path: str) -> Transport | None: return cls.TRANSPORTS.get(path) @classmethod def enumerate(cls) -> t.Iterable[Transport]: transports = {t.get_path(): t for t in trezorlib.transport.enumerate_devices()} for path in transports: if path not in cls.TRANSPORTS: cls.TRANSPORTS[path] = Transport(transports[path]) for path in list(cls.TRANSPORTS): if path not in transports: cls.TRANSPORTS.pop(path, None) return cls.TRANSPORTS.values() FILTERS: dict[int, t.Callable[[int, bytes], tuple[int, bytes]]] = {} def log_message(prefix: str, msg_id: int, data: bytes) -> None: try: msg = trezorlib.mapping.DEFAULT_MAPPING.decode(msg_id, data) LOG.info("=== %s: [%s] %s", prefix, msg_id, format_message(msg)) except Exception: LOG.info("=== %s: [%s] undecoded bytes %s", prefix, msg_id, data.hex()) def decode_data(hex_data: str) -> tuple[int, bytes]: data = bytes.fromhex(hex_data) headerlen = struct.calcsize(">HL") msg_type, datalen = struct.unpack(">HL", data[:headerlen]) return msg_type, data[headerlen : headerlen + datalen] def encode_data(msg_type: int, msg_data: bytes) -> str: data = struct.pack(">HL", msg_type, len(msg_data)) + msg_data return data.hex() def check_origin() -> None: response.set_header("Access-Control-Allow-Origin", "*") @post("/") # type: ignore [Untyped function decorator] def index(): check_origin() return {"version": "2.0.27"} @post("/configure") # type: ignore [Untyped function decorator] def do_configure(): return index() @post("/enumerate") # type: ignore [Untyped function decorator] def do_enumerate(): check_origin() trezor_json = [transport.to_json() for transport in Transport.enumerate()] return json.dumps(trezor_json) @post("/acquire//") # type: ignore [Untyped function decorator] def do_acquire(path: str, sid: str): check_origin() if sid == "null": sid = None # type: ignore [is incompatible with declared type] trezor = Transport.find(path) if trezor is None: response.status = 404 return {"error": "invalid path"} try: return {"session": trezor.acquire(sid)} except Exception: response.status = 400 return {"error": "wrong previous session"} @post("/release/") # type: ignore [Untyped function decorator] def do_release(sid: str): check_origin() session = Session.find(sid) if session is None: response.status = 404 return {"error": "invalid session"} session.release() return {"session": sid} @post("/call/") # type: ignore [Untyped function decorator] def do_call(sid: str): check_origin() session = Session.find(sid) if session is None: response.status = 404 return {"error": "invalid session"} msg_type, msg_data = decode_data(request.body.read().decode()) if msg_type in FILTERS: msg_type, msg_data = FILTERS[msg_type](msg_type, msg_data) log_message("CALLING", msg_type, msg_data) session.transport.write(msg_type, msg_data) resp_type, resp_data = session.transport.read() if resp_type in FILTERS: resp_type, resp_data = FILTERS[resp_type](resp_type, resp_data) log_message("RESPONSE", resp_type, resp_data) return encode_data(resp_type, resp_data) @post("/post/") # type: ignore [Untyped function decorator] def do_post(sid: str): check_origin() session = Session.find(sid) if session is None: response.status = 404 return {"error": "invalid session"} msg_type, msg_data = decode_data(request.body.read().decode()) session.transport.write(msg_type, msg_data) return {"session": sid} @post("/read/") # type: ignore [Untyped function decorator] def do_read(sid: str): check_origin() session = Session.find(sid) if session is None: response.status = 404 return {"error": "invalid session"} resp_type, resp_data = session.transport.read() print("=== RESPONSE:") msg = trezorlib.mapping.DEFAULT_MAPPING.decode(resp_type, resp_data) print(format_message(msg)) return encode_data(resp_type, resp_data) @post("/listen") # type: ignore [Untyped function decorator] def do_listen(): check_origin() try: data = json.load(request.body) except Exception: response.status = 400 return {"error": "invalid json"} for _ in range(10): trezor_json = [transport.to_json() for transport in Transport.enumerate()] if trezor_json != data: # `yield` turns the function into a generator which allows gevent to # run it in a greenlet, so that the time.sleep() call doesn't block yield json.dumps(trezor_json) return time.sleep(1) # def example_filter(msg_id: int, data: bytes) -> tuple[int, bytes]: # msg = trezorlib.mapping.DEFAULT_MAPPING.decode(msg_id, data) # assert isinstance(msg, messages.Features) # msg.model = "Example" # return trezorlib.mapping.DEFAULT_MAPPING.encode(msg) # FILTERS[messages.Features.MESSAGE_WIRE_TYPE] = example_filter @click.command() @click.argument("port", type=int, default=21325) def main(port: int) -> None: run(host="127.0.0.1", port=port, server="gevent") if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/rng_entropy_collector.py0000775000175000017500000000167714636513242021505 0ustar00matejcikmatejcik#!/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() -> None: 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 _ in range(0, arg2, step): entropy = misc.get_entropy(client, step) f.write(entropy) client.close() if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/trezor-otp.py0000775000175000017500000000704614636513242017212 0ustar00matejcikmatejcik#!/usr/bin/env python3 # This file is part of the Trezor project. # # Copyright (C) 2012-2022 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 . 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: str, domain: str, secret: str) -> str: 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: str, domain: str, secret: bytes) -> bytes: 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) -> None: 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: str, secret: str, type: str = "totp") -> None: 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: str): 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() -> None: 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: str) -> None: c = Config() s = c.get(domain) print(s) def main() -> None: 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() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tools/trezorctl_script_client.py0000664000175000017500000000717214636513242022034 0ustar00matejcikmatejcik""" Reference client implementation consuming trezorctl's script interface (ScriptUI class) available by using `--script` flag in any trezorctl command. Function `get_address()` is showing the communication with ScriptUI on a specific example """ from __future__ import annotations import os import subprocess import typing as t import click def parse_args_from_line(line: str) -> tuple[str, dict[str, t.Any]]: # ?PIN code=123 # ?PASSPHRASE available_on_device command, *args = line.split(" ") result = {} for arg in args: if "=" in arg: key, value = arg.split("=") result[key] = value else: result[arg] = True return command, result def get_pin_from_user(code: str | None = None) -> str: # ?PIN # ?PIN code=Current while True: try: pin = click.prompt( f"Enter PIN (code: {code})", hide_input=True, default="", show_default=False, ) except click.Abort: return "CANCEL" if not all(c in "123456789" for c in pin): click.echo("PIN must only be numbers 1-9") continue return ":" + pin def show_button_request( code: str | None = None, pages: str | None = None, name: str | None = None ) -> None: # ?BUTTON code=Other # ?BUTTON code=SignTx pages=2 # ?BUTTON code=ProtectCall name=confirm_set_pin print(f"Please confirm action on Trezor (code={code} name={name} pages={pages})") def get_passphrase_from_user(available_on_device: bool = False) -> str: # ?PASSPHRASE # ?PASSPHRASE available_on_device if available_on_device: if click.confirm("Enter passphrase on device?", default=True): return "ON_DEVICE" env_passphrase = os.getenv("PASSPHRASE") if env_passphrase: if click.confirm("Use env PASSPHRASE?", default=False): return ":" + env_passphrase while True: try: passphrase = click.prompt("Enter passphrase", hide_input=True, default="") except click.Abort: return "CANCEL" passphrase2 = click.prompt( "Enter passphrase again", hide_input=True, default="" ) if passphrase != passphrase2: click.echo("Passphrases do not match") continue return ":" + passphrase def get_address() -> str: args = """ trezorctl --script get-address -n "m/49h/0h/0h/0/0" """.strip() p = subprocess.Popen( args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, shell=True, bufsize=0, ) assert p.stdout is not None assert p.stdin is not None text_result = [] while True: line = p.stdout.readline().strip() if not line: break if line.startswith("?"): command, args = parse_args_from_line(line) if command == "?PIN": response = get_pin_from_user(**args) p.stdin.write(response + "\n") elif command == "?PASSPHRASE": response = get_passphrase_from_user(**args) p.stdin.write(response + "\n") elif command == "?BUTTON": show_button_request(**args) else: print("Unrecognized script command:", line) text_result.append(line) print(line) address = text_result[-1] print("Address:", address) return address def clear_session_to_enable_pin(): os.system("trezorctl clear-session") if __name__ == "__main__": get_address() clear_session_to_enable_pin() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1719309986.0 trezor-0.13.9/tox.ini0000664000175000017500000000224014636513242014652 0ustar00matejcikmatejcik# NOTE: for running the tests locally in `nix-shell`, it is necessary # to spawn the `nix-shell` with `fullDeps` argument, so the command is: # `nix-shell --arg fullDeps true` # This will make sure all the python versions are installed. # (it could be beneficial to comment out `bitcoind` in `shell.nix` locally before running it, # as building that from source takes a very long time) [tox] envlist = py{38,39,310,311}-{minimal,default,full} py{38,39,310,311}-click{7,80} py{38,39,310,311}-click81 [testenv] deps = -rrequirements.txt !minimal: pytest>=3.6 !minimal: pytest-random-order !minimal: importlib-metadata!=0.21 full: -rrequirements-optional.txt commands = # Generate local files python setup.py build # Working in the local directory, try to compile all bytecode python -m compileall src tests # Smoke-test trezorctl trezorctl --help # Run test suite !minimal: pytest --random-order tests [testenv:py{38,39,310,311}-click{7,80,81}] deps = -rrequirements.txt click7: click>=7,<8 click80: click>=8.0,<8.1 click81: click>=8.1,<8.2 commands = # Smoke-test trezorctl trezorctl --version