bitbox_api/
lib.rs

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