Skip to content

Commit ce8f04b

Browse files
committed
keystore: encode xpub to bytes, not string
It is used as an intermediate representation when getting an xpub in Rust. Bytes are enough, no need to base58Check encode/decode. keystore_encode_xpub then becomes unused (only used in test_keystore_functional.c).
1 parent 57add1e commit ce8f04b

File tree

8 files changed

+42
-69
lines changed

8 files changed

+42
-69
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ add_custom_target(rust-bindgen
323323
--allowlist-function keystore_encode_xpub_at_keypath
324324
--allowlist-function keystore_encrypt_and_store_seed
325325
--allowlist-var XPUB_ENCODED_LEN
326+
--allowlist-var BIP32_SERIALIZED_LEN
326327
--allowlist-function lock_animation_start
327328
--allowlist-function lock_animation_stop
328329
--allowlist-function delay_us

src/keystore.c

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -681,27 +681,17 @@ bool keystore_get_ed25519_seed(uint8_t* seed_out)
681681
return true;
682682
}
683683

684-
bool keystore_encode_xpub(const struct ext_key* xpub, char* out, size_t out_len)
685-
{
686-
uint8_t bytes[BIP32_SERIALIZED_LEN] = {0};
687-
if (bip32_key_serialize(xpub, BIP32_FLAG_KEY_PUBLIC, bytes, sizeof(bytes)) != WALLY_OK) {
688-
return false;
689-
}
690-
return rust_base58_encode_check(
691-
rust_util_bytes(bytes, sizeof(bytes)), rust_util_cstr_mut(out, out_len));
692-
}
693-
694684
USE_RESULT bool keystore_encode_xpub_at_keypath(
695685
const uint32_t* keypath,
696686
size_t keypath_len,
697-
char* out,
698-
size_t out_len)
687+
uint8_t* out)
699688
{
700689
struct ext_key derived_xpub __attribute__((__cleanup__(keystore_zero_xkey))) = {0};
701690
if (!keystore_get_xpub(keypath, keypath_len, &derived_xpub)) {
702691
return false;
703692
}
704-
return keystore_encode_xpub(&derived_xpub, out, out_len);
693+
return bip32_key_serialize(&derived_xpub, BIP32_FLAG_KEY_PUBLIC, out, BIP32_SERIALIZED_LEN) ==
694+
WALLY_OK;
705695
}
706696

707697
static void _tagged_hash(const char* tag, const uint8_t* msg, size_t msg_len, uint8_t* hash_out)

src/keystore.h

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -249,24 +249,14 @@ USE_RESULT bool keystore_get_u2f_seed(uint8_t* seed_out);
249249
USE_RESULT bool keystore_get_ed25519_seed(uint8_t* seed_out);
250250

251251
/**
252-
* Encode an xpub as a base58 string.
253-
* @param[in] xpub the xpub to encode.
254-
* @param[out] out resulting string, must be at least of size `XPUB_ENCODED_LEN` (including the null
255-
* terminator).
256-
* @param[in] out_len size of `out`.
257-
* @return false on failure, true on success.
258-
*/
259-
USE_RESULT bool keystore_encode_xpub(const struct ext_key* xpub, char* out, size_t out_len);
260-
261-
/**
262-
* Encode an xpub as a base58 string at the given `keypath`.
263-
* Args the same as `keystore_encode_xpub`.
252+
* Encode an xpub at the given `keypath` as 78 bytes according to BIP32. The version bytes are
253+
* the ones corresponding to `xpub`, i.e. 0x0488B21E.
254+
* `out` must be `BIP32_SERIALIZED_LEN` long.
264255
*/
265256
USE_RESULT bool keystore_encode_xpub_at_keypath(
266257
const uint32_t* keypath,
267258
size_t keypath_len,
268-
char* out,
269-
size_t out_len);
259+
uint8_t* out);
270260

271261
/**
272262
* Return the tweaked taproot pubkey at the given keypath.

src/rust/bitbox02-rust/src/bip32.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,28 @@ pub fn serialize_xpub_str(xpub: &pb::XPub, xpub_type: XPubType) -> Result<String
6161
.into_string())
6262
}
6363

64-
/// Parses a Base58Check-encoded xpub string. The 4 version bytes are not checked and discarded.
65-
pub fn parse_xpub(xpub: &str) -> Result<pb::XPub, ()> {
66-
let decoded = bs58::decode(xpub).with_check(None).into_vec().or(Err(()))?;
67-
if decoded.len() != 78 {
64+
/// Parses an 78-ytes xpub bytestring, encoded according to BIP32. The 4 version bytes are not
65+
/// checked and discarded.
66+
pub fn parse_xpub_bytes(xpub: &[u8]) -> Result<pb::XPub, ()> {
67+
if xpub.len() != 78 {
6868
return Err(());
6969
}
7070
Ok(pb::XPub {
71-
depth: decoded[4..5].to_vec(),
72-
parent_fingerprint: decoded[5..9].to_vec(),
73-
child_num: u32::from_be_bytes(core::convert::TryInto::try_into(&decoded[9..13]).unwrap()),
74-
chain_code: decoded[13..45].to_vec(),
75-
public_key: decoded[45..78].to_vec(),
71+
depth: xpub[4..5].to_vec(),
72+
parent_fingerprint: xpub[5..9].to_vec(),
73+
child_num: u32::from_be_bytes(core::convert::TryInto::try_into(&xpub[9..13]).unwrap()),
74+
chain_code: xpub[13..45].to_vec(),
75+
public_key: xpub[45..78].to_vec(),
7676
})
7777
}
7878

79+
/// Parses a Base58Check-encoded xpub string. The 4 version bytes are not checked and discarded.
80+
#[cfg(test)]
81+
pub fn parse_xpub(xpub: &str) -> Result<pb::XPub, ()> {
82+
let decoded = bs58::decode(xpub).with_check(None).into_vec().or(Err(()))?;
83+
parse_xpub_bytes(&decoded)
84+
}
85+
7986
#[cfg(test)]
8087
mod tests {
8188
use super::*;

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@ use bitbox02::keystore;
2424

2525
/// Derives an xpub from the keystore seed at the given keypath.
2626
pub fn get_xpub(keypath: &[u32]) -> Result<pb::XPub, ()> {
27-
// Convert from C keystore to Rust by encoding the xpub in C and decoding it in Rust. Would be a
28-
// bit better to encode/decoding using the raw 78 bytes, not the base58Check encoding.
29-
bip32::parse_xpub(&keystore::encode_xpub_at_keypath(keypath)?)
27+
// Convert from C keystore to Rust by encoding the xpub in C and decoding it in Rust.
28+
bip32::parse_xpub_bytes(&keystore::encode_xpub_at_keypath(keypath)?)
3029
}
3130

3231
/// Return the hash160 of the secp256k1 public key at the keypath.

src/rust/bitbox02/src/keystore.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
extern crate alloc;
1616
use alloc::string::String;
17+
use alloc::vec;
1718
use alloc::vec::Vec;
1819

1920
use core::convert::TryInto;
@@ -198,19 +199,16 @@ pub fn secp256k1_pubkey_uncompressed(
198199
}
199200
}
200201

201-
pub fn encode_xpub_at_keypath(keypath: &[u32]) -> Result<String, ()> {
202-
let mut xpub = [0u8; bitbox02_sys::XPUB_ENCODED_LEN as _];
202+
pub fn encode_xpub_at_keypath(keypath: &[u32]) -> Result<Vec<u8>, ()> {
203+
let mut xpub = vec![0u8; bitbox02_sys::BIP32_SERIALIZED_LEN as _];
203204
match unsafe {
204205
bitbox02_sys::keystore_encode_xpub_at_keypath(
205206
keypath.as_ptr(),
206207
keypath.len() as _,
207208
xpub.as_mut_ptr(),
208-
xpub.len() as _,
209209
)
210210
} {
211-
true => Ok(crate::util::str_from_null_terminated(&xpub[..])
212-
.unwrap()
213-
.into()),
211+
true => Ok(xpub),
214212
false => Err(()),
215213
}
216214
}

test/unit-test/test_keystore.c

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -449,28 +449,6 @@ static void _test_keystore_get_bip39_mnemonic(void** state)
449449
assert_true(keystore_get_bip39_mnemonic(mnemonic, strlen(expected_mnemonic) + 1));
450450
}
451451

452-
static void _test_keystore_encode_xpub(void** state)
453-
{
454-
struct ext_key xpub = {0};
455-
assert_int_equal(
456-
bip32_key_from_seed(
457-
(const unsigned char*)"seedseedseedseed",
458-
BIP32_ENTROPY_LEN_128,
459-
BIP32_VER_MAIN_PRIVATE,
460-
BIP32_FLAG_SKIP_HASH,
461-
&xpub),
462-
WALLY_OK);
463-
assert_int_equal(bip32_key_strip_private_key(&xpub), WALLY_OK);
464-
char out[XPUB_ENCODED_LEN] = {0};
465-
assert_false(keystore_encode_xpub(&xpub, out, 110));
466-
467-
assert_true(keystore_encode_xpub(&xpub, out, sizeof(out)));
468-
assert_string_equal(
469-
out,
470-
"xpub661MyMwAqRbcFLG1NSwsGkQxYGaRj3qDsDB6g64CviEc82D3r7Dp4eMnWdarcVkpPbMgwwuLLPPwCXVQFWWomv"
471-
"yj6QKEuDXWvNbCDF98tgM");
472-
}
473-
474452
static void _test_keystore_create_and_store_seed(void** state)
475453
{
476454
const uint8_t seed_random[32] =
@@ -736,7 +714,6 @@ int main(void)
736714
cmocka_unit_test(_test_keystore_unlock),
737715
cmocka_unit_test(_test_keystore_lock),
738716
cmocka_unit_test(_test_keystore_get_bip39_mnemonic),
739-
cmocka_unit_test(_test_keystore_encode_xpub),
740717
cmocka_unit_test(_test_keystore_create_and_store_seed),
741718
cmocka_unit_test(_test_keystore_get_ed25519_seed),
742719
cmocka_unit_test(_test_secp256k1_schnorr_sign),

test/unit-test/test_keystore_functional.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <memory/bitbox02_smarteeprom.h>
2424
#include <memory/smarteeprom.h>
2525
#include <mock_memory.h>
26+
#include <rust/rust.h>
2627
#include <securechip/securechip.h>
2728
#include <util.h>
2829

@@ -107,6 +108,16 @@ static void _assert_equal_memory_hex(const uint8_t* buf, size_t buf_size, const
107108
assert_string_equal(buf_hex, expected_hex);
108109
}
109110

111+
static bool _encode_xpub(const struct ext_key* xpub, char* out, size_t out_len)
112+
{
113+
uint8_t bytes[BIP32_SERIALIZED_LEN] = {0};
114+
if (bip32_key_serialize(xpub, BIP32_FLAG_KEY_PUBLIC, bytes, sizeof(bytes)) != WALLY_OK) {
115+
return false;
116+
}
117+
return rust_base58_encode_check(
118+
rust_util_bytes(bytes, sizeof(bytes)), rust_util_cstr_mut(out, out_len));
119+
}
120+
110121
static void _check_pubs(const char* expected_xpub, const char* expected_pubkey_uncompressed_hex)
111122
{
112123
struct ext_key __attribute__((__cleanup__(keystore_zero_xkey))) xpub;
@@ -120,7 +131,7 @@ static void _check_pubs(const char* expected_xpub, const char* expected_pubkey_u
120131

121132
assert_true(keystore_get_xpub(keypath, 3, &xpub));
122133
char xpub_serialized[120];
123-
assert_true(keystore_encode_xpub(&xpub, xpub_serialized, sizeof(xpub_serialized)));
134+
assert_true(_encode_xpub(&xpub, xpub_serialized, sizeof(xpub_serialized)));
124135
assert_string_equal(xpub_serialized, expected_xpub);
125136

126137
uint8_t pubkey_uncompressed[EC_PUBLIC_KEY_UNCOMPRESSED_LEN];

0 commit comments

Comments
 (0)