Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 91 additions & 35 deletions src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;

Expand Down Expand Up @@ -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<Vec<u8>, 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())
}
}

Expand Down Expand Up @@ -622,4 +614,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));
}
}
}
37 changes: 0 additions & 37 deletions src/rust/bitbox02-rust/src/hww/api/bitcoin/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> {
Expand All @@ -45,12 +36,6 @@ pub fn serialize_varint(value: u64) -> Vec<u8> {
out
}

/// Performs a data push onto `v`: the varint length of data followed by data.
pub fn push_data(v: &mut Vec<u8>, data: &[u8]) {
v.extend_from_slice(&serialize_varint(data.len() as _));
v.extend_from_slice(data);
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -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(),
);
}
}