Skip to content

Commit 57acc8d

Browse files
committed
Merge branch 'rust-bip85'
2 parents 289ed6d + 1ce7ce2 commit 57acc8d

File tree

12 files changed

+277
-296
lines changed

12 files changed

+277
-296
lines changed

src/keystore.c

Lines changed: 7 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -488,22 +488,15 @@ 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);
@@ -733,90 +726,6 @@ bool keystore_get_ed25519_seed(uint8_t* seed_out)
733726
return true;
734727
}
735728

736-
static bool _bip85_entropy(const uint32_t* keypath, size_t keypath_len, uint8_t* out)
737-
{
738-
struct ext_key xprv __attribute__((__cleanup__(keystore_zero_xkey))) = {0};
739-
if (!_get_xprv_twice(keypath, keypath_len, &xprv)) {
740-
return false;
741-
}
742-
const uint8_t* priv_key = xprv.priv_key + 1; // first byte is 0
743-
const uint8_t key[] = "bip-entropy-from-k";
744-
return wally_hmac_sha512(key, sizeof(key), priv_key, 32, out, 64) == WALLY_OK;
745-
}
746-
747-
bool keystore_bip85_bip39(
748-
uint32_t words,
749-
uint32_t index,
750-
char* mnemonic_out,
751-
size_t mnemonic_out_size)
752-
{
753-
size_t seed_size;
754-
switch (words) {
755-
case 12:
756-
seed_size = 16;
757-
break;
758-
case 18:
759-
seed_size = 24;
760-
break;
761-
case 24:
762-
seed_size = 32;
763-
break;
764-
default:
765-
return false;
766-
}
767-
768-
if (index >= BIP32_INITIAL_HARDENED_CHILD) {
769-
return false;
770-
}
771-
772-
const uint32_t keypath[] = {
773-
83696968 + BIP32_INITIAL_HARDENED_CHILD,
774-
39 + BIP32_INITIAL_HARDENED_CHILD,
775-
0 + BIP32_INITIAL_HARDENED_CHILD,
776-
words + BIP32_INITIAL_HARDENED_CHILD,
777-
index + BIP32_INITIAL_HARDENED_CHILD,
778-
};
779-
780-
uint8_t entropy[64] = {0};
781-
UTIL_CLEANUP_64(entropy);
782-
if (!_bip85_entropy(keypath, sizeof(keypath) / sizeof(uint32_t), entropy)) {
783-
return false;
784-
}
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;
794-
}
795-
796-
bool keystore_bip85_ln(uint32_t index, uint8_t* entropy_out)
797-
{
798-
if (index >= BIP32_INITIAL_HARDENED_CHILD) {
799-
return false;
800-
}
801-
802-
const uint32_t keypath[] = {
803-
83696968 + BIP32_INITIAL_HARDENED_CHILD,
804-
19534 + BIP32_INITIAL_HARDENED_CHILD,
805-
0 + BIP32_INITIAL_HARDENED_CHILD,
806-
12 + BIP32_INITIAL_HARDENED_CHILD,
807-
index + BIP32_INITIAL_HARDENED_CHILD,
808-
};
809-
810-
uint8_t entropy[64] = {0};
811-
UTIL_CLEANUP_64(entropy);
812-
if (!_bip85_entropy(keypath, sizeof(keypath) / sizeof(uint32_t), entropy)) {
813-
return false;
814-
}
815-
816-
memcpy(entropy_out, entropy, 16);
817-
return true;
818-
}
819-
820729
USE_RESULT bool keystore_encode_xpub_at_keypath(
821730
const uint32_t* keypath,
822731
size_t keypath_len,

src/keystore.h

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,14 @@ void keystore_lock(void);
116116
USE_RESULT bool keystore_is_locked(void);
117117

118118
/**
119-
* @param[out] mnemonic_out resulting mnemonic
120-
* @param[in] mnemonic_out_size size of mnemonic_out. Should be at least 216 bytes (longest possible
121-
* 24 word phrase plus null terminator).
122-
* @return returns false if the keystore is not unlocked or the mnemonic does not fit.
123-
* The resulting string should be safely zeroed after use.
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.
124121
*/
125-
USE_RESULT bool keystore_get_bip39_mnemonic(char* mnemonic_out, size_t mnemonic_out_size);
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);
126127

127128
/**
128129
* Turn a bip39 mnemonic into a seed. Make sure to use UTIL_CLEANUP_32 to destroy it.
@@ -230,31 +231,6 @@ USE_RESULT bool keystore_get_u2f_seed(uint8_t* seed_out);
230231
*/
231232
USE_RESULT bool keystore_get_ed25519_seed(uint8_t* seed_out);
232233

233-
/**
234-
* Computes a BIP39 mnemonic according to BIP-85:
235-
* https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki#bip39
236-
* @param[in] words must be 12, 18 or 24.
237-
* @param[in] index must be smaller than `BIP32_INITIAL_HARDENED_CHILD`.
238-
* @param[out] mnemonic_out resulting mnemonic
239-
* @param[in] mnemonic_out_size size of mnemonic_out. Should be at least 216 bytes (longest possible
240-
* 24 word phrase plus null terminator).
241-
*/
242-
USE_RESULT bool keystore_bip85_bip39(
243-
uint32_t words,
244-
uint32_t index,
245-
char* mnemonic_out,
246-
size_t mnemonic_out_size);
247-
248-
/**
249-
* Computes a 16 byte deterministic seed specifically for Lightning hot wallets according to BIP-85.
250-
* It is the same as BIP-85 with app number 39', but instead using app number 19534' (= 0x4c4e =
251-
* 'LN'). https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki#bip39
252-
* Restricted to 16 byte output entropy.
253-
* @param[in] index must be smaller than `BIP32_INITIAL_HARDENED_CHILD`.
254-
* @param[out] entropy_out resulting entropy, must be at least 16 bytes in size.
255-
*/
256-
USE_RESULT bool keystore_bip85_ln(uint32_t index, uint8_t* entropy_out);
257-
258234
/**
259235
* Encode an xpub at the given `keypath` as 78 bytes according to BIP32. The version bytes are
260236
* the ones corresponding to `xpub`, i.e. 0x0488B21E.

src/rust/bitbox02-rust/src/hash.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2025 Shift Crypto AG
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use alloc::vec::Vec;
16+
17+
/// Implements the digest traits for Sha512 backing it with the wally_sha512 C function. This is
18+
/// done to avoid using a second sha512 implementation like `sha2::Sha512`, which bloats the binary
19+
/// by an additional ~12.7kB (at the time of writing).
20+
///
21+
/// This implementation accumulates the data to be hashed in heap, it does **not** hash in a
22+
/// streaming fashion, even when using `update()`.
23+
#[derive(Default, Clone)]
24+
pub struct Sha512 {
25+
message: Vec<u8>,
26+
}
27+
28+
impl digest::HashMarker for Sha512 {}
29+
30+
impl digest::OutputSizeUser for Sha512 {
31+
type OutputSize = digest::typenum::U64;
32+
}
33+
34+
impl digest::FixedOutput for Sha512 {
35+
fn finalize_into(self, out: &mut digest::Output<Self>) {
36+
// use digest::Digest;
37+
// out.copy_from_slice(&sha2::Sha512::digest(&self.message));
38+
out.copy_from_slice(&bitbox02::sha512(&self.message));
39+
}
40+
}
41+
42+
impl digest::Update for Sha512 {
43+
fn update(&mut self, data: &[u8]) {
44+
self.message.extend(data);
45+
}
46+
}
47+
48+
impl digest::Reset for Sha512 {
49+
fn reset(&mut self) {
50+
self.message.clear()
51+
}
52+
}
53+
54+
impl digest::core_api::BlockSizeUser for Sha512 {
55+
type BlockSize = digest::typenum::U128;
56+
}

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

Lines changed: 4 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,5 +160,7 @@ async fn process_ln(
160160
})
161161
.await?;
162162

163-
keystore::bip85_ln(account_number).map_err(|_| Error::Generic)
163+
Ok(keystore::bip85_ln(account_number)
164+
.map_err(|_| Error::Generic)?
165+
.to_vec())
164166
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use pb::response::Response;
2222
use crate::hal::Ui;
2323
use crate::workflow::{confirm, mnemonic, unlock};
2424

25-
use bitbox02::keystore;
25+
use crate::keystore;
2626

2727
/// Handle the ShowMnemonic API call. This shows the seed encoded as
2828
/// 12/18/24 BIP39 English words. Afterwards, for each word, the user

0 commit comments

Comments
 (0)