Skip to content
Merged
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
2 changes: 1 addition & 1 deletion deps/device-protocol
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions docs/security/evidence/dice-entropy-reset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# On-device dice entropy in the ResetDevice flow

Emulator captures of `ResetDevice(dice_entropy=true, display_random=true,
strength=256)` driven by `scripts/emulator/capture-dice-flow.py` via
DebugLinkDecision.input injection.

The screen runs with `display_constant_power(true)` (PIN-matrix precedent:
dice rolls are seed material, and OLED supply current correlates with lit
pixels). The display driver fills x<128 with the inverse of x>=128, which is
why the left half of every capture shows a readable inverse copy — the user
faces the right half.

- `01-dice-screen-initial.png` — entry screen: `ROLL 1/99` counter, seven
selector cells (digits 1–6 + `<` undo), active cell rendered inverse-video
(white box, black glyph), `PRESS next HOLD ok` hint. Inactive digits are
legible on hardware (white on 0x22 gray) but collapse to solid white in the
1bpp DebugLink threshold; the inverse half documents them.
- `02-after-three-rolls.png` — after injecting `123`: counter `ROLL 4/99`,
status `Entered 3 (3)`.
- `03-after-undo.png` — after injecting `u`: counter back to `ROLL 3/99`,
status `Removed #3`.
- `04-digest-confirm.png` — completion screen: `99 rolls recorded. Digest:
6CFC611198F53A73` = the first 8 bytes of SHA-256 of the ASCII roll string,
independently recomputed host-side from the injected chunks (append/undo
rules simulated) and matching exactly.
- `05-postmix-internal-entropy.png` — the standard Internal Entropy screen
now shows the POST-dice-mix value: the displayed commitment is
`SHA256(rng32 || rolls)`, produced before EntropyRequest is sent, so
`sha256(displayed || external)` still reproduces the mnemonic (asserted by
`test_msg_resetdevice.py::test_reset_device_dice`).
- `06-backup-explainer.png` — flow continues into the unchanged backup path.

Emulator captures do not satisfy Gate-3 on their own: an on-device pass of
the entry screen (short-press advance, 800 ms hold commit, undo, digest
match against physically entered rolls) is still owed before release. The
hardware press/release/debounce path (`dice_on_press`/`dice_on_release`)
does not execute in the emulator at all.
49 changes: 49 additions & 0 deletions include/keepkey/firmware/dice_input.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* This file is part of the KeepKey project.
*
* Copyright (C) 2026 KeepKey
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef KEEPKEY_FIRMWARE_DICE_INPUT_H
#define KEEPKEY_FIRMWARE_DICE_INPUT_H

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

/* d6 carries log2(6) = 2.585 bits per roll; targets follow the Coldcard
* convention of 50 rolls per 128-bit seed and 99 per 256-bit. */
#define DICE_MAX_ROLLS 99

/// Number of rolls required for a given seed strength (128/192/256).
uint32_t dice_rolls_for_strength(uint32_t strength_bits);

/// Collect `target` dice rolls on the device with the single button:
/// short press advances the 1-6/UNDO selector, holding the button commits
/// the selection. Announces itself with ButtonRequest_DiceRoll and accepts
/// input only after the host's ButtonAck. Under DEBUG_LINK, characters
/// '1'-'6' and 'u' (undo) arriving in DebugLinkDecision.input are treated
/// as committed selections.
///
/// Fills `rolls` with `target` ASCII digits '1'-'6' (no terminator is
/// appended past target; the caller owns zeroization). Returns false if the
/// host cancelled (Cancel/Initialize).
bool dice_input_collect(char *rolls, uint32_t target);

/// entropy = SHA256(entropy[32] || rolls[count]); the caller displays or
/// commits only the post-mix value.
void dice_mix(uint8_t entropy[32], const char *rolls, uint32_t count);

#endif
4 changes: 3 additions & 1 deletion include/keepkey/firmware/reset.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ extern char mnemonic_scratch_word[MAX_WORD_LEN + ADDITIONAL_WORD_PAD];
void reset_init(bool display_random, uint32_t _strength,
bool passphrase_protection, bool pin_protection,
const char* language, const char* label, bool _no_backup,
uint32_t _auto_lock_delay_ms, uint32_t _u2f_counter);
uint32_t _auto_lock_delay_ms, uint32_t _u2f_counter,
bool dice_entropy);
void reset_entropy(const uint8_t* ext_entropy, uint32_t len);
uint32_t reset_get_int_entropy(uint8_t* entropy);
const char* reset_get_word(void);
uint32_t reset_get_dice_digest(uint8_t* digest);

#endif
5 changes: 5 additions & 0 deletions include/keepkey/transport/messages.options
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ DebugLinkState.recovery_cipher max_size:27
DebugLinkState.recovery_auto_completed_word max_size:12
DebugLinkState.firmware_hash max_size:32
DebugLinkState.storage_hash max_size:32
DebugLinkState.dice_digest max_size:32

# Sized so the decoded struct stays within MSG_TINY_BFR_SZ (64B): the tiny
# message path pb_decodes DebugLinkDecision straight into that buffer.
DebugLinkDecision.input max_size:41

DebugLinkFlashDumpResponse.data max_size:1024

Expand Down
1 change: 1 addition & 0 deletions lib/firmware/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ set(sources
bip85.c
coins.c
crypto.c
dice_input.c
fsm.c
home_sm.c
passphrase_sm.c
Expand Down
Loading
Loading