Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bitcoin: add support for tr() wallet policies/descriptors #1231

Merged
merged 3 commits into from
Sep 24, 2024
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately.

### [Unreleased]
- Bitcoin: add support for sending to silent payment (BIP-352) addresses
- Bitcoin: add support for Taproot wallet policies and Miniscript on Taproot (MiniTapscript)
- Bitcoin: add support for regtest
- Cardano: add support for vote delegation

Expand Down
25 changes: 11 additions & 14 deletions src/keystore.c
Original file line number Diff line number Diff line change
Expand Up @@ -941,9 +941,10 @@ bool keystore_secp256k1_schnorr_bip86_pubkey(const uint8_t* pubkey33, uint8_t* p
return secp256k1_xonly_pubkey_serialize(ctx, pubkey_out, &tweaked_xonly_pubkey) == 1;
}

static bool _schnorr_bip86_keypair(
static bool _schnorr_keypair(
const uint32_t* keypath,
size_t keypath_len,
const uint8_t* tweak,
secp256k1_keypair* keypair_out,
secp256k1_xonly_pubkey* pubkey_out)
{
Expand All @@ -959,36 +960,32 @@ static bool _schnorr_bip86_keypair(
if (!secp256k1_keypair_create(ctx, keypair_out, secret_key)) {
return false;
}
if (!secp256k1_keypair_xonly_pub(ctx, pubkey_out, NULL, keypair_out)) {
return false;
}
uint8_t pubkey_serialized[32] = {0};
if (!secp256k1_xonly_pubkey_serialize(ctx, pubkey_serialized, pubkey_out)) {
return false;
if (tweak != NULL) {
if (secp256k1_keypair_xonly_tweak_add(ctx, keypair_out, tweak) != 1) {
return false;
}
}
uint8_t hash[32] = {0};
_tagged_hash("TapTweak", pubkey_serialized, sizeof(pubkey_serialized), hash);

if (secp256k1_keypair_xonly_tweak_add(ctx, keypair_out, hash) != 1) {
if (!secp256k1_keypair_xonly_pub(ctx, pubkey_out, NULL, keypair_out)) {
return false;
}
return secp256k1_keypair_xonly_pub(ctx, pubkey_out, NULL, keypair_out) == 1;
return true;
}

static void _cleanup_keypair(secp256k1_keypair* keypair)
{
util_zero(keypair, sizeof(secp256k1_keypair));
}

bool keystore_secp256k1_schnorr_bip86_sign(
bool keystore_secp256k1_schnorr_sign(
const uint32_t* keypath,
size_t keypath_len,
const uint8_t* msg32,
const uint8_t* tweak,
uint8_t* sig64_out)
{
secp256k1_keypair __attribute__((__cleanup__(_cleanup_keypair))) keypair = {0};
secp256k1_xonly_pubkey pubkey = {0};
if (!_schnorr_bip86_keypair(keypath, keypath_len, &keypair, &pubkey)) {
if (!_schnorr_keypair(keypath, keypath_len, tweak, &keypair, &pubkey)) {
return false;
}
const secp256k1_context* ctx = wally_get_secp_context();
Expand Down
5 changes: 4 additions & 1 deletion src/keystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,12 +295,15 @@ USE_RESULT bool keystore_secp256k1_schnorr_bip86_pubkey(
* @param[in] keypath derivation keypath
* @param[in] keypath_len number of elements in keypath
* @param[in] msg32 32 byte message to sign
* @param[in] tweak 32 bytes, tweak private key before signing with this tweak. Use NULL to not
* tweak.
* @param[out] sig64_out resulting 64 byte signature
*/
USE_RESULT bool keystore_secp256k1_schnorr_bip86_sign(
USE_RESULT bool keystore_secp256k1_schnorr_sign(
const uint32_t* keypath,
size_t keypath_len,
const uint8_t* msg32,
const uint8_t* tweak,
uint8_t* sig64_out);

/**
Expand Down
3 changes: 2 additions & 1 deletion src/rust/bitbox02-rust/src/hww/api/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ async fn address_policy(
.await?;
}

let address = common::Payload::from_policy(&parsed, keypath)?.address(coin_params)?;
let address =
common::Payload::from_policy(coin_params, &parsed, keypath)?.address(coin_params)?;
if display {
confirm::confirm(&confirm::Params {
title,
Expand Down
44 changes: 41 additions & 3 deletions src/rust/bitbox02-rust/src/hww/api/bitcoin/bip341.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ pub struct Args {
pub hash_outputs: [u8; 32],
// Data about this input:
pub input_index: u32,
// tapleaf_hash as described in https://github.com/bitcoin/bips/blob/85cda4e225b4d5fd7aff403f69d827f23f6afbbc/bip-0342.mediawiki#common-signature-message-extension
// Providing this means we use the above tapscript message extension.
pub tapleaf_hash: Option<[u8; 32]>,
}

/// Compute the BIP341 signature hash.
///
/// https://github.com/bitcoin/bips/blob/bb8dc57da9b3c6539b88378348728a2ff43f7e9c/bip-0341.mediawiki#common-signature-message
///
/// The hash_type is assumed 0 (`SIGHASH_DEFAULT`). The `ext_flag` is
/// assumed 0 and `annex` is assumed to be not present.
/// The hash_type is assumed 0 (`SIGHASH_DEFAULT`). `annex` is assumed to be not present.
pub fn sighash(args: &Args) -> [u8; 32] {
let tag = Sha256::digest(b"TapSighash");
let mut ctx = Sha256::new();
Expand All @@ -53,10 +55,28 @@ pub fn sighash(args: &Args) -> [u8; 32] {
ctx.update(args.hash_sequences);
ctx.update(args.hash_outputs);
// spend_type is 0 because ext_flag is 0 and annex is absent.
ctx.update(0u8.to_le_bytes());
let ext_flag = if args.tapleaf_hash.is_some() {
// ext_flag = 1 for Taproot leaf scripts
// See https://github.com/bitcoin/bips/blob/85cda4e225b4d5fd7aff403f69d827f23f6afbbc/bip-0342.mediawiki#common-signature-message-extension
1
} else {
0
};
let spend_type: u8 = 2 * ext_flag;
ctx.update(spend_type.to_le_bytes());
// Data about this input:
ctx.update(args.input_index.to_le_bytes());

if let Some(hash) = args.tapleaf_hash.as_ref() {
// See https://github.com/bitcoin/bips/blob/85cda4e225b4d5fd7aff403f69d827f23f6afbbc/bip-0342.mediawiki#common-signature-message-extension
// tapleaf_hash
ctx.update(hash);
// keyversion
ctx.update(0u8.to_le_bytes());
// codesep_pos - we do not use any OP_CODESEPARATORs.
let codesep_pos: u32 = 0xFFFFFFFF;
ctx.update(codesep_pos.to_le_bytes());
}
ctx.finalize().into()
}

Expand All @@ -79,7 +99,25 @@ mod tests {
hash_sequences: *b"\x18\x95\x9c\x72\x21\xab\x5c\xe9\xe2\x6c\x3c\xd6\x7b\x22\xc2\x4f\x8b\xaa\x54\xba\xc2\x81\xd8\xe6\xb0\x5e\x40\x0e\x6c\x3a\x95\x7e",
hash_outputs: *b"\xa2\xe6\xda\xb7\xc1\xf0\xdc\xd2\x97\xc8\xd6\x16\x47\xfd\x17\xd8\x21\x54\x1e\xa6\x9c\x3c\xc3\x7d\xcb\xad\x7f\x90\xd4\xeb\x4b\xc5",
input_index: 4,
tapleaf_hash: None,
}),
*b"\x4f\x90\x0a\x0b\xae\x3f\x14\x46\xfd\x48\x49\x0c\x29\x58\xb5\xa0\x23\x22\x8f\x01\x66\x1c\xda\x34\x96\xa1\x1d\xa5\x02\xa7\xf7\xef");
}

#[test]
fn test_sighash_tapleaf() {
assert_eq!(
sighash(&Args {
version: 2,
locktime: 500000000,
hash_prevouts: *b"\xe3\xb3\x3b\xb4\xef\x3a\x52\xad\x1f\xff\xb5\x55\xc0\xd8\x28\x28\xeb\x22\x73\x70\x36\xea\xeb\x02\xa2\x35\xd8\x2b\x90\x9c\x4c\x3f",
hash_amounts: *b"\x58\xa6\x96\x4a\x4f\x5f\x8f\x0b\x64\x2d\xed\x0a\x8a\x55\x3b\xe7\x62\x2a\x71\x9d\xa7\x1d\x1f\x5b\xef\xce\xfc\xde\xe8\xe0\xfd\xe6",
hash_scriptpubkeys: *b"\x23\xad\x0f\x61\xad\x2b\xca\x5b\xa6\xa7\x69\x3f\x50\xfc\xe9\x88\xe1\x7c\x37\x80\xbf\x2b\x1e\x72\x0c\xfb\xb3\x8f\xbd\xd5\x2e\x21",
hash_sequences: *b"\x18\x95\x9c\x72\x21\xab\x5c\xe9\xe2\x6c\x3c\xd6\x7b\x22\xc2\x4f\x8b\xaa\x54\xba\xc2\x81\xd8\xe6\xb0\x5e\x40\x0e\x6c\x3a\x95\x7e",
hash_outputs: *b"\xa2\xe6\xda\xb7\xc1\xf0\xdc\xd2\x97\xc8\xd6\x16\x47\xfd\x17\xd8\x21\x54\x1e\xa6\x9c\x3c\xc3\x7d\xcb\xad\x7f\x90\xd4\xeb\x4b\xc5",
input_index: 4,
tapleaf_hash: Some(*b"\x34\xe7\x21\x15\xc0\x9c\x91\x3c\x8b\xe1\x2e\x46\xfc\x14\x5f\xcf\x7c\x53\xca\xd9\xca\x2a\x05\xf9\x3a\x7c\xa2\xe0\xca\x88\xd0\x07"),
}),
*b"\xba\xe0\xaa\xcb\xa5\xae\xa9\xee\xbe\x19\xe1\x57\xa9\x8f\x1e\xe7\x0d\x7d\x28\x8c\x28\x0f\x27\x3e\x63\xbb\x8a\x85\xd1\xee\xf3\xc2");
}
}
15 changes: 13 additions & 2 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 Shift Crypto AG
// Copyright 2022-2024 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 Down Expand Up @@ -156,6 +156,7 @@ impl Payload {
/// derived using keypath m/48'/1'/0'/3'/11/5 derives the payload for
/// wsh(and_v(v:pk(@0/11/5),pk(@1/21/5))).
pub fn from_policy(
params: &Params,
policy: &super::policies::ParsedPolicy,
keypath: &[u32],
) -> Result<Self, Error> {
Expand All @@ -165,6 +166,16 @@ impl Payload {
data: Sha256::digest(wsh.witness_script()).to_vec(),
output_type: BtcOutputType::P2wsh,
}),
super::policies::Descriptor::Tr(tr) => {
if params.taproot_support {
Ok(Payload {
data: tr.output_key().to_vec(),
output_type: BtcOutputType::P2tr,
})
} else {
Err(Error::InvalidInput)
}
}
}
}

Expand All @@ -186,7 +197,7 @@ impl Payload {
keypath[keypath.len() - 2],
keypath[keypath.len() - 1],
),
ValidatedScriptConfig::Policy(policy) => Self::from_policy(policy, keypath),
ValidatedScriptConfig::Policy(policy) => Self::from_policy(params, policy, keypath),
}
}

Expand Down
Loading