├── .clippy.toml ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .vscode └── settings.json ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── change_pin.rs ├── features.rs ├── find.rs ├── interaction.rs ├── sign_message.rs └── sign_tx.rs ├── rustfmt.toml ├── scripts ├── generate-messages.py └── generate-protos.sh └── src ├── client ├── bitcoin.rs ├── common.rs ├── ethereum.rs └── mod.rs ├── error.rs ├── flows └── sign_tx.rs ├── lib.rs ├── messages.rs ├── protos ├── messages.rs ├── messages_binance.rs ├── messages_bitcoin.rs ├── messages_bootloader.rs ├── messages_cardano.rs ├── messages_common.rs ├── messages_crypto.rs ├── messages_debug.rs ├── messages_eos.rs ├── messages_ethereum.rs ├── messages_ethereum_definitions.rs ├── messages_ethereum_eip712.rs ├── messages_management.rs ├── messages_monero.rs ├── messages_nem.rs ├── messages_ripple.rs ├── messages_stellar.rs ├── messages_tezos.rs ├── messages_webauthn.rs └── mod.rs ├── transport ├── error.rs ├── mod.rs ├── protocol.rs ├── udp.rs └── webusb.rs └── utils.rs /.clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.60" 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | name: build ${{ matrix.features }} 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | features: ["", "--features bitcoin", "--features ethereum", "--all-features"] 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: dtolnay/rust-toolchain@stable 23 | - uses: Swatinem/rust-cache@v2 24 | - name: cargo build 25 | run: cargo build --no-default-features ${{ matrix.features }} 26 | 27 | clippy: 28 | name: clippy 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v3 32 | - uses: dtolnay/rust-toolchain@clippy 33 | - uses: Swatinem/rust-cache@v2 34 | - name: clippy 35 | run: cargo clippy --workspace --tests --all-features 36 | env: 37 | RUSTFLAGS: "-D warnings" 38 | 39 | docs: 40 | name: docs 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v3 44 | - uses: dtolnay/rust-toolchain@nightly 45 | with: 46 | components: rust-docs 47 | - uses: Swatinem/rust-cache@v2 48 | - name: doc 49 | run: cargo doc --workspace --all-features --no-deps --document-private-items 50 | env: 51 | RUSTDOCFLAGS: "--cfg docsrs -D warnings" 52 | 53 | fmt: 54 | name: fmt 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v3 58 | - uses: dtolnay/rust-toolchain@nightly 59 | with: 60 | components: rustfmt 61 | - name: fmt --check 62 | run: cargo fmt --all --check 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "trezor-common"] 2 | path = trezor-common 3 | url = https://github.com/trezor/trezor-common.git 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": ["bitcoin", "ethereum"] 3 | } 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trezor-client" 3 | version = "0.1.0" 4 | authors = [ 5 | "joshie", 6 | "DaniPopes <57450786+DaniPopes@users.noreply.github.com>", 7 | "romanz", 8 | "Steven Roose ", 9 | ] 10 | license = "CC0-1.0" 11 | homepage = "https://github.com/joshieDo/rust-trezor-api" 12 | repository = "https://github.com/joshieDo/rust-trezor-api" 13 | description = "Client library for interfacing with Trezor hardware wallet devices" 14 | keywords = ["ethereum", "bitcoin", "trezor", "wallet"] 15 | categories = ["api-bindings", "cryptography::cryptocurrencies"] 16 | readme = "README.md" 17 | exclude = [".github/", ".vscode/", "examples/", "scripts/", "trezor-common/", "rustfmt.toml"] 18 | edition = "2021" 19 | rust-version = "1.60" 20 | 21 | [dependencies] 22 | protobuf = "3.2" 23 | byteorder = "1.4" 24 | rusb = "0.9" 25 | 26 | hex = { version = "0.4", default-features = false, features = ["std"] } 27 | thiserror = "1.0" 28 | tracing = "0.1" 29 | 30 | # bitcoin 31 | bitcoin = { version = "0.30", optional = true } 32 | unicode-normalization = { version = "0.1.22", optional = true } 33 | 34 | # ethereum 35 | primitive-types = { version = "0.12", default-features = false, optional = true } 36 | 37 | [dev-dependencies] 38 | tracing-subscriber = "0.3" 39 | 40 | [features] 41 | default = ["bitcoin", "ethereum"] 42 | 43 | # Client implementations 44 | bitcoin = ["dep:bitcoin", "unicode-normalization"] 45 | ethereum = ["primitive-types"] 46 | 47 | # Just bindings to the Trezor protobufs 48 | binance = [] 49 | cardano = [] 50 | eos = [] 51 | monero = [] 52 | nem = [] 53 | ripple = [] 54 | stellar = [] 55 | tezos = [] 56 | webauthn = [] 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # moved to https://github.com/trezor/trezor-firmware/tree/master/rust/trezor-client 2 | 3 | # trezor-client 4 | 5 | [![Downloads][downloads-badge]][crates-io] 6 | [![License][license-badge]][license-url] 7 | [![CI Status][actions-badge]][actions-url] 8 | 9 | A fork of a [fork](https://github.com/romanz/rust-trezor-api) of a [library](https://github.com/stevenroose/rust-trezor-api) that provides a way to communicate with a Trezor T device from a Rust project. 10 | 11 | Previous iterations provided implementations for Bitcoin only. **This crate also provides an Ethereum interface**, mainly for use in [ethers-rs](https://github.com/gakonst/ethers-rs/). 12 | 13 | ## Requirements 14 | 15 | **MSRV: 1.60** 16 | 17 | See the [Trezor guide](https://trezor.io/learn/a/os-requirements-for-trezor) on how to install and use the Trezor Suite app. 18 | 19 | Last tested with firmware v2.4.2. 20 | 21 | ## Examples / Tests 22 | 23 | `cargo run --example features` 24 | 25 | ## Features 26 | 27 | - `bitcoin` and `ethereum`: client implementation and full support; 28 | - `cardano`, `lisk`, `monero`, `nem`, `ontology`, `ripple`, `stellar`, `tezos`, and`tron`: only protobuf bindings. 29 | 30 | ## Future 31 | 32 | At the moment, not looking into expanding more than what's necessary to maintain compatability/usability with ethers-rs. 33 | 34 | ## Credits 35 | 36 | - [Trezor](https://github.com/trezor/trezor-firmware) 37 | - [stevenroose](https://github.com/stevenroose) 38 | - [romanz](https://github.com/romanz) 39 | 40 | [downloads-badge]: https://img.shields.io/crates/d/trezor-client?style=for-the-badge&logo=rust 41 | [crates-io]: https://crates.io/crates/trezor-client 42 | [license-badge]: https://img.shields.io/badge/license-CC0--1.0-blue.svg?style=for-the-badge 43 | [license-url]: https://github.com/joshieDo/rust-trezor-api/blob/master/LICENSE 44 | [actions-badge]: https://img.shields.io/github/actions/workflow/status/joshieDo/rust-trezor-api/ci.yml?branch=master&style=for-the-badge 45 | [actions-url]: https://github.com/joshieDo/rust-trezor-api/actions?query=workflow%3ACI+branch%3Amaster 46 | -------------------------------------------------------------------------------- /examples/change_pin.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | fn read_pin() -> String { 4 | println!("Enter PIN"); 5 | let mut pin = String::new(); 6 | if io::stdin().read_line(&mut pin).unwrap() != 5 { 7 | println!("must enter pin, received: {}", pin); 8 | } 9 | // trim newline 10 | pin[..4].to_owned() 11 | } 12 | 13 | fn do_main() -> Result<(), trezor_client::Error> { 14 | // init with debugging 15 | let mut trezor = trezor_client::unique(true)?; 16 | trezor.init_device(None)?; 17 | 18 | let old_pin = trezor.change_pin(false)?.button_request()?.ack()?.pin_matrix_request()?; 19 | 20 | let new_pin1 = old_pin.ack_pin(read_pin())?.pin_matrix_request()?; 21 | 22 | let new_pin2 = new_pin1.ack_pin(read_pin())?.pin_matrix_request()?; 23 | 24 | new_pin2.ack_pin(read_pin())?.ok()?; 25 | 26 | Ok(()) 27 | } 28 | 29 | fn main() { 30 | do_main().unwrap() 31 | } 32 | -------------------------------------------------------------------------------- /examples/features.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | fn convert_path_from_str(derivation: &str) -> Vec { 4 | let derivation = derivation.to_string(); 5 | let elements = derivation.split('/').skip(1).collect::>(); 6 | 7 | let mut path = vec![]; 8 | for derivation_index in elements { 9 | let hardened = derivation_index.contains('\''); 10 | let mut index = derivation_index.replace('\'', "").parse::().unwrap(); 11 | if hardened { 12 | index |= 0x80000000; 13 | } 14 | path.push(index); 15 | } 16 | 17 | path 18 | } 19 | 20 | fn device_selector() -> trezor_client::Trezor { 21 | let mut devices = trezor_client::find_devices(false); 22 | 23 | if devices.is_empty() { 24 | panic!("No devices connected"); 25 | } else if devices.len() == 1 { 26 | devices.remove(0).connect().expect("connection error") 27 | } else { 28 | println!("Choose device:"); 29 | for (i, dev) in devices.iter().enumerate() { 30 | println!("{}: {}", i + 1, dev); 31 | } 32 | println!("Enter device number: "); 33 | let mut inp = String::new(); 34 | io::stdin().read_line(&mut inp).expect("stdin error"); 35 | let idx: usize = inp[..inp.len() - 1].parse().expect("invalid number"); 36 | if idx >= devices.len() { 37 | panic!("Index out of range"); 38 | } 39 | devices.remove(idx).connect().unwrap() 40 | } 41 | } 42 | 43 | fn do_main() -> Result<(), trezor_client::Error> { 44 | // init with debugging 45 | tracing_subscriber::fmt().with_max_level(tracing::Level::TRACE).init(); 46 | 47 | let mut trezor = device_selector(); 48 | trezor.init_device(None)?; 49 | let f = trezor.features().expect("no features").clone(); 50 | 51 | println!("Features:"); 52 | println!("vendor: {}", f.vendor()); 53 | println!("version: {}.{}.{}", f.major_version(), f.minor_version(), f.patch_version()); 54 | println!("device id: {}", f.device_id()); 55 | println!("label: {}", f.label()); 56 | println!("is initialized: {}", f.initialized()); 57 | println!("pin protection: {}", f.pin_protection()); 58 | println!("passphrase protection: {}", f.passphrase_protection()); 59 | println!( 60 | "{:?}", 61 | trezor.ethereum_get_address(convert_path_from_str("m/44'/60'/1'/0/0")).unwrap() 62 | ); 63 | drop(trezor); 64 | 65 | let mut trezor2 = device_selector(); 66 | 67 | trezor2.init_device(Some(f.session_id().to_vec()))?; 68 | 69 | println!( 70 | "{:?}", 71 | trezor2.ethereum_get_address(convert_path_from_str("m/44'/60'/1'/0/0")).unwrap() 72 | ); 73 | //optional bool bootloader_mode = 5; // is device in bootloader mode? 74 | //optional string language = 9; // device language 75 | //optional bytes revision = 13; // SCM revision of firmware 76 | //optional bytes bootloader_hash = 14; // hash of the bootloader 77 | //optional bool imported = 15; // was storage imported from an external source? 78 | //optional bool pin_cached = 16; // is PIN already cached in session? 79 | //optional bool passphrase_cached = 17; // is passphrase already cached in session? 80 | //optional bool firmware_present = 18; // is valid firmware loaded? 81 | //optional bool needs_backup = 19; // does storage need backup? (equals to 82 | // Storage.needs_backup) optional uint32 flags = 20; // device flags (equals 83 | // to Storage.flags) optional string model = 21; // device hardware model 84 | //optional uint32 fw_major = 22; // reported firmware version if in bootloader 85 | // mode optional uint32 fw_minor = 23; // reported firmware version if in 86 | // bootloader mode optional uint32 fw_patch = 24; // reported firmware version 87 | // if in bootloader mode optional string fw_vendor = 25; // reported firmware 88 | // vendor if in bootloader mode optional bytes fw_vendor_keys = 26; // reported 89 | // firmware vendor keys (their hash) optional bool unfinished_backup = 27; // report 90 | // unfinished backup (equals to Storage.unfinished_backup) optional bool no_backup = 28; 91 | // // report no backup (equals to Storage.no_backup) 92 | 93 | Ok(()) 94 | } 95 | 96 | fn main() { 97 | do_main().unwrap() 98 | } 99 | -------------------------------------------------------------------------------- /examples/find.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let trezors = trezor_client::find_devices(false); 3 | println!("Found {} devices: ", trezors.len()); 4 | for t in trezors.into_iter() { 5 | println!("- {}", t); 6 | { 7 | let mut client = t.connect().unwrap(); 8 | println!("{:?}", client.initialize(None).unwrap()); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/interaction.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use bitcoin::{bip32, network::constants::Network, Address}; 4 | use trezor_client::{Error, TrezorMessage, TrezorResponse}; 5 | 6 | fn handle_interaction(resp: TrezorResponse) -> Result { 7 | match resp { 8 | TrezorResponse::Ok(res) => Ok(res), 9 | TrezorResponse::Failure(_) => resp.ok(), // assering ok() returns the failure error 10 | TrezorResponse::ButtonRequest(req) => handle_interaction(req.ack()?), 11 | TrezorResponse::PinMatrixRequest(req) => { 12 | println!("Enter PIN"); 13 | let mut pin = String::new(); 14 | if io::stdin().read_line(&mut pin).unwrap() != 5 { 15 | println!("must enter pin, received: {}", pin); 16 | } 17 | // trim newline 18 | handle_interaction(req.ack_pin(pin[..4].to_owned())?) 19 | } 20 | TrezorResponse::PassphraseRequest(req) => { 21 | println!("Enter passphrase"); 22 | let mut pass = String::new(); 23 | io::stdin().read_line(&mut pass).unwrap(); 24 | // trim newline 25 | handle_interaction(req.ack_passphrase(pass[..pass.len() - 1].to_owned())?) 26 | } 27 | } 28 | } 29 | 30 | fn do_main() -> Result<(), trezor_client::Error> { 31 | // init with debugging 32 | let mut trezor = trezor_client::unique(true)?; 33 | trezor.init_device(None)?; 34 | 35 | let xpub = handle_interaction( 36 | trezor.get_public_key( 37 | &vec![ 38 | bip32::ChildNumber::from_hardened_idx(0).unwrap(), 39 | bip32::ChildNumber::from_hardened_idx(0).unwrap(), 40 | bip32::ChildNumber::from_hardened_idx(0).unwrap(), 41 | ] 42 | .into(), 43 | trezor_client::protos::InputScriptType::SPENDADDRESS, 44 | Network::Testnet, 45 | true, 46 | )?, 47 | )?; 48 | println!("{}", xpub); 49 | println!("{:?}", xpub); 50 | println!("{}", Address::p2pkh(&xpub.to_pub(), Network::Testnet)); 51 | 52 | Ok(()) 53 | } 54 | 55 | fn main() { 56 | do_main().unwrap() 57 | } 58 | -------------------------------------------------------------------------------- /examples/sign_message.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use bitcoin::{bip32, network::constants::Network, Address}; 4 | use trezor_client::{InputScriptType, TrezorMessage, TrezorResponse}; 5 | 6 | fn handle_interaction(resp: TrezorResponse) -> T { 7 | match resp { 8 | TrezorResponse::Ok(res) => res, 9 | // assering ok() returns the failure error 10 | TrezorResponse::Failure(_) => resp.ok().unwrap(), 11 | TrezorResponse::ButtonRequest(req) => handle_interaction(req.ack().unwrap()), 12 | TrezorResponse::PinMatrixRequest(req) => { 13 | println!("Enter PIN"); 14 | let mut pin = String::new(); 15 | if io::stdin().read_line(&mut pin).unwrap() != 5 { 16 | println!("must enter pin, received: {}", pin); 17 | } 18 | // trim newline 19 | handle_interaction(req.ack_pin(pin[..4].to_owned()).unwrap()) 20 | } 21 | TrezorResponse::PassphraseRequest(req) => { 22 | println!("Enter passphrase"); 23 | let mut pass = String::new(); 24 | io::stdin().read_line(&mut pass).unwrap(); 25 | // trim newline 26 | handle_interaction(req.ack_passphrase(pass[..pass.len() - 1].to_owned()).unwrap()) 27 | } 28 | } 29 | } 30 | 31 | fn main() { 32 | tracing_subscriber::fmt().with_max_level(tracing::Level::TRACE).init(); 33 | 34 | // init with debugging 35 | let mut trezor = trezor_client::unique(false).unwrap(); 36 | trezor.init_device(None).unwrap(); 37 | 38 | let pubkey = handle_interaction( 39 | trezor 40 | .get_public_key( 41 | &vec![ 42 | bip32::ChildNumber::from_hardened_idx(0).unwrap(), 43 | bip32::ChildNumber::from_hardened_idx(0).unwrap(), 44 | bip32::ChildNumber::from_hardened_idx(1).unwrap(), 45 | ] 46 | .into(), 47 | trezor_client::protos::InputScriptType::SPENDADDRESS, 48 | Network::Testnet, 49 | true, 50 | ) 51 | .unwrap(), 52 | ); 53 | let addr = Address::p2pkh(&pubkey.to_pub(), Network::Testnet); 54 | println!("address: {}", addr); 55 | 56 | let (addr, signature) = handle_interaction( 57 | trezor 58 | .sign_message( 59 | "regel het".to_owned(), 60 | &vec![ 61 | bip32::ChildNumber::from_hardened_idx(0).unwrap(), 62 | bip32::ChildNumber::from_hardened_idx(0).unwrap(), 63 | bip32::ChildNumber::from_hardened_idx(1).unwrap(), 64 | ] 65 | .into(), 66 | InputScriptType::SPENDADDRESS, 67 | Network::Testnet, 68 | ) 69 | .unwrap(), 70 | ); 71 | println!("Addr from device: {}", addr); 72 | println!("Signature: {:?}", signature); 73 | } 74 | -------------------------------------------------------------------------------- /examples/sign_tx.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | use bitcoin::{ 4 | bip32, blockdata::script::Builder, consensus::encode::Decodable, network::constants::Network, 5 | psbt, Address, Sequence, Transaction, TxIn, TxOut, 6 | }; 7 | 8 | use trezor_client::{Error, SignTxProgress, TrezorMessage, TrezorResponse}; 9 | 10 | fn handle_interaction(resp: TrezorResponse) -> T { 11 | match resp { 12 | TrezorResponse::Ok(res) => res, 13 | // assering ok() returns the failure error 14 | TrezorResponse::Failure(_) => resp.ok().unwrap(), 15 | TrezorResponse::ButtonRequest(req) => handle_interaction(req.ack().unwrap()), 16 | TrezorResponse::PinMatrixRequest(req) => { 17 | println!("Enter PIN"); 18 | let mut pin = String::new(); 19 | if io::stdin().read_line(&mut pin).unwrap() != 5 { 20 | println!("must enter pin, received: {}", pin); 21 | } 22 | // trim newline 23 | handle_interaction(req.ack_pin(pin[..4].to_owned()).unwrap()) 24 | } 25 | TrezorResponse::PassphraseRequest(req) => { 26 | println!("Enter passphrase"); 27 | let mut pass = String::new(); 28 | io::stdin().read_line(&mut pass).unwrap(); 29 | // trim newline 30 | handle_interaction(req.ack_passphrase(pass[..pass.len() - 1].to_owned()).unwrap()) 31 | } 32 | } 33 | } 34 | 35 | fn tx_progress( 36 | psbt: &mut psbt::PartiallySignedTransaction, 37 | progress: SignTxProgress, 38 | raw_tx: &mut Vec, 39 | ) -> Result<(), Error> { 40 | if let Some(part) = progress.get_serialized_tx_part() { 41 | raw_tx.write_all(part).unwrap(); 42 | } 43 | 44 | if !progress.finished() { 45 | let progress = handle_interaction(progress.ack_psbt(psbt, Network::Testnet).unwrap()); 46 | tx_progress(psbt, progress, raw_tx) 47 | } else { 48 | Ok(()) 49 | } 50 | } 51 | 52 | fn main() { 53 | tracing_subscriber::fmt().with_max_level(tracing::Level::TRACE).init(); 54 | 55 | // init with debugging 56 | let mut trezor = trezor_client::unique(false).unwrap(); 57 | trezor.init_device(None).unwrap(); 58 | 59 | let pubkey = handle_interaction( 60 | trezor 61 | .get_public_key( 62 | &vec![ 63 | bip32::ChildNumber::from_hardened_idx(0).unwrap(), 64 | bip32::ChildNumber::from_hardened_idx(0).unwrap(), 65 | bip32::ChildNumber::from_hardened_idx(1).unwrap(), 66 | ] 67 | .into(), 68 | trezor_client::protos::InputScriptType::SPENDADDRESS, 69 | Network::Testnet, 70 | true, 71 | ) 72 | .unwrap(), 73 | ); 74 | let addr = Address::p2pkh(&pubkey.to_pub(), Network::Testnet); 75 | println!("address: {}", addr); 76 | 77 | let mut psbt = psbt::PartiallySignedTransaction { 78 | unsigned_tx: Transaction { 79 | version: 1, 80 | lock_time: bitcoin::absolute::LockTime::from_consensus(0), 81 | input: vec![TxIn { 82 | previous_output: "c5bdb27907b78ce03f94e4bf2e94f7a39697b9074b79470019e3dbc76a10ecb6:0".parse().unwrap(), 83 | sequence: Sequence(0xffffffff), 84 | script_sig: Builder::new().into_script(), 85 | witness: Default::default(), 86 | }], 87 | output: vec![TxOut { 88 | value: 14245301, 89 | script_pubkey: addr.script_pubkey(), 90 | }], 91 | }, 92 | inputs: vec![psbt::Input { 93 | non_witness_utxo: Some(Transaction::consensus_decode(&mut &hex::decode("020000000001011eb5a3e65946f88b00d67b321e5fd980b32a2316fb1fc9b712baa6a1033a04e30100000017160014f0f81ee77d552b4c81497451d1abf5c22ce8e352feffffff02b55dd900000000001976a9142c3cf5686f47c1de9cc90b4255cc2a1ef8c01b3188acfb0391ae6800000017a914a3a79e37ad366d9bf9471b28a9a8f64b50de0c968702483045022100c0aa7b262967fc2803c8a9f38f26682edba7cafb7d4870ebdc116040ad5338b502205dfebd08e993af2e6aa3118a438ad70ed9f6e09bc6abfd21f8f2957af936bc070121031f4e69fcf110bb31f019321834c0948b5487f2782489f370f66dc20f7ac767ca8bf81500").unwrap()[..]).unwrap()), 94 | ..Default::default() 95 | }], 96 | outputs: vec![ 97 | psbt::Output { 98 | ..Default::default() 99 | }, 100 | ], 101 | proprietary: Default::default(), 102 | unknown: Default::default(), 103 | version: 0, 104 | xpub: Default::default(), 105 | }; 106 | 107 | println!("psbt before: {:?}", psbt); 108 | println!("unsigned txid: {}", psbt.unsigned_tx.txid()); 109 | println!( 110 | "unsigned tx: {}", 111 | hex::encode(bitcoin::consensus::encode::serialize(&psbt.unsigned_tx)) 112 | ); 113 | 114 | let mut raw_tx = Vec::new(); 115 | let progress = handle_interaction(trezor.sign_tx(&psbt, Network::Testnet).unwrap()); 116 | tx_progress(&mut psbt, progress, &mut raw_tx).unwrap(); 117 | 118 | println!("signed tx: {}", hex::encode(raw_tx)); 119 | } 120 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | imports_granularity = "Crate" 3 | use_small_heuristics = "Max" 4 | comment_width = 100 5 | wrap_comments = true 6 | binop_separator = "Back" 7 | trailing_comma = "Vertical" 8 | trailing_semicolon = false 9 | use_field_init_shorthand = true 10 | 11 | ignore = ["src/protos/messages_*"] 12 | -------------------------------------------------------------------------------- /scripts/generate-messages.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Generates the `trezor_message_impl!` macro calls for the `src/messages.rs` file. 4 | 5 | from os import path 6 | 7 | # Path to the `messages.proto` file 8 | PATH = path.abspath(path.join(__file__, "../../trezor-common/protob/messages.proto")) 9 | # Prefix of the enum variants 10 | PREFIX = "MessageType_" 11 | # Mapping of block name to feature name 12 | FEATURES = { 13 | # no features 14 | "Management": "default", 15 | "Bootloader": "default", 16 | "Crypto": "default", 17 | "Debug": "default", 18 | # 19 | "Bitcoin": "bitcoin", 20 | "Ethereum": "ethereum", 21 | # 22 | "Binance": "binance", 23 | "Cardano": "cardano", 24 | "EOS": "eos", 25 | "Monero": "monero", 26 | "NEM": "nem", 27 | "Ripple": "ripple", 28 | "Stellar": "stellar", 29 | "Tezos": "tezos", 30 | "WebAuthn": "webauthn", 31 | } 32 | MACRO = "trezor_message_impl" 33 | INDENT = "\t" 34 | 35 | 36 | def main(): 37 | blocks = get_blocks() 38 | features = {} 39 | defaults = [] 40 | for block, variants in blocks.items(): 41 | f = FEATURES.get(block) 42 | if not f or f == "default": 43 | defaults.extend(variants) 44 | else: 45 | vs = features.get(f) 46 | if vs: 47 | vs.extend(variants) 48 | else: 49 | features[f] = variants 50 | 51 | items = list(features.items()) 52 | items.sort() 53 | 54 | out = write_block(defaults) 55 | for feature, variants in items: 56 | if variants and feature: 57 | out += "\n" 58 | out += write_block(variants, feature) 59 | print(out) 60 | 61 | 62 | # Parse feature blocks based on comments in the `messages.proto` file 63 | def get_blocks() -> dict[str, list[str]]: 64 | blocks = {} 65 | current_block = "" 66 | with open(PATH, "r") as file: 67 | in_enum = False 68 | in_block_comment = False 69 | for line in file: 70 | line = line.strip() 71 | 72 | if "/*" in line: 73 | in_block_comment = True 74 | if "*/" in line: 75 | in_block_comment = False 76 | if in_block_comment: 77 | continue 78 | 79 | if line.startswith("enum MessageType {"): 80 | in_enum = True 81 | continue 82 | if in_enum: 83 | if line == "}": 84 | break 85 | if line.startswith("//"): 86 | comment = line.removeprefix("//").strip() 87 | if comment[0].isupper() and len(comment.split(" ")) == 1: 88 | current_block = comment 89 | blocks[current_block] = [] 90 | elif line.startswith(PREFIX): 91 | blocks[current_block].append(line.split(" ")[0]) 92 | return blocks 93 | 94 | 95 | # Writes a macro block 96 | def write_block(variants: list[str], feature: str = "") -> str: 97 | s = "" 98 | if feature: 99 | s += f'#[cfg(feature = "{feature}")]\n' 100 | s += f"{MACRO}! {{\n" 101 | for variant in variants: 102 | s += f"{INDENT}{variant.removeprefix(PREFIX)} => {variant},\n" 103 | s += "}\n" 104 | return s 105 | 106 | 107 | if __name__ == "__main__": 108 | main() 109 | -------------------------------------------------------------------------------- /scripts/generate-protos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Generates src/protos/ 4 | # Requires the `protoc-gen-rust` binary (`cargo install protoc-gen-rust`). 5 | # Overwrites src/protos/mod.rs, but the change should not be committed, and 6 | # instead should be handled manually. 7 | 8 | root="$(dirname "$(dirname "$0")")" 9 | out_dir="$root/src/protos" 10 | proto_dir="$root/trezor-common/protob" 11 | 12 | protoc \ 13 | --proto_path "$proto_dir" \ 14 | --rust_out "$out_dir" \ 15 | "$proto_dir"/*.proto 16 | -------------------------------------------------------------------------------- /src/client/bitcoin.rs: -------------------------------------------------------------------------------- 1 | use super::{Trezor, TrezorResponse}; 2 | use crate::{error::Result, flows::sign_tx::SignTxProgress, protos, utils}; 3 | use bitcoin::{ 4 | address::NetworkUnchecked, bip32, network::constants::Network, psbt, 5 | secp256k1::ecdsa::RecoverableSignature, Address, 6 | }; 7 | 8 | pub use crate::protos::InputScriptType; 9 | 10 | impl Trezor { 11 | pub fn get_public_key( 12 | &mut self, 13 | path: &bip32::DerivationPath, 14 | script_type: InputScriptType, 15 | network: Network, 16 | show_display: bool, 17 | ) -> Result> { 18 | let mut req = protos::GetPublicKey::new(); 19 | req.address_n = utils::convert_path(path); 20 | req.set_show_display(show_display); 21 | req.set_coin_name(utils::coin_name(network)?); 22 | req.set_script_type(script_type); 23 | self.call(req, Box::new(|_, m| Ok(m.xpub().parse()?))) 24 | } 25 | 26 | //TODO(stevenroose) multisig 27 | pub fn get_address( 28 | &mut self, 29 | path: &bip32::DerivationPath, 30 | script_type: InputScriptType, 31 | network: Network, 32 | show_display: bool, 33 | ) -> Result> { 34 | let mut req = protos::GetAddress::new(); 35 | req.address_n = utils::convert_path(path); 36 | req.set_coin_name(utils::coin_name(network)?); 37 | req.set_show_display(show_display); 38 | req.set_script_type(script_type); 39 | self.call(req, Box::new(|_, m| parse_address(m.address()))) 40 | } 41 | 42 | pub fn sign_tx( 43 | &mut self, 44 | psbt: &psbt::PartiallySignedTransaction, 45 | network: Network, 46 | ) -> Result, protos::TxRequest>> { 47 | let tx = &psbt.unsigned_tx; 48 | let mut req = protos::SignTx::new(); 49 | req.set_inputs_count(tx.input.len() as u32); 50 | req.set_outputs_count(tx.output.len() as u32); 51 | req.set_coin_name(utils::coin_name(network)?); 52 | req.set_version(tx.version as u32); 53 | req.set_lock_time(tx.lock_time.to_consensus_u32()); 54 | self.call(req, Box::new(|c, m| Ok(SignTxProgress::new(c, m)))) 55 | } 56 | 57 | pub fn sign_message( 58 | &mut self, 59 | message: String, 60 | path: &bip32::DerivationPath, 61 | script_type: InputScriptType, 62 | network: Network, 63 | ) -> Result> { 64 | let mut req = protos::SignMessage::new(); 65 | req.address_n = utils::convert_path(path); 66 | // Normalize to Unicode NFC. 67 | let msg_bytes = nfc_normalize(&message).into_bytes(); 68 | req.set_message(msg_bytes); 69 | req.set_coin_name(utils::coin_name(network)?); 70 | req.set_script_type(script_type); 71 | self.call( 72 | req, 73 | Box::new(|_, m| { 74 | let address = parse_address(m.address())?; 75 | let signature = utils::parse_recoverable_signature(m.signature())?; 76 | Ok((address, signature)) 77 | }), 78 | ) 79 | } 80 | } 81 | 82 | fn parse_address(s: &str) -> Result
{ 83 | let address = s.parse::>()?; 84 | Ok(address.assume_checked()) 85 | } 86 | 87 | // Modified from: 88 | // https://github.com/rust-lang/rust/blob/2a8221dbdfd180a2d56d4b0089f4f3952d8c2bcd/compiler/rustc_parse/src/lexer/mod.rs#LL754C5-L754C5 89 | fn nfc_normalize(string: &str) -> String { 90 | use unicode_normalization::{is_nfc_quick, IsNormalized, UnicodeNormalization}; 91 | match is_nfc_quick(string.chars()) { 92 | IsNormalized::Yes => string.to_string(), 93 | _ => string.chars().nfc().collect::(), 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/client/common.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::{Error, Result}, 3 | messages::TrezorMessage, 4 | protos, Trezor, 5 | }; 6 | use std::fmt; 7 | 8 | // Some types with raw protos that we use in the public interface so they have to be exported. 9 | pub use protos::{ 10 | button_request::ButtonRequestType, pin_matrix_request::PinMatrixRequestType, Features, 11 | }; 12 | 13 | #[cfg(feature = "bitcoin")] 14 | pub use protos::InputScriptType; 15 | 16 | /// The different options for the number of words in a seed phrase. 17 | pub enum WordCount { 18 | W12 = 12, 19 | W18 = 18, 20 | W24 = 24, 21 | } 22 | 23 | /// The different types of user interactions the Trezor device can request. 24 | #[derive(PartialEq, Eq, Clone, Debug)] 25 | pub enum InteractionType { 26 | Button, 27 | PinMatrix, 28 | Passphrase, 29 | PassphraseState, 30 | } 31 | 32 | //TODO(stevenroose) should this be FnOnce and put in an FnBox? 33 | /// Function to be passed to the `Trezor.call` method to process the Trezor response message into a 34 | /// general-purpose type. 35 | pub type ResultHandler<'a, T, R> = dyn Fn(&'a mut Trezor, R) -> Result; 36 | 37 | /// A button request message sent by the device. 38 | pub struct ButtonRequest<'a, T, R: TrezorMessage> { 39 | pub message: protos::ButtonRequest, 40 | pub client: &'a mut Trezor, 41 | pub result_handler: Box>, 42 | } 43 | 44 | impl<'a, T, R: TrezorMessage> fmt::Debug for ButtonRequest<'a, T, R> { 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 | fmt::Debug::fmt(&self.message, f) 47 | } 48 | } 49 | 50 | impl<'a, T, R: TrezorMessage> ButtonRequest<'a, T, R> { 51 | /// The type of button request. 52 | pub fn request_type(&self) -> ButtonRequestType { 53 | self.message.code() 54 | } 55 | 56 | /// Ack the request and get the next message from the device. 57 | pub fn ack(self) -> Result> { 58 | let req = protos::ButtonAck::new(); 59 | self.client.call(req, self.result_handler) 60 | } 61 | } 62 | 63 | /// A PIN matrix request message sent by the device. 64 | pub struct PinMatrixRequest<'a, T, R: TrezorMessage> { 65 | pub message: protos::PinMatrixRequest, 66 | pub client: &'a mut Trezor, 67 | pub result_handler: Box>, 68 | } 69 | 70 | impl<'a, T, R: TrezorMessage> fmt::Debug for PinMatrixRequest<'a, T, R> { 71 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 72 | fmt::Debug::fmt(&self.message, f) 73 | } 74 | } 75 | 76 | impl<'a, T, R: TrezorMessage> PinMatrixRequest<'a, T, R> { 77 | /// The type of PIN matrix request. 78 | pub fn request_type(&self) -> PinMatrixRequestType { 79 | self.message.type_() 80 | } 81 | 82 | /// Ack the request with a PIN and get the next message from the device. 83 | pub fn ack_pin(self, pin: String) -> Result> { 84 | let mut req = protos::PinMatrixAck::new(); 85 | req.set_pin(pin); 86 | self.client.call(req, self.result_handler) 87 | } 88 | } 89 | 90 | /// A passphrase request message sent by the device. 91 | pub struct PassphraseRequest<'a, T, R: TrezorMessage> { 92 | pub message: protos::PassphraseRequest, 93 | pub client: &'a mut Trezor, 94 | pub result_handler: Box>, 95 | } 96 | 97 | impl<'a, T, R: TrezorMessage> fmt::Debug for PassphraseRequest<'a, T, R> { 98 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 99 | fmt::Debug::fmt(&self.message, f) 100 | } 101 | } 102 | 103 | impl<'a, T, R: TrezorMessage> PassphraseRequest<'a, T, R> { 104 | /// Check whether the use is supposed to enter the passphrase on the device or not. 105 | pub fn on_device(&self) -> bool { 106 | self.message._on_device() 107 | } 108 | 109 | /// Ack the request with a passphrase and get the next message from the device. 110 | pub fn ack_passphrase(self, passphrase: String) -> Result> { 111 | let mut req = protos::PassphraseAck::new(); 112 | req.set_passphrase(passphrase); 113 | self.client.call(req, self.result_handler) 114 | } 115 | 116 | /// Ack the request without a passphrase to let the user enter it on the device 117 | /// and get the next message from the device. 118 | pub fn ack(self, on_device: bool) -> Result> { 119 | let mut req = protos::PassphraseAck::new(); 120 | if on_device { 121 | req.set_on_device(on_device); 122 | } 123 | self.client.call(req, self.result_handler) 124 | } 125 | } 126 | 127 | /// A response from a Trezor device. On every message exchange, instead of the expected/desired 128 | /// response, the Trezor can ask for some user interaction, or can send a failure. 129 | #[derive(Debug)] 130 | pub enum TrezorResponse<'a, T, R: TrezorMessage> { 131 | Ok(T), 132 | Failure(protos::Failure), 133 | ButtonRequest(ButtonRequest<'a, T, R>), 134 | PinMatrixRequest(PinMatrixRequest<'a, T, R>), 135 | PassphraseRequest(PassphraseRequest<'a, T, R>), 136 | //TODO(stevenroose) This should be taken out of this enum and intrinsically attached to the 137 | // PassphraseRequest variant. However, it's currently impossible to do this. It might be 138 | // possible to do with FnBox (currently nightly) or when Box becomes possible. 139 | } 140 | 141 | impl<'a, T, R: TrezorMessage> fmt::Display for TrezorResponse<'a, T, R> { 142 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 143 | match self { 144 | // TODO(stevenroose) should we make T: Debug? 145 | TrezorResponse::Ok(ref _m) => f.write_str("Ok"), 146 | TrezorResponse::Failure(ref m) => write!(f, "Failure: {:?}", m), 147 | TrezorResponse::ButtonRequest(ref r) => write!(f, "ButtonRequest: {:?}", r), 148 | TrezorResponse::PinMatrixRequest(ref r) => write!(f, "PinMatrixRequest: {:?}", r), 149 | TrezorResponse::PassphraseRequest(ref r) => write!(f, "PassphraseRequest: {:?}", r), 150 | } 151 | } 152 | } 153 | 154 | impl<'a, T, R: TrezorMessage> TrezorResponse<'a, T, R> { 155 | /// Get the actual `Ok` response value or an error if not `Ok`. 156 | pub fn ok(self) -> Result { 157 | match self { 158 | TrezorResponse::Ok(m) => Ok(m), 159 | TrezorResponse::Failure(m) => Err(Error::FailureResponse(m)), 160 | TrezorResponse::ButtonRequest(_) => { 161 | Err(Error::UnexpectedInteractionRequest(InteractionType::Button)) 162 | } 163 | TrezorResponse::PinMatrixRequest(_) => { 164 | Err(Error::UnexpectedInteractionRequest(InteractionType::PinMatrix)) 165 | } 166 | TrezorResponse::PassphraseRequest(_) => { 167 | Err(Error::UnexpectedInteractionRequest(InteractionType::Passphrase)) 168 | } 169 | } 170 | } 171 | 172 | /// Get the button request object or an error if not `ButtonRequest`. 173 | pub fn button_request(self) -> Result> { 174 | match self { 175 | TrezorResponse::ButtonRequest(r) => Ok(r), 176 | TrezorResponse::Ok(_) => Err(Error::UnexpectedMessageType(R::MESSAGE_TYPE)), 177 | TrezorResponse::Failure(m) => Err(Error::FailureResponse(m)), 178 | TrezorResponse::PinMatrixRequest(_) => { 179 | Err(Error::UnexpectedInteractionRequest(InteractionType::PinMatrix)) 180 | } 181 | TrezorResponse::PassphraseRequest(_) => { 182 | Err(Error::UnexpectedInteractionRequest(InteractionType::Passphrase)) 183 | } 184 | } 185 | } 186 | 187 | /// Get the PIN matrix request object or an error if not `PinMatrixRequest`. 188 | pub fn pin_matrix_request(self) -> Result> { 189 | match self { 190 | TrezorResponse::PinMatrixRequest(r) => Ok(r), 191 | TrezorResponse::Ok(_) => Err(Error::UnexpectedMessageType(R::MESSAGE_TYPE)), 192 | TrezorResponse::Failure(m) => Err(Error::FailureResponse(m)), 193 | TrezorResponse::ButtonRequest(_) => { 194 | Err(Error::UnexpectedInteractionRequest(InteractionType::Button)) 195 | } 196 | TrezorResponse::PassphraseRequest(_) => { 197 | Err(Error::UnexpectedInteractionRequest(InteractionType::Passphrase)) 198 | } 199 | } 200 | } 201 | 202 | /// Get the passphrase request object or an error if not `PassphraseRequest`. 203 | pub fn passphrase_request(self) -> Result> { 204 | match self { 205 | TrezorResponse::PassphraseRequest(r) => Ok(r), 206 | TrezorResponse::Ok(_) => Err(Error::UnexpectedMessageType(R::MESSAGE_TYPE)), 207 | TrezorResponse::Failure(m) => Err(Error::FailureResponse(m)), 208 | TrezorResponse::ButtonRequest(_) => { 209 | Err(Error::UnexpectedInteractionRequest(InteractionType::Button)) 210 | } 211 | TrezorResponse::PinMatrixRequest(_) => { 212 | Err(Error::UnexpectedInteractionRequest(InteractionType::PinMatrix)) 213 | } 214 | } 215 | } 216 | } 217 | 218 | pub fn handle_interaction(resp: TrezorResponse<'_, T, R>) -> Result { 219 | match resp { 220 | TrezorResponse::Ok(res) => Ok(res), 221 | TrezorResponse::Failure(_) => resp.ok(), // assering ok() returns the failure error 222 | TrezorResponse::ButtonRequest(req) => handle_interaction(req.ack()?), 223 | TrezorResponse::PinMatrixRequest(_) => Err(Error::UnsupportedNetwork), 224 | TrezorResponse::PassphraseRequest(req) => handle_interaction({ 225 | let on_device = req.on_device(); 226 | req.ack(!on_device)? 227 | }), 228 | } 229 | } 230 | 231 | /// When resetting the device, it will ask for entropy to aid key generation. 232 | pub struct EntropyRequest<'a> { 233 | pub client: &'a mut Trezor, 234 | } 235 | 236 | impl<'a> EntropyRequest<'a> { 237 | /// Provide exactly 32 bytes or entropy. 238 | pub fn ack_entropy(self, entropy: Vec) -> Result> { 239 | if entropy.len() != 32 { 240 | return Err(Error::InvalidEntropy) 241 | } 242 | 243 | let mut req = protos::EntropyAck::new(); 244 | req.set_entropy(entropy); 245 | self.client.call(req, Box::new(|_, _| Ok(()))) 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/client/ethereum.rs: -------------------------------------------------------------------------------- 1 | use super::{handle_interaction, Trezor}; 2 | use crate::{ 3 | error::Result, 4 | protos::{self, ethereum_sign_tx_eip1559::EthereumAccessList}, 5 | }; 6 | use primitive_types::U256; 7 | 8 | /// Access list item. 9 | #[derive(Debug, Clone, PartialEq, Eq)] 10 | pub struct AccessListItem { 11 | /// Accessed address 12 | pub address: String, 13 | /// Accessed storage keys 14 | pub storage_keys: Vec>, 15 | } 16 | 17 | /// An ECDSA signature. 18 | #[derive(Debug, Clone, PartialEq, Eq, Copy)] 19 | pub struct Signature { 20 | /// R value 21 | pub r: U256, 22 | /// S Value 23 | pub s: U256, 24 | /// V value in 'Electrum' notation. 25 | pub v: u64, 26 | } 27 | 28 | impl Trezor { 29 | // ETHEREUM 30 | pub fn ethereum_get_address(&mut self, path: Vec) -> Result { 31 | let mut req = protos::EthereumGetAddress::new(); 32 | req.address_n = path; 33 | let address = handle_interaction( 34 | self.call(req, Box::new(|_, m: protos::EthereumAddress| Ok(m.address().into())))?, 35 | )?; 36 | Ok(address) 37 | } 38 | 39 | pub fn ethereum_sign_message(&mut self, message: Vec, path: Vec) -> Result { 40 | let mut req = protos::EthereumSignMessage::new(); 41 | req.address_n = path; 42 | req.set_message(message); 43 | let signature = handle_interaction(self.call( 44 | req, 45 | Box::new(|_, m: protos::EthereumMessageSignature| { 46 | let signature = m.signature(); 47 | 48 | // why are you in the end 49 | let v = signature[64] as u64; 50 | let r = U256::from_big_endian(&signature[0..32]); 51 | let s = U256::from_big_endian(&signature[32..64]); 52 | 53 | Ok(Signature { r, v, s }) 54 | }), 55 | )?)?; 56 | 57 | Ok(signature) 58 | } 59 | 60 | #[allow(clippy::too_many_arguments)] 61 | pub fn ethereum_sign_tx( 62 | &mut self, 63 | path: Vec, 64 | nonce: Vec, 65 | gas_price: Vec, 66 | gas_limit: Vec, 67 | to: String, 68 | value: Vec, 69 | _data: Vec, 70 | chain_id: u64, 71 | ) -> Result { 72 | let mut req = protos::EthereumSignTx::new(); 73 | let mut data = _data; 74 | 75 | req.address_n = path; 76 | req.set_nonce(nonce); 77 | req.set_gas_price(gas_price); 78 | req.set_gas_limit(gas_limit); 79 | req.set_value(value); 80 | req.set_chain_id(chain_id); 81 | req.set_to(to); 82 | 83 | req.set_data_length(data.len() as u32); 84 | req.set_data_initial_chunk(data.splice(..std::cmp::min(1024, data.len()), []).collect()); 85 | 86 | let mut resp = 87 | handle_interaction(self.call(req, Box::new(|_, m: protos::EthereumTxRequest| Ok(m)))?)?; 88 | 89 | while resp.data_length() > 0 { 90 | let mut ack = protos::EthereumTxAck::new(); 91 | ack.set_data_chunk(data.splice(..std::cmp::min(1024, data.len()), []).collect()); 92 | 93 | resp = self.call(ack, Box::new(|_, m: protos::EthereumTxRequest| Ok(m)))?.ok()?; 94 | } 95 | 96 | if resp.signature_v() <= 1 { 97 | resp.set_signature_v(resp.signature_v() + 2 * (chain_id as u32) + 35); 98 | } 99 | 100 | Ok(Signature { 101 | r: resp.signature_r().into(), 102 | v: resp.signature_v().into(), 103 | s: resp.signature_s().into(), 104 | }) 105 | } 106 | 107 | #[allow(clippy::too_many_arguments)] 108 | pub fn ethereum_sign_eip1559_tx( 109 | &mut self, 110 | path: Vec, 111 | nonce: Vec, 112 | gas_limit: Vec, 113 | to: String, 114 | value: Vec, 115 | _data: Vec, 116 | chain_id: u64, 117 | max_gas_fee: Vec, 118 | max_priority_fee: Vec, 119 | access_list: Vec, 120 | ) -> Result { 121 | let mut req = protos::EthereumSignTxEIP1559::new(); 122 | let mut data = _data; 123 | 124 | req.address_n = path; 125 | req.set_nonce(nonce); 126 | req.set_max_gas_fee(max_gas_fee); 127 | req.set_max_priority_fee(max_priority_fee); 128 | req.set_gas_limit(gas_limit); 129 | req.set_value(value); 130 | req.set_chain_id(chain_id); 131 | req.set_to(to); 132 | 133 | if !access_list.is_empty() { 134 | req.access_list = access_list 135 | .into_iter() 136 | .map(|item| EthereumAccessList { 137 | address: Some(item.address), 138 | storage_keys: item.storage_keys, 139 | ..Default::default() 140 | }) 141 | .collect(); 142 | } 143 | 144 | req.set_data_length(data.len() as u32); 145 | req.set_data_initial_chunk(data.splice(..std::cmp::min(1024, data.len()), []).collect()); 146 | 147 | let mut resp = 148 | handle_interaction(self.call(req, Box::new(|_, m: protos::EthereumTxRequest| Ok(m)))?)?; 149 | 150 | while resp.data_length() > 0 { 151 | let mut ack = protos::EthereumTxAck::new(); 152 | ack.set_data_chunk(data.splice(..std::cmp::min(1024, data.len()), []).collect()); 153 | 154 | resp = self.call(ack, Box::new(|_, m: protos::EthereumTxRequest| Ok(m)))?.ok()? 155 | } 156 | 157 | if resp.signature_v() <= 1 { 158 | resp.set_signature_v(resp.signature_v() + 2 * (chain_id as u32) + 35); 159 | } 160 | 161 | Ok(Signature { 162 | r: resp.signature_r().into(), 163 | v: resp.signature_v().into(), 164 | s: resp.signature_s().into(), 165 | }) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/client/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "bitcoin")] 2 | mod bitcoin; 3 | #[cfg(feature = "bitcoin")] 4 | pub use self::bitcoin::*; 5 | 6 | #[cfg(feature = "ethereum")] 7 | mod ethereum; 8 | #[cfg(feature = "ethereum")] 9 | pub use ethereum::*; 10 | 11 | pub mod common; 12 | pub use common::*; 13 | 14 | use crate::{ 15 | error::{Error, Result}, 16 | messages::TrezorMessage, 17 | protos, 18 | protos::MessageType::*, 19 | transport::{ProtoMessage, Transport}, 20 | Model, 21 | }; 22 | use protobuf::MessageField; 23 | use tracing::{debug, trace}; 24 | 25 | /// A Trezor client. 26 | pub struct Trezor { 27 | model: Model, 28 | // Cached features for later inspection. 29 | features: Option, 30 | transport: Box, 31 | } 32 | 33 | /// Create a new Trezor instance with the given transport. 34 | pub fn trezor_with_transport(model: Model, transport: Box) -> Trezor { 35 | Trezor { model, transport, features: None } 36 | } 37 | 38 | impl Trezor { 39 | /// Get the model of the Trezor device. 40 | pub fn model(&self) -> Model { 41 | self.model 42 | } 43 | 44 | /// Get the features of the Trezor device. 45 | pub fn features(&self) -> Option<&protos::Features> { 46 | self.features.as_ref() 47 | } 48 | 49 | /// Sends a message and returns the raw ProtoMessage struct that was responded by the device. 50 | /// This method is only exported for users that want to expand the features of this library 51 | /// f.e. for supporting additional coins etc. 52 | pub fn call_raw(&mut self, message: S) -> Result { 53 | let proto_msg = ProtoMessage(S::MESSAGE_TYPE, message.write_to_bytes()?); 54 | self.transport.write_message(proto_msg).map_err(Error::TransportSendMessage)?; 55 | self.transport.read_message().map_err(Error::TransportReceiveMessage) 56 | } 57 | 58 | /// Sends a message and returns a TrezorResponse with either the expected response message, 59 | /// a failure or an interaction request. 60 | /// This method is only exported for users that want to expand the features of this library 61 | /// f.e. for supporting additional coins etc. 62 | pub fn call<'a, T, S: TrezorMessage, R: TrezorMessage>( 63 | &'a mut self, 64 | message: S, 65 | result_handler: Box>, 66 | ) -> Result> { 67 | trace!("Sending {:?} msg: {:?}", S::MESSAGE_TYPE, message); 68 | let resp = self.call_raw(message)?; 69 | if resp.message_type() == R::MESSAGE_TYPE { 70 | let resp_msg = resp.into_message()?; 71 | trace!("Received {:?} msg: {:?}", R::MESSAGE_TYPE, resp_msg); 72 | Ok(TrezorResponse::Ok(result_handler(self, resp_msg)?)) 73 | } else { 74 | match resp.message_type() { 75 | MessageType_Failure => { 76 | let fail_msg = resp.into_message()?; 77 | debug!("Received failure: {:?}", fail_msg); 78 | Ok(TrezorResponse::Failure(fail_msg)) 79 | } 80 | MessageType_ButtonRequest => { 81 | let req_msg = resp.into_message()?; 82 | trace!("Received ButtonRequest: {:?}", req_msg); 83 | Ok(TrezorResponse::ButtonRequest(ButtonRequest { 84 | message: req_msg, 85 | client: self, 86 | result_handler, 87 | })) 88 | } 89 | MessageType_PinMatrixRequest => { 90 | let req_msg = resp.into_message()?; 91 | trace!("Received PinMatrixRequest: {:?}", req_msg); 92 | Ok(TrezorResponse::PinMatrixRequest(PinMatrixRequest { 93 | message: req_msg, 94 | client: self, 95 | result_handler, 96 | })) 97 | } 98 | MessageType_PassphraseRequest => { 99 | let req_msg = resp.into_message()?; 100 | trace!("Received PassphraseRequest: {:?}", req_msg); 101 | Ok(TrezorResponse::PassphraseRequest(PassphraseRequest { 102 | message: req_msg, 103 | client: self, 104 | result_handler, 105 | })) 106 | } 107 | mtype => { 108 | debug!( 109 | "Received unexpected msg type: {:?}; raw msg: {}", 110 | mtype, 111 | hex::encode(resp.into_payload()) 112 | ); 113 | Err(Error::UnexpectedMessageType(mtype)) 114 | } 115 | } 116 | } 117 | } 118 | 119 | pub fn init_device(&mut self, session_id: Option>) -> Result<()> { 120 | let features = self.initialize(session_id)?.ok()?; 121 | self.features = Some(features); 122 | Ok(()) 123 | } 124 | 125 | pub fn initialize( 126 | &mut self, 127 | session_id: Option>, 128 | ) -> Result> { 129 | let mut req = protos::Initialize::new(); 130 | if let Some(session_id) = session_id { 131 | req.set_session_id(session_id); 132 | } 133 | self.call(req, Box::new(|_, m| Ok(m))) 134 | } 135 | 136 | pub fn ping(&mut self, message: &str) -> Result> { 137 | let mut req = protos::Ping::new(); 138 | req.set_message(message.to_owned()); 139 | self.call(req, Box::new(|_, _| Ok(()))) 140 | } 141 | 142 | pub fn change_pin(&mut self, remove: bool) -> Result> { 143 | let mut req = protos::ChangePin::new(); 144 | req.set_remove(remove); 145 | self.call(req, Box::new(|_, _| Ok(()))) 146 | } 147 | 148 | pub fn wipe_device(&mut self) -> Result> { 149 | let req = protos::WipeDevice::new(); 150 | self.call(req, Box::new(|_, _| Ok(()))) 151 | } 152 | 153 | pub fn recover_device( 154 | &mut self, 155 | word_count: WordCount, 156 | passphrase_protection: bool, 157 | pin_protection: bool, 158 | label: String, 159 | dry_run: bool, 160 | ) -> Result> { 161 | let mut req = protos::RecoveryDevice::new(); 162 | req.set_word_count(word_count as u32); 163 | req.set_passphrase_protection(passphrase_protection); 164 | req.set_pin_protection(pin_protection); 165 | req.set_label(label); 166 | req.set_enforce_wordlist(true); 167 | req.set_dry_run(dry_run); 168 | req.set_type( 169 | protos::recovery_device::RecoveryDeviceType::RecoveryDeviceType_ScrambledWords, 170 | ); 171 | //TODO(stevenroose) support languages 172 | req.set_language("english".to_owned()); 173 | self.call(req, Box::new(|_, _| Ok(()))) 174 | } 175 | 176 | #[allow(clippy::too_many_arguments)] 177 | pub fn reset_device( 178 | &mut self, 179 | display_random: bool, 180 | strength: usize, 181 | passphrase_protection: bool, 182 | pin_protection: bool, 183 | label: String, 184 | skip_backup: bool, 185 | no_backup: bool, 186 | ) -> Result, protos::EntropyRequest>> { 187 | let mut req = protos::ResetDevice::new(); 188 | req.set_display_random(display_random); 189 | req.set_strength(strength as u32); 190 | req.set_passphrase_protection(passphrase_protection); 191 | req.set_pin_protection(pin_protection); 192 | req.set_label(label); 193 | req.set_skip_backup(skip_backup); 194 | req.set_no_backup(no_backup); 195 | self.call(req, Box::new(|c, _| Ok(EntropyRequest { client: c }))) 196 | } 197 | 198 | pub fn backup(&mut self) -> Result> { 199 | let req = protos::BackupDevice::new(); 200 | self.call(req, Box::new(|_, _| Ok(()))) 201 | } 202 | 203 | //TODO(stevenroose) support U2F stuff? currently ignored all 204 | 205 | pub fn apply_settings( 206 | &mut self, 207 | label: Option, 208 | use_passphrase: Option, 209 | homescreen: Option>, 210 | auto_lock_delay_ms: Option, 211 | ) -> Result> { 212 | let mut req = protos::ApplySettings::new(); 213 | if let Some(label) = label { 214 | req.set_label(label); 215 | } 216 | if let Some(use_passphrase) = use_passphrase { 217 | req.set_use_passphrase(use_passphrase); 218 | } 219 | if let Some(homescreen) = homescreen { 220 | req.set_homescreen(homescreen); 221 | } 222 | if let Some(auto_lock_delay_ms) = auto_lock_delay_ms { 223 | req.set_auto_lock_delay_ms(auto_lock_delay_ms as u32); 224 | } 225 | self.call(req, Box::new(|_, _| Ok(()))) 226 | } 227 | 228 | pub fn sign_identity( 229 | &mut self, 230 | identity: protos::IdentityType, 231 | digest: Vec, 232 | curve: String, 233 | ) -> Result, protos::SignedIdentity>> { 234 | let mut req = protos::SignIdentity::new(); 235 | req.identity = MessageField::some(identity); 236 | req.set_challenge_hidden(digest); 237 | req.set_challenge_visual("".to_owned()); 238 | req.set_ecdsa_curve_name(curve); 239 | self.call(req, Box::new(|_, m| Ok(m.signature().to_owned()))) 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! # Error Handling 2 | 3 | use crate::{client::InteractionType, protos, transport::error::Error as TransportError}; 4 | 5 | /// Trezor result type. Aliased to [`std::result::Result`] with the error type 6 | /// set to [`Error`]. 7 | pub type Result = std::result::Result; 8 | 9 | /// Trezor error. 10 | #[derive(Debug, thiserror::Error)] 11 | pub enum Error { 12 | /// Less than one device was plugged in. 13 | #[error("Trezor device not found")] 14 | NoDeviceFound, 15 | /// More than one device was plugged in. 16 | #[error("multiple Trezor devices found")] 17 | DeviceNotUnique, 18 | /// Transport error connecting to device. 19 | #[error("transport connect: {0}")] 20 | TransportConnect(#[source] TransportError), 21 | /// Transport error while beginning a session. 22 | #[error("transport beginning session: {0}")] 23 | TransportBeginSession(#[source] TransportError), 24 | /// Transport error while ending a session. 25 | #[error("transport ending session: {0}")] 26 | TransportEndSession(#[source] TransportError), 27 | /// Transport error while sending a message. 28 | #[error("transport sending message: {0}")] 29 | TransportSendMessage(#[source] TransportError), 30 | /// Transport error while receiving a message. 31 | #[error("transport receiving message: {0}")] 32 | TransportReceiveMessage(#[source] TransportError), 33 | /// Received an unexpected message type from the device. 34 | #[error("received unexpected message type: {0:?}")] 35 | UnexpectedMessageType(protos::MessageType), //TODO(stevenroose) type alias 36 | /// Error reading or writing protobuf messages. 37 | #[error(transparent)] 38 | Protobuf(#[from] protobuf::Error), 39 | /// A failure message was returned by the device. 40 | #[error("failure received: code={:?} message=\"{}\"", .0.code(), .0.message())] 41 | FailureResponse(protos::Failure), 42 | /// An unexpected interaction request was returned by the device. 43 | #[error("unexpected interaction request: {0:?}")] 44 | UnexpectedInteractionRequest(InteractionType), 45 | /// The given Bitcoin network is not supported. 46 | #[error("given network is not supported")] 47 | UnsupportedNetwork, 48 | /// Provided entropy is not 32 bytes. 49 | #[error("provided entropy is not 32 bytes")] 50 | InvalidEntropy, 51 | /// The device erenced a non-existing input or output index. 52 | #[error("device referenced non-existing input or output index: {0}")] 53 | TxRequestInvalidIndex(usize), 54 | 55 | /// User provided invalid PSBT. 56 | #[error("PSBT missing input tx: {0}")] 57 | InvalidPsbt(String), 58 | 59 | // bitcoin 60 | /// Error in Base58 decoding 61 | #[cfg(feature = "bitcoin")] 62 | #[error(transparent)] 63 | Base58(#[from] bitcoin::base58::Error), 64 | /// The device erenced an unknown TXID. 65 | #[cfg(feature = "bitcoin")] 66 | #[error("device referenced unknown TXID: {0}")] 67 | TxRequestUnknownTxid(bitcoin::hashes::sha256d::Hash), 68 | /// The PSBT is missing the full tx for given input. 69 | #[cfg(feature = "bitcoin")] 70 | #[error("PSBT missing input tx: {0}")] 71 | PsbtMissingInputTx(bitcoin::hashes::sha256d::Hash), 72 | /// Device produced invalid TxRequest message. 73 | #[cfg(feature = "bitcoin")] 74 | #[error("malformed TxRequest: {0:?}")] 75 | MalformedTxRequest(protos::TxRequest), 76 | /// Error encoding/decoding a Bitcoin data structure. 77 | #[cfg(feature = "bitcoin")] 78 | #[error(transparent)] 79 | BitcoinEncode(#[from] bitcoin::consensus::encode::Error), 80 | /// Elliptic curve crypto error. 81 | #[cfg(feature = "bitcoin")] 82 | #[error(transparent)] 83 | Secp256k1(#[from] bitcoin::secp256k1::Error), 84 | /// Bip32 error. 85 | #[cfg(feature = "bitcoin")] 86 | #[error(transparent)] 87 | Bip32(#[from] bitcoin::bip32::Error), 88 | /// Address error. 89 | #[cfg(feature = "bitcoin")] 90 | #[error(transparent)] 91 | Address(#[from] bitcoin::address::Error), 92 | } 93 | -------------------------------------------------------------------------------- /src/flows/sign_tx.rs: -------------------------------------------------------------------------------- 1 | //! Logic to handle the sign_tx command flow. 2 | 3 | use crate::{ 4 | client::*, 5 | error::{Error, Result}, 6 | protos::{ 7 | self, 8 | tx_ack::{ 9 | transaction_type::{TxOutputBinType, TxOutputType}, 10 | TransactionType, 11 | }, 12 | }, 13 | utils, 14 | }; 15 | use bitcoin::{hashes::sha256d, psbt, Network, Transaction}; 16 | use protos::{ 17 | tx_ack::transaction_type::TxInputType, tx_request::RequestType as TxRequestType, 18 | OutputScriptType, 19 | }; 20 | use tracing::trace; 21 | 22 | // Some types with raw protos that we use in the public interface so they have to be exported. 23 | pub use protos::{ 24 | ButtonRequest as ButtonRequestType, Features, InputScriptType, 25 | PinMatrixRequest as PinMatrixRequestType, 26 | }; 27 | 28 | /// Fulfill a TxRequest for TXINPUT. 29 | fn ack_input_request( 30 | req: &protos::TxRequest, 31 | psbt: &psbt::PartiallySignedTransaction, 32 | ) -> Result { 33 | if req.details.is_none() || !req.details.has_request_index() { 34 | return Err(Error::MalformedTxRequest(req.clone())) 35 | } 36 | 37 | // Choose either the tx we are signing or a dependent tx. 38 | let input_index = req.details.request_index() as usize; 39 | let input = if req.details.has_tx_hash() { 40 | let req_hash: sha256d::Hash = utils::from_rev_bytes(req.details.tx_hash()) 41 | .ok_or_else(|| Error::MalformedTxRequest(req.clone()))?; 42 | trace!("Preparing ack for input {}:{}", req_hash, input_index); 43 | let inp = utils::psbt_find_input(psbt, req_hash)?; 44 | let tx = inp.non_witness_utxo.as_ref().ok_or(Error::PsbtMissingInputTx(req_hash))?; 45 | let opt = &tx.input.get(input_index); 46 | opt.ok_or_else(|| Error::TxRequestInvalidIndex(input_index))? 47 | } else { 48 | trace!("Preparing ack for tx input #{}", input_index); 49 | let opt = &psbt.unsigned_tx.input.get(input_index); 50 | opt.ok_or(Error::TxRequestInvalidIndex(input_index))? 51 | }; 52 | 53 | let mut data_input = TxInputType::new(); 54 | data_input 55 | .set_prev_hash(utils::to_rev_bytes(input.previous_output.txid.as_raw_hash()).to_vec()); 56 | data_input.set_prev_index(input.previous_output.vout); 57 | data_input.set_script_sig(input.script_sig.to_bytes()); 58 | data_input.set_sequence(input.sequence.to_consensus_u32()); 59 | 60 | // Extra data only for currently signing tx. 61 | if !req.details.has_tx_hash() { 62 | let psbt_input = psbt 63 | .inputs 64 | .get(input_index) 65 | .ok_or_else(|| Error::InvalidPsbt("not enough psbt inputs".to_owned()))?; 66 | 67 | // Get the output we are spending from the PSBT input. 68 | let txout = if let Some(ref txout) = psbt_input.witness_utxo { 69 | txout 70 | } else if let Some(ref tx) = psbt_input.non_witness_utxo { 71 | tx.output.get(input.previous_output.vout as usize).ok_or_else(|| { 72 | Error::InvalidPsbt(format!("invalid utxo for PSBT input {}", input_index)) 73 | })? 74 | } else { 75 | return Err(Error::InvalidPsbt(format!("no utxo for PSBT input {}", input_index))) 76 | }; 77 | 78 | // If there is exactly 1 HD keypath known, we can provide it. If more it's multisig. 79 | if psbt_input.bip32_derivation.len() == 1 { 80 | let (_, (_, path)) = psbt_input.bip32_derivation.iter().next().unwrap(); 81 | data_input.address_n = path.as_ref().iter().map(|i| (*i).into()).collect(); 82 | } 83 | 84 | // Since we know the keypath, we probably have to sign it. So update script_type. 85 | let script_type = { 86 | let script_pubkey = &txout.script_pubkey; 87 | 88 | if script_pubkey.is_p2pkh() { 89 | InputScriptType::SPENDADDRESS 90 | } else if script_pubkey.is_v0_p2wpkh() || script_pubkey.is_v0_p2wsh() { 91 | InputScriptType::SPENDWITNESS 92 | } else if script_pubkey.is_p2sh() && psbt_input.witness_script.is_some() { 93 | InputScriptType::SPENDP2SHWITNESS 94 | } else { 95 | //TODO(stevenroose) normal p2sh is probably multisig 96 | InputScriptType::EXTERNAL 97 | } 98 | }; 99 | data_input.set_script_type(script_type); 100 | //TODO(stevenroose) multisig 101 | 102 | data_input.set_amount(txout.value); 103 | } 104 | 105 | trace!("Prepared input to ack: {:?}", data_input); 106 | let mut txdata = TransactionType::new(); 107 | txdata.inputs.push(data_input); 108 | let mut msg = protos::TxAck::new(); 109 | msg.tx = protobuf::MessageField::some(txdata); 110 | Ok(msg) 111 | } 112 | 113 | /// Fulfill a TxRequest for TXOUTPUT. 114 | fn ack_output_request( 115 | req: &protos::TxRequest, 116 | psbt: &psbt::PartiallySignedTransaction, 117 | network: Network, 118 | ) -> Result { 119 | if req.details.is_none() || !req.details.has_request_index() { 120 | return Err(Error::MalformedTxRequest(req.clone())) 121 | } 122 | 123 | // For outputs, the Trezor only needs bin_outputs to be set for dependent txs and full outputs 124 | // for the signing tx. 125 | let mut txdata = TransactionType::new(); 126 | if req.details.has_tx_hash() { 127 | // Dependent tx, take the output from the PSBT and just create bin_output. 128 | let output_index = req.details.request_index() as usize; 129 | let req_hash: sha256d::Hash = utils::from_rev_bytes(req.details.tx_hash()) 130 | .ok_or_else(|| Error::MalformedTxRequest(req.clone()))?; 131 | trace!("Preparing ack for output {}:{}", req_hash, output_index); 132 | let inp = utils::psbt_find_input(psbt, req_hash)?; 133 | let output = if let Some(ref tx) = inp.non_witness_utxo { 134 | let opt = &tx.output.get(output_index); 135 | opt.ok_or_else(|| Error::TxRequestInvalidIndex(output_index))? 136 | } else if let Some(ref utxo) = inp.witness_utxo { 137 | utxo 138 | } else { 139 | return Err(Error::InvalidPsbt("not all inputs have utxo data".to_owned())) 140 | }; 141 | 142 | let mut bin_output = TxOutputBinType::new(); 143 | bin_output.set_amount(output.value); 144 | bin_output.set_script_pubkey(output.script_pubkey.to_bytes()); 145 | 146 | trace!("Prepared bin_output to ack: {:?}", bin_output); 147 | txdata.bin_outputs.push(bin_output); 148 | } else { 149 | // Signing tx, we need to fill the full output meta object. 150 | let output_index = req.details.request_index() as usize; 151 | trace!("Preparing ack for tx output #{}", output_index); 152 | let opt = &psbt.unsigned_tx.output.get(output_index); 153 | let output = opt.ok_or(Error::TxRequestInvalidIndex(output_index))?; 154 | 155 | let mut data_output = TxOutputType::new(); 156 | data_output.set_amount(output.value); 157 | // Set script type to PAYTOADDRESS unless we find out otherwise from the PSBT. 158 | data_output.set_script_type(OutputScriptType::PAYTOADDRESS); 159 | if let Some(addr) = utils::address_from_script(&output.script_pubkey, network) { 160 | data_output.set_address(addr.to_string()); 161 | } 162 | 163 | let psbt_output = psbt 164 | .outputs 165 | .get(output_index) 166 | .ok_or_else(|| Error::InvalidPsbt("output indices don't match".to_owned()))?; 167 | if psbt_output.bip32_derivation.len() == 1 { 168 | let (_, (_, path)) = psbt_output.bip32_derivation.iter().next().unwrap(); 169 | data_output.address_n = path.as_ref().iter().map(|i| (*i).into()).collect(); 170 | 171 | // Since we know the keypath, it's probably a change output. So update script_type. 172 | let script_pubkey = &psbt.unsigned_tx.output[output_index].script_pubkey; 173 | if script_pubkey.is_op_return() { 174 | data_output.set_script_type(OutputScriptType::PAYTOOPRETURN); 175 | data_output.set_op_return_data(script_pubkey.as_bytes()[1..].to_vec()); 176 | } else if psbt_output.witness_script.is_some() { 177 | if psbt_output.redeem_script.is_some() { 178 | data_output.set_script_type(OutputScriptType::PAYTOP2SHWITNESS); 179 | } else { 180 | data_output.set_script_type(OutputScriptType::PAYTOWITNESS); 181 | } 182 | } else { 183 | data_output.set_script_type(OutputScriptType::PAYTOADDRESS); 184 | } 185 | } 186 | 187 | trace!("Prepared output to ack: {:?}", data_output); 188 | txdata.outputs.push(data_output); 189 | }; 190 | 191 | let mut msg = protos::TxAck::new(); 192 | msg.tx = protobuf::MessageField::some(txdata); 193 | Ok(msg) 194 | } 195 | 196 | /// Fulfill a TxRequest for TXMETA. 197 | fn ack_meta_request( 198 | req: &protos::TxRequest, 199 | psbt: &psbt::PartiallySignedTransaction, 200 | ) -> Result { 201 | if req.details.is_none() { 202 | return Err(Error::MalformedTxRequest(req.clone())) 203 | } 204 | 205 | // Choose either the tx we are signing or a dependent tx. 206 | let tx: &Transaction = if req.details.has_tx_hash() { 207 | // dependeny tx, look for it in PSBT inputs 208 | let req_hash: sha256d::Hash = utils::from_rev_bytes(req.details.tx_hash()) 209 | .ok_or_else(|| Error::MalformedTxRequest(req.clone()))?; 210 | trace!("Preparing ack for tx meta of {}", req_hash); 211 | let inp = utils::psbt_find_input(psbt, req_hash)?; 212 | inp.non_witness_utxo.as_ref().ok_or(Error::PsbtMissingInputTx(req_hash))? 213 | } else { 214 | // currently signing tx 215 | trace!("Preparing ack for tx meta of tx being signed"); 216 | &psbt.unsigned_tx 217 | }; 218 | 219 | let mut txdata = TransactionType::new(); 220 | txdata.set_version(tx.version as u32); 221 | txdata.set_lock_time(tx.lock_time.to_consensus_u32()); 222 | txdata.set_inputs_cnt(tx.input.len() as u32); 223 | txdata.set_outputs_cnt(tx.output.len() as u32); 224 | //TODO(stevenroose) python does something with extra data? 225 | 226 | trace!("Prepared tx meta to ack: {:?}", txdata); 227 | let mut msg = protos::TxAck::new(); 228 | msg.tx = protobuf::MessageField::some(txdata); 229 | Ok(msg) 230 | } 231 | 232 | /// Object to track the progress in the transaction signing flow. The device will ask for various 233 | /// parts of the transaction and dependent transactions and can at any point also ask for user 234 | /// interaction. The information asked for by the device is provided based on a PSBT object and the 235 | /// resulting extra signatures are also added to the PSBT file. 236 | /// 237 | /// It's important to always first check with the `finished()` method if more data is requested by 238 | /// the device. If you're not yet finished you must call the `ack_psbt()` method to send more 239 | /// information to the device. 240 | pub struct SignTxProgress<'a> { 241 | client: &'a mut Trezor, 242 | req: protos::TxRequest, 243 | } 244 | 245 | impl<'a> SignTxProgress<'a> { 246 | /// Only intended for internal usage. 247 | pub fn new(client: &mut Trezor, req: protos::TxRequest) -> SignTxProgress<'_> { 248 | SignTxProgress { client, req } 249 | } 250 | 251 | /// Inspector to the request message received from the device. 252 | pub fn tx_request(&self) -> &protos::TxRequest { 253 | &self.req 254 | } 255 | 256 | /// Check whether or not the signing process is finished. 257 | pub fn finished(&self) -> bool { 258 | self.req.request_type() == TxRequestType::TXFINISHED 259 | } 260 | 261 | /// Check if a signature is provided by the device. 262 | pub fn has_signature(&self) -> bool { 263 | let serialized = &self.req.serialized; 264 | serialized.is_some() && serialized.has_signature_index() && serialized.has_signature() 265 | } 266 | 267 | /// Get the signature provided from the device along with the input index of the signature. 268 | pub fn get_signature(&self) -> Option<(usize, &[u8])> { 269 | if self.has_signature() { 270 | let serialized = &self.req.serialized; 271 | Some((serialized.signature_index() as usize, serialized.signature())) 272 | } else { 273 | None 274 | } 275 | } 276 | 277 | //TODO(stevenroose) We used to have a method here `apply_signature(&mut psbt)` that would put 278 | // the received signature in the correct PSBT input. However, since the signature is just a raw 279 | // signature instead of a scriptSig, this is harder. It can be done, but then we'd have to have 280 | // the pubkey provided in the PSBT (possible thought HD path) and we'd have to do some Script 281 | // inspection to see if we should put it as a p2pkh sciptSig or witness data. 282 | 283 | /// Check if a part of the serialized signed tx is provided by the device. 284 | pub fn has_serialized_tx_part(&self) -> bool { 285 | let serialized = &self.req.serialized; 286 | serialized.is_some() && serialized.has_serialized_tx() 287 | } 288 | 289 | /// Get the part of the serialized signed tx from the device. 290 | pub fn get_serialized_tx_part(&self) -> Option<&[u8]> { 291 | if self.has_serialized_tx_part() { 292 | Some(self.req.serialized.serialized_tx()) 293 | } else { 294 | None 295 | } 296 | } 297 | 298 | /// Manually provide a TxAck message to the device. 299 | /// 300 | /// This method will panic if `finished()` returned true, 301 | /// so it should always be checked in advance. 302 | pub fn ack_msg( 303 | self, 304 | ack: protos::TxAck, 305 | ) -> Result, protos::TxRequest>> { 306 | assert!(!self.finished()); 307 | 308 | self.client.call(ack, Box::new(|c, m| Ok(SignTxProgress::new(c, m)))) 309 | } 310 | 311 | /// Provide additional PSBT information to the device. 312 | /// 313 | /// This method will panic if `apply()` returned true, 314 | /// so it should always be checked in advance. 315 | pub fn ack_psbt( 316 | self, 317 | psbt: &psbt::PartiallySignedTransaction, 318 | network: Network, 319 | ) -> Result, protos::TxRequest>> { 320 | assert!(self.req.request_type() != TxRequestType::TXFINISHED); 321 | 322 | let ack = match self.req.request_type() { 323 | TxRequestType::TXINPUT => ack_input_request(&self.req, psbt), 324 | TxRequestType::TXOUTPUT => ack_output_request(&self.req, psbt, network), 325 | TxRequestType::TXMETA => ack_meta_request(&self.req, psbt), 326 | TxRequestType::TXEXTRADATA => unimplemented!(), //TODO(stevenroose) implement 327 | TxRequestType::TXORIGINPUT | 328 | TxRequestType::TXORIGOUTPUT | 329 | TxRequestType::TXPAYMENTREQ => unimplemented!(), 330 | TxRequestType::TXFINISHED => unreachable!(), 331 | }?; 332 | self.ack_msg(ack) 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Trezor API library 2 | //! 3 | //! ## Connecting 4 | //! 5 | //! Use the public top-level methods `find_devices()` and `unique()` to find devices. When using 6 | //! `find_devices()`, a list of different available devices is returned. To connect to one or more 7 | //! of them, use their `connect()` method. 8 | //! 9 | //! ## Logging 10 | //! 11 | //! We use the log package interface, so any logger that supports log can be attached. 12 | //! Please be aware that `trace` logging can contain sensitive data. 13 | 14 | #![warn(rust_2018_idioms)] 15 | 16 | mod messages; 17 | mod transport; 18 | 19 | pub mod client; 20 | pub mod error; 21 | pub mod protos; 22 | #[cfg(feature = "bitcoin")] 23 | pub mod utils; 24 | 25 | mod flows { 26 | #[cfg(feature = "bitcoin")] 27 | pub mod sign_tx; 28 | } 29 | 30 | pub use client::{ 31 | ButtonRequest, ButtonRequestType, EntropyRequest, Features, PassphraseRequest, 32 | PinMatrixRequest, PinMatrixRequestType, Trezor, TrezorResponse, WordCount, 33 | }; 34 | pub use error::{Error, Result}; 35 | pub use messages::TrezorMessage; 36 | 37 | #[cfg(feature = "bitcoin")] 38 | pub use flows::sign_tx::SignTxProgress; 39 | #[cfg(feature = "bitcoin")] 40 | pub use protos::InputScriptType; 41 | 42 | use std::fmt; 43 | use tracing::{debug, warn}; 44 | use transport::{udp::UdpTransport, webusb::WebUsbTransport}; 45 | 46 | /// The different kind of Trezor device models. 47 | #[derive(PartialEq, Eq, Clone, Debug, Copy)] 48 | pub enum Model { 49 | TrezorLegacy, 50 | Trezor, 51 | TrezorBootloader, 52 | TrezorEmulator, 53 | } 54 | 55 | impl fmt::Display for Model { 56 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 57 | f.write_str(self.as_str()) 58 | } 59 | } 60 | 61 | impl Model { 62 | pub const fn as_str(&self) -> &'static str { 63 | match self { 64 | Model::TrezorLegacy => "Trezor (legacy)", 65 | Model::Trezor => "Trezor", 66 | Model::TrezorBootloader => "Trezor (bootloader)", 67 | Model::TrezorEmulator => "Trezor Emulator", 68 | } 69 | } 70 | } 71 | 72 | /// A device found by the `find_devices()` method. It can be connected to using the `connect()` 73 | /// method. 74 | #[derive(Debug)] 75 | pub struct AvailableDevice { 76 | pub model: Model, 77 | pub debug: bool, 78 | transport: transport::AvailableDeviceTransport, 79 | } 80 | 81 | impl fmt::Display for AvailableDevice { 82 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 83 | write!(f, "{} (transport: {}) (debug: {})", self.model, &self.transport, self.debug) 84 | } 85 | } 86 | 87 | impl AvailableDevice { 88 | /// Connect to the device. 89 | pub fn connect(self) -> Result { 90 | let transport = transport::connect(&self).map_err(Error::TransportConnect)?; 91 | Ok(client::trezor_with_transport(self.model, transport)) 92 | } 93 | } 94 | 95 | /// Search for all available devices. 96 | /// Most devices will show up twice both either debugging enables or disabled. 97 | pub fn find_devices(debug: bool) -> Vec { 98 | let mut devices = vec![]; 99 | 100 | match WebUsbTransport::find_devices(debug) { 101 | Ok(usb) => devices.extend(usb), 102 | Err(err) => { 103 | warn!("{}", Error::TransportConnect(err)) 104 | } 105 | }; 106 | 107 | match UdpTransport::find_devices(debug, None) { 108 | Ok(udp) => devices.extend(udp), 109 | Err(err) => { 110 | warn!("{}", Error::TransportConnect(err)) 111 | } 112 | }; 113 | 114 | devices 115 | } 116 | 117 | /// Try to get a single device. Optionally specify whether debug should be enabled or not. 118 | /// Can error if there are multiple or no devices available. 119 | /// For more fine-grained device selection, use `find_devices()`. 120 | /// When using USB mode, the device will show up both with debug and without debug, so it's 121 | /// necessary to specify the debug option in order to find a unique one. 122 | pub fn unique(debug: bool) -> Result { 123 | let mut devices = find_devices(debug); 124 | match devices.len() { 125 | 0 => Err(Error::NoDeviceFound), 126 | 1 => Ok(devices.remove(0).connect()?), 127 | _ => { 128 | debug!("Trezor devices found: {:?}", devices); 129 | Err(Error::DeviceNotUnique) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/messages.rs: -------------------------------------------------------------------------------- 1 | //! This module implements the `message_type` getter for all protobuf message types. 2 | 3 | use crate::protos::{MessageType::*, *}; 4 | 5 | /// Extends the protobuf Message trait to also have a static getter for the message 6 | /// type code. 7 | pub trait TrezorMessage: protobuf::Message + std::fmt::Debug { 8 | const MESSAGE_TYPE: MessageType; 9 | 10 | #[inline] 11 | #[deprecated(note = "Use `MESSAGE_TYPE` instead")] 12 | fn message_type() -> MessageType { 13 | Self::MESSAGE_TYPE 14 | } 15 | } 16 | 17 | /// This macro provides the TrezorMessage trait for a protobuf message. 18 | macro_rules! trezor_message_impl { 19 | ($($struct:ident => $mtype:expr),+ $(,)?) => {$( 20 | impl TrezorMessage for $struct { 21 | const MESSAGE_TYPE: MessageType = $mtype; 22 | } 23 | )+}; 24 | } 25 | 26 | trezor_message_impl! { 27 | Initialize => MessageType_Initialize, 28 | Ping => MessageType_Ping, 29 | Success => MessageType_Success, 30 | Failure => MessageType_Failure, 31 | ChangePin => MessageType_ChangePin, 32 | WipeDevice => MessageType_WipeDevice, 33 | GetEntropy => MessageType_GetEntropy, 34 | Entropy => MessageType_Entropy, 35 | LoadDevice => MessageType_LoadDevice, 36 | ResetDevice => MessageType_ResetDevice, 37 | SetBusy => MessageType_SetBusy, 38 | Features => MessageType_Features, 39 | PinMatrixRequest => MessageType_PinMatrixRequest, 40 | PinMatrixAck => MessageType_PinMatrixAck, 41 | Cancel => MessageType_Cancel, 42 | LockDevice => MessageType_LockDevice, 43 | ApplySettings => MessageType_ApplySettings, 44 | ButtonRequest => MessageType_ButtonRequest, 45 | ButtonAck => MessageType_ButtonAck, 46 | ApplyFlags => MessageType_ApplyFlags, 47 | GetNonce => MessageType_GetNonce, 48 | Nonce => MessageType_Nonce, 49 | BackupDevice => MessageType_BackupDevice, 50 | EntropyRequest => MessageType_EntropyRequest, 51 | EntropyAck => MessageType_EntropyAck, 52 | PassphraseRequest => MessageType_PassphraseRequest, 53 | PassphraseAck => MessageType_PassphraseAck, 54 | RecoveryDevice => MessageType_RecoveryDevice, 55 | WordRequest => MessageType_WordRequest, 56 | WordAck => MessageType_WordAck, 57 | GetFeatures => MessageType_GetFeatures, 58 | SdProtect => MessageType_SdProtect, 59 | ChangeWipeCode => MessageType_ChangeWipeCode, 60 | EndSession => MessageType_EndSession, 61 | DoPreauthorized => MessageType_DoPreauthorized, 62 | PreauthorizedRequest => MessageType_PreauthorizedRequest, 63 | CancelAuthorization => MessageType_CancelAuthorization, 64 | RebootToBootloader => MessageType_RebootToBootloader, 65 | GetFirmwareHash => MessageType_GetFirmwareHash, 66 | FirmwareHash => MessageType_FirmwareHash, 67 | UnlockPath => MessageType_UnlockPath, 68 | UnlockedPathRequest => MessageType_UnlockedPathRequest, 69 | SetU2FCounter => MessageType_SetU2FCounter, 70 | GetNextU2FCounter => MessageType_GetNextU2FCounter, 71 | NextU2FCounter => MessageType_NextU2FCounter, 72 | Deprecated_PassphraseStateRequest => MessageType_Deprecated_PassphraseStateRequest, 73 | Deprecated_PassphraseStateAck => MessageType_Deprecated_PassphraseStateAck, 74 | FirmwareErase => MessageType_FirmwareErase, 75 | FirmwareUpload => MessageType_FirmwareUpload, 76 | FirmwareRequest => MessageType_FirmwareRequest, 77 | SelfTest => MessageType_SelfTest, 78 | CipherKeyValue => MessageType_CipherKeyValue, 79 | CipheredKeyValue => MessageType_CipheredKeyValue, 80 | SignIdentity => MessageType_SignIdentity, 81 | SignedIdentity => MessageType_SignedIdentity, 82 | GetECDHSessionKey => MessageType_GetECDHSessionKey, 83 | ECDHSessionKey => MessageType_ECDHSessionKey, 84 | CosiCommit => MessageType_CosiCommit, 85 | CosiCommitment => MessageType_CosiCommitment, 86 | CosiSign => MessageType_CosiSign, 87 | CosiSignature => MessageType_CosiSignature, 88 | DebugLinkDecision => MessageType_DebugLinkDecision, 89 | DebugLinkGetState => MessageType_DebugLinkGetState, 90 | DebugLinkState => MessageType_DebugLinkState, 91 | DebugLinkStop => MessageType_DebugLinkStop, 92 | DebugLinkLog => MessageType_DebugLinkLog, 93 | DebugLinkMemoryRead => MessageType_DebugLinkMemoryRead, 94 | DebugLinkMemory => MessageType_DebugLinkMemory, 95 | DebugLinkMemoryWrite => MessageType_DebugLinkMemoryWrite, 96 | DebugLinkFlashErase => MessageType_DebugLinkFlashErase, 97 | DebugLinkLayout => MessageType_DebugLinkLayout, 98 | DebugLinkReseedRandom => MessageType_DebugLinkReseedRandom, 99 | DebugLinkRecordScreen => MessageType_DebugLinkRecordScreen, 100 | DebugLinkEraseSdCard => MessageType_DebugLinkEraseSdCard, 101 | DebugLinkWatchLayout => MessageType_DebugLinkWatchLayout, 102 | DebugLinkResetDebugEvents => MessageType_DebugLinkResetDebugEvents, 103 | } 104 | 105 | #[cfg(feature = "binance")] 106 | trezor_message_impl! { 107 | BinanceGetAddress => MessageType_BinanceGetAddress, 108 | BinanceAddress => MessageType_BinanceAddress, 109 | BinanceGetPublicKey => MessageType_BinanceGetPublicKey, 110 | BinancePublicKey => MessageType_BinancePublicKey, 111 | BinanceSignTx => MessageType_BinanceSignTx, 112 | BinanceTxRequest => MessageType_BinanceTxRequest, 113 | BinanceTransferMsg => MessageType_BinanceTransferMsg, 114 | BinanceOrderMsg => MessageType_BinanceOrderMsg, 115 | BinanceCancelMsg => MessageType_BinanceCancelMsg, 116 | BinanceSignedTx => MessageType_BinanceSignedTx, 117 | } 118 | 119 | #[cfg(feature = "bitcoin")] 120 | trezor_message_impl! { 121 | GetPublicKey => MessageType_GetPublicKey, 122 | PublicKey => MessageType_PublicKey, 123 | SignTx => MessageType_SignTx, 124 | TxRequest => MessageType_TxRequest, 125 | TxAck => MessageType_TxAck, 126 | GetAddress => MessageType_GetAddress, 127 | Address => MessageType_Address, 128 | TxAckPaymentRequest => MessageType_TxAckPaymentRequest, 129 | SignMessage => MessageType_SignMessage, 130 | VerifyMessage => MessageType_VerifyMessage, 131 | MessageSignature => MessageType_MessageSignature, 132 | GetOwnershipId => MessageType_GetOwnershipId, 133 | OwnershipId => MessageType_OwnershipId, 134 | GetOwnershipProof => MessageType_GetOwnershipProof, 135 | OwnershipProof => MessageType_OwnershipProof, 136 | AuthorizeCoinJoin => MessageType_AuthorizeCoinJoin, 137 | } 138 | 139 | #[cfg(feature = "cardano")] 140 | trezor_message_impl! { 141 | CardanoGetPublicKey => MessageType_CardanoGetPublicKey, 142 | CardanoPublicKey => MessageType_CardanoPublicKey, 143 | CardanoGetAddress => MessageType_CardanoGetAddress, 144 | CardanoAddress => MessageType_CardanoAddress, 145 | CardanoTxItemAck => MessageType_CardanoTxItemAck, 146 | CardanoTxAuxiliaryDataSupplement => MessageType_CardanoTxAuxiliaryDataSupplement, 147 | CardanoTxWitnessRequest => MessageType_CardanoTxWitnessRequest, 148 | CardanoTxWitnessResponse => MessageType_CardanoTxWitnessResponse, 149 | CardanoTxHostAck => MessageType_CardanoTxHostAck, 150 | CardanoTxBodyHash => MessageType_CardanoTxBodyHash, 151 | CardanoSignTxFinished => MessageType_CardanoSignTxFinished, 152 | CardanoSignTxInit => MessageType_CardanoSignTxInit, 153 | CardanoTxInput => MessageType_CardanoTxInput, 154 | CardanoTxOutput => MessageType_CardanoTxOutput, 155 | CardanoAssetGroup => MessageType_CardanoAssetGroup, 156 | CardanoToken => MessageType_CardanoToken, 157 | CardanoTxCertificate => MessageType_CardanoTxCertificate, 158 | CardanoTxWithdrawal => MessageType_CardanoTxWithdrawal, 159 | CardanoTxAuxiliaryData => MessageType_CardanoTxAuxiliaryData, 160 | CardanoPoolOwner => MessageType_CardanoPoolOwner, 161 | CardanoPoolRelayParameters => MessageType_CardanoPoolRelayParameters, 162 | CardanoGetNativeScriptHash => MessageType_CardanoGetNativeScriptHash, 163 | CardanoNativeScriptHash => MessageType_CardanoNativeScriptHash, 164 | CardanoTxMint => MessageType_CardanoTxMint, 165 | CardanoTxCollateralInput => MessageType_CardanoTxCollateralInput, 166 | CardanoTxRequiredSigner => MessageType_CardanoTxRequiredSigner, 167 | CardanoTxInlineDatumChunk => MessageType_CardanoTxInlineDatumChunk, 168 | CardanoTxReferenceScriptChunk => MessageType_CardanoTxReferenceScriptChunk, 169 | CardanoTxReferenceInput => MessageType_CardanoTxReferenceInput, 170 | } 171 | 172 | #[cfg(feature = "eos")] 173 | trezor_message_impl! { 174 | EosGetPublicKey => MessageType_EosGetPublicKey, 175 | EosPublicKey => MessageType_EosPublicKey, 176 | EosSignTx => MessageType_EosSignTx, 177 | EosTxActionRequest => MessageType_EosTxActionRequest, 178 | EosTxActionAck => MessageType_EosTxActionAck, 179 | EosSignedTx => MessageType_EosSignedTx, 180 | } 181 | 182 | #[cfg(feature = "ethereum")] 183 | trezor_message_impl! { 184 | EthereumGetPublicKey => MessageType_EthereumGetPublicKey, 185 | EthereumPublicKey => MessageType_EthereumPublicKey, 186 | EthereumGetAddress => MessageType_EthereumGetAddress, 187 | EthereumAddress => MessageType_EthereumAddress, 188 | EthereumSignTx => MessageType_EthereumSignTx, 189 | EthereumSignTxEIP1559 => MessageType_EthereumSignTxEIP1559, 190 | EthereumTxRequest => MessageType_EthereumTxRequest, 191 | EthereumTxAck => MessageType_EthereumTxAck, 192 | EthereumSignMessage => MessageType_EthereumSignMessage, 193 | EthereumVerifyMessage => MessageType_EthereumVerifyMessage, 194 | EthereumMessageSignature => MessageType_EthereumMessageSignature, 195 | EthereumSignTypedData => MessageType_EthereumSignTypedData, 196 | EthereumTypedDataStructRequest => MessageType_EthereumTypedDataStructRequest, 197 | EthereumTypedDataStructAck => MessageType_EthereumTypedDataStructAck, 198 | EthereumTypedDataValueRequest => MessageType_EthereumTypedDataValueRequest, 199 | EthereumTypedDataValueAck => MessageType_EthereumTypedDataValueAck, 200 | EthereumTypedDataSignature => MessageType_EthereumTypedDataSignature, 201 | EthereumSignTypedHash => MessageType_EthereumSignTypedHash, 202 | } 203 | 204 | #[cfg(feature = "monero")] 205 | trezor_message_impl! { 206 | MoneroTransactionInitRequest => MessageType_MoneroTransactionInitRequest, 207 | MoneroTransactionInitAck => MessageType_MoneroTransactionInitAck, 208 | MoneroTransactionSetInputRequest => MessageType_MoneroTransactionSetInputRequest, 209 | MoneroTransactionSetInputAck => MessageType_MoneroTransactionSetInputAck, 210 | MoneroTransactionInputViniRequest => MessageType_MoneroTransactionInputViniRequest, 211 | MoneroTransactionInputViniAck => MessageType_MoneroTransactionInputViniAck, 212 | MoneroTransactionAllInputsSetRequest => MessageType_MoneroTransactionAllInputsSetRequest, 213 | MoneroTransactionAllInputsSetAck => MessageType_MoneroTransactionAllInputsSetAck, 214 | MoneroTransactionSetOutputRequest => MessageType_MoneroTransactionSetOutputRequest, 215 | MoneroTransactionSetOutputAck => MessageType_MoneroTransactionSetOutputAck, 216 | MoneroTransactionAllOutSetRequest => MessageType_MoneroTransactionAllOutSetRequest, 217 | MoneroTransactionAllOutSetAck => MessageType_MoneroTransactionAllOutSetAck, 218 | MoneroTransactionSignInputRequest => MessageType_MoneroTransactionSignInputRequest, 219 | MoneroTransactionSignInputAck => MessageType_MoneroTransactionSignInputAck, 220 | MoneroTransactionFinalRequest => MessageType_MoneroTransactionFinalRequest, 221 | MoneroTransactionFinalAck => MessageType_MoneroTransactionFinalAck, 222 | MoneroKeyImageExportInitRequest => MessageType_MoneroKeyImageExportInitRequest, 223 | MoneroKeyImageExportInitAck => MessageType_MoneroKeyImageExportInitAck, 224 | MoneroKeyImageSyncStepRequest => MessageType_MoneroKeyImageSyncStepRequest, 225 | MoneroKeyImageSyncStepAck => MessageType_MoneroKeyImageSyncStepAck, 226 | MoneroKeyImageSyncFinalRequest => MessageType_MoneroKeyImageSyncFinalRequest, 227 | MoneroKeyImageSyncFinalAck => MessageType_MoneroKeyImageSyncFinalAck, 228 | MoneroGetAddress => MessageType_MoneroGetAddress, 229 | MoneroAddress => MessageType_MoneroAddress, 230 | MoneroGetWatchKey => MessageType_MoneroGetWatchKey, 231 | MoneroWatchKey => MessageType_MoneroWatchKey, 232 | DebugMoneroDiagRequest => MessageType_DebugMoneroDiagRequest, 233 | DebugMoneroDiagAck => MessageType_DebugMoneroDiagAck, 234 | MoneroGetTxKeyRequest => MessageType_MoneroGetTxKeyRequest, 235 | MoneroGetTxKeyAck => MessageType_MoneroGetTxKeyAck, 236 | MoneroLiveRefreshStartRequest => MessageType_MoneroLiveRefreshStartRequest, 237 | MoneroLiveRefreshStartAck => MessageType_MoneroLiveRefreshStartAck, 238 | MoneroLiveRefreshStepRequest => MessageType_MoneroLiveRefreshStepRequest, 239 | MoneroLiveRefreshStepAck => MessageType_MoneroLiveRefreshStepAck, 240 | MoneroLiveRefreshFinalRequest => MessageType_MoneroLiveRefreshFinalRequest, 241 | MoneroLiveRefreshFinalAck => MessageType_MoneroLiveRefreshFinalAck, 242 | } 243 | 244 | #[cfg(feature = "nem")] 245 | trezor_message_impl! { 246 | NEMGetAddress => MessageType_NEMGetAddress, 247 | NEMAddress => MessageType_NEMAddress, 248 | NEMSignTx => MessageType_NEMSignTx, 249 | NEMSignedTx => MessageType_NEMSignedTx, 250 | NEMDecryptMessage => MessageType_NEMDecryptMessage, 251 | NEMDecryptedMessage => MessageType_NEMDecryptedMessage, 252 | } 253 | 254 | #[cfg(feature = "ripple")] 255 | trezor_message_impl! { 256 | RippleGetAddress => MessageType_RippleGetAddress, 257 | RippleAddress => MessageType_RippleAddress, 258 | RippleSignTx => MessageType_RippleSignTx, 259 | RippleSignedTx => MessageType_RippleSignedTx, 260 | } 261 | 262 | #[cfg(feature = "stellar")] 263 | trezor_message_impl! { 264 | StellarSignTx => MessageType_StellarSignTx, 265 | StellarTxOpRequest => MessageType_StellarTxOpRequest, 266 | StellarGetAddress => MessageType_StellarGetAddress, 267 | StellarAddress => MessageType_StellarAddress, 268 | StellarCreateAccountOp => MessageType_StellarCreateAccountOp, 269 | StellarPaymentOp => MessageType_StellarPaymentOp, 270 | StellarPathPaymentStrictReceiveOp => MessageType_StellarPathPaymentStrictReceiveOp, 271 | StellarManageSellOfferOp => MessageType_StellarManageSellOfferOp, 272 | StellarCreatePassiveSellOfferOp => MessageType_StellarCreatePassiveSellOfferOp, 273 | StellarSetOptionsOp => MessageType_StellarSetOptionsOp, 274 | StellarChangeTrustOp => MessageType_StellarChangeTrustOp, 275 | StellarAllowTrustOp => MessageType_StellarAllowTrustOp, 276 | StellarAccountMergeOp => MessageType_StellarAccountMergeOp, 277 | StellarManageDataOp => MessageType_StellarManageDataOp, 278 | StellarBumpSequenceOp => MessageType_StellarBumpSequenceOp, 279 | StellarManageBuyOfferOp => MessageType_StellarManageBuyOfferOp, 280 | StellarPathPaymentStrictSendOp => MessageType_StellarPathPaymentStrictSendOp, 281 | StellarSignedTx => MessageType_StellarSignedTx, 282 | } 283 | 284 | #[cfg(feature = "tezos")] 285 | trezor_message_impl! { 286 | TezosGetAddress => MessageType_TezosGetAddress, 287 | TezosAddress => MessageType_TezosAddress, 288 | TezosSignTx => MessageType_TezosSignTx, 289 | TezosSignedTx => MessageType_TezosSignedTx, 290 | TezosGetPublicKey => MessageType_TezosGetPublicKey, 291 | TezosPublicKey => MessageType_TezosPublicKey, 292 | } 293 | 294 | #[cfg(feature = "webauthn")] 295 | trezor_message_impl! { 296 | WebAuthnListResidentCredentials => MessageType_WebAuthnListResidentCredentials, 297 | WebAuthnCredentials => MessageType_WebAuthnCredentials, 298 | WebAuthnAddResidentCredential => MessageType_WebAuthnAddResidentCredential, 299 | WebAuthnRemoveResidentCredential => MessageType_WebAuthnRemoveResidentCredential, 300 | } 301 | -------------------------------------------------------------------------------- /src/protos/messages_bootloader.rs: -------------------------------------------------------------------------------- 1 | // This file is generated by rust-protobuf 3.2.0. Do not edit 2 | // .proto file is parsed by protoc --rust-out=... 3 | // @generated 4 | 5 | // https://github.com/rust-lang/rust-clippy/issues/702 6 | #![allow(unknown_lints)] 7 | #![allow(clippy::all)] 8 | 9 | #![allow(unused_attributes)] 10 | #![cfg_attr(rustfmt, rustfmt::skip)] 11 | 12 | #![allow(box_pointers)] 13 | #![allow(dead_code)] 14 | #![allow(missing_docs)] 15 | #![allow(non_camel_case_types)] 16 | #![allow(non_snake_case)] 17 | #![allow(non_upper_case_globals)] 18 | #![allow(trivial_casts)] 19 | #![allow(unused_results)] 20 | #![allow(unused_mut)] 21 | 22 | //! Generated file from `messages-bootloader.proto` 23 | 24 | /// Generated files are compatible only with the same version 25 | /// of protobuf runtime. 26 | const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_2_0; 27 | 28 | /// * 29 | /// Request: Ask device to erase its firmware (so it can be replaced via FirmwareUpload) 30 | /// @start 31 | /// @next FirmwareRequest 32 | #[derive(PartialEq,Clone,Default,Debug)] 33 | // @@protoc_insertion_point(message:hw.trezor.messages.bootloader.FirmwareErase) 34 | pub struct FirmwareErase { 35 | // message fields 36 | // @@protoc_insertion_point(field:hw.trezor.messages.bootloader.FirmwareErase.length) 37 | pub length: ::std::option::Option, 38 | // special fields 39 | // @@protoc_insertion_point(special_field:hw.trezor.messages.bootloader.FirmwareErase.special_fields) 40 | pub special_fields: ::protobuf::SpecialFields, 41 | } 42 | 43 | impl<'a> ::std::default::Default for &'a FirmwareErase { 44 | fn default() -> &'a FirmwareErase { 45 | ::default_instance() 46 | } 47 | } 48 | 49 | impl FirmwareErase { 50 | pub fn new() -> FirmwareErase { 51 | ::std::default::Default::default() 52 | } 53 | 54 | // optional uint32 length = 1; 55 | 56 | pub fn length(&self) -> u32 { 57 | self.length.unwrap_or(0) 58 | } 59 | 60 | pub fn clear_length(&mut self) { 61 | self.length = ::std::option::Option::None; 62 | } 63 | 64 | pub fn has_length(&self) -> bool { 65 | self.length.is_some() 66 | } 67 | 68 | // Param is passed by value, moved 69 | pub fn set_length(&mut self, v: u32) { 70 | self.length = ::std::option::Option::Some(v); 71 | } 72 | 73 | fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { 74 | let mut fields = ::std::vec::Vec::with_capacity(1); 75 | let mut oneofs = ::std::vec::Vec::with_capacity(0); 76 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 77 | "length", 78 | |m: &FirmwareErase| { &m.length }, 79 | |m: &mut FirmwareErase| { &mut m.length }, 80 | )); 81 | ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( 82 | "FirmwareErase", 83 | fields, 84 | oneofs, 85 | ) 86 | } 87 | } 88 | 89 | impl ::protobuf::Message for FirmwareErase { 90 | const NAME: &'static str = "FirmwareErase"; 91 | 92 | fn is_initialized(&self) -> bool { 93 | true 94 | } 95 | 96 | fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { 97 | while let Some(tag) = is.read_raw_tag_or_eof()? { 98 | match tag { 99 | 8 => { 100 | self.length = ::std::option::Option::Some(is.read_uint32()?); 101 | }, 102 | tag => { 103 | ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; 104 | }, 105 | }; 106 | } 107 | ::std::result::Result::Ok(()) 108 | } 109 | 110 | // Compute sizes of nested messages 111 | #[allow(unused_variables)] 112 | fn compute_size(&self) -> u64 { 113 | let mut my_size = 0; 114 | if let Some(v) = self.length { 115 | my_size += ::protobuf::rt::uint32_size(1, v); 116 | } 117 | my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); 118 | self.special_fields.cached_size().set(my_size as u32); 119 | my_size 120 | } 121 | 122 | fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { 123 | if let Some(v) = self.length { 124 | os.write_uint32(1, v)?; 125 | } 126 | os.write_unknown_fields(self.special_fields.unknown_fields())?; 127 | ::std::result::Result::Ok(()) 128 | } 129 | 130 | fn special_fields(&self) -> &::protobuf::SpecialFields { 131 | &self.special_fields 132 | } 133 | 134 | fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { 135 | &mut self.special_fields 136 | } 137 | 138 | fn new() -> FirmwareErase { 139 | FirmwareErase::new() 140 | } 141 | 142 | fn clear(&mut self) { 143 | self.length = ::std::option::Option::None; 144 | self.special_fields.clear(); 145 | } 146 | 147 | fn default_instance() -> &'static FirmwareErase { 148 | static instance: FirmwareErase = FirmwareErase { 149 | length: ::std::option::Option::None, 150 | special_fields: ::protobuf::SpecialFields::new(), 151 | }; 152 | &instance 153 | } 154 | } 155 | 156 | impl ::protobuf::MessageFull for FirmwareErase { 157 | fn descriptor() -> ::protobuf::reflect::MessageDescriptor { 158 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); 159 | descriptor.get(|| file_descriptor().message_by_package_relative_name("FirmwareErase").unwrap()).clone() 160 | } 161 | } 162 | 163 | impl ::std::fmt::Display for FirmwareErase { 164 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 165 | ::protobuf::text_format::fmt(self, f) 166 | } 167 | } 168 | 169 | impl ::protobuf::reflect::ProtobufValue for FirmwareErase { 170 | type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; 171 | } 172 | 173 | /// * 174 | /// Response: Ask for firmware chunk 175 | /// @next FirmwareUpload 176 | #[derive(PartialEq,Clone,Default,Debug)] 177 | // @@protoc_insertion_point(message:hw.trezor.messages.bootloader.FirmwareRequest) 178 | pub struct FirmwareRequest { 179 | // message fields 180 | // @@protoc_insertion_point(field:hw.trezor.messages.bootloader.FirmwareRequest.offset) 181 | pub offset: ::std::option::Option, 182 | // @@protoc_insertion_point(field:hw.trezor.messages.bootloader.FirmwareRequest.length) 183 | pub length: ::std::option::Option, 184 | // special fields 185 | // @@protoc_insertion_point(special_field:hw.trezor.messages.bootloader.FirmwareRequest.special_fields) 186 | pub special_fields: ::protobuf::SpecialFields, 187 | } 188 | 189 | impl<'a> ::std::default::Default for &'a FirmwareRequest { 190 | fn default() -> &'a FirmwareRequest { 191 | ::default_instance() 192 | } 193 | } 194 | 195 | impl FirmwareRequest { 196 | pub fn new() -> FirmwareRequest { 197 | ::std::default::Default::default() 198 | } 199 | 200 | // required uint32 offset = 1; 201 | 202 | pub fn offset(&self) -> u32 { 203 | self.offset.unwrap_or(0) 204 | } 205 | 206 | pub fn clear_offset(&mut self) { 207 | self.offset = ::std::option::Option::None; 208 | } 209 | 210 | pub fn has_offset(&self) -> bool { 211 | self.offset.is_some() 212 | } 213 | 214 | // Param is passed by value, moved 215 | pub fn set_offset(&mut self, v: u32) { 216 | self.offset = ::std::option::Option::Some(v); 217 | } 218 | 219 | // required uint32 length = 2; 220 | 221 | pub fn length(&self) -> u32 { 222 | self.length.unwrap_or(0) 223 | } 224 | 225 | pub fn clear_length(&mut self) { 226 | self.length = ::std::option::Option::None; 227 | } 228 | 229 | pub fn has_length(&self) -> bool { 230 | self.length.is_some() 231 | } 232 | 233 | // Param is passed by value, moved 234 | pub fn set_length(&mut self, v: u32) { 235 | self.length = ::std::option::Option::Some(v); 236 | } 237 | 238 | fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { 239 | let mut fields = ::std::vec::Vec::with_capacity(2); 240 | let mut oneofs = ::std::vec::Vec::with_capacity(0); 241 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 242 | "offset", 243 | |m: &FirmwareRequest| { &m.offset }, 244 | |m: &mut FirmwareRequest| { &mut m.offset }, 245 | )); 246 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 247 | "length", 248 | |m: &FirmwareRequest| { &m.length }, 249 | |m: &mut FirmwareRequest| { &mut m.length }, 250 | )); 251 | ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( 252 | "FirmwareRequest", 253 | fields, 254 | oneofs, 255 | ) 256 | } 257 | } 258 | 259 | impl ::protobuf::Message for FirmwareRequest { 260 | const NAME: &'static str = "FirmwareRequest"; 261 | 262 | fn is_initialized(&self) -> bool { 263 | if self.offset.is_none() { 264 | return false; 265 | } 266 | if self.length.is_none() { 267 | return false; 268 | } 269 | true 270 | } 271 | 272 | fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { 273 | while let Some(tag) = is.read_raw_tag_or_eof()? { 274 | match tag { 275 | 8 => { 276 | self.offset = ::std::option::Option::Some(is.read_uint32()?); 277 | }, 278 | 16 => { 279 | self.length = ::std::option::Option::Some(is.read_uint32()?); 280 | }, 281 | tag => { 282 | ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; 283 | }, 284 | }; 285 | } 286 | ::std::result::Result::Ok(()) 287 | } 288 | 289 | // Compute sizes of nested messages 290 | #[allow(unused_variables)] 291 | fn compute_size(&self) -> u64 { 292 | let mut my_size = 0; 293 | if let Some(v) = self.offset { 294 | my_size += ::protobuf::rt::uint32_size(1, v); 295 | } 296 | if let Some(v) = self.length { 297 | my_size += ::protobuf::rt::uint32_size(2, v); 298 | } 299 | my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); 300 | self.special_fields.cached_size().set(my_size as u32); 301 | my_size 302 | } 303 | 304 | fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { 305 | if let Some(v) = self.offset { 306 | os.write_uint32(1, v)?; 307 | } 308 | if let Some(v) = self.length { 309 | os.write_uint32(2, v)?; 310 | } 311 | os.write_unknown_fields(self.special_fields.unknown_fields())?; 312 | ::std::result::Result::Ok(()) 313 | } 314 | 315 | fn special_fields(&self) -> &::protobuf::SpecialFields { 316 | &self.special_fields 317 | } 318 | 319 | fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { 320 | &mut self.special_fields 321 | } 322 | 323 | fn new() -> FirmwareRequest { 324 | FirmwareRequest::new() 325 | } 326 | 327 | fn clear(&mut self) { 328 | self.offset = ::std::option::Option::None; 329 | self.length = ::std::option::Option::None; 330 | self.special_fields.clear(); 331 | } 332 | 333 | fn default_instance() -> &'static FirmwareRequest { 334 | static instance: FirmwareRequest = FirmwareRequest { 335 | offset: ::std::option::Option::None, 336 | length: ::std::option::Option::None, 337 | special_fields: ::protobuf::SpecialFields::new(), 338 | }; 339 | &instance 340 | } 341 | } 342 | 343 | impl ::protobuf::MessageFull for FirmwareRequest { 344 | fn descriptor() -> ::protobuf::reflect::MessageDescriptor { 345 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); 346 | descriptor.get(|| file_descriptor().message_by_package_relative_name("FirmwareRequest").unwrap()).clone() 347 | } 348 | } 349 | 350 | impl ::std::fmt::Display for FirmwareRequest { 351 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 352 | ::protobuf::text_format::fmt(self, f) 353 | } 354 | } 355 | 356 | impl ::protobuf::reflect::ProtobufValue for FirmwareRequest { 357 | type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; 358 | } 359 | 360 | /// * 361 | /// Request: Send firmware in binary form to the device 362 | /// @next FirmwareRequest 363 | /// @next Success 364 | /// @next Failure 365 | #[derive(PartialEq,Clone,Default,Debug)] 366 | // @@protoc_insertion_point(message:hw.trezor.messages.bootloader.FirmwareUpload) 367 | pub struct FirmwareUpload { 368 | // message fields 369 | // @@protoc_insertion_point(field:hw.trezor.messages.bootloader.FirmwareUpload.payload) 370 | pub payload: ::std::option::Option<::std::vec::Vec>, 371 | // @@protoc_insertion_point(field:hw.trezor.messages.bootloader.FirmwareUpload.hash) 372 | pub hash: ::std::option::Option<::std::vec::Vec>, 373 | // special fields 374 | // @@protoc_insertion_point(special_field:hw.trezor.messages.bootloader.FirmwareUpload.special_fields) 375 | pub special_fields: ::protobuf::SpecialFields, 376 | } 377 | 378 | impl<'a> ::std::default::Default for &'a FirmwareUpload { 379 | fn default() -> &'a FirmwareUpload { 380 | ::default_instance() 381 | } 382 | } 383 | 384 | impl FirmwareUpload { 385 | pub fn new() -> FirmwareUpload { 386 | ::std::default::Default::default() 387 | } 388 | 389 | // required bytes payload = 1; 390 | 391 | pub fn payload(&self) -> &[u8] { 392 | match self.payload.as_ref() { 393 | Some(v) => v, 394 | None => &[], 395 | } 396 | } 397 | 398 | pub fn clear_payload(&mut self) { 399 | self.payload = ::std::option::Option::None; 400 | } 401 | 402 | pub fn has_payload(&self) -> bool { 403 | self.payload.is_some() 404 | } 405 | 406 | // Param is passed by value, moved 407 | pub fn set_payload(&mut self, v: ::std::vec::Vec) { 408 | self.payload = ::std::option::Option::Some(v); 409 | } 410 | 411 | // Mutable pointer to the field. 412 | // If field is not initialized, it is initialized with default value first. 413 | pub fn mut_payload(&mut self) -> &mut ::std::vec::Vec { 414 | if self.payload.is_none() { 415 | self.payload = ::std::option::Option::Some(::std::vec::Vec::new()); 416 | } 417 | self.payload.as_mut().unwrap() 418 | } 419 | 420 | // Take field 421 | pub fn take_payload(&mut self) -> ::std::vec::Vec { 422 | self.payload.take().unwrap_or_else(|| ::std::vec::Vec::new()) 423 | } 424 | 425 | // optional bytes hash = 2; 426 | 427 | pub fn hash(&self) -> &[u8] { 428 | match self.hash.as_ref() { 429 | Some(v) => v, 430 | None => &[], 431 | } 432 | } 433 | 434 | pub fn clear_hash(&mut self) { 435 | self.hash = ::std::option::Option::None; 436 | } 437 | 438 | pub fn has_hash(&self) -> bool { 439 | self.hash.is_some() 440 | } 441 | 442 | // Param is passed by value, moved 443 | pub fn set_hash(&mut self, v: ::std::vec::Vec) { 444 | self.hash = ::std::option::Option::Some(v); 445 | } 446 | 447 | // Mutable pointer to the field. 448 | // If field is not initialized, it is initialized with default value first. 449 | pub fn mut_hash(&mut self) -> &mut ::std::vec::Vec { 450 | if self.hash.is_none() { 451 | self.hash = ::std::option::Option::Some(::std::vec::Vec::new()); 452 | } 453 | self.hash.as_mut().unwrap() 454 | } 455 | 456 | // Take field 457 | pub fn take_hash(&mut self) -> ::std::vec::Vec { 458 | self.hash.take().unwrap_or_else(|| ::std::vec::Vec::new()) 459 | } 460 | 461 | fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { 462 | let mut fields = ::std::vec::Vec::with_capacity(2); 463 | let mut oneofs = ::std::vec::Vec::with_capacity(0); 464 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 465 | "payload", 466 | |m: &FirmwareUpload| { &m.payload }, 467 | |m: &mut FirmwareUpload| { &mut m.payload }, 468 | )); 469 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 470 | "hash", 471 | |m: &FirmwareUpload| { &m.hash }, 472 | |m: &mut FirmwareUpload| { &mut m.hash }, 473 | )); 474 | ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( 475 | "FirmwareUpload", 476 | fields, 477 | oneofs, 478 | ) 479 | } 480 | } 481 | 482 | impl ::protobuf::Message for FirmwareUpload { 483 | const NAME: &'static str = "FirmwareUpload"; 484 | 485 | fn is_initialized(&self) -> bool { 486 | if self.payload.is_none() { 487 | return false; 488 | } 489 | true 490 | } 491 | 492 | fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { 493 | while let Some(tag) = is.read_raw_tag_or_eof()? { 494 | match tag { 495 | 10 => { 496 | self.payload = ::std::option::Option::Some(is.read_bytes()?); 497 | }, 498 | 18 => { 499 | self.hash = ::std::option::Option::Some(is.read_bytes()?); 500 | }, 501 | tag => { 502 | ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; 503 | }, 504 | }; 505 | } 506 | ::std::result::Result::Ok(()) 507 | } 508 | 509 | // Compute sizes of nested messages 510 | #[allow(unused_variables)] 511 | fn compute_size(&self) -> u64 { 512 | let mut my_size = 0; 513 | if let Some(v) = self.payload.as_ref() { 514 | my_size += ::protobuf::rt::bytes_size(1, &v); 515 | } 516 | if let Some(v) = self.hash.as_ref() { 517 | my_size += ::protobuf::rt::bytes_size(2, &v); 518 | } 519 | my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); 520 | self.special_fields.cached_size().set(my_size as u32); 521 | my_size 522 | } 523 | 524 | fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { 525 | if let Some(v) = self.payload.as_ref() { 526 | os.write_bytes(1, v)?; 527 | } 528 | if let Some(v) = self.hash.as_ref() { 529 | os.write_bytes(2, v)?; 530 | } 531 | os.write_unknown_fields(self.special_fields.unknown_fields())?; 532 | ::std::result::Result::Ok(()) 533 | } 534 | 535 | fn special_fields(&self) -> &::protobuf::SpecialFields { 536 | &self.special_fields 537 | } 538 | 539 | fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { 540 | &mut self.special_fields 541 | } 542 | 543 | fn new() -> FirmwareUpload { 544 | FirmwareUpload::new() 545 | } 546 | 547 | fn clear(&mut self) { 548 | self.payload = ::std::option::Option::None; 549 | self.hash = ::std::option::Option::None; 550 | self.special_fields.clear(); 551 | } 552 | 553 | fn default_instance() -> &'static FirmwareUpload { 554 | static instance: FirmwareUpload = FirmwareUpload { 555 | payload: ::std::option::Option::None, 556 | hash: ::std::option::Option::None, 557 | special_fields: ::protobuf::SpecialFields::new(), 558 | }; 559 | &instance 560 | } 561 | } 562 | 563 | impl ::protobuf::MessageFull for FirmwareUpload { 564 | fn descriptor() -> ::protobuf::reflect::MessageDescriptor { 565 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); 566 | descriptor.get(|| file_descriptor().message_by_package_relative_name("FirmwareUpload").unwrap()).clone() 567 | } 568 | } 569 | 570 | impl ::std::fmt::Display for FirmwareUpload { 571 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 572 | ::protobuf::text_format::fmt(self, f) 573 | } 574 | } 575 | 576 | impl ::protobuf::reflect::ProtobufValue for FirmwareUpload { 577 | type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; 578 | } 579 | 580 | /// * 581 | /// Request: Perform a device self-test 582 | /// @next Success 583 | /// @next Failure 584 | #[derive(PartialEq,Clone,Default,Debug)] 585 | // @@protoc_insertion_point(message:hw.trezor.messages.bootloader.SelfTest) 586 | pub struct SelfTest { 587 | // message fields 588 | // @@protoc_insertion_point(field:hw.trezor.messages.bootloader.SelfTest.payload) 589 | pub payload: ::std::option::Option<::std::vec::Vec>, 590 | // special fields 591 | // @@protoc_insertion_point(special_field:hw.trezor.messages.bootloader.SelfTest.special_fields) 592 | pub special_fields: ::protobuf::SpecialFields, 593 | } 594 | 595 | impl<'a> ::std::default::Default for &'a SelfTest { 596 | fn default() -> &'a SelfTest { 597 | ::default_instance() 598 | } 599 | } 600 | 601 | impl SelfTest { 602 | pub fn new() -> SelfTest { 603 | ::std::default::Default::default() 604 | } 605 | 606 | // optional bytes payload = 1; 607 | 608 | pub fn payload(&self) -> &[u8] { 609 | match self.payload.as_ref() { 610 | Some(v) => v, 611 | None => &[], 612 | } 613 | } 614 | 615 | pub fn clear_payload(&mut self) { 616 | self.payload = ::std::option::Option::None; 617 | } 618 | 619 | pub fn has_payload(&self) -> bool { 620 | self.payload.is_some() 621 | } 622 | 623 | // Param is passed by value, moved 624 | pub fn set_payload(&mut self, v: ::std::vec::Vec) { 625 | self.payload = ::std::option::Option::Some(v); 626 | } 627 | 628 | // Mutable pointer to the field. 629 | // If field is not initialized, it is initialized with default value first. 630 | pub fn mut_payload(&mut self) -> &mut ::std::vec::Vec { 631 | if self.payload.is_none() { 632 | self.payload = ::std::option::Option::Some(::std::vec::Vec::new()); 633 | } 634 | self.payload.as_mut().unwrap() 635 | } 636 | 637 | // Take field 638 | pub fn take_payload(&mut self) -> ::std::vec::Vec { 639 | self.payload.take().unwrap_or_else(|| ::std::vec::Vec::new()) 640 | } 641 | 642 | fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { 643 | let mut fields = ::std::vec::Vec::with_capacity(1); 644 | let mut oneofs = ::std::vec::Vec::with_capacity(0); 645 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 646 | "payload", 647 | |m: &SelfTest| { &m.payload }, 648 | |m: &mut SelfTest| { &mut m.payload }, 649 | )); 650 | ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( 651 | "SelfTest", 652 | fields, 653 | oneofs, 654 | ) 655 | } 656 | } 657 | 658 | impl ::protobuf::Message for SelfTest { 659 | const NAME: &'static str = "SelfTest"; 660 | 661 | fn is_initialized(&self) -> bool { 662 | true 663 | } 664 | 665 | fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { 666 | while let Some(tag) = is.read_raw_tag_or_eof()? { 667 | match tag { 668 | 10 => { 669 | self.payload = ::std::option::Option::Some(is.read_bytes()?); 670 | }, 671 | tag => { 672 | ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; 673 | }, 674 | }; 675 | } 676 | ::std::result::Result::Ok(()) 677 | } 678 | 679 | // Compute sizes of nested messages 680 | #[allow(unused_variables)] 681 | fn compute_size(&self) -> u64 { 682 | let mut my_size = 0; 683 | if let Some(v) = self.payload.as_ref() { 684 | my_size += ::protobuf::rt::bytes_size(1, &v); 685 | } 686 | my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); 687 | self.special_fields.cached_size().set(my_size as u32); 688 | my_size 689 | } 690 | 691 | fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { 692 | if let Some(v) = self.payload.as_ref() { 693 | os.write_bytes(1, v)?; 694 | } 695 | os.write_unknown_fields(self.special_fields.unknown_fields())?; 696 | ::std::result::Result::Ok(()) 697 | } 698 | 699 | fn special_fields(&self) -> &::protobuf::SpecialFields { 700 | &self.special_fields 701 | } 702 | 703 | fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { 704 | &mut self.special_fields 705 | } 706 | 707 | fn new() -> SelfTest { 708 | SelfTest::new() 709 | } 710 | 711 | fn clear(&mut self) { 712 | self.payload = ::std::option::Option::None; 713 | self.special_fields.clear(); 714 | } 715 | 716 | fn default_instance() -> &'static SelfTest { 717 | static instance: SelfTest = SelfTest { 718 | payload: ::std::option::Option::None, 719 | special_fields: ::protobuf::SpecialFields::new(), 720 | }; 721 | &instance 722 | } 723 | } 724 | 725 | impl ::protobuf::MessageFull for SelfTest { 726 | fn descriptor() -> ::protobuf::reflect::MessageDescriptor { 727 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); 728 | descriptor.get(|| file_descriptor().message_by_package_relative_name("SelfTest").unwrap()).clone() 729 | } 730 | } 731 | 732 | impl ::std::fmt::Display for SelfTest { 733 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 734 | ::protobuf::text_format::fmt(self, f) 735 | } 736 | } 737 | 738 | impl ::protobuf::reflect::ProtobufValue for SelfTest { 739 | type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; 740 | } 741 | 742 | static file_descriptor_proto_data: &'static [u8] = b"\ 743 | \n\x19messages-bootloader.proto\x12\x1dhw.trezor.messages.bootloader\x1a\ 744 | \x0emessages.proto\"'\n\rFirmwareErase\x12\x16\n\x06length\x18\x01\x20\ 745 | \x01(\rR\x06length\"A\n\x0fFirmwareRequest\x12\x16\n\x06offset\x18\x01\ 746 | \x20\x02(\rR\x06offset\x12\x16\n\x06length\x18\x02\x20\x02(\rR\x06length\ 747 | \">\n\x0eFirmwareUpload\x12\x18\n\x07payload\x18\x01\x20\x02(\x0cR\x07pa\ 748 | yload\x12\x12\n\x04hash\x18\x02\x20\x01(\x0cR\x04hash\"$\n\x08SelfTest\ 749 | \x12\x18\n\x07payload\x18\x01\x20\x01(\x0cR\x07payloadB>\n#com.satoshila\ 750 | bs.trezor.lib.protobufB\x17TrezorMessageBootloaderJ\xa4\t\n\x06\x12\x04\ 751 | \0\0-\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\x08\n\x01\x02\x12\x03\x01\0&\ 752 | \n\x08\n\x01\x08\x12\x03\x04\0<\n.\n\x02\x08\x01\x12\x03\x04\0<\x1a#\x20\ 753 | Sugar\x20for\x20easier\x20handling\x20in\x20Java\n\n\x08\n\x01\x08\x12\ 754 | \x03\x05\08\n\t\n\x02\x08\x08\x12\x03\x05\08\n\t\n\x02\x03\0\x12\x03\x07\ 755 | \0\x18\n\x83\x01\n\x02\x04\0\x12\x04\x0e\0\x10\x01\x1aw*\n\x20Request:\ 756 | \x20Ask\x20device\x20to\x20erase\x20its\x20firmware\x20(so\x20it\x20can\ 757 | \x20be\x20replaced\x20via\x20FirmwareUpload)\n\x20@start\n\x20@next\x20F\ 758 | irmwareRequest\n\n\n\n\x03\x04\0\x01\x12\x03\x0e\x08\x15\n%\n\x04\x04\0\ 759 | \x02\0\x12\x03\x0f\x04\x1f\"\x18\x20length\x20of\x20new\x20firmware\n\n\ 760 | \x0c\n\x05\x04\0\x02\0\x04\x12\x03\x0f\x04\x0c\n\x0c\n\x05\x04\0\x02\0\ 761 | \x05\x12\x03\x0f\r\x13\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x0f\x14\x1a\n\ 762 | \x0c\n\x05\x04\0\x02\0\x03\x12\x03\x0f\x1d\x1e\nF\n\x02\x04\x01\x12\x04\ 763 | \x16\0\x19\x01\x1a:*\n\x20Response:\x20Ask\x20for\x20firmware\x20chunk\n\ 764 | \x20@next\x20FirmwareUpload\n\n\n\n\x03\x04\x01\x01\x12\x03\x16\x08\x17\ 765 | \n1\n\x04\x04\x01\x02\0\x12\x03\x17\x04\x1f\"$\x20offset\x20of\x20reques\ 766 | ted\x20firmware\x20chunk\n\n\x0c\n\x05\x04\x01\x02\0\x04\x12\x03\x17\x04\ 767 | \x0c\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x17\r\x13\n\x0c\n\x05\x04\x01\ 768 | \x02\0\x01\x12\x03\x17\x14\x1a\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x17\ 769 | \x1d\x1e\n1\n\x04\x04\x01\x02\x01\x12\x03\x18\x04\x1f\"$\x20length\x20of\ 770 | \x20requested\x20firmware\x20chunk\n\n\x0c\n\x05\x04\x01\x02\x01\x04\x12\ 771 | \x03\x18\x04\x0c\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03\x18\r\x13\n\x0c\ 772 | \n\x05\x04\x01\x02\x01\x01\x12\x03\x18\x14\x1a\n\x0c\n\x05\x04\x01\x02\ 773 | \x01\x03\x12\x03\x18\x1d\x1e\nx\n\x02\x04\x02\x12\x04!\0$\x01\x1al*\n\ 774 | \x20Request:\x20Send\x20firmware\x20in\x20binary\x20form\x20to\x20the\ 775 | \x20device\n\x20@next\x20FirmwareRequest\n\x20@next\x20Success\n\x20@nex\ 776 | t\x20Failure\n\n\n\n\x03\x04\x02\x01\x12\x03!\x08\x16\n0\n\x04\x04\x02\ 777 | \x02\0\x12\x03\"\x04\x1f\"#\x20firmware\x20to\x20be\x20loaded\x20into\ 778 | \x20device\n\n\x0c\n\x05\x04\x02\x02\0\x04\x12\x03\"\x04\x0c\n\x0c\n\x05\ 779 | \x04\x02\x02\0\x05\x12\x03\"\r\x12\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\ 780 | \"\x13\x1a\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\"\x1d\x1e\n\"\n\x04\x04\ 781 | \x02\x02\x01\x12\x03#\x04\x1c\"\x15\x20hash\x20of\x20the\x20payload\n\n\ 782 | \x0c\n\x05\x04\x02\x02\x01\x04\x12\x03#\x04\x0c\n\x0c\n\x05\x04\x02\x02\ 783 | \x01\x05\x12\x03#\r\x12\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03#\x13\x17\ 784 | \n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03#\x1a\x1b\nQ\n\x02\x04\x03\x12\ 785 | \x04+\0-\x01\x1aE*\n\x20Request:\x20Perform\x20a\x20device\x20self-test\ 786 | \n\x20@next\x20Success\n\x20@next\x20Failure\n\n\n\n\x03\x04\x03\x01\x12\ 787 | \x03+\x08\x10\n.\n\x04\x04\x03\x02\0\x12\x03,\x04\x1f\"!\x20payload\x20t\ 788 | o\x20be\x20used\x20in\x20self-test\n\n\x0c\n\x05\x04\x03\x02\0\x04\x12\ 789 | \x03,\x04\x0c\n\x0c\n\x05\x04\x03\x02\0\x05\x12\x03,\r\x12\n\x0c\n\x05\ 790 | \x04\x03\x02\0\x01\x12\x03,\x13\x1a\n\x0c\n\x05\x04\x03\x02\0\x03\x12\ 791 | \x03,\x1d\x1e\ 792 | "; 793 | 794 | /// `FileDescriptorProto` object which was a source for this generated file 795 | fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { 796 | static file_descriptor_proto_lazy: ::protobuf::rt::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::Lazy::new(); 797 | file_descriptor_proto_lazy.get(|| { 798 | ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap() 799 | }) 800 | } 801 | 802 | /// `FileDescriptor` object which allows dynamic access to files 803 | pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { 804 | static generated_file_descriptor_lazy: ::protobuf::rt::Lazy<::protobuf::reflect::GeneratedFileDescriptor> = ::protobuf::rt::Lazy::new(); 805 | static file_descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::FileDescriptor> = ::protobuf::rt::Lazy::new(); 806 | file_descriptor.get(|| { 807 | let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { 808 | let mut deps = ::std::vec::Vec::with_capacity(1); 809 | deps.push(super::messages::file_descriptor().clone()); 810 | let mut messages = ::std::vec::Vec::with_capacity(4); 811 | messages.push(FirmwareErase::generated_message_descriptor_data()); 812 | messages.push(FirmwareRequest::generated_message_descriptor_data()); 813 | messages.push(FirmwareUpload::generated_message_descriptor_data()); 814 | messages.push(SelfTest::generated_message_descriptor_data()); 815 | let mut enums = ::std::vec::Vec::with_capacity(0); 816 | ::protobuf::reflect::GeneratedFileDescriptor::new_generated( 817 | file_descriptor_proto(), 818 | deps, 819 | messages, 820 | enums, 821 | ) 822 | }); 823 | ::protobuf::reflect::FileDescriptor::new_generated_2(generated_file_descriptor) 824 | }) 825 | } 826 | -------------------------------------------------------------------------------- /src/protos/messages_ethereum_definitions.rs: -------------------------------------------------------------------------------- 1 | // This file is generated by rust-protobuf 3.2.0. Do not edit 2 | // .proto file is parsed by protoc --rust-out=... 3 | // @generated 4 | 5 | // https://github.com/rust-lang/rust-clippy/issues/702 6 | #![allow(unknown_lints)] 7 | #![allow(clippy::all)] 8 | 9 | #![allow(unused_attributes)] 10 | #![cfg_attr(rustfmt, rustfmt::skip)] 11 | 12 | #![allow(box_pointers)] 13 | #![allow(dead_code)] 14 | #![allow(missing_docs)] 15 | #![allow(non_camel_case_types)] 16 | #![allow(non_snake_case)] 17 | #![allow(non_upper_case_globals)] 18 | #![allow(trivial_casts)] 19 | #![allow(unused_results)] 20 | #![allow(unused_mut)] 21 | 22 | //! Generated file from `messages-ethereum-definitions.proto` 23 | 24 | /// Generated files are compatible only with the same version 25 | /// of protobuf runtime. 26 | const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_2_0; 27 | 28 | /// * 29 | /// Ethereum network definition. Used to (de)serialize the definition. 30 | /// 31 | /// Definition types should not be cross-parseable, i.e., it should not be possible to 32 | /// incorrectly parse network info as token info or vice versa. 33 | /// To achieve that, the first field is wire type varint while the second field is wire type 34 | /// length-delimited. Both are a mismatch for the token definition. 35 | /// 36 | /// @embed 37 | #[derive(PartialEq,Clone,Default,Debug)] 38 | // @@protoc_insertion_point(message:hw.trezor.messages.ethereum_definitions.EthereumNetworkInfo) 39 | pub struct EthereumNetworkInfo { 40 | // message fields 41 | // @@protoc_insertion_point(field:hw.trezor.messages.ethereum_definitions.EthereumNetworkInfo.chain_id) 42 | pub chain_id: ::std::option::Option, 43 | // @@protoc_insertion_point(field:hw.trezor.messages.ethereum_definitions.EthereumNetworkInfo.symbol) 44 | pub symbol: ::std::option::Option<::std::string::String>, 45 | // @@protoc_insertion_point(field:hw.trezor.messages.ethereum_definitions.EthereumNetworkInfo.slip44) 46 | pub slip44: ::std::option::Option, 47 | // @@protoc_insertion_point(field:hw.trezor.messages.ethereum_definitions.EthereumNetworkInfo.name) 48 | pub name: ::std::option::Option<::std::string::String>, 49 | // special fields 50 | // @@protoc_insertion_point(special_field:hw.trezor.messages.ethereum_definitions.EthereumNetworkInfo.special_fields) 51 | pub special_fields: ::protobuf::SpecialFields, 52 | } 53 | 54 | impl<'a> ::std::default::Default for &'a EthereumNetworkInfo { 55 | fn default() -> &'a EthereumNetworkInfo { 56 | ::default_instance() 57 | } 58 | } 59 | 60 | impl EthereumNetworkInfo { 61 | pub fn new() -> EthereumNetworkInfo { 62 | ::std::default::Default::default() 63 | } 64 | 65 | // required uint64 chain_id = 1; 66 | 67 | pub fn chain_id(&self) -> u64 { 68 | self.chain_id.unwrap_or(0) 69 | } 70 | 71 | pub fn clear_chain_id(&mut self) { 72 | self.chain_id = ::std::option::Option::None; 73 | } 74 | 75 | pub fn has_chain_id(&self) -> bool { 76 | self.chain_id.is_some() 77 | } 78 | 79 | // Param is passed by value, moved 80 | pub fn set_chain_id(&mut self, v: u64) { 81 | self.chain_id = ::std::option::Option::Some(v); 82 | } 83 | 84 | // required string symbol = 2; 85 | 86 | pub fn symbol(&self) -> &str { 87 | match self.symbol.as_ref() { 88 | Some(v) => v, 89 | None => "", 90 | } 91 | } 92 | 93 | pub fn clear_symbol(&mut self) { 94 | self.symbol = ::std::option::Option::None; 95 | } 96 | 97 | pub fn has_symbol(&self) -> bool { 98 | self.symbol.is_some() 99 | } 100 | 101 | // Param is passed by value, moved 102 | pub fn set_symbol(&mut self, v: ::std::string::String) { 103 | self.symbol = ::std::option::Option::Some(v); 104 | } 105 | 106 | // Mutable pointer to the field. 107 | // If field is not initialized, it is initialized with default value first. 108 | pub fn mut_symbol(&mut self) -> &mut ::std::string::String { 109 | if self.symbol.is_none() { 110 | self.symbol = ::std::option::Option::Some(::std::string::String::new()); 111 | } 112 | self.symbol.as_mut().unwrap() 113 | } 114 | 115 | // Take field 116 | pub fn take_symbol(&mut self) -> ::std::string::String { 117 | self.symbol.take().unwrap_or_else(|| ::std::string::String::new()) 118 | } 119 | 120 | // required uint32 slip44 = 3; 121 | 122 | pub fn slip44(&self) -> u32 { 123 | self.slip44.unwrap_or(0) 124 | } 125 | 126 | pub fn clear_slip44(&mut self) { 127 | self.slip44 = ::std::option::Option::None; 128 | } 129 | 130 | pub fn has_slip44(&self) -> bool { 131 | self.slip44.is_some() 132 | } 133 | 134 | // Param is passed by value, moved 135 | pub fn set_slip44(&mut self, v: u32) { 136 | self.slip44 = ::std::option::Option::Some(v); 137 | } 138 | 139 | // required string name = 4; 140 | 141 | pub fn name(&self) -> &str { 142 | match self.name.as_ref() { 143 | Some(v) => v, 144 | None => "", 145 | } 146 | } 147 | 148 | pub fn clear_name(&mut self) { 149 | self.name = ::std::option::Option::None; 150 | } 151 | 152 | pub fn has_name(&self) -> bool { 153 | self.name.is_some() 154 | } 155 | 156 | // Param is passed by value, moved 157 | pub fn set_name(&mut self, v: ::std::string::String) { 158 | self.name = ::std::option::Option::Some(v); 159 | } 160 | 161 | // Mutable pointer to the field. 162 | // If field is not initialized, it is initialized with default value first. 163 | pub fn mut_name(&mut self) -> &mut ::std::string::String { 164 | if self.name.is_none() { 165 | self.name = ::std::option::Option::Some(::std::string::String::new()); 166 | } 167 | self.name.as_mut().unwrap() 168 | } 169 | 170 | // Take field 171 | pub fn take_name(&mut self) -> ::std::string::String { 172 | self.name.take().unwrap_or_else(|| ::std::string::String::new()) 173 | } 174 | 175 | fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { 176 | let mut fields = ::std::vec::Vec::with_capacity(4); 177 | let mut oneofs = ::std::vec::Vec::with_capacity(0); 178 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 179 | "chain_id", 180 | |m: &EthereumNetworkInfo| { &m.chain_id }, 181 | |m: &mut EthereumNetworkInfo| { &mut m.chain_id }, 182 | )); 183 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 184 | "symbol", 185 | |m: &EthereumNetworkInfo| { &m.symbol }, 186 | |m: &mut EthereumNetworkInfo| { &mut m.symbol }, 187 | )); 188 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 189 | "slip44", 190 | |m: &EthereumNetworkInfo| { &m.slip44 }, 191 | |m: &mut EthereumNetworkInfo| { &mut m.slip44 }, 192 | )); 193 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 194 | "name", 195 | |m: &EthereumNetworkInfo| { &m.name }, 196 | |m: &mut EthereumNetworkInfo| { &mut m.name }, 197 | )); 198 | ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( 199 | "EthereumNetworkInfo", 200 | fields, 201 | oneofs, 202 | ) 203 | } 204 | } 205 | 206 | impl ::protobuf::Message for EthereumNetworkInfo { 207 | const NAME: &'static str = "EthereumNetworkInfo"; 208 | 209 | fn is_initialized(&self) -> bool { 210 | if self.chain_id.is_none() { 211 | return false; 212 | } 213 | if self.symbol.is_none() { 214 | return false; 215 | } 216 | if self.slip44.is_none() { 217 | return false; 218 | } 219 | if self.name.is_none() { 220 | return false; 221 | } 222 | true 223 | } 224 | 225 | fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { 226 | while let Some(tag) = is.read_raw_tag_or_eof()? { 227 | match tag { 228 | 8 => { 229 | self.chain_id = ::std::option::Option::Some(is.read_uint64()?); 230 | }, 231 | 18 => { 232 | self.symbol = ::std::option::Option::Some(is.read_string()?); 233 | }, 234 | 24 => { 235 | self.slip44 = ::std::option::Option::Some(is.read_uint32()?); 236 | }, 237 | 34 => { 238 | self.name = ::std::option::Option::Some(is.read_string()?); 239 | }, 240 | tag => { 241 | ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; 242 | }, 243 | }; 244 | } 245 | ::std::result::Result::Ok(()) 246 | } 247 | 248 | // Compute sizes of nested messages 249 | #[allow(unused_variables)] 250 | fn compute_size(&self) -> u64 { 251 | let mut my_size = 0; 252 | if let Some(v) = self.chain_id { 253 | my_size += ::protobuf::rt::uint64_size(1, v); 254 | } 255 | if let Some(v) = self.symbol.as_ref() { 256 | my_size += ::protobuf::rt::string_size(2, &v); 257 | } 258 | if let Some(v) = self.slip44 { 259 | my_size += ::protobuf::rt::uint32_size(3, v); 260 | } 261 | if let Some(v) = self.name.as_ref() { 262 | my_size += ::protobuf::rt::string_size(4, &v); 263 | } 264 | my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); 265 | self.special_fields.cached_size().set(my_size as u32); 266 | my_size 267 | } 268 | 269 | fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { 270 | if let Some(v) = self.chain_id { 271 | os.write_uint64(1, v)?; 272 | } 273 | if let Some(v) = self.symbol.as_ref() { 274 | os.write_string(2, v)?; 275 | } 276 | if let Some(v) = self.slip44 { 277 | os.write_uint32(3, v)?; 278 | } 279 | if let Some(v) = self.name.as_ref() { 280 | os.write_string(4, v)?; 281 | } 282 | os.write_unknown_fields(self.special_fields.unknown_fields())?; 283 | ::std::result::Result::Ok(()) 284 | } 285 | 286 | fn special_fields(&self) -> &::protobuf::SpecialFields { 287 | &self.special_fields 288 | } 289 | 290 | fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { 291 | &mut self.special_fields 292 | } 293 | 294 | fn new() -> EthereumNetworkInfo { 295 | EthereumNetworkInfo::new() 296 | } 297 | 298 | fn clear(&mut self) { 299 | self.chain_id = ::std::option::Option::None; 300 | self.symbol = ::std::option::Option::None; 301 | self.slip44 = ::std::option::Option::None; 302 | self.name = ::std::option::Option::None; 303 | self.special_fields.clear(); 304 | } 305 | 306 | fn default_instance() -> &'static EthereumNetworkInfo { 307 | static instance: EthereumNetworkInfo = EthereumNetworkInfo { 308 | chain_id: ::std::option::Option::None, 309 | symbol: ::std::option::Option::None, 310 | slip44: ::std::option::Option::None, 311 | name: ::std::option::Option::None, 312 | special_fields: ::protobuf::SpecialFields::new(), 313 | }; 314 | &instance 315 | } 316 | } 317 | 318 | impl ::protobuf::MessageFull for EthereumNetworkInfo { 319 | fn descriptor() -> ::protobuf::reflect::MessageDescriptor { 320 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); 321 | descriptor.get(|| file_descriptor().message_by_package_relative_name("EthereumNetworkInfo").unwrap()).clone() 322 | } 323 | } 324 | 325 | impl ::std::fmt::Display for EthereumNetworkInfo { 326 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 327 | ::protobuf::text_format::fmt(self, f) 328 | } 329 | } 330 | 331 | impl ::protobuf::reflect::ProtobufValue for EthereumNetworkInfo { 332 | type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; 333 | } 334 | 335 | /// * 336 | /// Ethereum token definition. Used to (de)serialize the definition. 337 | /// 338 | /// Definition types should not be cross-parseable, i.e., it should not be possible to 339 | /// incorrectly parse network info as token info or vice versa. 340 | /// To achieve that, the first field is wire type length-delimited while the second field 341 | /// is wire type varint. Both are a mismatch for the network definition. 342 | /// 343 | /// @embed 344 | #[derive(PartialEq,Clone,Default,Debug)] 345 | // @@protoc_insertion_point(message:hw.trezor.messages.ethereum_definitions.EthereumTokenInfo) 346 | pub struct EthereumTokenInfo { 347 | // message fields 348 | // @@protoc_insertion_point(field:hw.trezor.messages.ethereum_definitions.EthereumTokenInfo.address) 349 | pub address: ::std::option::Option<::std::vec::Vec>, 350 | // @@protoc_insertion_point(field:hw.trezor.messages.ethereum_definitions.EthereumTokenInfo.chain_id) 351 | pub chain_id: ::std::option::Option, 352 | // @@protoc_insertion_point(field:hw.trezor.messages.ethereum_definitions.EthereumTokenInfo.symbol) 353 | pub symbol: ::std::option::Option<::std::string::String>, 354 | // @@protoc_insertion_point(field:hw.trezor.messages.ethereum_definitions.EthereumTokenInfo.decimals) 355 | pub decimals: ::std::option::Option, 356 | // @@protoc_insertion_point(field:hw.trezor.messages.ethereum_definitions.EthereumTokenInfo.name) 357 | pub name: ::std::option::Option<::std::string::String>, 358 | // special fields 359 | // @@protoc_insertion_point(special_field:hw.trezor.messages.ethereum_definitions.EthereumTokenInfo.special_fields) 360 | pub special_fields: ::protobuf::SpecialFields, 361 | } 362 | 363 | impl<'a> ::std::default::Default for &'a EthereumTokenInfo { 364 | fn default() -> &'a EthereumTokenInfo { 365 | ::default_instance() 366 | } 367 | } 368 | 369 | impl EthereumTokenInfo { 370 | pub fn new() -> EthereumTokenInfo { 371 | ::std::default::Default::default() 372 | } 373 | 374 | // required bytes address = 1; 375 | 376 | pub fn address(&self) -> &[u8] { 377 | match self.address.as_ref() { 378 | Some(v) => v, 379 | None => &[], 380 | } 381 | } 382 | 383 | pub fn clear_address(&mut self) { 384 | self.address = ::std::option::Option::None; 385 | } 386 | 387 | pub fn has_address(&self) -> bool { 388 | self.address.is_some() 389 | } 390 | 391 | // Param is passed by value, moved 392 | pub fn set_address(&mut self, v: ::std::vec::Vec) { 393 | self.address = ::std::option::Option::Some(v); 394 | } 395 | 396 | // Mutable pointer to the field. 397 | // If field is not initialized, it is initialized with default value first. 398 | pub fn mut_address(&mut self) -> &mut ::std::vec::Vec { 399 | if self.address.is_none() { 400 | self.address = ::std::option::Option::Some(::std::vec::Vec::new()); 401 | } 402 | self.address.as_mut().unwrap() 403 | } 404 | 405 | // Take field 406 | pub fn take_address(&mut self) -> ::std::vec::Vec { 407 | self.address.take().unwrap_or_else(|| ::std::vec::Vec::new()) 408 | } 409 | 410 | // required uint64 chain_id = 2; 411 | 412 | pub fn chain_id(&self) -> u64 { 413 | self.chain_id.unwrap_or(0) 414 | } 415 | 416 | pub fn clear_chain_id(&mut self) { 417 | self.chain_id = ::std::option::Option::None; 418 | } 419 | 420 | pub fn has_chain_id(&self) -> bool { 421 | self.chain_id.is_some() 422 | } 423 | 424 | // Param is passed by value, moved 425 | pub fn set_chain_id(&mut self, v: u64) { 426 | self.chain_id = ::std::option::Option::Some(v); 427 | } 428 | 429 | // required string symbol = 3; 430 | 431 | pub fn symbol(&self) -> &str { 432 | match self.symbol.as_ref() { 433 | Some(v) => v, 434 | None => "", 435 | } 436 | } 437 | 438 | pub fn clear_symbol(&mut self) { 439 | self.symbol = ::std::option::Option::None; 440 | } 441 | 442 | pub fn has_symbol(&self) -> bool { 443 | self.symbol.is_some() 444 | } 445 | 446 | // Param is passed by value, moved 447 | pub fn set_symbol(&mut self, v: ::std::string::String) { 448 | self.symbol = ::std::option::Option::Some(v); 449 | } 450 | 451 | // Mutable pointer to the field. 452 | // If field is not initialized, it is initialized with default value first. 453 | pub fn mut_symbol(&mut self) -> &mut ::std::string::String { 454 | if self.symbol.is_none() { 455 | self.symbol = ::std::option::Option::Some(::std::string::String::new()); 456 | } 457 | self.symbol.as_mut().unwrap() 458 | } 459 | 460 | // Take field 461 | pub fn take_symbol(&mut self) -> ::std::string::String { 462 | self.symbol.take().unwrap_or_else(|| ::std::string::String::new()) 463 | } 464 | 465 | // required uint32 decimals = 4; 466 | 467 | pub fn decimals(&self) -> u32 { 468 | self.decimals.unwrap_or(0) 469 | } 470 | 471 | pub fn clear_decimals(&mut self) { 472 | self.decimals = ::std::option::Option::None; 473 | } 474 | 475 | pub fn has_decimals(&self) -> bool { 476 | self.decimals.is_some() 477 | } 478 | 479 | // Param is passed by value, moved 480 | pub fn set_decimals(&mut self, v: u32) { 481 | self.decimals = ::std::option::Option::Some(v); 482 | } 483 | 484 | // required string name = 5; 485 | 486 | pub fn name(&self) -> &str { 487 | match self.name.as_ref() { 488 | Some(v) => v, 489 | None => "", 490 | } 491 | } 492 | 493 | pub fn clear_name(&mut self) { 494 | self.name = ::std::option::Option::None; 495 | } 496 | 497 | pub fn has_name(&self) -> bool { 498 | self.name.is_some() 499 | } 500 | 501 | // Param is passed by value, moved 502 | pub fn set_name(&mut self, v: ::std::string::String) { 503 | self.name = ::std::option::Option::Some(v); 504 | } 505 | 506 | // Mutable pointer to the field. 507 | // If field is not initialized, it is initialized with default value first. 508 | pub fn mut_name(&mut self) -> &mut ::std::string::String { 509 | if self.name.is_none() { 510 | self.name = ::std::option::Option::Some(::std::string::String::new()); 511 | } 512 | self.name.as_mut().unwrap() 513 | } 514 | 515 | // Take field 516 | pub fn take_name(&mut self) -> ::std::string::String { 517 | self.name.take().unwrap_or_else(|| ::std::string::String::new()) 518 | } 519 | 520 | fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { 521 | let mut fields = ::std::vec::Vec::with_capacity(5); 522 | let mut oneofs = ::std::vec::Vec::with_capacity(0); 523 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 524 | "address", 525 | |m: &EthereumTokenInfo| { &m.address }, 526 | |m: &mut EthereumTokenInfo| { &mut m.address }, 527 | )); 528 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 529 | "chain_id", 530 | |m: &EthereumTokenInfo| { &m.chain_id }, 531 | |m: &mut EthereumTokenInfo| { &mut m.chain_id }, 532 | )); 533 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 534 | "symbol", 535 | |m: &EthereumTokenInfo| { &m.symbol }, 536 | |m: &mut EthereumTokenInfo| { &mut m.symbol }, 537 | )); 538 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 539 | "decimals", 540 | |m: &EthereumTokenInfo| { &m.decimals }, 541 | |m: &mut EthereumTokenInfo| { &mut m.decimals }, 542 | )); 543 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 544 | "name", 545 | |m: &EthereumTokenInfo| { &m.name }, 546 | |m: &mut EthereumTokenInfo| { &mut m.name }, 547 | )); 548 | ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( 549 | "EthereumTokenInfo", 550 | fields, 551 | oneofs, 552 | ) 553 | } 554 | } 555 | 556 | impl ::protobuf::Message for EthereumTokenInfo { 557 | const NAME: &'static str = "EthereumTokenInfo"; 558 | 559 | fn is_initialized(&self) -> bool { 560 | if self.address.is_none() { 561 | return false; 562 | } 563 | if self.chain_id.is_none() { 564 | return false; 565 | } 566 | if self.symbol.is_none() { 567 | return false; 568 | } 569 | if self.decimals.is_none() { 570 | return false; 571 | } 572 | if self.name.is_none() { 573 | return false; 574 | } 575 | true 576 | } 577 | 578 | fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { 579 | while let Some(tag) = is.read_raw_tag_or_eof()? { 580 | match tag { 581 | 10 => { 582 | self.address = ::std::option::Option::Some(is.read_bytes()?); 583 | }, 584 | 16 => { 585 | self.chain_id = ::std::option::Option::Some(is.read_uint64()?); 586 | }, 587 | 26 => { 588 | self.symbol = ::std::option::Option::Some(is.read_string()?); 589 | }, 590 | 32 => { 591 | self.decimals = ::std::option::Option::Some(is.read_uint32()?); 592 | }, 593 | 42 => { 594 | self.name = ::std::option::Option::Some(is.read_string()?); 595 | }, 596 | tag => { 597 | ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; 598 | }, 599 | }; 600 | } 601 | ::std::result::Result::Ok(()) 602 | } 603 | 604 | // Compute sizes of nested messages 605 | #[allow(unused_variables)] 606 | fn compute_size(&self) -> u64 { 607 | let mut my_size = 0; 608 | if let Some(v) = self.address.as_ref() { 609 | my_size += ::protobuf::rt::bytes_size(1, &v); 610 | } 611 | if let Some(v) = self.chain_id { 612 | my_size += ::protobuf::rt::uint64_size(2, v); 613 | } 614 | if let Some(v) = self.symbol.as_ref() { 615 | my_size += ::protobuf::rt::string_size(3, &v); 616 | } 617 | if let Some(v) = self.decimals { 618 | my_size += ::protobuf::rt::uint32_size(4, v); 619 | } 620 | if let Some(v) = self.name.as_ref() { 621 | my_size += ::protobuf::rt::string_size(5, &v); 622 | } 623 | my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); 624 | self.special_fields.cached_size().set(my_size as u32); 625 | my_size 626 | } 627 | 628 | fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { 629 | if let Some(v) = self.address.as_ref() { 630 | os.write_bytes(1, v)?; 631 | } 632 | if let Some(v) = self.chain_id { 633 | os.write_uint64(2, v)?; 634 | } 635 | if let Some(v) = self.symbol.as_ref() { 636 | os.write_string(3, v)?; 637 | } 638 | if let Some(v) = self.decimals { 639 | os.write_uint32(4, v)?; 640 | } 641 | if let Some(v) = self.name.as_ref() { 642 | os.write_string(5, v)?; 643 | } 644 | os.write_unknown_fields(self.special_fields.unknown_fields())?; 645 | ::std::result::Result::Ok(()) 646 | } 647 | 648 | fn special_fields(&self) -> &::protobuf::SpecialFields { 649 | &self.special_fields 650 | } 651 | 652 | fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { 653 | &mut self.special_fields 654 | } 655 | 656 | fn new() -> EthereumTokenInfo { 657 | EthereumTokenInfo::new() 658 | } 659 | 660 | fn clear(&mut self) { 661 | self.address = ::std::option::Option::None; 662 | self.chain_id = ::std::option::Option::None; 663 | self.symbol = ::std::option::Option::None; 664 | self.decimals = ::std::option::Option::None; 665 | self.name = ::std::option::Option::None; 666 | self.special_fields.clear(); 667 | } 668 | 669 | fn default_instance() -> &'static EthereumTokenInfo { 670 | static instance: EthereumTokenInfo = EthereumTokenInfo { 671 | address: ::std::option::Option::None, 672 | chain_id: ::std::option::Option::None, 673 | symbol: ::std::option::Option::None, 674 | decimals: ::std::option::Option::None, 675 | name: ::std::option::Option::None, 676 | special_fields: ::protobuf::SpecialFields::new(), 677 | }; 678 | &instance 679 | } 680 | } 681 | 682 | impl ::protobuf::MessageFull for EthereumTokenInfo { 683 | fn descriptor() -> ::protobuf::reflect::MessageDescriptor { 684 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); 685 | descriptor.get(|| file_descriptor().message_by_package_relative_name("EthereumTokenInfo").unwrap()).clone() 686 | } 687 | } 688 | 689 | impl ::std::fmt::Display for EthereumTokenInfo { 690 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 691 | ::protobuf::text_format::fmt(self, f) 692 | } 693 | } 694 | 695 | impl ::protobuf::reflect::ProtobufValue for EthereumTokenInfo { 696 | type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; 697 | } 698 | 699 | /// * 700 | /// Contains an encoded Ethereum network and/or token definition. See ethereum-definitions.md for details. 701 | /// @embed 702 | #[derive(PartialEq,Clone,Default,Debug)] 703 | // @@protoc_insertion_point(message:hw.trezor.messages.ethereum_definitions.EthereumDefinitions) 704 | pub struct EthereumDefinitions { 705 | // message fields 706 | // @@protoc_insertion_point(field:hw.trezor.messages.ethereum_definitions.EthereumDefinitions.encoded_network) 707 | pub encoded_network: ::std::option::Option<::std::vec::Vec>, 708 | // @@protoc_insertion_point(field:hw.trezor.messages.ethereum_definitions.EthereumDefinitions.encoded_token) 709 | pub encoded_token: ::std::option::Option<::std::vec::Vec>, 710 | // special fields 711 | // @@protoc_insertion_point(special_field:hw.trezor.messages.ethereum_definitions.EthereumDefinitions.special_fields) 712 | pub special_fields: ::protobuf::SpecialFields, 713 | } 714 | 715 | impl<'a> ::std::default::Default for &'a EthereumDefinitions { 716 | fn default() -> &'a EthereumDefinitions { 717 | ::default_instance() 718 | } 719 | } 720 | 721 | impl EthereumDefinitions { 722 | pub fn new() -> EthereumDefinitions { 723 | ::std::default::Default::default() 724 | } 725 | 726 | // optional bytes encoded_network = 1; 727 | 728 | pub fn encoded_network(&self) -> &[u8] { 729 | match self.encoded_network.as_ref() { 730 | Some(v) => v, 731 | None => &[], 732 | } 733 | } 734 | 735 | pub fn clear_encoded_network(&mut self) { 736 | self.encoded_network = ::std::option::Option::None; 737 | } 738 | 739 | pub fn has_encoded_network(&self) -> bool { 740 | self.encoded_network.is_some() 741 | } 742 | 743 | // Param is passed by value, moved 744 | pub fn set_encoded_network(&mut self, v: ::std::vec::Vec) { 745 | self.encoded_network = ::std::option::Option::Some(v); 746 | } 747 | 748 | // Mutable pointer to the field. 749 | // If field is not initialized, it is initialized with default value first. 750 | pub fn mut_encoded_network(&mut self) -> &mut ::std::vec::Vec { 751 | if self.encoded_network.is_none() { 752 | self.encoded_network = ::std::option::Option::Some(::std::vec::Vec::new()); 753 | } 754 | self.encoded_network.as_mut().unwrap() 755 | } 756 | 757 | // Take field 758 | pub fn take_encoded_network(&mut self) -> ::std::vec::Vec { 759 | self.encoded_network.take().unwrap_or_else(|| ::std::vec::Vec::new()) 760 | } 761 | 762 | // optional bytes encoded_token = 2; 763 | 764 | pub fn encoded_token(&self) -> &[u8] { 765 | match self.encoded_token.as_ref() { 766 | Some(v) => v, 767 | None => &[], 768 | } 769 | } 770 | 771 | pub fn clear_encoded_token(&mut self) { 772 | self.encoded_token = ::std::option::Option::None; 773 | } 774 | 775 | pub fn has_encoded_token(&self) -> bool { 776 | self.encoded_token.is_some() 777 | } 778 | 779 | // Param is passed by value, moved 780 | pub fn set_encoded_token(&mut self, v: ::std::vec::Vec) { 781 | self.encoded_token = ::std::option::Option::Some(v); 782 | } 783 | 784 | // Mutable pointer to the field. 785 | // If field is not initialized, it is initialized with default value first. 786 | pub fn mut_encoded_token(&mut self) -> &mut ::std::vec::Vec { 787 | if self.encoded_token.is_none() { 788 | self.encoded_token = ::std::option::Option::Some(::std::vec::Vec::new()); 789 | } 790 | self.encoded_token.as_mut().unwrap() 791 | } 792 | 793 | // Take field 794 | pub fn take_encoded_token(&mut self) -> ::std::vec::Vec { 795 | self.encoded_token.take().unwrap_or_else(|| ::std::vec::Vec::new()) 796 | } 797 | 798 | fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { 799 | let mut fields = ::std::vec::Vec::with_capacity(2); 800 | let mut oneofs = ::std::vec::Vec::with_capacity(0); 801 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 802 | "encoded_network", 803 | |m: &EthereumDefinitions| { &m.encoded_network }, 804 | |m: &mut EthereumDefinitions| { &mut m.encoded_network }, 805 | )); 806 | fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( 807 | "encoded_token", 808 | |m: &EthereumDefinitions| { &m.encoded_token }, 809 | |m: &mut EthereumDefinitions| { &mut m.encoded_token }, 810 | )); 811 | ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( 812 | "EthereumDefinitions", 813 | fields, 814 | oneofs, 815 | ) 816 | } 817 | } 818 | 819 | impl ::protobuf::Message for EthereumDefinitions { 820 | const NAME: &'static str = "EthereumDefinitions"; 821 | 822 | fn is_initialized(&self) -> bool { 823 | true 824 | } 825 | 826 | fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { 827 | while let Some(tag) = is.read_raw_tag_or_eof()? { 828 | match tag { 829 | 10 => { 830 | self.encoded_network = ::std::option::Option::Some(is.read_bytes()?); 831 | }, 832 | 18 => { 833 | self.encoded_token = ::std::option::Option::Some(is.read_bytes()?); 834 | }, 835 | tag => { 836 | ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; 837 | }, 838 | }; 839 | } 840 | ::std::result::Result::Ok(()) 841 | } 842 | 843 | // Compute sizes of nested messages 844 | #[allow(unused_variables)] 845 | fn compute_size(&self) -> u64 { 846 | let mut my_size = 0; 847 | if let Some(v) = self.encoded_network.as_ref() { 848 | my_size += ::protobuf::rt::bytes_size(1, &v); 849 | } 850 | if let Some(v) = self.encoded_token.as_ref() { 851 | my_size += ::protobuf::rt::bytes_size(2, &v); 852 | } 853 | my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); 854 | self.special_fields.cached_size().set(my_size as u32); 855 | my_size 856 | } 857 | 858 | fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { 859 | if let Some(v) = self.encoded_network.as_ref() { 860 | os.write_bytes(1, v)?; 861 | } 862 | if let Some(v) = self.encoded_token.as_ref() { 863 | os.write_bytes(2, v)?; 864 | } 865 | os.write_unknown_fields(self.special_fields.unknown_fields())?; 866 | ::std::result::Result::Ok(()) 867 | } 868 | 869 | fn special_fields(&self) -> &::protobuf::SpecialFields { 870 | &self.special_fields 871 | } 872 | 873 | fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { 874 | &mut self.special_fields 875 | } 876 | 877 | fn new() -> EthereumDefinitions { 878 | EthereumDefinitions::new() 879 | } 880 | 881 | fn clear(&mut self) { 882 | self.encoded_network = ::std::option::Option::None; 883 | self.encoded_token = ::std::option::Option::None; 884 | self.special_fields.clear(); 885 | } 886 | 887 | fn default_instance() -> &'static EthereumDefinitions { 888 | static instance: EthereumDefinitions = EthereumDefinitions { 889 | encoded_network: ::std::option::Option::None, 890 | encoded_token: ::std::option::Option::None, 891 | special_fields: ::protobuf::SpecialFields::new(), 892 | }; 893 | &instance 894 | } 895 | } 896 | 897 | impl ::protobuf::MessageFull for EthereumDefinitions { 898 | fn descriptor() -> ::protobuf::reflect::MessageDescriptor { 899 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); 900 | descriptor.get(|| file_descriptor().message_by_package_relative_name("EthereumDefinitions").unwrap()).clone() 901 | } 902 | } 903 | 904 | impl ::std::fmt::Display for EthereumDefinitions { 905 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 906 | ::protobuf::text_format::fmt(self, f) 907 | } 908 | } 909 | 910 | impl ::protobuf::reflect::ProtobufValue for EthereumDefinitions { 911 | type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; 912 | } 913 | 914 | /// * 915 | /// Ethereum definitions type enum. 916 | /// Used to check the encoded EthereumNetworkInfo or EthereumTokenInfo message. 917 | #[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] 918 | // @@protoc_insertion_point(enum:hw.trezor.messages.ethereum_definitions.EthereumDefinitionType) 919 | pub enum EthereumDefinitionType { 920 | // @@protoc_insertion_point(enum_value:hw.trezor.messages.ethereum_definitions.EthereumDefinitionType.NETWORK) 921 | NETWORK = 0, 922 | // @@protoc_insertion_point(enum_value:hw.trezor.messages.ethereum_definitions.EthereumDefinitionType.TOKEN) 923 | TOKEN = 1, 924 | } 925 | 926 | impl ::protobuf::Enum for EthereumDefinitionType { 927 | const NAME: &'static str = "EthereumDefinitionType"; 928 | 929 | fn value(&self) -> i32 { 930 | *self as i32 931 | } 932 | 933 | fn from_i32(value: i32) -> ::std::option::Option { 934 | match value { 935 | 0 => ::std::option::Option::Some(EthereumDefinitionType::NETWORK), 936 | 1 => ::std::option::Option::Some(EthereumDefinitionType::TOKEN), 937 | _ => ::std::option::Option::None 938 | } 939 | } 940 | 941 | const VALUES: &'static [EthereumDefinitionType] = &[ 942 | EthereumDefinitionType::NETWORK, 943 | EthereumDefinitionType::TOKEN, 944 | ]; 945 | } 946 | 947 | impl ::protobuf::EnumFull for EthereumDefinitionType { 948 | fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { 949 | static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); 950 | descriptor.get(|| file_descriptor().enum_by_package_relative_name("EthereumDefinitionType").unwrap()).clone() 951 | } 952 | 953 | fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { 954 | let index = *self as usize; 955 | Self::enum_descriptor().value_by_index(index) 956 | } 957 | } 958 | 959 | impl ::std::default::Default for EthereumDefinitionType { 960 | fn default() -> Self { 961 | EthereumDefinitionType::NETWORK 962 | } 963 | } 964 | 965 | impl EthereumDefinitionType { 966 | fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { 967 | ::protobuf::reflect::GeneratedEnumDescriptorData::new::("EthereumDefinitionType") 968 | } 969 | } 970 | 971 | static file_descriptor_proto_data: &'static [u8] = b"\ 972 | \n#messages-ethereum-definitions.proto\x12'hw.trezor.messages.ethereum_d\ 973 | efinitions\"t\n\x13EthereumNetworkInfo\x12\x19\n\x08chain_id\x18\x01\x20\ 974 | \x02(\x04R\x07chainId\x12\x16\n\x06symbol\x18\x02\x20\x02(\tR\x06symbol\ 975 | \x12\x16\n\x06slip44\x18\x03\x20\x02(\rR\x06slip44\x12\x12\n\x04name\x18\ 976 | \x04\x20\x02(\tR\x04name\"\x90\x01\n\x11EthereumTokenInfo\x12\x18\n\x07a\ 977 | ddress\x18\x01\x20\x02(\x0cR\x07address\x12\x19\n\x08chain_id\x18\x02\ 978 | \x20\x02(\x04R\x07chainId\x12\x16\n\x06symbol\x18\x03\x20\x02(\tR\x06sym\ 979 | bol\x12\x1a\n\x08decimals\x18\x04\x20\x02(\rR\x08decimals\x12\x12\n\x04n\ 980 | ame\x18\x05\x20\x02(\tR\x04name\"c\n\x13EthereumDefinitions\x12'\n\x0fen\ 981 | coded_network\x18\x01\x20\x01(\x0cR\x0eencodedNetwork\x12#\n\rencoded_to\ 982 | ken\x18\x02\x20\x01(\x0cR\x0cencodedToken*0\n\x16EthereumDefinitionType\ 983 | \x12\x0b\n\x07NETWORK\x10\0\x12\t\n\x05TOKEN\x10\x01BG\n#com.satoshilabs\ 984 | .trezor.lib.protobufB\x20TrezorMessageEthereumDefinitionsJ\xb0\x10\n\x06\ 985 | \x12\x04\0\0;\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\x08\n\x01\x02\x12\ 986 | \x03\x01\00\n\x08\n\x01\x08\x12\x03\x04\0<\n.\n\x02\x08\x01\x12\x03\x04\ 987 | \0<\x1a#\x20Sugar\x20for\x20easier\x20handling\x20in\x20Java\n\n\x08\n\ 988 | \x01\x08\x12\x03\x05\0A\n\t\n\x02\x08\x08\x12\x03\x05\0A\n|\n\x02\x05\0\ 989 | \x12\x04\x0c\x01\x0f\x01\x1ap*\n\x20Ethereum\x20definitions\x20type\x20e\ 990 | num.\n\x20Used\x20to\x20check\x20the\x20encoded\x20EthereumNetworkInfo\ 991 | \x20or\x20EthereumTokenInfo\x20message.\n\n\n\n\x03\x05\0\x01\x12\x03\ 992 | \x0c\x06\x1c\n\x0b\n\x04\x05\0\x02\0\x12\x03\r\x04\x10\n\x0c\n\x05\x05\0\ 993 | \x02\0\x01\x12\x03\r\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\r\x0e\ 994 | \x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x0e\x04\x0e\n\x0c\n\x05\x05\0\x02\ 995 | \x01\x01\x12\x03\x0e\x04\t\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x0e\x0c\ 996 | \r\n\x89\x03\n\x02\x04\0\x12\x04\x1b\0\x20\x01\x1a\xfc\x02*\n\x20Ethereu\ 997 | m\x20network\x20definition.\x20Used\x20to\x20(de)serialize\x20the\x20def\ 998 | inition.\n\n\x20Definition\x20types\x20should\x20not\x20be\x20cross-pars\ 999 | eable,\x20i.e.,\x20it\x20should\x20not\x20be\x20possible\x20to\n\x20inco\ 1000 | rrectly\x20parse\x20network\x20info\x20as\x20token\x20info\x20or\x20vice\ 1001 | \x20versa.\n\x20To\x20achieve\x20that,\x20the\x20first\x20field\x20is\ 1002 | \x20wire\x20type\x20varint\x20while\x20the\x20second\x20field\x20is\x20w\ 1003 | ire\x20type\n\x20length-delimited.\x20Both\x20are\x20a\x20mismatch\x20fo\ 1004 | r\x20the\x20token\x20definition.\n\n\x20@embed\n\n\n\n\x03\x04\0\x01\x12\ 1005 | \x03\x1b\x08\x1b\n\x0b\n\x04\x04\0\x02\0\x12\x03\x1c\x04!\n\x0c\n\x05\ 1006 | \x04\0\x02\0\x04\x12\x03\x1c\x04\x0c\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\ 1007 | \x1c\r\x13\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x1c\x14\x1c\n\x0c\n\x05\ 1008 | \x04\0\x02\0\x03\x12\x03\x1c\x1f\x20\n\x0b\n\x04\x04\0\x02\x01\x12\x03\ 1009 | \x1d\x04\x1f\n\x0c\n\x05\x04\0\x02\x01\x04\x12\x03\x1d\x04\x0c\n\x0c\n\ 1010 | \x05\x04\0\x02\x01\x05\x12\x03\x1d\r\x13\n\x0c\n\x05\x04\0\x02\x01\x01\ 1011 | \x12\x03\x1d\x14\x1a\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x1d\x1d\x1e\n\ 1012 | \x0b\n\x04\x04\0\x02\x02\x12\x03\x1e\x04\x1f\n\x0c\n\x05\x04\0\x02\x02\ 1013 | \x04\x12\x03\x1e\x04\x0c\n\x0c\n\x05\x04\0\x02\x02\x05\x12\x03\x1e\r\x13\ 1014 | \n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\x1e\x14\x1a\n\x0c\n\x05\x04\0\x02\ 1015 | \x02\x03\x12\x03\x1e\x1d\x1e\n\x0b\n\x04\x04\0\x02\x03\x12\x03\x1f\x04\ 1016 | \x1d\n\x0c\n\x05\x04\0\x02\x03\x04\x12\x03\x1f\x04\x0c\n\x0c\n\x05\x04\0\ 1017 | \x02\x03\x05\x12\x03\x1f\r\x13\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x1f\ 1018 | \x14\x18\n\x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x1f\x1b\x1c\n\x89\x03\n\ 1019 | \x02\x04\x01\x12\x04,\02\x01\x1a\xfc\x02*\n\x20Ethereum\x20token\x20defi\ 1020 | nition.\x20Used\x20to\x20(de)serialize\x20the\x20definition.\n\n\x20Defi\ 1021 | nition\x20types\x20should\x20not\x20be\x20cross-parseable,\x20i.e.,\x20i\ 1022 | t\x20should\x20not\x20be\x20possible\x20to\n\x20incorrectly\x20parse\x20\ 1023 | network\x20info\x20as\x20token\x20info\x20or\x20vice\x20versa.\n\x20To\ 1024 | \x20achieve\x20that,\x20the\x20first\x20field\x20is\x20wire\x20type\x20l\ 1025 | ength-delimited\x20while\x20the\x20second\x20field\n\x20is\x20wire\x20ty\ 1026 | pe\x20varint.\x20Both\x20are\x20a\x20mismatch\x20for\x20the\x20network\ 1027 | \x20definition.\n\n\x20@embed\n\n\n\n\x03\x04\x01\x01\x12\x03,\x08\x19\n\ 1028 | \x0b\n\x04\x04\x01\x02\0\x12\x03-\x04\x1f\n\x0c\n\x05\x04\x01\x02\0\x04\ 1029 | \x12\x03-\x04\x0c\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03-\r\x12\n\x0c\n\ 1030 | \x05\x04\x01\x02\0\x01\x12\x03-\x13\x1a\n\x0c\n\x05\x04\x01\x02\0\x03\ 1031 | \x12\x03-\x1d\x1e\n\x0b\n\x04\x04\x01\x02\x01\x12\x03.\x04!\n\x0c\n\x05\ 1032 | \x04\x01\x02\x01\x04\x12\x03.\x04\x0c\n\x0c\n\x05\x04\x01\x02\x01\x05\ 1033 | \x12\x03.\r\x13\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03.\x14\x1c\n\x0c\n\ 1034 | \x05\x04\x01\x02\x01\x03\x12\x03.\x1f\x20\n\x0b\n\x04\x04\x01\x02\x02\ 1035 | \x12\x03/\x04\x1f\n\x0c\n\x05\x04\x01\x02\x02\x04\x12\x03/\x04\x0c\n\x0c\ 1036 | \n\x05\x04\x01\x02\x02\x05\x12\x03/\r\x13\n\x0c\n\x05\x04\x01\x02\x02\ 1037 | \x01\x12\x03/\x14\x1a\n\x0c\n\x05\x04\x01\x02\x02\x03\x12\x03/\x1d\x1e\n\ 1038 | \x0b\n\x04\x04\x01\x02\x03\x12\x030\x04!\n\x0c\n\x05\x04\x01\x02\x03\x04\ 1039 | \x12\x030\x04\x0c\n\x0c\n\x05\x04\x01\x02\x03\x05\x12\x030\r\x13\n\x0c\n\ 1040 | \x05\x04\x01\x02\x03\x01\x12\x030\x14\x1c\n\x0c\n\x05\x04\x01\x02\x03\ 1041 | \x03\x12\x030\x1f\x20\n\x0b\n\x04\x04\x01\x02\x04\x12\x031\x04\x1d\n\x0c\ 1042 | \n\x05\x04\x01\x02\x04\x04\x12\x031\x04\x0c\n\x0c\n\x05\x04\x01\x02\x04\ 1043 | \x05\x12\x031\r\x13\n\x0c\n\x05\x04\x01\x02\x04\x01\x12\x031\x14\x18\n\ 1044 | \x0c\n\x05\x04\x01\x02\x04\x03\x12\x031\x1b\x1c\n~\n\x02\x04\x02\x12\x04\ 1045 | 8\0;\x01\x1ar*\n\x20Contains\x20an\x20encoded\x20Ethereum\x20network\x20\ 1046 | and/or\x20token\x20definition.\x20See\x20ethereum-definitions.md\x20for\ 1047 | \x20details.\n\x20@embed\n\n\n\n\x03\x04\x02\x01\x12\x038\x08\x1b\n'\n\ 1048 | \x04\x04\x02\x02\0\x12\x039\x04'\"\x1a\x20encoded\x20Ethereum\x20network\ 1049 | \n\n\x0c\n\x05\x04\x02\x02\0\x04\x12\x039\x04\x0c\n\x0c\n\x05\x04\x02\ 1050 | \x02\0\x05\x12\x039\r\x12\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x039\x13\"\n\ 1051 | \x0c\n\x05\x04\x02\x02\0\x03\x12\x039%&\n%\n\x04\x04\x02\x02\x01\x12\x03\ 1052 | :\x04%\"\x18\x20encoded\x20Ethereum\x20token\n\n\x0c\n\x05\x04\x02\x02\ 1053 | \x01\x04\x12\x03:\x04\x0c\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03:\r\x12\ 1054 | \n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03:\x13\x20\n\x0c\n\x05\x04\x02\ 1055 | \x02\x01\x03\x12\x03:#$\ 1056 | "; 1057 | 1058 | /// `FileDescriptorProto` object which was a source for this generated file 1059 | fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { 1060 | static file_descriptor_proto_lazy: ::protobuf::rt::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::Lazy::new(); 1061 | file_descriptor_proto_lazy.get(|| { 1062 | ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap() 1063 | }) 1064 | } 1065 | 1066 | /// `FileDescriptor` object which allows dynamic access to files 1067 | pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { 1068 | static generated_file_descriptor_lazy: ::protobuf::rt::Lazy<::protobuf::reflect::GeneratedFileDescriptor> = ::protobuf::rt::Lazy::new(); 1069 | static file_descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::FileDescriptor> = ::protobuf::rt::Lazy::new(); 1070 | file_descriptor.get(|| { 1071 | let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { 1072 | let mut deps = ::std::vec::Vec::with_capacity(0); 1073 | let mut messages = ::std::vec::Vec::with_capacity(3); 1074 | messages.push(EthereumNetworkInfo::generated_message_descriptor_data()); 1075 | messages.push(EthereumTokenInfo::generated_message_descriptor_data()); 1076 | messages.push(EthereumDefinitions::generated_message_descriptor_data()); 1077 | let mut enums = ::std::vec::Vec::with_capacity(1); 1078 | enums.push(EthereumDefinitionType::generated_enum_descriptor_data()); 1079 | ::protobuf::reflect::GeneratedFileDescriptor::new_generated( 1080 | file_descriptor_proto(), 1081 | deps, 1082 | messages, 1083 | enums, 1084 | ) 1085 | }); 1086 | ::protobuf::reflect::FileDescriptor::new_generated_2(generated_file_descriptor) 1087 | }) 1088 | } 1089 | -------------------------------------------------------------------------------- /src/protos/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(ambiguous_glob_reexports, unknown_lints, rustdoc::all)] 2 | 3 | // Common 4 | pub mod messages; 5 | pub mod messages_bootloader; 6 | pub mod messages_common; 7 | pub mod messages_crypto; 8 | pub mod messages_debug; 9 | pub mod messages_management; 10 | 11 | pub use messages::*; 12 | pub use messages_bootloader::*; 13 | pub use messages_common::*; 14 | pub use messages_crypto::*; 15 | pub use messages_debug::*; 16 | pub use messages_management::*; 17 | 18 | macro_rules! features { 19 | ($($feature:literal => { $($item:item)+ })+) => {$( 20 | $( 21 | #[cfg(feature = $feature)] 22 | $item 23 | )+ 24 | )+}; 25 | } 26 | 27 | features! { 28 | "bitcoin" => { 29 | pub mod messages_bitcoin; 30 | pub use messages_bitcoin::*; 31 | } 32 | 33 | "ethereum" => { 34 | pub mod messages_ethereum; 35 | pub mod messages_ethereum_eip712; 36 | pub mod messages_ethereum_definitions; 37 | 38 | pub use messages_ethereum::*; 39 | pub use messages_ethereum_eip712::*; 40 | pub use messages_ethereum_definitions::*; 41 | } 42 | 43 | "binance" => { 44 | pub mod messages_binance; 45 | pub use messages_binance::*; 46 | } 47 | 48 | "cardano" => { 49 | pub mod messages_cardano; 50 | pub use messages_cardano::*; 51 | } 52 | 53 | "eos" => { 54 | pub mod messages_eos; 55 | pub use messages_eos::*; 56 | } 57 | 58 | "monero" => { 59 | pub mod messages_monero; 60 | pub use messages_monero::*; 61 | } 62 | 63 | "nem" => { 64 | pub mod messages_nem; 65 | pub use messages_nem::*; 66 | } 67 | 68 | "ripple" => { 69 | pub mod messages_ripple; 70 | pub use messages_ripple::*; 71 | } 72 | 73 | "stellar" => { 74 | pub mod messages_stellar; 75 | pub use messages_stellar::*; 76 | } 77 | 78 | "tezos" => { 79 | pub mod messages_tezos; 80 | pub use messages_tezos::*; 81 | } 82 | 83 | "webauthn" => { 84 | pub mod messages_webauthn; 85 | pub use messages_webauthn::*; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/transport/error.rs: -------------------------------------------------------------------------------- 1 | //! # Error Handling 2 | 3 | /// Trezor error. 4 | #[derive(Debug, thiserror::Error)] 5 | pub enum Error { 6 | /// [rusb] error. 7 | #[error(transparent)] 8 | Usb(#[from] rusb::Error), 9 | 10 | /// [std::io] error. 11 | #[error(transparent)] 12 | IO(#[from] std::io::Error), 13 | 14 | /// The device to connect to was not found. 15 | #[error("the device to connect to was not found")] 16 | DeviceNotFound, 17 | 18 | /// The device is no longer available. 19 | #[error("the device is no longer available")] 20 | DeviceDisconnected, 21 | 22 | /// The device produced a data chunk of unexpected size. 23 | #[error("the device produced a data chunk of unexpected size")] 24 | UnexpectedChunkSizeFromDevice(usize), 25 | 26 | /// Timeout expired while reading from device. 27 | #[error("timeout expired while reading from device")] 28 | DeviceReadTimeout, 29 | 30 | /// The device sent a chunk with a wrong magic value. 31 | #[error("the device sent a chunk with a wrong magic value")] 32 | DeviceBadMagic, 33 | 34 | /// The device sent a message with a wrong session id. 35 | #[error("the device sent a message with a wrong session id")] 36 | DeviceBadSessionId, 37 | 38 | /// The device sent an unexpected sequence number. 39 | #[error("the device sent an unexpected sequence number")] 40 | DeviceUnexpectedSequenceNumber, 41 | 42 | /// Received a non-existing message type from the device. 43 | #[error("received a non-existing message type from the device")] 44 | InvalidMessageType(u32), 45 | 46 | /// Unable to determine device serial number. 47 | #[error("unable to determine device serial number")] 48 | NoDeviceSerial, 49 | } 50 | -------------------------------------------------------------------------------- /src/transport/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{AvailableDevice, Model}; 2 | use crate::protos::MessageType; 3 | use std::fmt; 4 | 5 | pub mod error; 6 | pub mod protocol; 7 | pub mod udp; 8 | pub mod webusb; 9 | 10 | /// An available transport for a Trezor device, containing any of the different supported 11 | /// transports. 12 | #[derive(Debug)] 13 | pub enum AvailableDeviceTransport { 14 | WebUsb(webusb::AvailableWebUsbTransport), 15 | Udp(udp::AvailableUdpTransport), 16 | } 17 | 18 | impl fmt::Display for AvailableDeviceTransport { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | match self { 21 | AvailableDeviceTransport::WebUsb(ref t) => write!(f, "{}", t), 22 | AvailableDeviceTransport::Udp(ref t) => write!(f, "{}", t), 23 | } 24 | } 25 | } 26 | 27 | /// A protobuf message accompanied by the message type. This type is used to pass messages over the 28 | /// transport and used to contain messages received from the transport. 29 | pub struct ProtoMessage(pub MessageType, pub Vec); 30 | 31 | impl ProtoMessage { 32 | pub fn new(mt: MessageType, payload: Vec) -> ProtoMessage { 33 | ProtoMessage(mt, payload) 34 | } 35 | pub fn message_type(&self) -> MessageType { 36 | self.0 37 | } 38 | pub fn payload(&self) -> &[u8] { 39 | &self.1 40 | } 41 | pub fn into_payload(self) -> Vec { 42 | self.1 43 | } 44 | 45 | /// Take the payload from the ProtoMessage and parse it to a protobuf message. 46 | pub fn into_message(self) -> Result { 47 | protobuf::Message::parse_from_bytes(&self.into_payload()) 48 | } 49 | } 50 | 51 | /// The transport interface that is implemented by the different ways to communicate with a Trezor 52 | /// device. 53 | pub trait Transport { 54 | fn session_begin(&mut self) -> Result<(), error::Error>; 55 | fn session_end(&mut self) -> Result<(), error::Error>; 56 | 57 | fn write_message(&mut self, message: ProtoMessage) -> Result<(), error::Error>; 58 | fn read_message(&mut self) -> Result; 59 | } 60 | 61 | /// A delegation method to connect an available device transport. It delegates to the different 62 | /// transport types. 63 | pub fn connect(available_device: &AvailableDevice) -> Result, error::Error> { 64 | match available_device.transport { 65 | AvailableDeviceTransport::WebUsb(_) => webusb::WebUsbTransport::connect(available_device), 66 | AvailableDeviceTransport::Udp(_) => udp::UdpTransport::connect(available_device), 67 | } 68 | } 69 | 70 | // A collection of transport-global constants. 71 | mod constants { 72 | pub const DEV_TREZOR_LEGACY: (u16, u16) = (0x534C, 0x0001); 73 | pub const DEV_TREZOR: (u16, u16) = (0x1209, 0x53C1); 74 | pub const DEV_TREZOR_BOOTLOADER: (u16, u16) = (0x1209, 0x53C0); 75 | } 76 | 77 | /// Derive the Trezor model from the USB device. 78 | pub(crate) fn derive_model(dev_id: (u16, u16)) -> Option { 79 | match dev_id { 80 | constants::DEV_TREZOR_LEGACY => Some(Model::TrezorLegacy), 81 | constants::DEV_TREZOR => Some(Model::Trezor), 82 | constants::DEV_TREZOR_BOOTLOADER => Some(Model::TrezorBootloader), 83 | _ => None, 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/transport/protocol.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | protos::MessageType, 3 | transport::{error::Error, ProtoMessage}, 4 | }; 5 | use byteorder::{BigEndian, ByteOrder}; 6 | use protobuf::Enum; 7 | use std::cmp; 8 | 9 | /// A link represents a serial connection to send and receive byte chunks from and to a device. 10 | pub trait Link { 11 | fn write_chunk(&mut self, chunk: Vec) -> Result<(), Error>; 12 | fn read_chunk(&mut self) -> Result, Error>; 13 | } 14 | 15 | /// A protocol is used to encode messages in chunks that can be sent to the device and to parse 16 | /// chunks into messages. 17 | pub trait Protocol { 18 | fn session_begin(&mut self) -> Result<(), Error>; 19 | fn session_end(&mut self) -> Result<(), Error>; 20 | fn write(&mut self, message: ProtoMessage) -> Result<(), Error>; 21 | fn read(&mut self) -> Result; 22 | } 23 | 24 | /// The length of the chunks sent. 25 | const REPLEN: usize = 64; 26 | 27 | /// V2 of the binary protocol. 28 | /// This version is currently not in use by any device and is subject to change. 29 | #[allow(dead_code)] 30 | pub struct ProtocolV2 { 31 | pub link: L, 32 | pub session_id: u32, 33 | } 34 | 35 | impl Protocol for ProtocolV2 { 36 | fn session_begin(&mut self) -> Result<(), Error> { 37 | let mut chunk = vec![0; REPLEN]; 38 | chunk[0] = 0x03; 39 | self.link.write_chunk(chunk)?; 40 | let resp = self.link.read_chunk()?; 41 | if resp[0] != 0x03 { 42 | println!("bad magic in v2 session_begin: {:x} instead of 0x03", resp[0]); 43 | return Err(Error::DeviceBadMagic) 44 | } 45 | self.session_id = BigEndian::read_u32(&resp[1..5]); 46 | Ok(()) 47 | } 48 | 49 | fn session_end(&mut self) -> Result<(), Error> { 50 | assert!(self.session_id != 0); 51 | let mut chunk = vec![0; REPLEN]; 52 | chunk[0] = 0x04; 53 | BigEndian::write_u32(&mut chunk[1..5], self.session_id); 54 | self.link.write_chunk(chunk)?; 55 | let resp = self.link.read_chunk()?; 56 | if resp[0] != 0x04 { 57 | println!("bad magic in v2 session_end: {:x} instead of 0x04", resp[0]); 58 | return Err(Error::DeviceBadMagic) 59 | } 60 | self.session_id = 0; 61 | Ok(()) 62 | } 63 | 64 | fn write(&mut self, message: ProtoMessage) -> Result<(), Error> { 65 | assert!(self.session_id != 0); 66 | 67 | // First generate the total payload, then write it to the transport in chunks. 68 | let mut data = vec![0; 8]; 69 | BigEndian::write_u32(&mut data[0..4], message.message_type() as u32); 70 | BigEndian::write_u32(&mut data[4..8], message.payload().len() as u32); 71 | data.extend(message.into_payload()); 72 | 73 | let mut cur: usize = 0; 74 | let mut seq: isize = -1; 75 | while cur < data.len() { 76 | // Build header. 77 | let mut chunk = if seq < 0 { 78 | let mut header = vec![0; 5]; 79 | header[0] = 0x01; 80 | BigEndian::write_u32(&mut header[1..5], self.session_id); 81 | header 82 | } else { 83 | let mut header = vec![0; 9]; 84 | header[0] = 0x01; 85 | BigEndian::write_u32(&mut header[1..5], self.session_id); 86 | BigEndian::write_u32(&mut header[5..9], seq as u32); 87 | header 88 | }; 89 | seq += 1; 90 | 91 | // Fill remainder. 92 | let end = cmp::min(cur + (REPLEN - chunk.len()), data.len()); 93 | chunk.extend(&data[cur..end]); 94 | cur = end; 95 | debug_assert!(chunk.len() <= REPLEN); 96 | chunk.resize(REPLEN, 0); 97 | 98 | self.link.write_chunk(chunk)?; 99 | } 100 | 101 | Ok(()) 102 | } 103 | 104 | fn read(&mut self) -> Result { 105 | debug_assert!(self.session_id != 0); 106 | 107 | let chunk = self.link.read_chunk()?; 108 | if chunk[0] != 0x01 { 109 | println!("bad magic in v2 read: {:x} instead of 0x01", chunk[0]); 110 | return Err(Error::DeviceBadMagic) 111 | } 112 | if BigEndian::read_u32(&chunk[1..5]) != self.session_id { 113 | return Err(Error::DeviceBadSessionId) 114 | } 115 | let message_type_id = BigEndian::read_u32(&chunk[5..9]); 116 | let message_type = MessageType::from_i32(message_type_id as i32) 117 | .ok_or(Error::InvalidMessageType(message_type_id))?; 118 | let data_length = BigEndian::read_u32(&chunk[9..13]) as usize; 119 | 120 | let mut data: Vec = chunk[13..].into(); 121 | let mut seq = 0; 122 | while data.len() < data_length { 123 | let chunk = self.link.read_chunk()?; 124 | if chunk[0] != 0x02 { 125 | println!("bad magic in v2 session_begin: {:x} instead of 0x02", chunk[0]); 126 | return Err(Error::DeviceBadMagic) 127 | } 128 | if BigEndian::read_u32(&chunk[1..5]) != self.session_id { 129 | return Err(Error::DeviceBadSessionId) 130 | } 131 | if BigEndian::read_u32(&chunk[5..9]) != seq as u32 { 132 | return Err(Error::DeviceUnexpectedSequenceNumber) 133 | } 134 | seq += 1; 135 | 136 | data.extend(&chunk[9..]); 137 | } 138 | 139 | Ok(ProtoMessage(message_type, data[0..data_length].into())) 140 | } 141 | } 142 | 143 | /// The original binary protocol. 144 | pub struct ProtocolV1 { 145 | pub link: L, 146 | } 147 | 148 | impl Protocol for ProtocolV1 { 149 | fn session_begin(&mut self) -> Result<(), Error> { 150 | Ok(()) // no sessions 151 | } 152 | 153 | fn session_end(&mut self) -> Result<(), Error> { 154 | Ok(()) // no sessions 155 | } 156 | 157 | fn write(&mut self, message: ProtoMessage) -> Result<(), Error> { 158 | // First generate the total payload, then write it to the transport in chunks. 159 | let mut data = vec![0; 8]; 160 | data[0] = 0x23; 161 | data[1] = 0x23; 162 | BigEndian::write_u16(&mut data[2..4], message.message_type() as u16); 163 | BigEndian::write_u32(&mut data[4..8], message.payload().len() as u32); 164 | data.extend(message.into_payload()); 165 | 166 | let mut cur: usize = 0; 167 | while cur < data.len() { 168 | let mut chunk = vec![0x3f]; 169 | let end = cmp::min(cur + (REPLEN - 1), data.len()); 170 | chunk.extend(&data[cur..end]); 171 | cur = end; 172 | debug_assert!(chunk.len() <= REPLEN); 173 | chunk.resize(REPLEN, 0); 174 | 175 | self.link.write_chunk(chunk)?; 176 | } 177 | 178 | Ok(()) 179 | } 180 | 181 | fn read(&mut self) -> Result { 182 | let chunk = self.link.read_chunk()?; 183 | if chunk[0] != 0x3f || chunk[1] != 0x23 || chunk[2] != 0x23 { 184 | println!( 185 | "bad magic in v1 read: {:x}{:x}{:x} instead of 0x3f2323", 186 | chunk[0], chunk[1], chunk[2] 187 | ); 188 | return Err(Error::DeviceBadMagic) 189 | } 190 | let message_type_id = BigEndian::read_u16(&chunk[3..5]) as u32; 191 | let message_type = MessageType::from_i32(message_type_id as i32) 192 | .ok_or(Error::InvalidMessageType(message_type_id))?; 193 | let data_length = BigEndian::read_u32(&chunk[5..9]) as usize; 194 | let mut data: Vec = chunk[9..].into(); 195 | 196 | while data.len() < data_length { 197 | let chunk = self.link.read_chunk()?; 198 | if chunk[0] != 0x3f { 199 | println!("bad magic in v1 read: {:x} instead of 0x3f", chunk[0]); 200 | return Err(Error::DeviceBadMagic) 201 | } 202 | 203 | data.extend(&chunk[1..]); 204 | } 205 | 206 | Ok(ProtoMessage(message_type, data[0..data_length].into())) 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/transport/udp.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | error::Error, 3 | protocol::{Link, Protocol, ProtocolV1}, 4 | AvailableDeviceTransport, ProtoMessage, Transport, 5 | }; 6 | use crate::{AvailableDevice, Model}; 7 | use std::{fmt, net::UdpSocket, result::Result, time::Duration}; 8 | 9 | // A collection of constants related to the Emulator Ports. 10 | mod constants { 11 | pub const DEFAULT_HOST: &str = "127.0.0.1"; 12 | pub const DEFAULT_PORT: &str = "21324"; 13 | pub const DEFAULT_DEBUG_PORT: &str = "21325"; 14 | pub const LOCAL_LISTENER: &str = "127.0.0.1:34254"; 15 | } 16 | 17 | use constants::{DEFAULT_DEBUG_PORT, DEFAULT_HOST, DEFAULT_PORT, LOCAL_LISTENER}; 18 | 19 | /// The chunk size for the serial protocol. 20 | const CHUNK_SIZE: usize = 64; 21 | 22 | const READ_TIMEOUT_MS: u64 = 100000; 23 | const WRITE_TIMEOUT_MS: u64 = 100000; 24 | 25 | /// An available transport for connecting with a device. 26 | #[derive(Debug)] 27 | pub struct AvailableUdpTransport { 28 | pub host: String, 29 | pub port: String, 30 | } 31 | 32 | impl fmt::Display for AvailableUdpTransport { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | write!(f, "udp:{}:{}", self.host, self.port) 35 | } 36 | } 37 | 38 | /// An actual serial HID USB link to a device over which bytes can be sent. 39 | struct UdpLink { 40 | pub socket: UdpSocket, 41 | pub device: (String, String), 42 | } 43 | // No need to implement drop as every member is owned 44 | 45 | impl Link for UdpLink { 46 | fn write_chunk(&mut self, chunk: Vec) -> Result<(), Error> { 47 | debug_assert_eq!(CHUNK_SIZE, chunk.len()); 48 | let timeout = Duration::from_millis(WRITE_TIMEOUT_MS); 49 | self.socket.set_write_timeout(Some(timeout))?; 50 | if let Err(e) = self.socket.send(&chunk) { 51 | return Err(e.into()) 52 | } 53 | Ok(()) 54 | } 55 | 56 | fn read_chunk(&mut self) -> Result, Error> { 57 | let mut chunk = vec![0; CHUNK_SIZE]; 58 | let timeout = Duration::from_millis(READ_TIMEOUT_MS); 59 | self.socket.set_read_timeout(Some(timeout))?; 60 | 61 | let n = self.socket.recv(&mut chunk)?; 62 | if n == CHUNK_SIZE { 63 | Ok(chunk) 64 | } else { 65 | Err(Error::DeviceReadTimeout) 66 | } 67 | } 68 | } 69 | 70 | impl UdpLink { 71 | pub fn open(path: &str) -> Result { 72 | let mut parts = path.split(':'); 73 | let link = Self { 74 | socket: UdpSocket::bind(LOCAL_LISTENER)?, 75 | device: ( 76 | parts.next().expect("Incorrect Path").to_owned(), 77 | parts.next().expect("Incorrect Path").to_owned(), 78 | ), 79 | }; 80 | link.socket.connect(path)?; 81 | Ok(link) 82 | } 83 | 84 | // Ping the port and compare against expected response 85 | fn ping(&self) -> Result { 86 | let mut resp = [0; CHUNK_SIZE]; 87 | self.socket.send("PINGPING".as_bytes())?; 88 | let size = self.socket.recv(&mut resp)?; 89 | Ok(&resp[..size] == "PONGPONG".as_bytes()) 90 | } 91 | } 92 | 93 | /// An implementation of the Transport interface for UDP devices. 94 | pub struct UdpTransport { 95 | protocol: ProtocolV1, 96 | } 97 | 98 | impl UdpTransport { 99 | pub fn find_devices(debug: bool, path: Option<&str>) -> Result, Error> { 100 | let mut devices = Vec::new(); 101 | let mut dest = String::new(); 102 | match path { 103 | Some(p) => dest = p.to_owned(), 104 | None => { 105 | dest.push_str(DEFAULT_HOST); 106 | dest.push(':'); 107 | dest.push_str(if debug { DEFAULT_DEBUG_PORT } else { DEFAULT_PORT }); 108 | } 109 | }; 110 | let link = UdpLink::open(&dest)?; 111 | if link.ping()? { 112 | devices.push(AvailableDevice { 113 | model: Model::TrezorEmulator, 114 | debug, 115 | transport: AvailableDeviceTransport::Udp(AvailableUdpTransport { 116 | host: link.device.0, 117 | port: link.device.1, 118 | }), 119 | }); 120 | } 121 | Ok(devices) 122 | } 123 | 124 | /// Connect to a device over the UDP transport. 125 | pub fn connect(device: &AvailableDevice) -> Result, Error> { 126 | let transport = match device.transport { 127 | AvailableDeviceTransport::Udp(ref t) => t, 128 | _ => panic!("passed wrong AvailableDevice in UdpTransport::connect"), 129 | }; 130 | let mut path = String::new(); 131 | path.push_str(&transport.host); 132 | path.push(':'); 133 | path.push_str(&transport.port); 134 | let link = UdpLink::open(&path)?; 135 | Ok(Box::new(UdpTransport { protocol: ProtocolV1 { link } })) 136 | } 137 | } 138 | 139 | impl super::Transport for UdpTransport { 140 | fn session_begin(&mut self) -> Result<(), Error> { 141 | self.protocol.session_begin() 142 | } 143 | fn session_end(&mut self) -> Result<(), Error> { 144 | self.protocol.session_end() 145 | } 146 | 147 | fn write_message(&mut self, message: ProtoMessage) -> Result<(), Error> { 148 | self.protocol.write(message) 149 | } 150 | fn read_message(&mut self) -> Result { 151 | self.protocol.read() 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/transport/webusb.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | transport::{ 3 | derive_model, 4 | error::Error, 5 | protocol::{Link, Protocol, ProtocolV1}, 6 | AvailableDeviceTransport, ProtoMessage, Transport, 7 | }, 8 | AvailableDevice, 9 | }; 10 | use rusb::*; 11 | use std::{fmt, result::Result, time::Duration}; 12 | 13 | // A collection of constants related to the WebUsb protocol. 14 | mod constants { 15 | pub use crate::transport::constants::*; 16 | 17 | pub const CONFIG_ID: u8 = 0; 18 | pub const INTERFACE_DESCRIPTOR: u8 = 0; 19 | pub const LIBUSB_CLASS_VENDOR_SPEC: u8 = 0xff; 20 | 21 | pub const INTERFACE: u8 = 0; 22 | pub const INTERFACE_DEBUG: u8 = 1; 23 | pub const ENDPOINT: u8 = 1; 24 | pub const ENDPOINT_DEBUG: u8 = 2; 25 | pub const READ_ENDPOINT_MASK: u8 = 0x80; 26 | } 27 | 28 | /// The chunk size for the serial protocol. 29 | const CHUNK_SIZE: usize = 64; 30 | 31 | const READ_TIMEOUT_MS: u64 = 100000; 32 | const WRITE_TIMEOUT_MS: u64 = 100000; 33 | 34 | /// An available transport for connecting with a device. 35 | #[derive(Debug)] 36 | pub struct AvailableWebUsbTransport { 37 | pub bus: u8, 38 | pub address: u8, 39 | } 40 | 41 | impl fmt::Display for AvailableWebUsbTransport { 42 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 43 | write!(f, "WebUSB ({}:{})", self.bus, self.address) 44 | } 45 | } 46 | 47 | /// An actual serial USB link to a device over which bytes can be sent. 48 | pub struct WebUsbLink { 49 | handle: DeviceHandle, 50 | endpoint: u8, 51 | } 52 | 53 | impl Link for WebUsbLink { 54 | fn write_chunk(&mut self, chunk: Vec) -> Result<(), Error> { 55 | debug_assert_eq!(CHUNK_SIZE, chunk.len()); 56 | let timeout = Duration::from_millis(WRITE_TIMEOUT_MS); 57 | if let Err(e) = self.handle.write_interrupt(self.endpoint, &chunk, timeout) { 58 | return Err(e.into()) 59 | } 60 | Ok(()) 61 | } 62 | 63 | fn read_chunk(&mut self) -> Result, Error> { 64 | let mut chunk = vec![0; CHUNK_SIZE]; 65 | let endpoint = constants::READ_ENDPOINT_MASK | self.endpoint; 66 | let timeout = Duration::from_millis(READ_TIMEOUT_MS); 67 | 68 | let n = self.handle.read_interrupt(endpoint, &mut chunk, timeout)?; 69 | if n == CHUNK_SIZE { 70 | Ok(chunk) 71 | } else { 72 | Err(Error::DeviceReadTimeout) 73 | } 74 | } 75 | } 76 | 77 | /// An implementation of the Transport interface for WebUSB devices. 78 | pub struct WebUsbTransport { 79 | protocol: ProtocolV1, 80 | } 81 | 82 | impl WebUsbTransport { 83 | pub fn find_devices(debug: bool) -> Result, Error> { 84 | let mut devices = Vec::new(); 85 | 86 | for dev in rusb::devices().unwrap().iter() { 87 | let desc = dev.device_descriptor()?; 88 | let dev_id = (desc.vendor_id(), desc.product_id()); 89 | 90 | let model = match derive_model(dev_id) { 91 | Some(m) => m, 92 | None => continue, 93 | }; 94 | 95 | // Check something with interface class code like python-trezor does. 96 | let class_code = dev 97 | .config_descriptor(constants::CONFIG_ID)? 98 | .interfaces() 99 | .find(|i| i.number() == constants::INTERFACE) 100 | .ok_or(rusb::Error::Other)? 101 | .descriptors() 102 | .find(|d| d.setting_number() == constants::INTERFACE_DESCRIPTOR) 103 | .ok_or(rusb::Error::Other)? 104 | .class_code(); 105 | if class_code != constants::LIBUSB_CLASS_VENDOR_SPEC { 106 | continue 107 | } 108 | 109 | devices.push(AvailableDevice { 110 | model, 111 | debug, 112 | transport: AvailableDeviceTransport::WebUsb(AvailableWebUsbTransport { 113 | bus: dev.bus_number(), 114 | address: dev.address(), 115 | }), 116 | }); 117 | } 118 | Ok(devices) 119 | } 120 | 121 | /// Connect to a device over the WebUSB transport. 122 | pub fn connect(device: &AvailableDevice) -> Result, Error> { 123 | let transport = match &device.transport { 124 | AvailableDeviceTransport::WebUsb(t) => t, 125 | _ => panic!("passed wrong AvailableDevice in WebUsbTransport::connect"), 126 | }; 127 | 128 | let interface = match device.debug { 129 | false => constants::INTERFACE, 130 | true => constants::INTERFACE_DEBUG, 131 | }; 132 | 133 | // Go over the devices again to match the desired device. 134 | let dev = rusb::devices()? 135 | .iter() 136 | .find(|dev| dev.bus_number() == transport.bus && dev.address() == transport.address) 137 | .ok_or(Error::DeviceDisconnected)?; 138 | // Check if there is not another device connected on this bus. 139 | let dev_desc = dev.device_descriptor()?; 140 | let dev_id = (dev_desc.vendor_id(), dev_desc.product_id()); 141 | if derive_model(dev_id).as_ref() != Some(&device.model) { 142 | return Err(Error::DeviceDisconnected) 143 | } 144 | let mut handle = dev.open()?; 145 | handle.claim_interface(interface)?; 146 | 147 | Ok(Box::new(WebUsbTransport { 148 | protocol: ProtocolV1 { 149 | link: WebUsbLink { 150 | handle, 151 | endpoint: match device.debug { 152 | false => constants::ENDPOINT, 153 | true => constants::ENDPOINT_DEBUG, 154 | }, 155 | }, 156 | }, 157 | })) 158 | } 159 | } 160 | 161 | impl super::Transport for WebUsbTransport { 162 | fn session_begin(&mut self) -> Result<(), Error> { 163 | self.protocol.session_begin() 164 | } 165 | fn session_end(&mut self) -> Result<(), Error> { 166 | self.protocol.session_end() 167 | } 168 | 169 | fn write_message(&mut self, message: ProtoMessage) -> Result<(), Error> { 170 | self.protocol.write(message) 171 | } 172 | fn read_message(&mut self) -> Result { 173 | self.protocol.read() 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use bitcoin::{ 3 | address, 4 | address::Payload, 5 | bip32, 6 | blockdata::script::Script, 7 | hashes::{sha256d, Hash}, 8 | psbt, 9 | secp256k1::ecdsa::{RecoverableSignature, RecoveryId}, 10 | Address, Network, 11 | }; 12 | 13 | /// Retrieve an address from the given script. 14 | pub fn address_from_script(script: &Script, network: Network) -> Option { 15 | let payload = Payload::from_script(script).ok()?; 16 | Some(Address::new(network, payload)) 17 | } 18 | 19 | /// Find the (first if multiple) PSBT input that refers to the given txid. 20 | pub fn psbt_find_input( 21 | psbt: &psbt::PartiallySignedTransaction, 22 | txid: sha256d::Hash, 23 | ) -> Result<&psbt::Input> { 24 | let inputs = &psbt.unsigned_tx.input; 25 | let idx = inputs 26 | .iter() 27 | .position(|tx| *tx.previous_output.txid.as_raw_hash() == txid) 28 | .ok_or(Error::TxRequestUnknownTxid(txid))?; 29 | psbt.inputs.get(idx).ok_or(Error::TxRequestInvalidIndex(idx)) 30 | } 31 | 32 | /// Get a hash from a reverse byte representation. 33 | pub fn from_rev_bytes(rev_bytes: &[u8]) -> Option { 34 | let mut bytes = rev_bytes.to_vec(); 35 | bytes.reverse(); 36 | sha256d::Hash::from_slice(&bytes).ok() 37 | } 38 | 39 | /// Get the reverse byte representation of a hash. 40 | pub fn to_rev_bytes(hash: &sha256d::Hash) -> [u8; 32] { 41 | let mut bytes = hash.to_byte_array(); 42 | bytes.reverse(); 43 | bytes 44 | } 45 | 46 | /// Parse a Bitcoin Core-style 65-byte recoverable signature. 47 | pub fn parse_recoverable_signature( 48 | sig: &[u8], 49 | ) -> Result { 50 | if sig.len() != 65 { 51 | return Err(bitcoin::secp256k1::Error::InvalidSignature) 52 | } 53 | 54 | // Bitcoin Core sets the first byte to `27 + rec + (fCompressed ? 4 : 0)`. 55 | let rec_id = RecoveryId::from_i32(if sig[0] >= 31 { 56 | (sig[0] - 31) as i32 57 | } else { 58 | (sig[0] - 27) as i32 59 | })?; 60 | 61 | RecoverableSignature::from_compact(&sig[1..], rec_id) 62 | } 63 | 64 | /// Convert a bitcoin network constant to the Trezor-compatible coin_name string. 65 | pub fn coin_name(network: Network) -> Result { 66 | match network { 67 | Network::Bitcoin => Ok("Bitcoin".to_owned()), 68 | Network::Testnet => Ok("Testnet".to_owned()), 69 | _ => Err(Error::UnsupportedNetwork), 70 | } 71 | } 72 | 73 | /// Convert a BIP-32 derivation path into a `Vec`. 74 | pub fn convert_path(path: &bip32::DerivationPath) -> Vec { 75 | path.into_iter().map(|i| u32::from(*i)).collect() 76 | } 77 | --------------------------------------------------------------------------------