-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
rust: add ability to connect to BitBox02 simulator
Beginning of adding useful tests for this library.
- Loading branch information
Showing
11 changed files
with
241 additions
and
9 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
[package] | ||
name = "bitbox-api" | ||
authors = ["Marko Bencun <[email protected]>"] | ||
version = "0.3.1" | ||
version = "0.4.0" | ||
homepage = "https://bitbox.swiss/" | ||
repository = "https://github.com/BitBoxSwiss/bitbox-api-rs/" | ||
readme = "README-rust.md" | ||
|
@@ -43,7 +43,9 @@ wasm-bindgen-futures = { version ="0.4.42", optional = true } | |
web-sys = { version = "0.3.64", features = ["Storage", "Window"], optional = true } | ||
|
||
[dev-dependencies] | ||
async-trait = "0.1.68" | ||
wasm-bindgen-test = "0.3.42" | ||
tokio = { version = "1", features = ["time", "macros", "rt"] } | ||
|
||
[build-dependencies] | ||
prost-build = { version = "0.11" } | ||
|
@@ -91,6 +93,7 @@ lto = true | |
# This may or may not cause trouble on macOS, see: https://github.com/libusb/hidapi/issues/503 | ||
multithreaded = [] | ||
usb = ["dep:hidapi"] | ||
simulator = [] | ||
wasm = [ | ||
"dep:enum-assoc", | ||
"dep:js-sys", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
#[cfg(any(feature = "wasm", feature = "usb"))] | ||
pub const VENDOR_ID: u16 = 0x03eb; | ||
#[cfg(any(feature = "wasm", feature = "usb"))] | ||
pub const PRODUCT_ID: u16 = 0x2403; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
use async_trait::async_trait; | ||
use std::io::{Read, Write}; | ||
use std::net::TcpStream; | ||
|
||
use std::sync::Mutex; | ||
|
||
use super::communication::{Error as CommunicationError, ReadWrite}; | ||
use super::runtime::Runtime; | ||
|
||
const DEFAULT_ENDPOINT: &str = "127.0.0.1:15423"; | ||
|
||
pub struct TcpClient { | ||
stream: Mutex<TcpStream>, | ||
} | ||
|
||
impl TcpClient { | ||
fn new(address: &str) -> Result<Self, std::io::Error> { | ||
let stream = TcpStream::connect(address)?; | ||
Ok(Self { | ||
stream: Mutex::new(stream), | ||
}) | ||
} | ||
} | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
pub enum Error { | ||
#[error("connection error")] | ||
Connect, | ||
} | ||
|
||
/// Connect to a running simulator at this endpoint. Endpoint defaults to `127.0.0.1:15423`. | ||
/// This tries to connect repeatedly for up to about 2 seconds. | ||
pub async fn try_connect<R: Runtime>(endpoint: Option<&str>) -> Result<Box<TcpClient>, Error> { | ||
for _ in 0..200 { | ||
match TcpClient::new(endpoint.unwrap_or(DEFAULT_ENDPOINT)) { | ||
Ok(client) => return Ok(Box::new(client)), | ||
Err(_) => R::sleep(std::time::Duration::from_millis(10)).await, | ||
} | ||
} | ||
Err(Error::Connect) | ||
} | ||
|
||
impl crate::util::Threading for TcpClient {} | ||
|
||
#[async_trait(?Send)] | ||
impl ReadWrite for TcpClient { | ||
fn write(&self, msg: &[u8]) -> Result<usize, CommunicationError> { | ||
let mut stream = self.stream.lock().unwrap(); | ||
stream.write(msg).map_err(|_| CommunicationError::Write) | ||
} | ||
|
||
async fn read(&self) -> Result<Vec<u8>, CommunicationError> { | ||
let mut stream = self.stream.lock().unwrap(); | ||
|
||
let mut buffer = vec![0; 64]; | ||
let n = stream | ||
.read(&mut buffer) | ||
.map_err(|_| CommunicationError::Read)?; | ||
buffer.truncate(n); | ||
Ok(buffer) | ||
} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
#![cfg(feature = "simulator")] | ||
|
||
#[cfg(not(feature = "tokio"))] | ||
compile_error!("Enable the tokio feature to run simulator tests"); | ||
|
||
use bitcoin::hashes::Hash; | ||
use std::process::Command; | ||
use std::str::FromStr; | ||
|
||
use bitbox_api::pb; | ||
|
||
type PairedBitBox = bitbox_api::PairedBitBox<bitbox_api::runtime::TokioRuntime>; | ||
|
||
async fn test_btc(bitbox: &PairedBitBox) { | ||
// btc_xpub | ||
{ | ||
let xpub = bitbox | ||
.btc_xpub( | ||
pb::BtcCoin::Tbtc, | ||
&"m/49'/1'/0'".try_into().unwrap(), | ||
pb::btc_pub_request::XPubType::Ypub, | ||
false, | ||
) | ||
.await | ||
.unwrap(); | ||
assert_eq!( | ||
xpub.as_str(), | ||
"ypub6WqXiL3fbDK5QNPe3hN4uSVkEvuE8wXoNCcecgggSuKVpU3Kc4fTvhuLgUhtnbAdaTb9gpz5PQdvzcsKPTLgW2CPkF5ZNRzQeKFT4NSc1xN", | ||
); | ||
} | ||
// btc_address | ||
{ | ||
let address = bitbox | ||
.btc_address( | ||
pb::BtcCoin::Tbtc, | ||
&"m/84'/1'/0'/1/10".try_into().unwrap(), | ||
&bitbox_api::btc::make_script_config_simple( | ||
pb::btc_script_config::SimpleType::P2wpkh, | ||
), | ||
false, | ||
) | ||
.await | ||
.unwrap(); | ||
assert_eq!( | ||
address.as_str(), | ||
"tb1qq064dxjgl9h9wzgsmzy6t6306qew42w9ka02u3" | ||
); | ||
} | ||
// btc_sign_message | ||
{ | ||
let keypath = bitbox_api::Keypath::try_from("m/49'/0'/0'/0/10").unwrap(); | ||
|
||
let xpub_str = bitbox | ||
.btc_xpub( | ||
pb::BtcCoin::Btc, | ||
&"m/49'/0'/0'".try_into().unwrap(), | ||
pb::btc_pub_request::XPubType::Xpub, | ||
false, | ||
) | ||
.await | ||
.unwrap(); | ||
let secp = bitcoin::secp256k1::Secp256k1::new(); | ||
let pubkey = bitcoin::bip32::Xpub::from_str(&xpub_str) | ||
.unwrap() | ||
.derive_pub( | ||
&secp, | ||
&"m/0/10".parse::<bitcoin::bip32::DerivationPath>().unwrap(), | ||
) | ||
.unwrap() | ||
.to_pub() | ||
.inner; | ||
|
||
let result = bitbox | ||
.btc_sign_message( | ||
pb::BtcCoin::Btc, | ||
pb::BtcScriptConfigWithKeypath { | ||
script_config: Some(bitbox_api::btc::make_script_config_simple( | ||
pb::btc_script_config::SimpleType::P2wpkhP2sh, | ||
)), | ||
keypath: keypath.to_vec(), | ||
}, | ||
b"message", | ||
) | ||
.await | ||
.unwrap(); | ||
|
||
pubkey | ||
.verify( | ||
&secp, | ||
&bitcoin::secp256k1::Message::from_digest( | ||
bitcoin::hashes::sha256d::Hash::hash( | ||
b"\x18Bitcoin Signed Message:\n\x07message", | ||
) | ||
.to_byte_array(), | ||
), | ||
&bitcoin::secp256k1::ecdsa::Signature::from_compact(&result.sig).unwrap(), | ||
) | ||
.unwrap(); | ||
} | ||
} | ||
|
||
#[tokio::test] | ||
async fn test_device() { | ||
let _server = Command::new("./tests/simulator") | ||
.spawn() | ||
.expect("failed to start server"); | ||
|
||
let noise_config = Box::new(bitbox_api::NoiseConfigNoCache {}); | ||
let bitbox = | ||
bitbox_api::BitBox::<bitbox_api::runtime::TokioRuntime>::from_simulator(None, noise_config) | ||
.await | ||
.unwrap(); | ||
let pairing_bitbox = bitbox.unlock_and_pair().await.unwrap(); | ||
let paired_bitbox = pairing_bitbox.wait_confirm().await.unwrap(); | ||
|
||
let device_info = paired_bitbox.device_info().await.unwrap(); | ||
|
||
assert_eq!(device_info.name, "My BitBox"); | ||
assert_eq!(paired_bitbox.product(), bitbox_api::Product::BitBox02Multi); | ||
|
||
assert!(paired_bitbox.restore_from_mnemonic().await.is_ok()); | ||
|
||
// --- Tests that run on the initialized/seeded device follow. | ||
// --- The simulator is initialized with the following mnemonic: | ||
// --- boring mistake dish oyster truth pigeon viable emerge sort crash wire portion cannon couple enact box walk height pull today solid off enable tide | ||
|
||
assert_eq!( | ||
paired_bitbox.root_fingerprint().await.unwrap().as_str(), | ||
"4c00739d" | ||
); | ||
|
||
assert!(paired_bitbox.show_mnemonic().await.is_ok()); | ||
|
||
test_btc(&paired_bitbox).await; | ||
} |