Skip to content

Commit 47315c5

Browse files
committed
keystore: use xpub from Rust when computing uncompressed pubkey
The goal use xpub from the Rust keystore, so that we can impement xpub caching in Rust, calling to C only for utility functions that are hard to port. If the xpub is derived in C.
1 parent f2a76cb commit 47315c5

File tree

7 files changed

+43
-39
lines changed

7 files changed

+43
-39
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ add_custom_target(rust-bindgen
312312
--allowlist-function keystore_get_bip39_mnemonic
313313
--allowlist-function keystore_get_bip39_word
314314
--allowlist-function keystore_get_ed25519_seed
315-
--allowlist-function keystore_secp256k1_pubkey_uncompressed
315+
--allowlist-function keystore_secp256k1_compressed_to_uncompressed
316316
--allowlist-function keystore_secp256k1_nonce_commit
317317
--allowlist-function keystore_secp256k1_sign
318318
--allowlist-function keystore_secp256k1_schnorr_bip86_sign

src/keystore.c

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -538,11 +538,9 @@ bool keystore_get_bip39_word(uint16_t idx, char** word_out)
538538
return bip39_get_word(NULL, idx, word_out) == WALLY_OK;
539539
}
540540

541-
// Reformats xpub from compressed 33 bytes to uncompressed 65 bytes (<0x04><64 bytes X><64 bytes
542-
// Y>),
543-
// pubkey must be 33 bytes
544-
// uncompressed_out must be 65 bytes.
545-
static bool _compressed_to_uncompressed(const uint8_t* pubkey_bytes, uint8_t* uncompressed_out)
541+
bool keystore_secp256k1_compressed_to_uncompressed(
542+
const uint8_t* pubkey_bytes,
543+
uint8_t* uncompressed_out)
546544
{
547545
const secp256k1_context* ctx = wally_get_secp_context();
548546
secp256k1_pubkey pubkey;
@@ -557,18 +555,6 @@ static bool _compressed_to_uncompressed(const uint8_t* pubkey_bytes, uint8_t* un
557555
return true;
558556
}
559557

560-
bool keystore_secp256k1_pubkey_uncompressed(
561-
const uint32_t* keypath,
562-
size_t keypath_len,
563-
uint8_t* pubkey_out)
564-
{
565-
struct ext_key xpub __attribute__((__cleanup__(keystore_zero_xkey))) = {0};
566-
if (!keystore_get_xpub(keypath, keypath_len, &xpub)) {
567-
return false;
568-
}
569-
return _compressed_to_uncompressed(xpub.pub_key, pubkey_out);
570-
}
571-
572558
bool keystore_secp256k1_nonce_commit(
573559
const uint32_t* keypath,
574560
size_t keypath_len,

src/keystore.h

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,17 +166,13 @@ void keystore_zero_xkey(struct ext_key* xkey);
166166
*/
167167
USE_RESULT bool keystore_get_bip39_word(uint16_t idx, char** word_out);
168168

169-
/**
170-
* Return the serialized secp256k1 public key at the keypath, in uncompressed format.
171-
* @param[in] keypath derivation keypath
172-
* @param[in] keypath_len size of keypath buffer
173-
* @param[out] pubkey_out serialized output. Must be EC_PUBLIC_KEY_UNCOMPRESSED_LEN bytes.
174-
* @return true on success, false if the keystore is locked or the input is invalid.
175-
*/
176-
USE_RESULT bool keystore_secp256k1_pubkey_uncompressed(
177-
const uint32_t* keypath,
178-
size_t keypath_len,
179-
uint8_t* pubkey_out);
169+
// Reformats pubkey from compressed 33 bytes to uncompressed 65 bytes (<0x04><64 bytes X><64 bytes
170+
// Y>),
171+
// pubkey must be 33 bytes
172+
// uncompressed_out must be 65 bytes.
173+
USE_RESULT bool keystore_secp256k1_compressed_to_uncompressed(
174+
const uint8_t* pubkey_bytes,
175+
uint8_t* uncompressed_out);
180176

181177
/**
182178
* Get a commitment to the original nonce before tweaking it with the host nonce. This is part of

src/rust/bitbox02-rust/src/hww/api/ethereum/pubrequest.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ async fn process_address(request: &pb::EthPubRequest) -> Result<Response, Error>
4747
if !super::keypath::is_valid_keypath_address(&request.keypath) {
4848
return Err(Error::InvalidInput);
4949
}
50-
let pubkey = bitbox02::keystore::secp256k1_pubkey_uncompressed(&request.keypath)
50+
let pubkey = crate::keystore::secp256k1_pubkey_uncompressed(&request.keypath)
5151
.or(Err(Error::InvalidInput))?;
5252
let address = super::address::from_pubkey(&pubkey);
5353

src/rust/bitbox02-rust/src/keystore.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ pub fn secp256k1_pubkey_hash160(keypath: &[u32]) -> Result<Vec<u8>, ()> {
3434
Ok(bitbox02::hash160(&xpub.public_key).to_vec())
3535
}
3636

37+
pub fn secp256k1_pubkey_uncompressed(
38+
keypath: &[u32],
39+
) -> Result<[u8; keystore::EC_PUBLIC_KEY_UNCOMPRESSED_LEN], ()> {
40+
let xpub = get_xpub(keypath)?;
41+
keystore::secp256k1_pubkey_compressed_to_uncompressed(&xpub.public_key)
42+
}
43+
3744
/// Returns fingerprint of the root public key at m/, which are the first four bytes of its hash160
3845
/// according to:
3946
/// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format
@@ -134,4 +141,18 @@ mod tests {
134141
);
135142
assert_eq!(root_fingerprint(), Ok(vec![0xf4, 0x0b, 0x46, 0x9a]));
136143
}
144+
145+
#[test]
146+
fn test_secp256k1_pubkey_uncompressed() {
147+
let keypath = &[44 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 2];
148+
149+
keystore::lock();
150+
assert_eq!(secp256k1_pubkey_uncompressed(keypath), Err(()));
151+
152+
mock_unlocked_using_mnemonic("sleep own lobster state clean thrive tail exist cactus bitter pass soccer clinic riot dream turkey before sport action praise tunnel hood donate man");
153+
assert_eq!(
154+
secp256k1_pubkey_uncompressed(keypath).unwrap(),
155+
*b"\x04\x77\xa4\x4a\xa9\xe8\xc8\xfb\x51\x05\xef\x5e\xe2\x39\x4e\x8a\xed\x89\xad\x73\xfc\x74\x36\x14\x25\xf0\x63\x47\xec\xfe\x32\x61\x31\xe1\x33\x93\x67\xee\x3c\xbe\x87\x71\x92\x85\xa0\x7f\x77\x4b\x17\xeb\x93\x3e\xcf\x0b\x9b\x82\xac\xeb\xc1\x95\x22\x6d\x63\x42\x44",
156+
);
157+
}
137158
}

src/rust/bitbox02/src/keystore.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,13 @@ pub fn get_bip39_wordlist() -> Result<Bip39Wordlist, ()> {
183183
Ok(result)
184184
}
185185

186-
pub fn secp256k1_pubkey_uncompressed(
187-
keypath: &[u32],
186+
pub fn secp256k1_pubkey_compressed_to_uncompressed(
187+
compressed_pubkey: &[u8],
188188
) -> Result<[u8; EC_PUBLIC_KEY_UNCOMPRESSED_LEN], ()> {
189189
let mut pubkey = [0u8; EC_PUBLIC_KEY_UNCOMPRESSED_LEN];
190190
match unsafe {
191-
bitbox02_sys::keystore_secp256k1_pubkey_uncompressed(
192-
keypath.as_ptr(),
193-
keypath.len() as _,
191+
bitbox02_sys::keystore_secp256k1_compressed_to_uncompressed(
192+
compressed_pubkey.as_ptr(),
194193
pubkey.as_mut_ptr(),
195194
)
196195
} {

test/unit-test/test_keystore_functional.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ static bool _encode_xpub(const struct ext_key* xpub, char* out, size_t out_len)
120120

121121
static void _check_pubs(const char* expected_xpub, const char* expected_pubkey_uncompressed_hex)
122122
{
123-
struct ext_key __attribute__((__cleanup__(keystore_zero_xkey))) xpub;
123+
struct ext_key __attribute__((__cleanup__(keystore_zero_xkey))) xpub_3;
124+
struct ext_key __attribute__((__cleanup__(keystore_zero_xkey))) xpub_5;
124125
uint32_t keypath[] = {
125126
44 + BIP32_INITIAL_HARDENED_CHILD,
126127
0 + BIP32_INITIAL_HARDENED_CHILD,
@@ -129,13 +130,14 @@ static void _check_pubs(const char* expected_xpub, const char* expected_pubkey_u
129130
2,
130131
};
131132

132-
assert_true(keystore_get_xpub(keypath, 3, &xpub));
133+
assert_true(keystore_get_xpub(keypath, 3, &xpub_3));
134+
assert_true(keystore_get_xpub(keypath, 5, &xpub_5));
133135
char xpub_serialized[120];
134-
assert_true(_encode_xpub(&xpub, xpub_serialized, sizeof(xpub_serialized)));
136+
assert_true(_encode_xpub(&xpub_3, xpub_serialized, sizeof(xpub_serialized)));
135137
assert_string_equal(xpub_serialized, expected_xpub);
136138

137139
uint8_t pubkey_uncompressed[EC_PUBLIC_KEY_UNCOMPRESSED_LEN];
138-
assert_true(keystore_secp256k1_pubkey_uncompressed(keypath, 5, pubkey_uncompressed));
140+
assert_true(keystore_secp256k1_compressed_to_uncompressed(xpub_5.pub_key, pubkey_uncompressed));
139141
_assert_equal_memory_hex(
140142
pubkey_uncompressed, sizeof(pubkey_uncompressed), expected_pubkey_uncompressed_hex);
141143
}

0 commit comments

Comments
 (0)