bitbox_api/
lib.rs

1//! Rust BitBox hardware wallet client library.
2
3#[cfg(all(feature = "wasm", feature = "multithreaded"))]
4compile_error!("wasm and multithreaded can't both be active");
5
6pub mod btc;
7pub mod cardano;
8pub mod error;
9pub mod eth;
10mod noise;
11pub mod runtime;
12#[cfg(feature = "simulator")]
13pub mod simulator;
14#[cfg(feature = "usb")]
15pub mod usb;
16#[cfg(feature = "wasm")]
17pub mod wasm;
18
19mod antiklepto;
20mod communication;
21mod constants;
22mod keypath;
23mod u2fframing;
24mod util;
25
26/// BitBox protobuf messages.
27#[allow(clippy::all)]
28pub mod pb {
29    include!("./shiftcrypto.bitbox02.rs");
30}
31
32use crate::error::{BitBoxError, Error};
33
34use pb::request::Request;
35use pb::response::Response;
36use runtime::Runtime;
37
38use noise_protocol::DH;
39use prost::Message;
40
41use std::sync::Mutex;
42
43pub use keypath::Keypath;
44pub use noise::PersistedNoiseConfig;
45pub use noise::{ConfigError, NoiseConfig, NoiseConfigData, NoiseConfigNoCache};
46pub use util::Threading;
47
48use communication::HwwCommunication;
49
50pub use communication::Product;
51
52const OP_I_CAN_HAS_HANDSHAEK: u8 = b'h';
53const OP_HER_COMEZ_TEH_HANDSHAEK: u8 = b'H';
54const OP_I_CAN_HAS_PAIRIN_VERIFICASHUN: u8 = b'v';
55const OP_NOISE_MSG: u8 = b'n';
56const _OP_ATTESTATION: u8 = b'a';
57const OP_UNLOCK: u8 = b'u';
58
59const RESPONSE_SUCCESS: u8 = 0x00;
60
61type Cipher = noise_rust_crypto::ChaCha20Poly1305;
62type HandshakeState =
63    noise_protocol::HandshakeState<noise_rust_crypto::X25519, Cipher, noise_rust_crypto::Sha256>;
64
65type CipherState = noise_protocol::CipherState<Cipher>;
66
67/// BitBox client. See `from_hid_device()`.
68pub struct BitBox<R: Runtime> {
69    communication: communication::HwwCommunication<R>,
70    noise_config: Box<dyn NoiseConfig>,
71}
72
73pub type PairingCode = String;
74
75impl<R: Runtime> BitBox<R> {
76    async fn from(
77        device: Box<dyn communication::ReadWrite>,
78        noise_config: Box<dyn NoiseConfig>,
79    ) -> Result<BitBox<R>, Error> {
80        Ok(BitBox {
81            communication: HwwCommunication::from(device).await?,
82            noise_config,
83        })
84    }
85
86    /// Creates a new BitBox instance. The provided noise config determines how the pairing
87    /// information is persisted. Use `usb::get_any_bitbox02()` to find a BitBox02 HID device.
88    ///
89    /// Use `bitbox_api::PersistedNoiseConfig::new(...)` to persist the pairing in a JSON file
90    /// (`serde` feature required) or provide your own implementation of the `NoiseConfig` trait.
91    #[cfg(feature = "usb")]
92    pub async fn from_hid_device(
93        device: hidapi::HidDevice,
94        noise_config: Box<dyn NoiseConfig>,
95    ) -> Result<BitBox<R>, Error> {
96        let comm = Box::new(communication::U2fHidCommunication::from(
97            Box::new(crate::usb::HidDevice::new(device)),
98            communication::FIRMWARE_CMD,
99        ));
100        Self::from(comm, noise_config).await
101    }
102
103    #[cfg(feature = "simulator")]
104    pub async fn from_simulator(
105        endpoint: Option<&str>,
106        noise_config: Box<dyn NoiseConfig>,
107    ) -> Result<BitBox<R>, Error> {
108        let comm = Box::new(communication::U2fHidCommunication::from(
109            crate::simulator::try_connect::<R>(endpoint).await?,
110            communication::FIRMWARE_CMD,
111        ));
112        Self::from(comm, noise_config).await
113    }
114
115    /// Invokes the device unlock and pairing.
116    pub async fn unlock_and_pair(self) -> Result<PairingBitBox<R>, Error> {
117        self.communication
118            .query(&[OP_UNLOCK])
119            .await
120            .or(Err(Error::Unknown))?;
121        self.pair().await
122    }
123
124    async fn handshake_query(&self, msg: &[u8]) -> Result<Vec<u8>, Error> {
125        let mut framed_msg = vec![OP_HER_COMEZ_TEH_HANDSHAEK];
126        framed_msg.extend_from_slice(msg);
127        let mut response = self.communication.query(&framed_msg).await?;
128        if response.is_empty() || response[0] != RESPONSE_SUCCESS {
129            return Err(Error::Noise);
130        }
131        Ok(response.split_off(1))
132    }
133
134    async fn pair(self) -> Result<PairingBitBox<R>, Error> {
135        let mut config_data = self.noise_config.read_config()?;
136        let host_static_key = match config_data.get_app_static_privkey() {
137            Some(k) => noise_rust_crypto::sensitive::Sensitive::from(k),
138            None => {
139                let k = noise_rust_crypto::X25519::genkey();
140                config_data.set_app_static_privkey(&k[..])?;
141                self.noise_config.store_config(&config_data)?;
142                k
143            }
144        };
145        let mut host = HandshakeState::new(
146            noise_protocol::patterns::noise_xx(),
147            true,
148            b"Noise_XX_25519_ChaChaPoly_SHA256",
149            Some(host_static_key),
150            None,
151            None,
152            None,
153        );
154
155        if self
156            .communication
157            .query(&[OP_I_CAN_HAS_HANDSHAEK])
158            .await?
159            .as_slice()
160            != [RESPONSE_SUCCESS]
161        {
162            return Err(Error::Noise);
163        }
164
165        let host_handshake_1 = host.write_message_vec(b"").or(Err(Error::Noise))?;
166        let bb02_handshake_1 = self.handshake_query(&host_handshake_1).await?;
167
168        host.read_message_vec(&bb02_handshake_1)
169            .or(Err(Error::Noise))?;
170        let host_handshake_2 = host.write_message_vec(b"").or(Err(Error::Noise))?;
171
172        let bb02_handshake_2 = self.handshake_query(&host_handshake_2).await?;
173        let remote_static_pubkey = host.get_rs().ok_or(Error::Noise)?;
174        let pairing_verfication_required_by_app = !self
175            .noise_config
176            .read_config()?
177            .contains_device_static_pubkey(&remote_static_pubkey);
178        let pairing_verification_required_by_device = bb02_handshake_2.as_slice() == [0x01];
179        if pairing_verfication_required_by_app || pairing_verification_required_by_device {
180            let format_hash = |h| {
181                let encoded = base32::encode(base32::Alphabet::RFC4648 { padding: true }, h);
182                format!(
183                    "{} {}\n{} {}",
184                    &encoded[0..5],
185                    &encoded[5..10],
186                    &encoded[10..15],
187                    &encoded[15..20]
188                )
189            };
190            let handshake_hash: [u8; 32] = host.get_hash().try_into().or(Err(Error::Noise))?;
191            let pairing_code = format_hash(&handshake_hash);
192
193            Ok(PairingBitBox::from(
194                self.communication,
195                host,
196                self.noise_config,
197                Some(pairing_code),
198            ))
199        } else {
200            Ok(PairingBitBox::from(
201                self.communication,
202                host,
203                self.noise_config,
204                None,
205            ))
206        }
207    }
208}
209
210/// BitBox in the pairing state. Use `get_pairing_code()` to display the pairing code to the user
211/// and `wait_confirm()` to proceed to the paired state.
212pub struct PairingBitBox<R: Runtime> {
213    communication: communication::HwwCommunication<R>,
214    host: HandshakeState,
215    noise_config: Box<dyn NoiseConfig>,
216    pairing_code: Option<String>,
217}
218
219impl<R: Runtime> PairingBitBox<R> {
220    fn from(
221        communication: communication::HwwCommunication<R>,
222        host: HandshakeState,
223        noise_config: Box<dyn NoiseConfig>,
224        pairing_code: Option<String>,
225    ) -> Self {
226        PairingBitBox {
227            communication,
228            host,
229            noise_config,
230            pairing_code,
231        }
232    }
233
234    /// If a pairing code confirmation is required, this returns the pairing code. You must display
235    /// it to the user and then call `wait_confirm()` to wait until the user confirms the code on
236    /// the BitBox.
237    ///
238    /// If the BitBox was paired before and the pairing was persisted, the pairing step is
239    /// skipped. In this case, `None` is returned. Also in this case, call `wait_confirm()` to
240    /// establish the encrypted connection.
241    pub fn get_pairing_code(&self) -> Option<String> {
242        self.pairing_code.clone()
243    }
244
245    /// Proceed to the paired state.
246    pub async fn wait_confirm(self) -> Result<PairedBitBox<R>, Error> {
247        if self.pairing_code.is_some() {
248            let response = self
249                .communication
250                .query(&[OP_I_CAN_HAS_PAIRIN_VERIFICASHUN])
251                .await?;
252            if response.as_slice() != [RESPONSE_SUCCESS] {
253                return Err(Error::NoisePairingRejected);
254            }
255
256            let remote_static_pubkey = self.host.get_rs().ok_or(Error::Noise)?;
257            let mut config_data = self.noise_config.read_config()?;
258            config_data.add_device_static_pubkey(&remote_static_pubkey);
259            self.noise_config.store_config(&config_data)?;
260        }
261        Ok(PairedBitBox::from(self.communication, self.host))
262    }
263}
264
265/// Paired BitBox. This is where you can invoke most API functions like getting xpubs, displaying
266/// receive addresses, etc.
267pub struct PairedBitBox<R: Runtime> {
268    communication: communication::HwwCommunication<R>,
269    noise_send: Mutex<CipherState>,
270    noise_recv: Mutex<CipherState>,
271}
272
273impl<R: Runtime> PairedBitBox<R> {
274    fn from(communication: communication::HwwCommunication<R>, host: HandshakeState) -> Self {
275        let (send, recv) = host.get_ciphers();
276        PairedBitBox {
277            communication,
278            noise_send: Mutex::new(send),
279            noise_recv: Mutex::new(recv),
280        }
281    }
282
283    fn validate_version(&self, comparison: &'static str) -> Result<(), Error> {
284        if semver::VersionReq::parse(comparison)
285            .or(Err(Error::Unknown))?
286            .matches(&self.communication.info.version)
287        {
288            Ok(())
289        } else {
290            Err(Error::Version(comparison))
291        }
292    }
293
294    async fn query_proto(&self, request: Request) -> Result<Response, Error> {
295        let mut encrypted = vec![OP_NOISE_MSG];
296        encrypted.extend_from_slice({
297            let mut send = self.noise_send.lock().unwrap();
298            let proto_msg = pb::Request {
299                request: Some(request),
300            };
301            &send.encrypt_vec(&proto_msg.encode_to_vec())
302        });
303
304        let response = self.communication.query(&encrypted).await?;
305        if response.is_empty() || response[0] != RESPONSE_SUCCESS {
306            return Err(Error::UnexpectedResponse);
307        }
308        let decrypted = {
309            let mut recv = self.noise_recv.lock().unwrap();
310            recv.decrypt_vec(&response[1..]).or(Err(Error::Noise))?
311        };
312        match pb::Response::decode(&decrypted[..]) {
313            Ok(pb::Response {
314                response: Some(Response::Error(pb::Error { code, .. })),
315            }) => match code {
316                101 => Err(BitBoxError::InvalidInput.into()),
317                102 => Err(BitBoxError::Memory.into()),
318                103 => Err(BitBoxError::Generic.into()),
319                104 => Err(BitBoxError::UserAbort.into()),
320                105 => Err(BitBoxError::InvalidState.into()),
321                106 => Err(BitBoxError::Disabled.into()),
322                107 => Err(BitBoxError::Duplicate.into()),
323                108 => Err(BitBoxError::NoiseEncrypt.into()),
324                109 => Err(BitBoxError::NoiseDecrypt.into()),
325                _ => Err(BitBoxError::Unknown.into()),
326            },
327            Ok(pb::Response {
328                response: Some(response),
329            }) => Ok(response),
330            _ => Err(Error::ProtobufDecode),
331        }
332    }
333
334    pub async fn device_info(&self) -> Result<pb::DeviceInfoResponse, Error> {
335        match self
336            .query_proto(Request::DeviceInfo(pb::DeviceInfoRequest {}))
337            .await?
338        {
339            Response::DeviceInfo(di) => Ok(di),
340            _ => Err(Error::UnexpectedResponse),
341        }
342    }
343
344    /// Returns which product we are connected to.
345    pub fn product(&self) -> Product {
346        self.communication.info.product
347    }
348
349    /// Returns the firmware version.
350    pub fn version(&self) -> &semver::Version {
351        &self.communication.info.version
352    }
353
354    /// Returns the hex-encoded 4-byte root fingerprint.
355    pub async fn root_fingerprint(&self) -> Result<String, Error> {
356        match self
357            .query_proto(Request::Fingerprint(pb::RootFingerprintRequest {}))
358            .await?
359        {
360            Response::Fingerprint(pb::RootFingerprintResponse { fingerprint }) => {
361                Ok(hex::encode(fingerprint))
362            }
363            _ => Err(Error::UnexpectedResponse),
364        }
365    }
366
367    /// Show recovery words on the Bitbox.
368    pub async fn show_mnemonic(&self) -> Result<(), Error> {
369        match self
370            .query_proto(Request::ShowMnemonic(pb::ShowMnemonicRequest {}))
371            .await?
372        {
373            Response::Success(_) => Ok(()),
374            _ => Err(Error::UnexpectedResponse),
375        }
376    }
377
378    /// Restore from recovery words on the Bitbox.
379    pub async fn restore_from_mnemonic(&self) -> Result<(), Error> {
380        let now = std::time::SystemTime::now();
381        let duration_since_epoch = now.duration_since(std::time::UNIX_EPOCH).unwrap();
382        match self
383            .query_proto(Request::RestoreFromMnemonic(
384                pb::RestoreFromMnemonicRequest {
385                    timestamp: duration_since_epoch.as_secs() as u32,
386                    timezone_offset: chrono::Local::now().offset().local_minus_utc(),
387                },
388            ))
389            .await?
390        {
391            Response::Success(_) => Ok(()),
392            _ => Err(Error::UnexpectedResponse),
393        }
394    }
395
396    /// Invokes the BIP85-BIP39 workflow on the device, letting the user select the number of words
397    /// (12, 28, 24) and an index and display a derived BIP-39 mnemonic.
398    pub async fn bip85_app_bip39(&self) -> Result<(), Error> {
399        self.validate_version(">=9.17.0")?;
400        match self
401            .query_proto(Request::Bip85(pb::Bip85Request {
402                app: Some(pb::bip85_request::App::Bip39(())),
403            }))
404            .await?
405        {
406            Response::Bip85(pb::Bip85Response {
407                app: Some(pb::bip85_response::App::Bip39(())),
408            }) => Ok(()),
409            _ => Err(Error::UnexpectedResponse),
410        }
411    }
412}