Skip to content

Commit 2890c25

Browse files
committed
Merge branch 'op-return-support'
2 parents 1bd47c2 + e0880cf commit 2890c25

File tree

12 files changed

+411
-7
lines changed

12 files changed

+411
-7
lines changed

CHANGELOG-npm.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## 0.12.0
4+
- btc: add support for OP_RETURN outputs
5+
36
## 0.11.0
47
- Add `btcXpubs()`
58

CHANGELOG-rust.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## 0.11.0
4+
- btc: add support for OP_RETURN outputs
5+
36
## 0.10.0
47
- Add `btc_xpubs()`
58

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "bitbox-api"
33
authors = ["Marko Bencun <benma@bitbox.swiss>"]
4-
version = "0.10.0"
4+
version = "0.11.0"
55
homepage = "https://bitbox.swiss/"
66
repository = "https://github.com/BitBoxSwiss/bitbox-api-rs/"
77
readme = "README-rust.md"
@@ -75,6 +75,10 @@ required-features = ["usb", "tokio/rt", "tokio/macros"]
7575
name = "btc_sign_psbt"
7676
required-features = ["usb", "tokio/rt", "tokio/macros"]
7777

78+
[[example]]
79+
name = "btc_sign_op_return"
80+
required-features = ["usb", "tokio/rt", "tokio/macros"]
81+
7882
[[example]]
7983
name = "btc_sign_msg"
8084
required-features = ["usb", "tokio/rt", "tokio/macros"]

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ example-btc-signtx:
1010
cargo run --example btc_signtx --features=usb,tokio/rt,tokio/macros
1111
example-btc-psbt:
1212
cargo run --example btc_sign_psbt --features=usb,tokio/rt,tokio/macros
13+
example-btc-sign-op-return:
14+
cargo run --example btc_sign_op_return --features=usb,tokio/rt,tokio/macros
1315
example-btc-sign-msg:
1416
cargo run --example btc_sign_msg --features=usb,tokio/rt,tokio/macros
1517
example-btc-miniscript:

NPM_VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.11.0
1+
0.12.0

examples/btc_sign_op_return.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use std::str::FromStr;
2+
3+
use bitbox_api::{pb, Keypath};
4+
5+
use bitcoin::{
6+
bip32::{ChildNumber, DerivationPath, Fingerprint, Xpub},
7+
blockdata::script::Builder,
8+
opcodes::all,
9+
psbt::Psbt,
10+
secp256k1, transaction, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut,
11+
Witness,
12+
};
13+
use semver::VersionReq;
14+
15+
async fn connect_bitbox() -> bitbox_api::PairedBitBox<bitbox_api::runtime::TokioRuntime> {
16+
let noise_config = Box::new(bitbox_api::NoiseConfigNoCache {});
17+
let device = bitbox_api::BitBox::<bitbox_api::runtime::TokioRuntime>::from_hid_device(
18+
bitbox_api::usb::get_any_bitbox02().unwrap(),
19+
noise_config,
20+
)
21+
.await
22+
.unwrap();
23+
let pairing = device.unlock_and_pair().await.unwrap();
24+
if let Some(pairing_code) = pairing.get_pairing_code().as_ref() {
25+
println!("Pairing code\n{pairing_code}");
26+
}
27+
pairing.wait_confirm().await.unwrap()
28+
}
29+
30+
async fn build_op_return_psbt(
31+
paired: &bitbox_api::PairedBitBox<bitbox_api::runtime::TokioRuntime>,
32+
) -> Psbt {
33+
let coin = pb::BtcCoin::Tbtc;
34+
let secp = secp256k1::Secp256k1::new();
35+
36+
let fingerprint_hex = paired.root_fingerprint().await.unwrap();
37+
let fingerprint = Fingerprint::from_str(&fingerprint_hex).unwrap();
38+
let account_path: DerivationPath = "m/84'/1'/0'".parse().unwrap();
39+
let input_path: DerivationPath = "m/84'/1'/0'/0/5".parse().unwrap();
40+
let change_path: DerivationPath = "m/84'/1'/0'/1/0".parse().unwrap();
41+
42+
let account_keypath = Keypath::from(&account_path);
43+
let account_xpub = paired
44+
.btc_xpub(
45+
coin,
46+
&account_keypath,
47+
pb::btc_pub_request::XPubType::Tpub,
48+
false,
49+
)
50+
.await
51+
.unwrap();
52+
53+
let account_xpub = Xpub::from_str(&account_xpub).unwrap();
54+
55+
let input_pub = account_xpub
56+
.derive_pub(
57+
&secp,
58+
&[
59+
ChildNumber::from_normal_idx(0).unwrap(),
60+
ChildNumber::from_normal_idx(5).unwrap(),
61+
],
62+
)
63+
.unwrap()
64+
.to_pub();
65+
let change_pub = account_xpub
66+
.derive_pub(
67+
&secp,
68+
&[
69+
ChildNumber::from_normal_idx(1).unwrap(),
70+
ChildNumber::from_normal_idx(0).unwrap(),
71+
],
72+
)
73+
.unwrap()
74+
.to_pub();
75+
76+
let prev_tx = Transaction {
77+
version: transaction::Version::TWO,
78+
lock_time: bitcoin::absolute::LockTime::ZERO,
79+
input: vec![TxIn {
80+
previous_output: "3131313131313131313131313131313131313131313131313131313131313131:0"
81+
.parse()
82+
.unwrap(),
83+
script_sig: ScriptBuf::new(),
84+
sequence: Sequence(0xFFFFFFFF),
85+
witness: Witness::default(),
86+
}],
87+
output: vec![TxOut {
88+
value: Amount::from_sat(50_000_000),
89+
script_pubkey: ScriptBuf::new_p2wpkh(&input_pub.wpubkey_hash()),
90+
}],
91+
};
92+
93+
let op_return_data = b"hello world";
94+
let op_return_script = Builder::new()
95+
.push_opcode(all::OP_RETURN)
96+
.push_slice(op_return_data)
97+
.into_script();
98+
99+
let tx = Transaction {
100+
version: transaction::Version::TWO,
101+
lock_time: bitcoin::absolute::LockTime::ZERO,
102+
input: vec![TxIn {
103+
previous_output: OutPoint {
104+
txid: prev_tx.compute_txid(),
105+
vout: 0,
106+
},
107+
script_sig: ScriptBuf::new(),
108+
sequence: Sequence(0xFFFFFFFF),
109+
witness: Witness::default(),
110+
}],
111+
output: vec![
112+
TxOut {
113+
value: Amount::from_sat(49_000_000),
114+
script_pubkey: ScriptBuf::new_p2wpkh(&change_pub.wpubkey_hash()),
115+
},
116+
TxOut {
117+
value: Amount::from_sat(0),
118+
script_pubkey: op_return_script,
119+
},
120+
],
121+
};
122+
123+
let mut psbt = Psbt::from_unsigned_tx(tx).unwrap();
124+
psbt.inputs[0].non_witness_utxo = Some(prev_tx.clone());
125+
psbt.inputs[0].witness_utxo = Some(prev_tx.output[0].clone());
126+
psbt.inputs[0]
127+
.bip32_derivation
128+
.insert(input_pub.0, (fingerprint, input_path.clone()));
129+
130+
psbt.outputs[0]
131+
.bip32_derivation
132+
.insert(change_pub.0, (fingerprint, change_path.clone()));
133+
134+
psbt
135+
}
136+
137+
#[tokio::main(flavor = "current_thread")]
138+
async fn main() {
139+
let paired = connect_bitbox().await;
140+
141+
let firmware_version = paired.version();
142+
if !VersionReq::parse(">=9.24.0")
143+
.unwrap()
144+
.matches(firmware_version)
145+
{
146+
eprintln!(
147+
"Connected firmware {firmware_version} does not support OP_RETURN outputs (requires >=9.24.0)."
148+
);
149+
return;
150+
}
151+
152+
let mut psbt = build_op_return_psbt(&paired).await;
153+
154+
paired
155+
.btc_sign_psbt(
156+
pb::BtcCoin::Tbtc,
157+
&mut psbt,
158+
None,
159+
pb::btc_sign_init_request::FormatUnit::Default,
160+
)
161+
.await
162+
.unwrap();
163+
}

messages/btc.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ enum BTCOutputType {
175175
P2WPKH = 3;
176176
P2WSH = 4;
177177
P2TR = 5;
178+
OP_RETURN = 6;
178179
}
179180

180181
message BTCSignOutputRequest {

sandbox/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)