Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
benma committed Jun 19, 2024
1 parent 5f7a844 commit e481155
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 31 deletions.
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ add_custom_target(rust-bindgen
--allowlist-function keystore_secp256k1_compressed_to_uncompressed
--allowlist-function keystore_secp256k1_nonce_commit
--allowlist-function keystore_secp256k1_sign
--allowlist-function keystore_secp256k1_schnorr_bip86_sign
--allowlist-function keystore_secp256k1_schnorr_sign
--allowlist-function keystore_bip39_mnemonic_to_seed
--allowlist-function keystore_mock_unlocked
--allowlist-var EC_PUBLIC_KEY_UNCOMPRESSED_LEN
Expand Down
26 changes: 13 additions & 13 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 @@ -962,33 +963,32 @@ static bool _schnorr_bip86_keypair(
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;
}
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) {
return false;
if (tweak != NULL) {
if (secp256k1_keypair_xonly_tweak_add(ctx, keypair_out, tweak) != 1) {
return false;
}
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);

#ifdef TESTING
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");
}
}
84 changes: 83 additions & 1 deletion src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ use miniscript::TranslatePk;

use crate::bip32;
use crate::workflow::confirm;
use crate::xpubcache::Bip32XpubCache;

use bitcoin::taproot::{LeafVersion, TapLeafHash, TapTweakHash};

use sha2::{Digest, Sha256};

Expand Down Expand Up @@ -100,7 +103,8 @@ fn parse_wallet_policy_pk(pk: &str) -> Result<(usize, u32, u32), ()> {
}

/// Given policy pubkeys like `@0/<left;right>/*` and the keys list, determine if the given keypath
/// is valid and whether it points to a receive or change address.
/// is valid and whether it points to a receive or change address. We also return the matched
/// pubkey.
///
/// Example: pubkeys "@0/<10;11>/*" and "@1/<20;21>/*", with our key [fp/48'/1'/0'/3']xpub...],
/// derived using keypath m/48'/1'/0'/3'/11/5 means that this is the address index 5 at the change
Expand Down Expand Up @@ -206,6 +210,24 @@ impl Tr<bitcoin::PublicKey> {
pub fn output_key(&self) -> [u8; 32] {
self.inner.spend_info().output_key().serialize()
}

/// Returns the tap leaf hash (as defined in BIP341) of the leaf whose script contains the given
/// pubkey (serialized as a compressed pubkey). If the pubkey is not present in any leaf script,
/// None is returned.
///
/// Note that we assume that each pubkey is unique according to BIP-388 and validated by
/// `validate_keys()`, so the leaf is unique.
fn get_leaf_hash_by_pubkey(&self, pk: &[u8; 33]) -> Option<TapLeafHash> {
for (_, ms) in self.inner.iter_scripts() {
if ms.iter_pk().any(|pk2| *pk == pk2.inner.serialize()) {
return Some(TapLeafHash::from_script(
&ms.encode(),
LeafVersion::TapScript,
));
}
}
None
}
}

impl Tr<String> {
Expand All @@ -219,6 +241,11 @@ impl Tr<String> {
}
}

pub enum TaprootSpendInfo {
KeySpend(TapTweakHash),
ScriptSpend(TapLeafHash),
}

/// See `ParsedPolicy`.
///
/// We don't use `miniscript::descriptor::Descriptor` as it supports much more than what we want
Expand Down Expand Up @@ -495,6 +522,44 @@ impl<'a> ParsedPolicy<'a> {
)?;
Ok(is_change)
}

/// Returns info needed to spend a Taproot UTXO at the given keypath.
///
/// If the keypath points to the Taproot internal key, we return the necessary Taproot tweak to
/// spend using the Taproot key path.
///
/// If th keypath points to a key used in a tap leaf script, we return the tap leaf hash (as
/// defined in BIP341), which is needed to in the sighash computation in the context of a
/// Taproot leaf script.
///
/// This works because all keypaths are distinct per BIP-388, and checked by `validate_keys()`,
/// so they keypath alone is sufficient to figure out if we are using key path or script
/// path, and if the latter, which leaf exactly.
pub fn taproot_spend_info(
&self,
xpub_cache: &mut Bip32XpubCache,
keypath: &[u32],
) -> Result<TaprootSpendInfo, Error> {
match self.derive_at_keypath(keypath)? {
Descriptor::Tr(tr) => {
let xpub = xpub_cache.get_xpub(keypath)?;
let is_keypath_spend =
xpub.public_key() == tr.inner.internal_key().inner.serialize();

if is_keypath_spend {
Ok(TaprootSpendInfo::KeySpend(
tr.inner.spend_info().tap_tweak(),
))
} else {
let leaf_hash = tr
.get_leaf_hash_by_pubkey(xpub.public_key().try_into().unwrap())
.ok_or(Error::InvalidInput)?;
return Ok(TaprootSpendInfo::ScriptSpend(leaf_hash));
}
}
_ => Err(Error::Generic),
}
}
}

/// Parses a policy as specified by 'Wallet policies': https://github.com/bitcoin/bips/pull/1389.
Expand Down Expand Up @@ -985,6 +1050,23 @@ mod tests {
Ok((false, 0))
);

assert_eq!(
get_change_and_address_index(
["@0/<10;11>/*", "@0/<20;21>/*"].iter(),
&[our_key.clone()],
&[true],
&[
48 + HARDENED,
1 + HARDENED,
0 + HARDENED,
3 + HARDENED,
20,
0,
],
),
Ok((false, 0))
);

assert_eq!(
get_change_and_address_index(
["@0/<10;11>/*", "@1/<20;21>/*"].iter(),
Expand Down
45 changes: 41 additions & 4 deletions src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use super::Error;

use super::common::format_amount;
use super::payment_request;
use super::policies::TaprootSpendInfo;
use super::script::serialize_varint;
use super::script_configs::{ValidatedScriptConfig, ValidatedScriptConfigWithKeypath};
use super::{bip143, bip341, common, keypath};
Expand All @@ -29,6 +30,7 @@ use alloc::vec::Vec;
use pb::request::Request;
use pb::response::Response;

use bitcoin::hashes::Hash;
use pb::btc_script_config::SimpleType;
use pb::btc_sign_init_request::FormatUnit;
use pb::btc_sign_next_response::Type as NextType;
Expand Down Expand Up @@ -943,6 +945,28 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result<Response, Error> {
return Err(Error::InvalidInput);
}

let spend_info = match &script_config_account.config {
ValidatedScriptConfig::SimpleType(SimpleType::P2tr) => {
// This is a BIP-86 spend, so we tweak the private key by the hash of the public
// key only, as there is no Taproot merkle root.
let xpub = xpub_cache.get_xpub(&tx_input.keypath)?;
let pubkey = bitcoin::PublicKey::from_slice(xpub.public_key())
.map_err(|_| Error::Generic)?;
TaprootSpendInfo::KeySpend(bitcoin::TapTweakHash::from_key_and_tweak(
pubkey.into(),
None,
))
}
ValidatedScriptConfig::Policy(policy) => {
// Get the Taproot tweak based on whether we spend using the internal key (key
// path spend) or if we spend using a leaf script. For key path spends, we must
// first tweak the private key to match the Taproot output key. For leaf
// scripts, we do not tweak.

policy.taproot_spend_info(&mut xpub_cache, &tx_input.keypath)?
}
_ => return Err(Error::Generic),
};
let sighash = bip341::sighash(&bip341::Args {
version: request.version,
locktime: request.locktime,
Expand All @@ -952,11 +976,24 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result<Response, Error> {
hash_sequences: hash_sequence.into(),
hash_outputs: hash_outputs.into(),
input_index,
tapleaf_hash: if let TaprootSpendInfo::ScriptSpend(leaf_hash) = &spend_info {
Some(leaf_hash.to_byte_array())
} else {
None
},
});

next_response.next.has_signature = true;
next_response.next.signature =
bitbox02::keystore::secp256k1_schnorr_bip86_sign(&tx_input.keypath, &sighash)?
.to_vec();
next_response.next.signature = bitbox02::keystore::secp256k1_schnorr_sign(
&tx_input.keypath,
&sighash,
if let TaprootSpendInfo::KeySpend(tweak_hash) = &spend_info {
Some(tweak_hash.as_byte_array())
} else {
None
},
)?
.to_vec();
} else {
// Sign all other supported inputs.

Expand Down Expand Up @@ -3091,7 +3128,7 @@ mod tests {
match result {
Ok(Response::BtcSignNext(next)) => {
assert!(next.has_signature);
assert_eq!(&next.signature, b"\x49\xec\x2b\xee\x76\xc3\x5f\xb2\xe7\x0f\xf8\x6d\x7e\xc7\x71\xbf\xd6\x91\x8e\xac\x0e\x06\xf9\x1b\xfc\x06\xbc\x5f\xdb\x99\x91\xcc\xfa\x88\x93\x4e\x4e\x2e\x51\xb3\x72\xba\xcd\x40\x43\xcc\xb9\xa5\xa2\x65\x05\xe1\xba\xb2\xe5\x9e\x0a\x47\x63\x9a\xf4\x7c\xfb\xaf");
assert_eq!(&next.signature, b"\xf4\xb7\x60\xfa\x7f\x1c\xa8\xa0\x01\x49\xbf\x43\x9c\x07\xdc\xd3\xaa\xfe\x4c\x98\x11\x16\x07\xce\xce\x4b\x80\x06\x6f\x7e\xf2\xe4\x40\x6d\x18\x83\x19\x90\xde\xf0\xbf\x4a\x5b\x56\x47\xdc\x42\x6e\xf1\xf7\x49\x52\x4a\xdf\x0a\x68\x96\x84\x4c\xd9\x0b\x79\x60\x31");
}
_ => panic!("wrong result"),
}
Expand Down
13 changes: 11 additions & 2 deletions src/rust/bitbox02/src/keystore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,13 +329,22 @@ pub fn bip85_ln(index: u32) -> Result<Vec<u8>, ()> {
}
}

pub fn secp256k1_schnorr_bip86_sign(keypath: &[u32], msg: &[u8; 32]) -> Result<[u8; 64], ()> {
pub fn secp256k1_schnorr_sign(
keypath: &[u32],
msg: &[u8; 32],
tweak: Option<&[u8; 32]>,
) -> Result<[u8; 64], ()> {
let mut signature = [0u8; 64];

match unsafe {
bitbox02_sys::keystore_secp256k1_schnorr_bip86_sign(
bitbox02_sys::keystore_secp256k1_schnorr_sign(
keypath.as_ptr(),
keypath.len() as _,
msg.as_ptr(),
match tweak {
Some(t) => t.as_ptr(),
None => core::ptr::null() as *const _,
},
signature.as_mut_ptr(),
)
} {
Expand Down
Loading

0 comments on commit e481155

Please sign in to comment.