Skip to content

Commit 1fed8f8

Browse files
committed
chore(example): add example using async-hwi
1 parent 8e2eeb1 commit 1fed8f8

File tree

4 files changed

+219
-1
lines changed

4 files changed

+219
-1
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ members = [
55
"examples/example_wallet_electrum",
66
"examples/example_wallet_esplora_blocking",
77
"examples/example_wallet_esplora_async",
8-
"examples/example_wallet_rpc",
8+
"examples/example_wallet_rpc",
9+
"examples/example_wallet_hwi_signer",
910
]
1011

1112
[workspace.package]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "example_wallet_hwi_signer"
3+
version = "0.1.0"
4+
edition = "2021"
5+
authors.workspace = true
6+
7+
[features]
8+
simulator = ["bitbox-api", "bitbox-api/simulator"]
9+
10+
[dependencies]
11+
bdk_wallet = { path = "../../wallet", features = ["file_store"] }
12+
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
13+
bitbox-api = {version = "0.6.0", features = ["tokio"], optional = true}
14+
async-hwi = { version = "0.0.27", features = ["bitbox"]}
15+
anyhow = "1"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Example signing with HWI Interface
2+
3+
4+
## Requirements
5+
6+
sudo apt install libudev-dev
7+
8+
## Build and run
9+
10+
`$ cargo run --bin example_wallet_hwi_signer`
11+
12+
## Running with simulator
13+
14+
Download a simulator at `https://github.com/BitBoxSwiss/bitbox02-firmware/releases/`.
15+
16+
Run the simulator and then run the example with `--features=simulator` enabled.
17+
18+
```sh
19+
20+
curl https://github.com/BitBoxSwiss/bitbox02-firmware/releases/download/firmware%2Fv9.19.0/bitbox02-multi-v9.19.0-simulator1.0.0-linux-amd64
21+
22+
./bitbox02-multi-v9.19.0-simulator1.0.0-linux-amd64
23+
24+
cargo run --bin example_wallet_hwi_signer --features simulator
25+
```
26+
27+
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
use async_hwi::bitbox::api::runtime::TokioRuntime;
2+
use async_hwi::bitbox::api::{usb, BitBox};
3+
use async_hwi::bitbox::NoiseConfigNoCache;
4+
use bdk_wallet::bitcoin::absolute::LockTime;
5+
use bdk_wallet::bitcoin::hashes::Hash;
6+
use bdk_wallet::bitcoin::{
7+
absolute, transaction, Amount, BlockHash, FeeRate, Network, OutPoint, Transaction, TxIn, TxOut,
8+
};
9+
use bdk_wallet::chain::{BlockId, TxUpdate};
10+
use bdk_wallet::file_store::Store;
11+
use bdk_wallet::Wallet;
12+
use bdk_wallet::{KeychainKind, Update};
13+
use std::sync::Arc;
14+
15+
use async_hwi::{bitbox::BitBox02, HWI};
16+
17+
const DB_MAGIC: &str = "bdk_wallet_hwi_signer";
18+
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
19+
const NETWORK: Network = Network::Regtest;
20+
const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdfCLpvozodGytD3gRUa1M5WQz4kNuDZVf1inhcsSHXRpyLWN3k3Qy3nucrzz5hw2iZiEs6spehpee2WxqfSi31ByRJEu4rZ/84h/1h/0h/0/*)";
21+
const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdfCLpvozodGytD3gRUa1M5WQz4kNuDZVf1inhcsSHXRpyLWN3k3Qy3nucrzz5hw2iZiEs6spehpee2WxqfSi31ByRJEu4rZ/84h/1h/0h/1/*)";
22+
23+
pub fn new_tx(locktime: u32) -> Transaction {
24+
Transaction {
25+
version: transaction::Version::ONE,
26+
lock_time: absolute::LockTime::from_consensus(locktime),
27+
input: vec![],
28+
output: vec![],
29+
}
30+
}
31+
32+
pub fn insert_checkpoint(wallet: &mut Wallet, block: BlockId) {
33+
let mut cp = wallet.latest_checkpoint();
34+
cp = cp.insert(block);
35+
wallet
36+
.apply_update(Update {
37+
chain: Some(cp),
38+
..Default::default()
39+
})
40+
.unwrap();
41+
}
42+
43+
fn feed_wallet(wallet: &mut Wallet) {
44+
let sendto_address = wallet.next_unused_address(KeychainKind::External);
45+
let change_addr = wallet.next_unused_address(KeychainKind::Internal);
46+
47+
let tx0 = Transaction {
48+
output: vec![TxOut {
49+
value: Amount::from_sat(76_000),
50+
script_pubkey: change_addr.script_pubkey(),
51+
}],
52+
..new_tx(0)
53+
};
54+
55+
let tx1 = Transaction {
56+
input: vec![TxIn {
57+
previous_output: OutPoint {
58+
txid: tx0.compute_txid(),
59+
vout: 0,
60+
},
61+
..Default::default()
62+
}],
63+
output: vec![
64+
TxOut {
65+
value: Amount::from_sat(50_000),
66+
script_pubkey: sendto_address.script_pubkey(),
67+
},
68+
TxOut {
69+
value: Amount::from_sat(25_000),
70+
script_pubkey: change_addr.script_pubkey(),
71+
},
72+
],
73+
..new_tx(0)
74+
};
75+
76+
insert_checkpoint(
77+
wallet,
78+
BlockId {
79+
height: 42,
80+
hash: BlockHash::all_zeros(),
81+
},
82+
);
83+
insert_checkpoint(
84+
wallet,
85+
BlockId {
86+
height: 1_000,
87+
hash: BlockHash::all_zeros(),
88+
},
89+
);
90+
91+
insert_checkpoint(
92+
wallet,
93+
BlockId {
94+
height: 2_000,
95+
hash: BlockHash::all_zeros(),
96+
},
97+
);
98+
99+
wallet
100+
.apply_update(Update {
101+
tx_update: TxUpdate {
102+
txs: vec![Arc::new(tx0), Arc::new(tx1)],
103+
..Default::default()
104+
},
105+
..Default::default()
106+
})
107+
.unwrap();
108+
}
109+
110+
#[tokio::main]
111+
async fn main() -> Result<(), anyhow::Error> {
112+
let db_path = "bdk-signer-example.db";
113+
let mut db = Store::<bdk_wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
114+
115+
let wallet_opt = Wallet::load()
116+
.descriptor(KeychainKind::External, Some(EXTERNAL_DESC))
117+
.descriptor(KeychainKind::Internal, Some(INTERNAL_DESC))
118+
.extract_keys()
119+
.check_network(NETWORK)
120+
.load_wallet(&mut db)?;
121+
122+
let mut wallet = match wallet_opt {
123+
Some(wallet) => wallet,
124+
None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC)
125+
.network(NETWORK)
126+
.create_wallet(&mut db)?,
127+
};
128+
129+
// Pairing with Bitbox connected Bitbox device
130+
let noise_config = Box::new(NoiseConfigNoCache {});
131+
132+
let bitbox = {
133+
#[cfg(feature = "simulator")]
134+
{
135+
bitbox_api::BitBox::<TokioRuntime>::from_simulator(None, noise_config).await?
136+
}
137+
138+
#[cfg(not(feature = "simulator"))]
139+
{
140+
BitBox::<TokioRuntime>::from_hid_device(usb::get_any_bitbox02().unwrap(), noise_config)
141+
.await?
142+
}
143+
};
144+
145+
let pairing_device = bitbox.unlock_and_pair().await?;
146+
let paired_device = pairing_device.wait_confirm().await?;
147+
148+
if let Ok(_) = paired_device.restore_from_mnemonic().await {
149+
println!("Initializing device with mnemonic...");
150+
} else {
151+
println!("Device already initialized proceeding...");
152+
}
153+
154+
let bb = BitBox02::from(paired_device);
155+
let bb = bb.with_network(NETWORK);
156+
157+
let receiving_wallet = wallet.next_unused_address(KeychainKind::External);
158+
159+
feed_wallet(&mut wallet);
160+
161+
let mut tx_builder = wallet.build_tx();
162+
163+
tx_builder
164+
.add_recipient(receiving_wallet.script_pubkey(), SEND_AMOUNT)
165+
.fee_rate(FeeRate::from_sat_per_vb(2).unwrap())
166+
.nlocktime(LockTime::from_height(0).unwrap());
167+
168+
let mut psbt = tx_builder.finish()?;
169+
170+
// Sign with the connected bitbox or any hardware device
171+
bb.sign_tx(&mut psbt).await?;
172+
173+
println!("Signing with bitbox done");
174+
Ok(())
175+
}

0 commit comments

Comments
 (0)