Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
benma committed Jun 18, 2024
1 parent 9881f94 commit 40cdf82
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 28 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
50 changes: 49 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::hashes::Hash;

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 @@ -496,6 +500,33 @@ impl<'a> ParsedPolicy<'a> {
)?;
Ok(is_change)
}

/// Returns true if this policy is a Taproot policy and the keypath points to the Taproot
/// internal key. This is useful to determine if we spend using the Taproot key path or the
/// script path.
///
/// 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.
pub fn taproot_spend_tweak(
&self,
xpub_cache: &mut Bip32XpubCache,
keypath: &[u32],
) -> Result<Option<[u8; 32]>, 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(Some(tr.inner.spend_info().tap_tweak().to_byte_array()))
} else {
Ok(None)
}
}
_ => Err(Error::Generic),
}
}
}

/// Parses a policy as specified by 'Wallet policies': https://github.com/bitcoin/bips/pull/1389.
Expand Down Expand Up @@ -986,6 +1017,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
35 changes: 31 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 @@ -29,6 +29,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 @@ -954,9 +955,35 @@ async fn _process(request: &pb::BtcSignInitRequest) -> Result<Response, Error> {
input_index,
});
next_response.next.has_signature = true;
next_response.next.signature =
bitbox02::keystore::secp256k1_schnorr_bip86_sign(&tx_input.keypath, &sighash)?
.to_vec();

let tap_tweak = 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)?;
Some(
bitcoin::TapTweakHash::from_key_and_tweak(pubkey.into(), None)
.to_byte_array(),
)
}
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_tweak(&mut xpub_cache, &tx_input.keypath)?
}
_ => return Err(Error::Generic),
};
next_response.next.signature = bitbox02::keystore::secp256k1_schnorr_sign(
&tx_input.keypath,
&sighash,
tap_tweak.as_ref(),
)?
.to_vec();
} else {
// Sign all other supported inputs.

Expand Down Expand Up @@ -3091,7 +3118,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
27 changes: 21 additions & 6 deletions test/unit-test/test_keystore.c
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,6 @@ static void _test_keystore_secp256k1_schnorr_bip86_sign(void** state)
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon "
"about",
"");
uint8_t pubkey[32] = {0};
const uint32_t keypath[] = {
86 + BIP32_INITIAL_HARDENED_CHILD,
0 + BIP32_INITIAL_HARDENED_CHILD,
Expand All @@ -714,17 +713,33 @@ static void _test_keystore_secp256k1_schnorr_bip86_sign(void** state)
struct ext_key xpub = {0};
assert_true(keystore_get_xpub(keypath, 5, &xpub));

assert_true(keystore_secp256k1_schnorr_bip86_pubkey(xpub.pub_key, pubkey));
uint8_t msg[32] = {0};
memset(msg, 0x88, sizeof(msg));
uint8_t sig[64] = {0};
uint8_t mock_aux_rand[32] = {0};

// Test without tweak
will_return(__wrap_random_32_bytes, mock_aux_rand);
assert_true(keystore_secp256k1_schnorr_bip86_sign(keypath, 5, msg, sig));
assert_true(keystore_secp256k1_schnorr_sign(keypath, 5, msg, NULL, sig));
const secp256k1_context* ctx = wally_get_secp_context();
secp256k1_xonly_pubkey pubkey_deserialized = {0};
assert_true(secp256k1_xonly_pubkey_parse(ctx, &pubkey_deserialized, pubkey));
assert_true(secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &pubkey_deserialized));
secp256k1_pubkey pubkey = {0};
assert_true(secp256k1_ec_pubkey_parse(ctx, &pubkey, xpub.pub_key, sizeof(xpub.pub_key)));
secp256k1_xonly_pubkey xonly_pubkey = {0};
assert_true(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pubkey, NULL, &pubkey));
assert_true(secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &xonly_pubkey));

// Test with tweak
const uint8_t tweak[32] =
"\xa3\x9f\xb1\x63\xdb\xd9\xb5\xe0\x84\x0a\xf3\xcc\x1e\xe4\x1d\x5b\x31\x24\x5c\x5d\xd8\xd6"
"\xbd\xc3\xd0\x26\xd0\x9b\x89\x64\x99\x7c";
will_return(__wrap_random_32_bytes, mock_aux_rand);
assert_true(keystore_secp256k1_schnorr_sign(keypath, 5, msg, tweak, sig));
secp256k1_pubkey tweaked_pubkey = {0};
assert_true(secp256k1_xonly_pubkey_tweak_add(ctx, &tweaked_pubkey, &xonly_pubkey, tweak));
secp256k1_xonly_pubkey tweaked_xonly_pubkey = {0};
assert_true(
secp256k1_xonly_pubkey_from_pubkey(ctx, &tweaked_xonly_pubkey, NULL, &tweaked_pubkey));
assert_true(secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &tweaked_xonly_pubkey));
}

int main(void)
Expand Down

0 comments on commit 40cdf82

Please sign in to comment.