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
14 changes: 9 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,26 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Install clang-format
run: sudo apt-get install -y clang-format-14

- name: Check code formatting
run: |
# Use pre-installed clang-format (ubuntu-latest ships with it)
CF=$(command -v clang-format-14 || command -v clang-format || true)
if [ -z "$CF" ]; then
echo "::warning::clang-format not found, skipping lint"
exit 0
fi
echo "Using: $CF ($($CF --version | head -1))"
FAILED=0
for f in $(find include/keepkey lib/firmware lib/board lib/transport/src \
-name '*.c' -o -name '*.h' 2>/dev/null | grep -v generated | grep -v '.pb.'); do
if ! clang-format-14 --style=file --dry-run --Werror "$f" 2>/dev/null; then
if ! $CF --style=file --dry-run --Werror "$f" 2>/dev/null; then
echo "::warning file=$f::Formatting differs from .clang-format"
FAILED=1
fi
done
if [ "$FAILED" = "1" ]; then
echo ""
echo "Run: clang-format -i <file> to fix formatting"
echo "Run: $CF -i <file> to fix formatting"
echo "Note: treating as warning until codebase is reformatted"
fi
# Warn-only until existing code is reformatted
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
1 change: 1 addition & 0 deletions include/keepkey/firmware/coins.def
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ X(true, "Testnet", true, "TEST", true, 111, true, 10000000, true, 19
X(true, "BitcoinCash", true, "BCH", true, 0, true, 500000, true, 5, true, "Bitcoin Signed Message:\n", true, 0x80000091, true, 0, true, 8, false, NO_CONTRACT, true, 76067358, true, false, true, true, true, SECP256K1_STRING, true, "bitcoincash", false, "", false, false, false, 0, false, 0, false, "", true, false )
X(true, "Namecoin", true, "NMC", true, 52, true, 10000000, true, 5, true, "Namecoin Signed Message:\n", true, 0x80000007, false, 0, true, 8, false, NO_CONTRACT, true, 27108450, true, false, true, false, true, SECP256K1_STRING, false, "", false, "", false, false, false, 0, false, 0, false, "", true, false )
X(true, "Litecoin", true, "LTC", true, 48, true, 1000000, true, 50, true, "Litecoin Signed Message:\n", true, 0x80000002, false, 0, true, 8, false, NO_CONTRACT, true, 27108450, true, true, true, false, true, SECP256K1_STRING, false, "", true, "ltc", false, false, true, 28471030, true, 78792518, false, "", true, false )
X(true, "Lynx", true, "LYNX", true, 45, true, 1000000, true, 22, true, "Bitcoin Signed Message:\n", true, 0x800000bf, false, 0, true, 8, false, NO_CONTRACT, true, 76067358, true, true, true, false, true, SECP256K1_STRING, false, "", true, "lynx", false, false, true, 77429938, true, 78792518, false, "", true, true )
X(true, "Dogecoin", true, "DOGE", true, 30, true, 1000000000, true, 22, true, "Dogecoin Signed Message:\n", true, 0x80000003, false, 0, true, 8, false, NO_CONTRACT, true, 49990397, true, false, true, false, true, SECP256K1_STRING, false, "", false, "", false, false, false, 0, false, 0, false, "", true, false )
X(true, "Dash", true, "DASH", true, 76, true, 100000, true, 16, true, "DarkCoin Signed Message:\n", true, 0x80000005, false, 0, true, 8, false, NO_CONTRACT, true, 50221772, true, false, true, false, true, SECP256K1_STRING, false, "", false, "", false, false, false, 0, false, 0, false, "", true, false )
X(true, ETHEREUM, true, "ETH", true, NA, true, 100000, true, NA, true, "Ethereum Signed Message:\n", true, 0x8000003c, true, 1, true, 18, false, NO_CONTRACT, false, 0, true, false, true, false, true, SECP256K1_STRING, false, "", false, "", false, false, false, 0, false, 0, false, "", true, false )
Expand Down
2 changes: 2 additions & 0 deletions include/keepkey/firmware/fsm.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ void fsm_msgMayachainGetAddress(const MayachainGetAddress *msg);
void fsm_msgMayachainSignTx(const MayachainSignTx *msg);
void fsm_msgMayachainMsgAck(const MayachainMsgAck *msg);

void fsm_msgGetBip85Mnemonic(const GetBip85Mnemonic *msg);

#if DEBUG_LINK
// void fsm_msgDebugLinkDecision(DebugLinkDecision *msg);
void fsm_msgDebugLinkGetState(DebugLinkGetState *msg);
Expand Down
17 changes: 17 additions & 0 deletions include/keepkey/transport/messages-solana.options
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
SolanaGetAddress.address_n max_count:8
SolanaGetAddress.coin_name max_size:21

SolanaAddress.address max_size:64

SolanaSignTx.address_n max_count:8
SolanaSignTx.coin_name max_size:21
SolanaSignTx.raw_tx max_size:2048

SolanaSignedTx.signature max_size:64

SolanaSignMessage.address_n max_count:8
SolanaSignMessage.coin_name max_size:21
SolanaSignMessage.message max_size:1024

SolanaMessageSignature.public_key max_size:32
SolanaMessageSignature.signature max_size:64
12 changes: 12 additions & 0 deletions include/keepkey/transport/messages-ton.options
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
TonGetAddress.address_n max_count:8
TonGetAddress.coin_name max_size:21

TonAddress.address max_size:50
TonAddress.raw_address max_size:70

TonSignTx.address_n max_count:8
TonSignTx.coin_name max_size:21
TonSignTx.raw_tx max_size:1024
TonSignTx.to_address max_size:50

TonSignedTx.signature max_size:64
14 changes: 14 additions & 0 deletions include/keepkey/transport/messages-tron.options
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
TronGetAddress.address_n max_count:8
TronGetAddress.coin_name max_size:21

TronAddress.address max_size:35

TronSignTx.address_n max_count:8
TronSignTx.coin_name max_size:21
TronSignTx.raw_data max_size:2048
TronSignTx.ref_block_bytes max_size:4
TronSignTx.ref_block_hash max_size:32
TronSignTx.contract_type max_size:64
TronSignTx.to_address max_size:35

TronSignedTx.signature max_size:65
2 changes: 2 additions & 0 deletions include/keepkey/transport/messages.options
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,5 @@ FlashHash.challenge max_size:32
FlashWrite.data max_size:1024

FlashHashResponse.data max_size:32

Bip85Mnemonic.mnemonic max_size:241
1 change: 1 addition & 0 deletions lib/firmware/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ set(sources
app_layout.c
authenticator.c
binance.c
bip85.c
coins.c
crypto.c
eip712.c
Expand Down
98 changes: 98 additions & 0 deletions lib/firmware/bip85.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#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 | 0; /* English language (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 @@ -58,6 +58,7 @@
#include "keepkey/firmware/storage.h"
#include "keepkey/firmware/tendermint.h"
#include "keepkey/firmware/thorchain.h"
#include "keepkey/firmware/bip85.h"
#include "keepkey/firmware/transaction.h"
#include "keepkey/firmware/txin_check.h"
#include "keepkey/firmware/u2f.h"
Expand Down Expand Up @@ -284,3 +285,4 @@ void fsm_msgClearSession(ClearSession *msg) {
#include "fsm_msg_tendermint.h"
#include "fsm_msg_thorchain.h"
#include "fsm_msg_mayachain.h"
#include "fsm_msg_bip85.h"
67 changes: 67 additions & 0 deletions lib/firmware/fsm_msg_bip85.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
void fsm_msgGetBip85Mnemonic(const GetBip85Mnemonic *msg) {
CHECK_INITIALIZED

/* Validate word count (required field, always present in nanopb) */
if (msg->word_count != 12 && msg->word_count != 18 && msg->word_count != 24) {
fsm_sendFailure(FailureType_Failure_SyntaxError,
"word_count must be 12, 18, or 24");
layoutHome();
return;
}

/* Reject index >= 0x80000000 (hardened-bit collision) */
if (msg->index & 0x80000000) {
fsm_sendFailure(FailureType_Failure_SyntaxError,
"index must be less than 2147483648");
layoutHome();
return;
}

CHECK_PIN

/* User confirmation — make clear a secret is being exported */
char desc[80];
snprintf(desc, sizeof(desc),
"Export %lu-word child seed at index %lu to host?",
(unsigned long)msg->word_count, (unsigned long)msg->index);

if (!confirm(ButtonRequestType_ButtonRequest_Other,
"BIP-85 Export Seed", "%s", desc)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled,
"BIP-85 derivation cancelled");
layoutHome();
return;
}

layout_simple_message("Deriving child seed...");

/* Derive the mnemonic */
static CONFIDENTIAL char mnemonic_buf[241];
if (!bip85_derive_mnemonic(msg->word_count, msg->index,
mnemonic_buf, sizeof(mnemonic_buf))) {
memzero(mnemonic_buf, sizeof(mnemonic_buf));
fsm_sendFailure(FailureType_Failure_Other,
"BIP-85 derivation failed");
layoutHome();
return;
}

/* Second confirmation before sending secret to host */
if (!confirm(ButtonRequestType_ButtonRequest_Other,
"Send to Computer?",
"The child seed will be sent to the connected computer. Continue?")) {
memzero(mnemonic_buf, sizeof(mnemonic_buf));
fsm_sendFailure(FailureType_Failure_ActionCancelled,
"BIP-85 export cancelled");
layoutHome();
return;
}

/* Send response */
RESP_INIT(Bip85Mnemonic);
strlcpy(resp->mnemonic, mnemonic_buf, sizeof(resp->mnemonic));
memzero(mnemonic_buf, sizeof(mnemonic_buf));

msg_write(MessageType_MessageType_Bip85Mnemonic, resp);
layoutHome();
}
4 changes: 4 additions & 0 deletions lib/firmware/messagemap.def
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
MSG_IN(MessageType_MessageType_MayachainSignTx, MayachainSignTx, fsm_msgMayachainSignTx)
MSG_IN(MessageType_MessageType_MayachainMsgAck, MayachainMsgAck, fsm_msgMayachainMsgAck)

MSG_IN(MessageType_MessageType_GetBip85Mnemonic, GetBip85Mnemonic, fsm_msgGetBip85Mnemonic)

/* Normal Out Messages */
MSG_OUT(MessageType_MessageType_Success, Success, NO_PROCESS_FUNC)
MSG_OUT(MessageType_MessageType_Failure, Failure, NO_PROCESS_FUNC)
Expand Down Expand Up @@ -132,6 +134,8 @@
MSG_OUT(MessageType_MessageType_MayachainMsgRequest, MayachainMsgRequest, NO_PROCESS_FUNC)
MSG_OUT(MessageType_MessageType_MayachainSignedTx, MayachainSignedTx, NO_PROCESS_FUNC)

MSG_OUT(MessageType_MessageType_Bip85Mnemonic, Bip85Mnemonic, NO_PROCESS_FUNC)

#if DEBUG_LINK
/* Debug Messages */
DEBUG_IN(MessageType_MessageType_DebugLinkDecision, DebugLinkDecision, NO_PROCESS_FUNC)
Expand Down
2 changes: 1 addition & 1 deletion lib/firmware/signing.c
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ void signing_init(const SignTx *msg, const CoinType *_coin,
branch_id = 0x5BA81B19; // Overwinter
break;
case 4:
branch_id = 0x76B809BB; // Sapling
branch_id = 0xC8E71055; // NU6
break;
}
}
Expand Down
Loading
Loading