From fef5ef814e2c2727231c22a7c335bddfd058ccb3 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Wed, 24 Sep 2025 22:16:42 +0200 Subject: [PATCH 1/2] bitcoin/common: add unit test for pk_script() --- .../src/hww/api/bitcoin/common.rs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs index 0eaac71095..2e0dcf2bf4 100644 --- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs +++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs @@ -622,4 +622,68 @@ mod tests { 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" ); } + + #[test] + fn test_pkscript() { + let params = super::super::params::get(pb::BtcCoin::Btc); + + let payload = Payload { + data: vec![], + output_type: BtcOutputType::Unknown, + }; + assert_eq!(payload.pk_script(params), Err(Error::InvalidInput)); + + struct Test { + payload: &'static str, + output_type: BtcOutputType, + expected_pkscript: &'static str, + } + + let tests = [ + Test { + payload: "669c6cb1883c50a1b10c34bd1693c1f34fe3d798", + output_type: BtcOutputType::P2pkh, + expected_pkscript: "76a914669c6cb1883c50a1b10c34bd1693c1f34fe3d79888ac", + }, + Test { + payload: "b59e844a19063a882b3c34b64b941a8acdad74ee", + output_type: BtcOutputType::P2sh, + expected_pkscript: "a914b59e844a19063a882b3c34b64b941a8acdad74ee87", + }, + Test { + payload: "b7cfb87a9806bb232e64f64e714785bd8366596b", + output_type: BtcOutputType::P2wpkh, + expected_pkscript: "0014b7cfb87a9806bb232e64f64e714785bd8366596b", + }, + Test { + payload: "526e8e589b4bf1de80774986d972aed96ae70f17572d35fe89e61e9e88e2dd4a", + output_type: BtcOutputType::P2wsh, + expected_pkscript: "0020526e8e589b4bf1de80774986d972aed96ae70f17572d35fe89e61e9e88e2dd4a", + }, + Test { + payload: "a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c", + output_type: BtcOutputType::P2tr, + expected_pkscript: "5120a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c", + }, + ]; + + for test in tests { + // OK + let payload = Payload { + data: hex::decode(test.payload).unwrap(), + output_type: test.output_type, + }; + assert_eq!( + hex::encode(payload.pk_script(params).unwrap()), + test.expected_pkscript + ); + + // Payload of wrong size + let payload = Payload { + data: hex::decode(&test.payload[2..]).unwrap(), + output_type: test.output_type, + }; + assert_eq!(payload.pk_script(params), Err(Error::Generic)); + } + } } From d6760358e0778bdc4af868fc9a00fc8a4b4d0e73 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Wed, 24 Sep 2025 21:58:08 +0200 Subject: [PATCH 2/2] bitcoin/common: use bitcoin crate to compute pkscript Remove our own code to improve clarity. --- .../src/hww/api/bitcoin/common.rs | 62 ++++++++----------- .../src/hww/api/bitcoin/script.rs | 37 ----------- 2 files changed, 27 insertions(+), 72 deletions(-) diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs index 2e0dcf2bf4..6e13d78f68 100644 --- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs +++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Shift Crypto AG +// Copyright 2022-2025 Shift Crypto AG // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,10 +25,11 @@ pub use pb::btc_sign_init_request::FormatUnit; pub use pb::{BtcCoin, BtcOutputType}; use super::script_configs::{ValidatedScriptConfig, ValidatedScriptConfigWithKeypath}; -use super::{multisig, params::Params, script}; +use super::{multisig, params::Params}; use sha2::{Digest, Sha256}; +use bitcoin::ScriptBuf; use bitcoin::bech32; use bitcoin::hashes::Hash; @@ -254,51 +255,42 @@ impl Payload { } } - /// Computes the pkScript from a pubkey hash or script hash or pubkey, depending on the output type. + /// Computes the pkScript from a pubkey hash or script hash or pubkey, depending on the output + /// type. pub fn pk_script(&self, params: &Params) -> Result, Error> { let payload = self.data.as_slice(); - match self.output_type { - BtcOutputType::Unknown => Err(Error::InvalidInput), + let script = match self.output_type { + BtcOutputType::Unknown => return Err(Error::InvalidInput), BtcOutputType::P2pkh => { - if payload.len() != HASH160_LEN { - return Err(Error::Generic); - } - let mut result = vec![script::OP_DUP, script::OP_HASH160]; - script::push_data(&mut result, payload); - result.extend_from_slice(&[script::OP_EQUALVERIFY, script::OP_CHECKSIG]); - Ok(result) + let pk_hash = + bitcoin::PubkeyHash::from_slice(payload).map_err(|_| Error::Generic)?; + + ScriptBuf::new_p2pkh(&pk_hash) } BtcOutputType::P2sh => { - if payload.len() != HASH160_LEN { - return Err(Error::Generic); - } - let mut result = vec![script::OP_HASH160]; - script::push_data(&mut result, payload); - result.push(script::OP_EQUAL); - Ok(result) + let script_hash = + bitcoin::ScriptHash::from_slice(payload).map_err(|_| Error::Generic)?; + ScriptBuf::new_p2sh(&script_hash) } - BtcOutputType::P2wpkh | BtcOutputType::P2wsh => { - if (self.output_type == BtcOutputType::P2wpkh && payload.len() != HASH160_LEN) - || (self.output_type == BtcOutputType::P2wsh && payload.len() != SHA256_LEN) - { - return Err(Error::Generic); - } - let mut result = vec![script::OP_0]; - script::push_data(&mut result, payload); - Ok(result) + BtcOutputType::P2wpkh => { + let wpkh = bitcoin::WPubkeyHash::from_slice(payload).map_err(|_| Error::Generic)?; + ScriptBuf::new_p2wpkh(&wpkh) + } + BtcOutputType::P2wsh => { + let wsh = bitcoin::WScriptHash::from_slice(payload).map_err(|_| Error::Generic)?; + ScriptBuf::new_p2wsh(&wsh) } BtcOutputType::P2tr => { if !params.taproot_support { return Err(Error::InvalidInput); } - if payload.len() != 32 { - return Err(Error::Generic); - } - let mut result = vec![script::OP_1]; - script::push_data(&mut result, payload); - Ok(result) + let tweaked = bitcoin::key::TweakedPublicKey::dangerous_assume_tweaked( + bitcoin::XOnlyPublicKey::from_slice(payload).map_err(|_| Error::Generic)?, + ); + ScriptBuf::new_p2tr_tweaked(tweaked) } - } + }; + Ok(script.into_bytes()) } } diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/script.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/script.rs index d66c3f082c..dbcb66af4c 100644 --- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/script.rs +++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/script.rs @@ -14,15 +14,6 @@ use alloc::vec::Vec; -// https://en.bitcoin.it/wiki/Script -pub const OP_0: u8 = 0; -pub const OP_1: u8 = 0x51; -pub const OP_HASH160: u8 = 0xa9; -pub const OP_DUP: u8 = 0x76; -pub const OP_EQUALVERIFY: u8 = 0x88; -pub const OP_CHECKSIG: u8 = 0xac; -pub const OP_EQUAL: u8 = 0x87; - /// Serialize a number in the VarInt encoding. /// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer pub fn serialize_varint(value: u64) -> Vec { @@ -45,12 +36,6 @@ pub fn serialize_varint(value: u64) -> Vec { out } -/// Performs a data push onto `v`: the varint length of data followed by data. -pub fn push_data(v: &mut Vec, data: &[u8]) { - v.extend_from_slice(&serialize_varint(data.len() as _)); - v.extend_from_slice(data); -} - #[cfg(test)] mod tests { use super::*; @@ -111,26 +96,4 @@ mod tests { b"\xff\xff\xff\xff\xff\xff\xff\xff\xff" ); } - - #[test] - fn test_push_data() { - assert_eq!( - { - let mut v = Vec::new(); - push_data(&mut v, b""); - v - }, - vec![0] - ); - - // Data with length 255. - assert_eq!( - { - let mut v = Vec::new(); - push_data(&mut v, b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); - v - }, - b"\xfd\xff\x00bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_vec(), - ); - } }