Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion include/keepkey/firmware/app_layout.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ void layout_zcash_address_text_notification(const char* desc,
const char* address,
NotificationType type);
void layout_pin(const char* str, char* pin);
void layout_cipher(const char* current_word, const char* cipher);
void layout_cipher(const char* current_word, const char* cipher,
const char* prev_word_info);
void layout_address(const char* address, QRSize qr_size);
void set_leaving_handler(leaving_handler_t leaving_func);

Expand Down
22 changes: 22 additions & 0 deletions include/keepkey/firmware/bip85.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef BIP85_H
#define BIP85_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

/**
* Derive a child BIP-39 mnemonic via BIP-85.
*
* Path: m/83696968'/39'/0'/<word_count>'/<index>'
*
* @param word_count Number of words: 12, 18, or 24.
* @param index Child index (0-based).
* @param mnemonic Output buffer (must be at least 241 bytes).
* @param mnemonic_len Size of the output buffer.
* @return true on success, false on error.
*/
bool bip85_derive_mnemonic(uint32_t word_count, uint32_t index, char *mnemonic,
size_t mnemonic_len);

#endif
2 changes: 2 additions & 0 deletions include/keepkey/firmware/fsm.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,6 @@ void fsm_msgFlashWrite(FlashWrite* msg);
void fsm_msgFlashHash(FlashHash* msg);
void fsm_msgSoftReset(SoftReset* msg);

void fsm_msgGetBip85Mnemonic(const GetBip85Mnemonic* msg);

#endif
48 changes: 39 additions & 9 deletions lib/board/signatures.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
#include "trezor/crypto/sha2.h"
#include "trezor/crypto/ecdsa.h"
#include "trezor/crypto/secp256k1.h"
#include "trezor/crypto/memzero.h"
#include "keepkey/board/memory.h"
#include "keepkey/board/memcmp_s.h"
#include "keepkey/board/signatures.h"
#include "keepkey/board/pubkeys.h"

Expand Down Expand Up @@ -68,23 +70,51 @@ int signatures_ok(void) {
return KEY_EXPIRED;
} /* Expired signing key */

/* F3 hardening: double-compute SHA-256, compare in constant time */
uint8_t firmware_fingerprint2[32];
sha256_Raw((uint8_t*)FLASH_APP_START, codelen, firmware_fingerprint);
asm volatile("" ::: "memory");
sha256_Raw((uint8_t*)FLASH_APP_START, codelen, firmware_fingerprint2);

if (ecdsa_verify_digest(&secp256k1, pubkey[sigindex1 - 1],
(uint8_t*)FLASH_META_SIG1,
firmware_fingerprint) != 0) { /* Failure */
if (memcmp_s(firmware_fingerprint, firmware_fingerprint2, 32) != 0) {
memzero(firmware_fingerprint, sizeof(firmware_fingerprint));
memzero(firmware_fingerprint2, sizeof(firmware_fingerprint2));
return SIG_FAIL;
}
memzero(firmware_fingerprint2, sizeof(firmware_fingerprint2));

if (ecdsa_verify_digest(&secp256k1, pubkey[sigindex2 - 1],
(uint8_t*)FLASH_META_SIG2,
firmware_fingerprint) != 0) { /* Failure */
/* F3 hardening: infective aggregation — accumulate all three ECDSA
* results instead of early-returning on each. Forces attacker to
* corrupt all three verify calls, not just skip one branch. */
volatile int verify_acc = 0;
volatile int verify_sentinel = 0;

verify_acc |=
ecdsa_verify_digest(&secp256k1, pubkey[sigindex1 - 1],
(uint8_t*)FLASH_META_SIG1, firmware_fingerprint);
verify_sentinel++;
asm volatile("" ::: "memory");

verify_acc |=
ecdsa_verify_digest(&secp256k1, pubkey[sigindex2 - 1],
(uint8_t*)FLASH_META_SIG2, firmware_fingerprint);
verify_sentinel++;
asm volatile("" ::: "memory");

verify_acc |=
ecdsa_verify_digest(&secp256k1, pubkey[sigindex3 - 1],
(uint8_t*)FLASH_META_SIG3, firmware_fingerprint);
verify_sentinel++;
asm volatile("" ::: "memory");

memzero(firmware_fingerprint, sizeof(firmware_fingerprint));

/* All three verifies must have executed and all must have passed */
if (verify_sentinel != 3) {
return SIG_FAIL;
}

if (ecdsa_verify_digest(&secp256k1, pubkey[sigindex3 - 1],
(uint8_t*)FLASH_META_SIG3,
firmware_fingerprint) != 0) { /* Failure */
if (verify_acc != 0) {
return SIG_FAIL;
}

Expand Down
1 change: 1 addition & 0 deletions lib/firmware/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ set(sources
app_confirm.c
app_layout.c
authenticator.c
bip85.c
binance.c
coins.c
crypto.c
Expand Down
17 changes: 14 additions & 3 deletions lib/firmware/app_layout.c
Original file line number Diff line number Diff line change
Expand Up @@ -766,16 +766,27 @@ void layout_pin(const char* str, char pin[]) {
* OUTPUT
* none
*/
void layout_cipher(const char* current_word, const char* cipher) {
void layout_cipher(const char* current_word, const char* cipher,
const char* prev_word_info) {
DrawableParams sp;
const Font* title_font = get_body_font();
Canvas* canvas = layout_get_canvas();

call_leaving_handler();
layout_clear();

/* Draw prompt */
sp.y = 11;
/* Draw previous word info at top-left -- must be x < 76 to avoid
* being wiped by cipher animation which clears x >= CIPHER_START_X */
if (prev_word_info && prev_word_info[0]) {
sp.y = 2;
sp.x = 4;
sp.color = CIPHER_FONT_COLOR; /* gray -- less prominent than current word */
draw_string(canvas, title_font, prev_word_info, &sp, 68,
font_height(title_font));
}

/* Draw prompt -- push down when prev word is shown */
sp.y = (prev_word_info && prev_word_info[0]) ? 14 : 11;
sp.x = 4;
sp.color = BODY_COLOR;
draw_string(canvas, title_font, "Recovery Cipher:", &sp, 58,
Expand Down
105 changes: 105 additions & 0 deletions lib/firmware/bip85.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include "keepkey/firmware/bip85.h"
#include "keepkey/firmware/storage.h"
#include "trezor/crypto/bip32.h"
#include "trezor/crypto/bip39.h"
#include "trezor/crypto/curves.h"
#include "trezor/crypto/hmac.h"
#include "trezor/crypto/memzero.h"

#include <string.h>

/*
* BIP-85: Deterministic Entropy From BIP32 Keychains
*
* For BIP-39 mnemonic derivation:
* path = m / 83696968' / 39' / 0' / <word_count>' / <index>'
* k = derived_node.private_key (32 bytes)
* hmac = HMAC-SHA512(key="bip-entropy-from-k", msg=k)
* entropy = hmac[0 : entropy_bytes]
* 12 words -> 16 bytes, 18 words -> 24 bytes, 24 words -> 32 bytes
* mnemonic = bip39_from_entropy(entropy)
*/

/* BIP-85 application number for deriving entropy from a key */
static const uint8_t BIP85_HMAC_KEY[] = "bip-entropy-from-k";
#define BIP85_HMAC_KEY_LEN 18

bool bip85_derive_mnemonic(uint32_t word_count, uint32_t index, char *mnemonic,
size_t mnemonic_len) {
/* Reject index >= 0x80000000 to avoid hardened-bit collision */
if (index & 0x80000000) {
return false;
}

/* Validate word count and compute entropy length */
int entropy_bytes;
switch (word_count) {
case 12:
entropy_bytes = 16;
break;
case 18:
entropy_bytes = 24;
break;
case 24:
entropy_bytes = 32;
break;
default:
return false;
}

/* BIP-85 derivation path: m/83696968'/39'/0'/<word_count>'/<index>' */
uint32_t address_n[5];
address_n[0] = 0x80000000 | 83696968; /* purpose (hardened) */
address_n[1] = 0x80000000 | 39; /* BIP-39 app (hardened) */
address_n[2] = 0x80000000; /* English language 0 (hardened) */
address_n[3] = 0x80000000 | word_count; /* word count (hardened) */
address_n[4] = 0x80000000 | index; /* child index (hardened) */

/* Get the master node from storage (respects passphrase) */
static CONFIDENTIAL HDNode node;
if (!storage_getRootNode(SECP256K1_NAME, true, &node)) {
memzero(&node, sizeof(node));
return false;
}

/* Derive to the BIP-85 path */
for (int i = 0; i < 5; i++) {
if (hdnode_private_ckd(&node, address_n[i]) == 0) {
memzero(&node, sizeof(node));
return false;
}
}

/* HMAC-SHA512(key="bip-entropy-from-k", msg=private_key) */
static CONFIDENTIAL uint8_t hmac_out[64];
hmac_sha512(BIP85_HMAC_KEY, BIP85_HMAC_KEY_LEN, node.private_key, 32,
hmac_out);

/* We no longer need the derived node */
memzero(&node, sizeof(node));

/* Truncate HMAC output to the required entropy length */
static CONFIDENTIAL uint8_t entropy[32];
memcpy(entropy, hmac_out, entropy_bytes);
memzero(hmac_out, sizeof(hmac_out));

/* Convert entropy to BIP-39 mnemonic */
const char *words = mnemonic_from_data(entropy, entropy_bytes);
memzero(entropy, sizeof(entropy));

if (!words) {
return false;
}

/* Copy to output buffer */
size_t words_len = strlen(words);
if (words_len >= mnemonic_len) {
mnemonic_clear();
return false;
}

memcpy(mnemonic, words, words_len + 1);
mnemonic_clear();

return true;
}
2 changes: 2 additions & 0 deletions lib/firmware/fsm.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "keepkey/firmware/app_confirm.h"
#include "keepkey/firmware/app_layout.h"
#include "keepkey/firmware/authenticator.h"
#include "keepkey/firmware/bip85.h"
#include "keepkey/firmware/coins.h"
#include "keepkey/firmware/cosmos.h"
#include "keepkey/firmware/binance.h"
Expand Down Expand Up @@ -301,3 +302,4 @@ void fsm_msgClearSession(ClearSession* msg) {
#include "fsm_msg_solana.h"
#include "fsm_msg_hive.h"
#include "fsm_msg_zcash.h"
#include "fsm_msg_bip85.h"
Loading