Skip to content

Commit a781609

Browse files
committed
keystore: add keystore_bip39_mnemonic_from_seed()
Helper funtion that takes the seed as an input arg, de-duping some code. This version is also easier to export to Rust than the wally's bip39_mnemonic_from_bytes, which mallocs.
1 parent 829ffa0 commit a781609

File tree

4 files changed

+79
-23
lines changed

4 files changed

+79
-23
lines changed

src/keystore.c

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -488,29 +488,36 @@ bool keystore_is_locked(void)
488488
return !unlocked;
489489
}
490490

491-
bool keystore_get_bip39_mnemonic(char* mnemonic_out, size_t mnemonic_out_size)
491+
bool keystore_bip39_mnemonic_from_seed(
492+
const uint8_t* seed,
493+
size_t seed_size,
494+
char* mnemonic_out,
495+
size_t mnemonic_out_size)
492496
{
493-
if (keystore_is_locked()) {
494-
return false;
495-
}
496497
char* mnemonic = NULL;
497-
{ // block so that `seed` is zeroed as soon as possible
498-
uint8_t seed[KEYSTORE_MAX_SEED_LENGTH] = {0};
499-
UTIL_CLEANUP_32(seed);
500-
size_t seed_length = 0;
501-
if (!keystore_copy_seed(seed, &seed_length)) {
502-
return false;
503-
}
504-
if (bip39_mnemonic_from_bytes(NULL, seed, seed_length, &mnemonic) != WALLY_OK) {
505-
return false;
506-
}
498+
if (bip39_mnemonic_from_bytes(NULL, seed, seed_size, &mnemonic) != WALLY_OK) {
499+
return false;
507500
}
508501
int snprintf_result = snprintf(mnemonic_out, mnemonic_out_size, "%s", mnemonic);
509502
util_cleanup_str(&mnemonic);
510503
free(mnemonic);
511504
return snprintf_result >= 0 && snprintf_result < (int)mnemonic_out_size;
512505
}
513506

507+
bool keystore_get_bip39_mnemonic(char* mnemonic_out, size_t mnemonic_out_size)
508+
{
509+
if (keystore_is_locked()) {
510+
return false;
511+
}
512+
uint8_t seed[KEYSTORE_MAX_SEED_LENGTH] = {0};
513+
UTIL_CLEANUP_32(seed);
514+
size_t seed_length = 0;
515+
if (!keystore_copy_seed(seed, &seed_length)) {
516+
return false;
517+
}
518+
return keystore_bip39_mnemonic_from_seed(seed, seed_length, mnemonic_out, mnemonic_out_size);
519+
}
520+
514521
bool keystore_bip39_mnemonic_to_seed(const char* mnemonic, uint8_t* seed_out, size_t* seed_len_out)
515522
{
516523
return bip39_mnemonic_to_bytes(NULL, mnemonic, seed_out, 32, seed_len_out) == WALLY_OK;
@@ -782,15 +789,7 @@ bool keystore_bip85_bip39(
782789
if (!_bip85_entropy(keypath, sizeof(keypath) / sizeof(uint32_t), entropy)) {
783790
return false;
784791
}
785-
786-
char* mnemonic = NULL;
787-
if (bip39_mnemonic_from_bytes(NULL, entropy, seed_size, &mnemonic) != WALLY_OK) {
788-
return false;
789-
}
790-
int snprintf_result = snprintf(mnemonic_out, mnemonic_out_size, "%s", mnemonic);
791-
util_cleanup_str(&mnemonic);
792-
free(mnemonic);
793-
return snprintf_result >= 0 && snprintf_result < (int)mnemonic_out_size;
792+
return keystore_bip39_mnemonic_from_seed(entropy, seed_size, mnemonic_out, mnemonic_out_size);
794793
}
795794

796795
USE_RESULT bool keystore_encode_xpub_at_keypath(

src/keystore.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ void keystore_lock(void);
115115
*/
116116
USE_RESULT bool keystore_is_locked(void);
117117

118+
/**
119+
* Converts a 16/24/32 byte seed into a BIP-39 mnemonic string.
120+
* Returns false if the seed size is invalid or the output string buffer is not large enough.
121+
*/
122+
USE_RESULT bool keystore_bip39_mnemonic_from_seed(
123+
const uint8_t* seed,
124+
size_t seed_size,
125+
char* mnemonic_out,
126+
size_t mnemonic_out_size);
127+
118128
/**
119129
* @param[out] mnemonic_out resulting mnemonic
120130
* @param[in] mnemonic_out_size size of mnemonic_out. Should be at least 216 bytes (longest possible

src/rust/bitbox02-sys/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const ALLOWLIST_FNS: &[&str] = &[
8484
"keystore_secp256k1_sign",
8585
"keystore_unlock",
8686
"keystore_unlock_bip39",
87+
"keystore_bip39_mnemonic_from_seed",
8788
"label_create",
8889
"localtime",
8990
"lock_animation_start",

src/rust/bitbox02/src/keystore.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,25 @@ pub fn get_bip39_mnemonic() -> Result<zeroize::Zeroizing<String>, ()> {
136136
}
137137
}
138138

139+
pub fn bip39_mnemonic_from_seed(seed: &[u8]) -> Result<zeroize::Zeroizing<String>, ()> {
140+
let mut mnemonic = zeroize::Zeroizing::new([0u8; 256]);
141+
match unsafe {
142+
bitbox02_sys::keystore_bip39_mnemonic_from_seed(
143+
seed.as_ptr(),
144+
seed.len() as _,
145+
mnemonic.as_mut_ptr(),
146+
mnemonic.len() as _,
147+
)
148+
} {
149+
false => Err(()),
150+
true => Ok(zeroize::Zeroizing::new(
151+
crate::util::str_from_null_terminated(&mnemonic[..])
152+
.unwrap()
153+
.into(),
154+
)),
155+
}
156+
}
157+
139158
/// `idx` must be smaller than BIP39_WORDLIST_LEN.
140159
pub fn get_bip39_word(idx: u16) -> Result<zeroize::Zeroizing<String>, ()> {
141160
let mut word_ptr: *mut u8 = core::ptr::null_mut();
@@ -520,4 +539,31 @@ mod tests {
520539
"4604b4b710fe91f584fff084e1a9159fe4f8408fff380596a604948474ce4fa3"
521540
);
522541
}
542+
543+
#[test]
544+
fn test_bip39_mnemonic_from_seed() {
545+
// 12 words
546+
let seed = b"\xae\x6a\x40\x26\x1f\x0a\xcc\x16\x57\x04\x9c\xb2\x1a\xf5\xfb\xf7";
547+
assert_eq!(
548+
bip39_mnemonic_from_seed(seed).unwrap().as_str(),
549+
"purpose faith another dignity proud arctic foster near rare stumble leave urge",
550+
);
551+
552+
// 18 words
553+
let seed = b"\x2a\x3e\x07\xa9\xe7\x5e\xd7\x3a\xa6\xb2\xe1\xaf\x90\x3d\x50\x17\xde\x80\x4f\xdf\x2b\x45\xc2\x4b";
554+
assert_eq!(
555+
bip39_mnemonic_from_seed(seed).unwrap().as_str(),
556+
"clay usual tuna solid uniform outer onion found question limit favorite cook trend child lake hamster seat foot",
557+
);
558+
559+
// 24 words
560+
let seed = b"\x24\x1d\x5b\x78\x35\x90\xc2\x1f\x79\x69\x8e\x7c\xe8\x92\xdd\x03\xfb\x2c\x8f\xad\xc2\x44\x0e\xc2\x3a\xa5\xde\x9e\x2d\x23\x81\xb0";
561+
assert_eq!(
562+
bip39_mnemonic_from_seed(seed).unwrap().as_str(),
563+
"catch turn task hen around autumn toss crack language duty resemble among ready elephant require embrace attract balcony practice rule tissue mushroom almost athlete",
564+
);
565+
566+
// Invalid seed side
567+
assert!(bip39_mnemonic_from_seed(b"foo").is_err());
568+
}
523569
}

0 commit comments

Comments
 (0)