Skip to content

Commit 9e4b45d

Browse files
committed
keystore: port keystore_bip85_bip39 to Rust
1 parent 80e6088 commit 9e4b45d

File tree

6 files changed

+76
-130
lines changed

6 files changed

+76
-130
lines changed

src/keystore.c

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -726,58 +726,6 @@ bool keystore_get_ed25519_seed(uint8_t* seed_out)
726726
return true;
727727
}
728728

729-
static bool _bip85_entropy(const uint32_t* keypath, size_t keypath_len, uint8_t* out)
730-
{
731-
struct ext_key xprv __attribute__((__cleanup__(keystore_zero_xkey))) = {0};
732-
if (!_get_xprv_twice(keypath, keypath_len, &xprv)) {
733-
return false;
734-
}
735-
const uint8_t* priv_key = xprv.priv_key + 1; // first byte is 0
736-
const uint8_t key[] = "bip-entropy-from-k";
737-
return wally_hmac_sha512(key, sizeof(key), priv_key, 32, out, 64) == WALLY_OK;
738-
}
739-
740-
bool keystore_bip85_bip39(
741-
uint32_t words,
742-
uint32_t index,
743-
char* mnemonic_out,
744-
size_t mnemonic_out_size)
745-
{
746-
size_t seed_size;
747-
switch (words) {
748-
case 12:
749-
seed_size = 16;
750-
break;
751-
case 18:
752-
seed_size = 24;
753-
break;
754-
case 24:
755-
seed_size = 32;
756-
break;
757-
default:
758-
return false;
759-
}
760-
761-
if (index >= BIP32_INITIAL_HARDENED_CHILD) {
762-
return false;
763-
}
764-
765-
const uint32_t keypath[] = {
766-
83696968 + BIP32_INITIAL_HARDENED_CHILD,
767-
39 + BIP32_INITIAL_HARDENED_CHILD,
768-
0 + BIP32_INITIAL_HARDENED_CHILD,
769-
words + BIP32_INITIAL_HARDENED_CHILD,
770-
index + BIP32_INITIAL_HARDENED_CHILD,
771-
};
772-
773-
uint8_t entropy[64] = {0};
774-
UTIL_CLEANUP_64(entropy);
775-
if (!_bip85_entropy(keypath, sizeof(keypath) / sizeof(uint32_t), entropy)) {
776-
return false;
777-
}
778-
return keystore_bip39_mnemonic_from_seed(entropy, seed_size, mnemonic_out, mnemonic_out_size);
779-
}
780-
781729
USE_RESULT bool keystore_encode_xpub_at_keypath(
782730
const uint32_t* keypath,
783731
size_t keypath_len,

src/keystore.h

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -231,21 +231,6 @@ USE_RESULT bool keystore_get_u2f_seed(uint8_t* seed_out);
231231
*/
232232
USE_RESULT bool keystore_get_ed25519_seed(uint8_t* seed_out);
233233

234-
/**
235-
* Computes a BIP39 mnemonic according to BIP-85:
236-
* https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki#bip39
237-
* @param[in] words must be 12, 18 or 24.
238-
* @param[in] index must be smaller than `BIP32_INITIAL_HARDENED_CHILD`.
239-
* @param[out] mnemonic_out resulting mnemonic
240-
* @param[in] mnemonic_out_size size of mnemonic_out. Should be at least 216 bytes (longest possible
241-
* 24 word phrase plus null terminator).
242-
*/
243-
USE_RESULT bool keystore_bip85_bip39(
244-
uint32_t words,
245-
uint32_t index,
246-
char* mnemonic_out,
247-
size_t mnemonic_out_size);
248-
249234
/**
250235
* Encode an xpub at the given `keypath` as 78 bytes according to BIP32. The version bytes are
251236
* the ones corresponding to `xpub`, i.e. 0x0488B21E.

src/rust/bitbox02-rust/src/hww/api/bip85.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use pb::response::Response;
2020
use crate::hal::Ui;
2121
use crate::workflow::confirm;
2222

23-
use bitbox02::keystore;
23+
use crate::keystore;
2424

2525
use alloc::vec::Vec;
2626

@@ -160,7 +160,7 @@ async fn process_ln(
160160
})
161161
.await?;
162162

163-
Ok(crate::keystore::bip85_ln(account_number)
163+
Ok(keystore::bip85_ln(account_number)
164164
.map_err(|_| Error::Generic)?
165165
.to_vec())
166166
}

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,34 @@ fn bip85_entropy(keypath: &[u32]) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
5454
Ok(out)
5555
}
5656

57+
/// Computes a BIP39 mnemonic according to BIP-85:
58+
/// https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki#bip39
59+
/// `words` must be 12, 18 or 24.
60+
/// `index` must be smaller than `bip32::HARDENED`.
61+
pub fn bip85_bip39(words: u32, index: u32) -> Result<zeroize::Zeroizing<String>, ()> {
62+
if index >= HARDENED {
63+
return Err(());
64+
}
65+
66+
let seed_size: usize = match words {
67+
12 => 16,
68+
18 => 24,
69+
24 => 32,
70+
_ => return Err(()),
71+
};
72+
73+
let keypath = [
74+
83696968 + HARDENED,
75+
39 + HARDENED,
76+
0 + HARDENED,
77+
words + HARDENED,
78+
index + HARDENED,
79+
];
80+
81+
let entropy = bip85_entropy(&keypath)?;
82+
keystore::bip39_mnemonic_from_seed(&entropy[..seed_size])
83+
}
84+
5785
/// Computes a 16 byte deterministic seed specifically for Lightning hot wallets according to BIP-85.
5886
/// It is the same as BIP-85 with app number 39', but instead using app number 19534' (= 0x4c4e =
5987
/// 'LN'). https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki#bip39
@@ -152,6 +180,52 @@ mod tests {
152180
assert_eq!(root_fingerprint(), Ok(vec![0xf4, 0x0b, 0x46, 0x9a]));
153181
}
154182

183+
#[test]
184+
fn test_bip85_bip39() {
185+
keystore::lock();
186+
assert!(bip85_bip39(12, 0).is_err());
187+
188+
// Test fixtures generated using:
189+
// `docker build -t bip85 .`
190+
// `podman run --rm bip85 --index 0 --bip39-mnemonic "virtual weapon code laptop defy cricket vicious target wave leopard garden give" bip39 --num-words 12`
191+
// `podman run --rm bip85 --index 1 --bip39-mnemonic "virtual weapon code laptop defy cricket vicious target wave leopard garden give" bip39 --num-words 12`
192+
// `podman run --rm bip85 --index 2147483647 --bip39-mnemonic "virtual weapon code laptop defy cricket vicious target wave leopard garden give" bip39 --num-words 12`
193+
// `podman run --rm bip85 --index 0 --bip39-mnemonic "virtual weapon code laptop defy cricket vicious target wave leopard garden give" bip39 --num-words 18`
194+
// `podman run --rm bip85 --index 0 --bip39-mnemonic "virtual weapon code laptop defy cricket vicious target wave leopard garden give" bip39 --num-words 24`
195+
// in https://github.com/ethankosakovsky/bip85/tree/435a0589746c1036735d0a5081167e08abfa7413.
196+
197+
mock_unlocked_using_mnemonic(
198+
"virtual weapon code laptop defy cricket vicious target wave leopard garden give",
199+
"",
200+
);
201+
202+
assert_eq!(
203+
bip85_bip39(12, 0).unwrap().as_ref() as &str,
204+
"slender whip place siren tissue chaos ankle door only assume tent shallow",
205+
);
206+
assert_eq!(
207+
bip85_bip39(12, 1).unwrap().as_ref() as &str,
208+
"income soft level reunion height pony crane use unfold win keen satisfy",
209+
);
210+
assert_eq!(
211+
bip85_bip39(12, HARDENED - 1).unwrap().as_ref() as &str,
212+
"carry build nerve market domain energy mistake script puzzle replace mixture idea",
213+
);
214+
assert_eq!(
215+
bip85_bip39(18, 0).unwrap().as_ref() as &str,
216+
"enact peasant tragic habit expand jar senior melody coin acid logic upper soccer later earn napkin planet stereo",
217+
);
218+
assert_eq!(
219+
bip85_bip39(24, 0).unwrap().as_ref() as &str,
220+
"cabbage wink october add anchor mean tray surprise gasp tomorrow garbage habit beyond merge where arrive beef gentle animal office drop panel chest size",
221+
);
222+
223+
// Invalid number of words.
224+
assert!(bip85_bip39(10, 0).is_err());
225+
// Index too high.
226+
assert!(bip85_bip39(12, HARDENED).is_err());
227+
}
228+
155229
#[test]
156230
fn test_bip85_ln() {
157231
keystore::lock();

src/rust/bitbox02-sys/build.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ const ALLOWLIST_FNS: &[&str] = &[
6767
"delay_us",
6868
"empty_create",
6969
"keystore_bip39_mnemonic_to_seed",
70-
"keystore_bip85_bip39",
7170
"keystore_copy_seed",
7271
"keystore_create_and_store_seed",
7372
"keystore_encode_xpub_at_keypath",

src/rust/bitbox02/src/keystore.rs

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -319,20 +319,6 @@ pub fn get_ed25519_seed() -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
319319
}
320320
}
321321

322-
pub fn bip85_bip39(words: u32, index: u32) -> Result<zeroize::Zeroizing<String>, ()> {
323-
let mut mnemonic = zeroize::Zeroizing::new([0u8; 256]);
324-
match unsafe {
325-
bitbox02_sys::keystore_bip85_bip39(words, index, mnemonic.as_mut_ptr(), mnemonic.len() as _)
326-
} {
327-
false => Err(()),
328-
true => Ok(zeroize::Zeroizing::new(
329-
crate::util::str_from_null_terminated(&mnemonic[..])
330-
.unwrap()
331-
.into(),
332-
)),
333-
}
334-
}
335-
336322
pub fn secp256k1_schnorr_sign(
337323
keypath: &[u32],
338324
msg: &[u8; 32],
@@ -450,52 +436,6 @@ mod tests {
450436
);
451437
}
452438

453-
#[test]
454-
fn test_bip85_bip39() {
455-
lock();
456-
assert!(bip85_bip39(12, 0).is_err());
457-
458-
// Test fixtures generated using:
459-
// `docker build -t bip85 .`
460-
// `podman run --rm bip85 --index 0 --bip39-mnemonic "virtual weapon code laptop defy cricket vicious target wave leopard garden give" bip39 --num-words 12`
461-
// `podman run --rm bip85 --index 1 --bip39-mnemonic "virtual weapon code laptop defy cricket vicious target wave leopard garden give" bip39 --num-words 12`
462-
// `podman run --rm bip85 --index 2147483647 --bip39-mnemonic "virtual weapon code laptop defy cricket vicious target wave leopard garden give" bip39 --num-words 12`
463-
// `podman run --rm bip85 --index 0 --bip39-mnemonic "virtual weapon code laptop defy cricket vicious target wave leopard garden give" bip39 --num-words 18`
464-
// `podman run --rm bip85 --index 0 --bip39-mnemonic "virtual weapon code laptop defy cricket vicious target wave leopard garden give" bip39 --num-words 24`
465-
// in https://github.com/ethankosakovsky/bip85/tree/435a0589746c1036735d0a5081167e08abfa7413.
466-
467-
mock_unlocked_using_mnemonic(
468-
"virtual weapon code laptop defy cricket vicious target wave leopard garden give",
469-
"",
470-
);
471-
472-
assert_eq!(
473-
bip85_bip39(12, 0).unwrap().as_ref() as &str,
474-
"slender whip place siren tissue chaos ankle door only assume tent shallow",
475-
);
476-
assert_eq!(
477-
bip85_bip39(12, 1).unwrap().as_ref() as &str,
478-
"income soft level reunion height pony crane use unfold win keen satisfy",
479-
);
480-
assert_eq!(
481-
bip85_bip39(12, HARDENED - 1).unwrap().as_ref() as &str,
482-
"carry build nerve market domain energy mistake script puzzle replace mixture idea",
483-
);
484-
assert_eq!(
485-
bip85_bip39(18, 0).unwrap().as_ref() as &str,
486-
"enact peasant tragic habit expand jar senior melody coin acid logic upper soccer later earn napkin planet stereo",
487-
);
488-
assert_eq!(
489-
bip85_bip39(24, 0).unwrap().as_ref() as &str,
490-
"cabbage wink october add anchor mean tray surprise gasp tomorrow garbage habit beyond merge where arrive beef gentle animal office drop panel chest size",
491-
);
492-
493-
// Invalid number of words.
494-
assert!(bip85_bip39(10, 0).is_err());
495-
// Index too high.
496-
assert!(bip85_bip39(12, HARDENED).is_err());
497-
}
498-
499439
#[test]
500440
fn test_secp256k1_get_private_key() {
501441
lock();

0 commit comments

Comments
 (0)