Skip to content

Commit 8156107

Browse files
committed
feat(bhwi): add Trezor sans-I/O interpreter
1 parent 2fded9e commit 8156107

7 files changed

Lines changed: 805 additions & 0 deletions

File tree

bhwi/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ bitbox = [
2626
"dep:zeroize",
2727
"dep:zeroize_derive",
2828
]
29+
trezor = ["dep:prost"]
2930

3031
[dependencies]
3132
base64ct = { workspace = true, features = ["alloc"] }

bhwi/src/common.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#[cfg(feature = "bitbox")]
22
use crate::bitbox;
33
use crate::miniscript::descriptor::{DescriptorPublicKey, WalletPolicy};
4+
#[cfg(feature = "trezor")]
5+
use crate::trezor;
46
use crate::{coldcard, jade, ledger};
57
use bitcoin::Network;
68
use bitcoin::address::AddressType;
@@ -230,6 +232,8 @@ pub type ColdcardInterpreter<'a> =
230232
coldcard::ColdcardInterpreter<'a, Command, Transmit, Response, Error>;
231233
pub type JadeInterpreter = jade::JadeInterpreter<Command, Transmit, Response, Error>;
232234
pub type LedgerInterpreter = ledger::LedgerInterpreter<Command, Transmit, Response, Error>;
235+
#[cfg(feature = "trezor")]
236+
pub type TrezorInterpreter = trezor::TrezorInterpreter<Command, Transmit, Response, Error>;
233237

234238
impl From<Vec<u8>> for Transmit {
235239
fn from(payload: Vec<u8>) -> Transmit {

bhwi/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub mod device;
99
pub mod jade;
1010
pub mod ledger;
1111
pub mod policy;
12+
#[cfg(feature = "trezor")]
13+
pub mod trezor;
1214

1315
pub trait Interpreter {
1416
type Command;

bhwi/src/trezor/api.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use crate::trezor::error::TrezorError;
2+
use crate::trezor::proto::{bitcoin as btc, common as pb, management as mgmt};
3+
use prost::Message;
4+
5+
const HEADER: [u8; 2] = *b"##";
6+
7+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8+
#[repr(u16)]
9+
pub enum MessageType {
10+
Initialize = 0,
11+
Success = 2,
12+
Failure = 3,
13+
GetPublicKey = 11,
14+
PublicKey = 12,
15+
Features = 17,
16+
PinMatrixRequest = 18,
17+
ButtonRequest = 26,
18+
ButtonAck = 27,
19+
GetAddress = 29,
20+
Address = 30,
21+
PassphraseRequest = 41,
22+
PassphraseAck = 42,
23+
GetFeatures = 55,
24+
}
25+
26+
pub fn frame(msg_type: u16, payload: &[u8]) -> Vec<u8> {
27+
let mut out = Vec::with_capacity(8 + payload.len());
28+
out.extend_from_slice(&HEADER);
29+
out.extend_from_slice(&msg_type.to_be_bytes());
30+
out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
31+
out.extend_from_slice(payload);
32+
out
33+
}
34+
35+
pub fn parse_frame(data: &[u8]) -> Result<(u16, Vec<u8>), TrezorError> {
36+
if data.len() < 8 || data[0..2] != HEADER {
37+
return Err(TrezorError::MalformedFrame);
38+
}
39+
let msg_type = u16::from_be_bytes([data[2], data[3]]);
40+
let len = u32::from_be_bytes([data[4], data[5], data[6], data[7]]) as usize;
41+
let payload = data
42+
.get(8..8 + len)
43+
.ok_or(TrezorError::MalformedFrame)?
44+
.to_vec();
45+
Ok((msg_type, payload))
46+
}
47+
48+
pub fn decode<M: Message + Default>(payload: &[u8]) -> Result<M, TrezorError> {
49+
M::decode(payload).map_err(TrezorError::Decode)
50+
}
51+
52+
fn encode<M: Message>(msg_type: MessageType, msg: &M) -> Vec<u8> {
53+
frame(msg_type as u16, &msg.encode_to_vec())
54+
}
55+
56+
pub fn initialize() -> Vec<u8> {
57+
encode(MessageType::Initialize, &mgmt::Initialize::default())
58+
}
59+
60+
pub fn get_features() -> Vec<u8> {
61+
encode(MessageType::GetFeatures, &mgmt::GetFeatures::default())
62+
}
63+
64+
pub fn button_ack() -> Vec<u8> {
65+
encode(MessageType::ButtonAck, &pb::ButtonAck::default())
66+
}
67+
68+
pub fn passphrase_ack_on_device() -> Vec<u8> {
69+
let msg = pb::PassphraseAck {
70+
on_device: Some(true),
71+
passphrase: None,
72+
..Default::default()
73+
};
74+
encode(MessageType::PassphraseAck, &msg)
75+
}
76+
77+
pub fn get_public_key(
78+
address_n: Vec<u32>,
79+
show_display: bool,
80+
script_type: btc::InputScriptType,
81+
coin_name: String,
82+
) -> Vec<u8> {
83+
let msg = btc::GetPublicKey {
84+
address_n,
85+
show_display: Some(show_display),
86+
coin_name: Some(coin_name),
87+
script_type: Some(script_type as i32),
88+
ignore_xpub_magic: Some(true),
89+
..Default::default()
90+
};
91+
encode(MessageType::GetPublicKey, &msg)
92+
}
93+
94+
pub fn get_address(
95+
address_n: Vec<u32>,
96+
show_display: bool,
97+
script_type: btc::InputScriptType,
98+
coin_name: String,
99+
) -> Vec<u8> {
100+
let msg = btc::GetAddress {
101+
address_n,
102+
show_display: Some(show_display),
103+
coin_name: Some(coin_name),
104+
script_type: Some(script_type as i32),
105+
..Default::default()
106+
};
107+
encode(MessageType::GetAddress, &msg)
108+
}

bhwi/src/trezor/error.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use crate::common;
2+
3+
#[derive(Debug, thiserror::Error)]
4+
pub enum TrezorError {
5+
#[error("protobuf decode error: {0}")]
6+
Decode(#[from] prost::DecodeError),
7+
#[error("malformed trezor message frame")]
8+
MalformedFrame,
9+
#[error("unexpected trezor message type {0} while {1}")]
10+
UnexpectedMessage(u16, &'static str),
11+
#[error("device failure: {1}")]
12+
Failure(i32, String),
13+
#[error("device is locked: {0}")]
14+
Locked(&'static str),
15+
#[error("device returned a key for the wrong network")]
16+
NetworkMismatch,
17+
#[error("device refused the operation")]
18+
ActionCancelled,
19+
#[error("unsupported command: {0}")]
20+
Unsupported(&'static str),
21+
#[error("unsupported display address: {0}")]
22+
UnsupportedDisplayAddress(&'static str),
23+
#[error("invalid input: {0}")]
24+
InvalidInput(String),
25+
}
26+
27+
impl From<TrezorError> for common::Error {
28+
fn from(e: TrezorError) -> Self {
29+
match e {
30+
TrezorError::Decode(err) => common::Error::Serialization(err.to_string()),
31+
TrezorError::MalformedFrame => {
32+
common::Error::Serialization("malformed trezor message frame".into())
33+
}
34+
TrezorError::UnexpectedMessage(t, ctx) => {
35+
common::Error::unexpected_result(t.to_be_bytes().to_vec(), format!("trezor: {ctx}"))
36+
}
37+
TrezorError::Failure(_, msg) => common::Error::Device(msg),
38+
TrezorError::Locked(ctx) => common::Error::Device(format!("device is locked: {ctx}")),
39+
TrezorError::NetworkMismatch => {
40+
common::Error::InvalidInput("device returned a key for the wrong network".into())
41+
}
42+
TrezorError::ActionCancelled => common::Error::AuthenticationRefused,
43+
TrezorError::Unsupported(s) => common::Error::InvalidInput(s.into()),
44+
TrezorError::UnsupportedDisplayAddress(s) => {
45+
common::Error::UnsupportedDisplayAddress(s.into())
46+
}
47+
TrezorError::InvalidInput(s) => common::Error::InvalidInput(s),
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)