Skip to content

Commit 565159b

Browse files
committed
Merge branch 'op_return'
2 parents aaa5954 + 906a560 commit 565159b

File tree

11 files changed

+305
-86
lines changed

11 files changed

+305
-86
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
1414
- simulator: simulate a Nova device
1515
- Add API call to fetch multiple xpubs at once
1616
- Add the option for the simulator to write its memory to file.
17+
- Bitcoin: add support for OP_RETURN outputs
1718

1819
### 9.23.2
1920
- Improve touch sensor reliability when the sdcard is inserted

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 {

py/bitbox02/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
# 7.1.0
66
- Add `btc_xpubs()` to fetch multiple xpubs at once
7+
- Bitcoin: add support for OP_RETURN outputs
78

89
# 7.0.0
910
- get_info: add optional device initialized boolean to returned tuple

py/bitbox02/bitbox02/bitbox02/bitbox02.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,16 @@ def btc_sign(
467467
# Attaching output info supported since v9.22.0.
468468
self._require_atleast(semver.VersionInfo(9, 22, 0))
469469

470+
if any(
471+
map(
472+
lambda output: isinstance(output, BTCOutputExternal)
473+
and output.type == btc.BTCOutputType.OP_RETURN,
474+
outputs,
475+
)
476+
):
477+
# OP_RETURN supported sice v9.24.0
478+
self._require_atleast(semver.VersionInfo(9, 24, 0))
479+
470480
supports_antiklepto = self.version >= semver.VersionInfo(9, 4, 0)
471481

472482
sigs: List[Tuple[int, bytes]] = []

py/bitbox02/bitbox02/communication/generated/btc_pb2.py

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class _BTCOutputTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
7070
P2WPKH: _BTCOutputType.ValueType # 3
7171
P2WSH: _BTCOutputType.ValueType # 4
7272
P2TR: _BTCOutputType.ValueType # 5
73+
OP_RETURN: _BTCOutputType.ValueType # 6
7374

7475
class BTCOutputType(_BTCOutputType, metaclass=_BTCOutputTypeEnumTypeWrapper): ...
7576

@@ -79,6 +80,7 @@ P2SH: BTCOutputType.ValueType # 2
7980
P2WPKH: BTCOutputType.ValueType # 3
8081
P2WSH: BTCOutputType.ValueType # 4
8182
P2TR: BTCOutputType.ValueType # 5
83+
OP_RETURN: BTCOutputType.ValueType # 6
8284
global___BTCOutputType = BTCOutputType
8385

8486
@typing.final

py/send_message.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,43 @@ def _sign_btc_policy(self) -> None:
788788
for input_index, sig in sigs:
789789
print("Signature for input {}: {}".format(input_index, sig.hex()))
790790

791+
def _sign_btc_op_return(
792+
self,
793+
format_unit: "bitbox02.btc.BTCSignInitRequest.FormatUnit.V" = bitbox02.btc.BTCSignInitRequest.FormatUnit.DEFAULT,
794+
) -> None:
795+
# pylint: disable=no-member
796+
bip44_account: int = 0 + HARDENED
797+
inputs, outputs = _btc_demo_inputs_outputs(bip44_account)
798+
outputs.append(
799+
bitbox02.BTCOutputExternal(
800+
output_type=bitbox02.btc.OP_RETURN,
801+
output_payload=b"hello world",
802+
value=0,
803+
)
804+
)
805+
sigs = self._device.btc_sign(
806+
bitbox02.btc.BTC,
807+
[
808+
bitbox02.btc.BTCScriptConfigWithKeypath(
809+
script_config=bitbox02.btc.BTCScriptConfig(
810+
simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH
811+
),
812+
keypath=[84 + HARDENED, 0 + HARDENED, bip44_account],
813+
),
814+
bitbox02.btc.BTCScriptConfigWithKeypath(
815+
script_config=bitbox02.btc.BTCScriptConfig(
816+
simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH
817+
),
818+
keypath=[49 + HARDENED, 0 + HARDENED, bip44_account],
819+
),
820+
],
821+
inputs=inputs,
822+
outputs=outputs,
823+
format_unit=format_unit,
824+
)
825+
for input_index, sig in sigs:
826+
print("Signature for input {}: {}".format(input_index, sig.hex()))
827+
791828
def _sign_btc_tx_from_raw(self) -> None:
792829
"""
793830
Experiment with testnet transactions.
@@ -896,6 +933,7 @@ def _sign_btc_tx(self) -> None:
896933
("Taproot inputs", self._sign_btc_taproot_inputs),
897934
("Taproot output", self._sign_btc_taproot_output),
898935
("Policy", self._sign_btc_policy),
936+
("OP_RETURN", self._sign_btc_op_return),
899937
("From testnet tx ID", self._sign_btc_tx_from_raw),
900938
)
901939
choice = ask_user(choices)

src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs

Lines changed: 117 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2022-2024 Shift Crypto AG
1+
// Copyright 2022-2025 Shift Crypto AG
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -25,10 +25,11 @@ pub use pb::btc_sign_init_request::FormatUnit;
2525
pub use pb::{BtcCoin, BtcOutputType};
2626

2727
use super::script_configs::{ValidatedScriptConfig, ValidatedScriptConfigWithKeypath};
28-
use super::{multisig, params::Params, script};
28+
use super::{multisig, params::Params};
2929

3030
use sha2::{Digest, Sha256};
3131

32+
use bitcoin::ScriptBuf;
3233
use bitcoin::bech32;
3334
use bitcoin::hashes::Hash;
3435

@@ -210,7 +211,8 @@ impl Payload {
210211
}
211212
}
212213

213-
/// Converts a payload to an address.
214+
/// Converts a payload to an address. Returns an error for invalid input or if an address does
215+
/// not exist for the output type (e.g. OP_RETURN).
214216
pub fn address(&self, params: &Params) -> Result<String, ()> {
215217
let payload = self.data.as_slice();
216218
match self.output_type {
@@ -251,54 +253,51 @@ impl Payload {
251253
}
252254
encode_segwit_addr(params.bech32_hrp, 1, payload)
253255
}
256+
BtcOutputType::OpReturn => Err(()),
254257
}
255258
}
256259

257-
/// Computes the pkScript from a pubkey hash or script hash or pubkey, depending on the output type.
260+
/// Computes the pkScript from a pubkey hash or script hash or pubkey, depending on the output
261+
/// type.
258262
pub fn pk_script(&self, params: &Params) -> Result<Vec<u8>, Error> {
259263
let payload = self.data.as_slice();
260-
match self.output_type {
261-
BtcOutputType::Unknown => Err(Error::InvalidInput),
264+
let script = match self.output_type {
265+
BtcOutputType::Unknown => return Err(Error::InvalidInput),
262266
BtcOutputType::P2pkh => {
263-
if payload.len() != HASH160_LEN {
264-
return Err(Error::Generic);
265-
}
266-
let mut result = vec![script::OP_DUP, script::OP_HASH160];
267-
script::push_data(&mut result, payload);
268-
result.extend_from_slice(&[script::OP_EQUALVERIFY, script::OP_CHECKSIG]);
269-
Ok(result)
267+
let pk_hash =
268+
bitcoin::PubkeyHash::from_slice(payload).map_err(|_| Error::Generic)?;
269+
270+
ScriptBuf::new_p2pkh(&pk_hash)
270271
}
271272
BtcOutputType::P2sh => {
272-
if payload.len() != HASH160_LEN {
273-
return Err(Error::Generic);
274-
}
275-
let mut result = vec![script::OP_HASH160];
276-
script::push_data(&mut result, payload);
277-
result.push(script::OP_EQUAL);
278-
Ok(result)
273+
let script_hash =
274+
bitcoin::ScriptHash::from_slice(payload).map_err(|_| Error::Generic)?;
275+
ScriptBuf::new_p2sh(&script_hash)
279276
}
280-
BtcOutputType::P2wpkh | BtcOutputType::P2wsh => {
281-
if (self.output_type == BtcOutputType::P2wpkh && payload.len() != HASH160_LEN)
282-
|| (self.output_type == BtcOutputType::P2wsh && payload.len() != SHA256_LEN)
283-
{
284-
return Err(Error::Generic);
285-
}
286-
let mut result = vec![script::OP_0];
287-
script::push_data(&mut result, payload);
288-
Ok(result)
277+
BtcOutputType::P2wpkh => {
278+
let wpkh = bitcoin::WPubkeyHash::from_slice(payload).map_err(|_| Error::Generic)?;
279+
ScriptBuf::new_p2wpkh(&wpkh)
280+
}
281+
BtcOutputType::P2wsh => {
282+
let wsh = bitcoin::WScriptHash::from_slice(payload).map_err(|_| Error::Generic)?;
283+
ScriptBuf::new_p2wsh(&wsh)
289284
}
290285
BtcOutputType::P2tr => {
291286
if !params.taproot_support {
292287
return Err(Error::InvalidInput);
293288
}
294-
if payload.len() != 32 {
295-
return Err(Error::Generic);
296-
}
297-
let mut result = vec![script::OP_1];
298-
script::push_data(&mut result, payload);
299-
Ok(result)
289+
let tweaked = bitcoin::key::TweakedPublicKey::dangerous_assume_tweaked(
290+
bitcoin::XOnlyPublicKey::from_slice(payload).map_err(|_| Error::Generic)?,
291+
);
292+
ScriptBuf::new_p2tr_tweaked(tweaked)
300293
}
301-
}
294+
BtcOutputType::OpReturn => {
295+
let pushbytes: &bitcoin::script::PushBytes =
296+
payload.try_into().map_err(|_| Error::InvalidInput)?;
297+
ScriptBuf::new_op_return(pushbytes)
298+
}
299+
};
300+
Ok(script.into_bytes())
302301
}
303302
}
304303

@@ -622,4 +621,86 @@ mod tests {
622621
b"\x25\x0e\xc8\x02\xb6\xd3\xdb\x98\x42\xd1\xbd\xbe\x0e\xe4\x8d\x52\xf9\xa4\xb4\x6e\x60\xcb\xbb\xab\x3b\xcc\x4e\xe9\x15\x73\xfc\xe8"
623622
);
624623
}
624+
625+
#[test]
626+
fn test_pkscript() {
627+
let params = super::super::params::get(pb::BtcCoin::Btc);
628+
629+
let payload = Payload {
630+
data: vec![],
631+
output_type: BtcOutputType::Unknown,
632+
};
633+
assert_eq!(payload.pk_script(params), Err(Error::InvalidInput));
634+
635+
struct Test {
636+
payload: &'static str,
637+
output_type: BtcOutputType,
638+
expected_pkscript: &'static str,
639+
}
640+
641+
let tests = [
642+
Test {
643+
payload: "669c6cb1883c50a1b10c34bd1693c1f34fe3d798",
644+
output_type: BtcOutputType::P2pkh,
645+
expected_pkscript: "76a914669c6cb1883c50a1b10c34bd1693c1f34fe3d79888ac",
646+
},
647+
Test {
648+
payload: "b59e844a19063a882b3c34b64b941a8acdad74ee",
649+
output_type: BtcOutputType::P2sh,
650+
expected_pkscript: "a914b59e844a19063a882b3c34b64b941a8acdad74ee87",
651+
},
652+
Test {
653+
payload: "b7cfb87a9806bb232e64f64e714785bd8366596b",
654+
output_type: BtcOutputType::P2wpkh,
655+
expected_pkscript: "0014b7cfb87a9806bb232e64f64e714785bd8366596b",
656+
},
657+
Test {
658+
payload: "526e8e589b4bf1de80774986d972aed96ae70f17572d35fe89e61e9e88e2dd4a",
659+
output_type: BtcOutputType::P2wsh,
660+
expected_pkscript: "0020526e8e589b4bf1de80774986d972aed96ae70f17572d35fe89e61e9e88e2dd4a",
661+
},
662+
Test {
663+
payload: "a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c",
664+
output_type: BtcOutputType::P2tr,
665+
expected_pkscript: "5120a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c",
666+
},
667+
Test {
668+
payload: "aabbcc",
669+
output_type: BtcOutputType::OpReturn,
670+
expected_pkscript: "6a03aabbcc",
671+
},
672+
Test {
673+
payload: "",
674+
output_type: BtcOutputType::OpReturn,
675+
expected_pkscript: "6a00",
676+
},
677+
Test {
678+
// 80 byte payload
679+
payload: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
680+
output_type: BtcOutputType::OpReturn,
681+
expected_pkscript: "6a4c50aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
682+
},
683+
];
684+
685+
for test in tests {
686+
// OK
687+
let payload = Payload {
688+
data: hex::decode(test.payload).unwrap(),
689+
output_type: test.output_type,
690+
};
691+
assert_eq!(
692+
hex::encode(payload.pk_script(params).unwrap()),
693+
test.expected_pkscript,
694+
);
695+
696+
// Payload of wrong size. Does not apply to OpReturn, almost any size is accepted.
697+
if test.output_type != BtcOutputType::OpReturn {
698+
let payload = Payload {
699+
data: hex::decode(&test.payload[2..]).unwrap(),
700+
output_type: test.output_type,
701+
};
702+
assert_eq!(payload.pk_script(params), Err(Error::Generic));
703+
}
704+
}
705+
}
625706
}

src/rust/bitbox02-rust/src/hww/api/bitcoin/script.rs

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,6 @@
1414

1515
use alloc::vec::Vec;
1616

17-
// https://en.bitcoin.it/wiki/Script
18-
pub const OP_0: u8 = 0;
19-
pub const OP_1: u8 = 0x51;
20-
pub const OP_HASH160: u8 = 0xa9;
21-
pub const OP_DUP: u8 = 0x76;
22-
pub const OP_EQUALVERIFY: u8 = 0x88;
23-
pub const OP_CHECKSIG: u8 = 0xac;
24-
pub const OP_EQUAL: u8 = 0x87;
25-
2617
/// Serialize a number in the VarInt encoding.
2718
/// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
2819
pub fn serialize_varint(value: u64) -> Vec<u8> {
@@ -45,12 +36,6 @@ pub fn serialize_varint(value: u64) -> Vec<u8> {
4536
out
4637
}
4738

48-
/// Performs a data push onto `v`: the varint length of data followed by data.
49-
pub fn push_data(v: &mut Vec<u8>, data: &[u8]) {
50-
v.extend_from_slice(&serialize_varint(data.len() as _));
51-
v.extend_from_slice(data);
52-
}
53-
5439
#[cfg(test)]
5540
mod tests {
5641
use super::*;
@@ -111,26 +96,4 @@ mod tests {
11196
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff"
11297
);
11398
}
114-
115-
#[test]
116-
fn test_push_data() {
117-
assert_eq!(
118-
{
119-
let mut v = Vec::new();
120-
push_data(&mut v, b"");
121-
v
122-
},
123-
vec![0]
124-
);
125-
126-
// Data with length 255.
127-
assert_eq!(
128-
{
129-
let mut v = Vec::new();
130-
push_data(&mut v, b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
131-
v
132-
},
133-
b"\xfd\xff\x00bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_vec(),
134-
);
135-
}
13699
}

0 commit comments

Comments
 (0)