Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
47c4781
feat(core): taproot, transport and board work for 7.14.2 through 7.15
BitHighlander Aug 23, 2026
e2ffeca
feat(chains): EVM disclosure, hive, TRON/TON messages, Tendermint policy
BitHighlander Aug 23, 2026
7c22083
feat(zcash): Orchard shielded signing, with the RNG gate it needs
BitHighlander Aug 23, 2026
0715928
feat(clearsign): describe what the device can verify, and attest schemas
BitHighlander Aug 23, 2026
d106436
feat(rng): auditable entropy source, on-device dice, seed-time gate
BitHighlander Aug 23, 2026
910e383
feat(storage): the version gate, the bitcoin-only band, and KDF harde…
BitHighlander Aug 23, 2026
a4e9d60
build: bitcoin-only variant, release infrastructure and CI gates
BitHighlander Aug 23, 2026
6f3becc
docs: release documentation for 7.14.2, 7.14.3 and 7.15
BitHighlander Aug 23, 2026
2593d25
build(deps): pin device-protocol and python-keepkey
BitHighlander Aug 23, 2026
b005778
docs(release): trim to what's current for the 7.15 merge
BitHighlander Aug 23, 2026
3f74217
Merge pull request #463 from BitHighlander/docs/trim-release-7.15
BitHighlander Aug 23, 2026
3c0a907
docs(release): drop remaining process/handoff docs
BitHighlander Aug 23, 2026
0588c8d
fix(zcash): derive the RedPallas nonce per the RedDSA spec
BitHighlander Aug 23, 2026
e23bcbd
fix(security): remediate the 7.15 audit findings
BitHighlander Aug 23, 2026
8661918
fix(signing): bound multisig signatures and stop writing past the field
BitHighlander Aug 23, 2026
a3da828
fix(signing): close audited disclosure and ZIP-229 gaps
BitHighlander Aug 23, 2026
9e4054d
fix(security): seed-backup clipping, session teardown, storage lock, …
BitHighlander Aug 23, 2026
effdcd9
revert(layout): do not page constant-power screens; keep the measurement
BitHighlander Aug 23, 2026
b7bada9
rehearsal: stage upstream 7.15 onto fork develop
BitHighlander Aug 26, 2026
41ec6ee
fix(release): close RNG and Bitcoin signing blockers
BitHighlander Aug 26, 2026
400ef2c
Merge remote-tracking branch 'origin/develop' into rehearsal/7.15-to-…
BitHighlander Aug 26, 2026
021a53b
fix: close PR 604 security audit findings
BitHighlander Aug 26, 2026
3ee3535
fix(authenticator): preserve cleanup on validated inputs
BitHighlander Aug 26, 2026
c1b42f2
ci: preserve variant gates after 7.15 restack
BitHighlander Aug 26, 2026
23c10d5
fix(authenticator): use 7.15 cancellation result
BitHighlander Aug 26, 2026
b484078
fix(recovery): complete 7.15 security state port
BitHighlander Aug 26, 2026
4c7f7d6
test(rng): restore emulator fault-injection seam
BitHighlander Aug 26, 2026
bf88b95
test(rng): isolate boot-lifetime latch cases
BitHighlander Aug 26, 2026
1ace61e
fix(build): keep RNG test seam emulator-only
BitHighlander Aug 26, 2026
b5b3c9c
test(bitcoin-only): gate full-feature companion cases
BitHighlander Aug 26, 2026
c054665
test(report): validate the selected product variant
BitHighlander Aug 26, 2026
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
533 changes: 435 additions & 98 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

291 changes: 231 additions & 60 deletions .github/workflows/release.yml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ build
.DS_Store
.vscode/

build-btconly-check/
43 changes: 40 additions & 3 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,47 @@
title = "KeepKey firmware gitleaks configuration"
title = "KeepKey firmware Gitleaks configuration"

# Start from the upstream ruleset and only narrow it, never widen it.
[extend]
useDefault = true

[allowlist]
[[allowlists]]
description = "Published AES test vectors in current and historical trezor-crypto layouts"
targetRules = ["generic-api-key"]
condition = "AND"
regexTarget = "line"
paths = [
'''^deps/crypto/trezor-firmware/crypto/aes/aestst\.c$''',
'''^deps/crypto/trezor-crypto/aes/aestst\.c$''',
]
regexes = ['''(?i)^[[:space:]]*//[[:space:]]*key[[:space:]]*=[[:space:]]*[0-9a-f]+[[:space:]]*$''']

[[allowlists]]
description = "Ed25519 C type names in current and historical trezor-crypto layouts"
targetRules = ["generic-api-key"]
condition = "AND"
regexTarget = "line"
paths = [
'''^deps/crypto/trezor-firmware/crypto/ed25519-donna/ed25519-(blake2b|keccak|sha3)\.h$''',
'''^deps/crypto/trezor-firmware/crypto/ed25519-donna/ed25519\.[ch]$''',
'''^deps/crypto/trezor-crypto/ed25519-donna/ed25519-(blake2b|keccak|sha3)\.h$''',
'''^deps/crypto/trezor-crypto/ed25519-donna/ed25519\.[ch]$''',
]
regexes = ['''ed25519.*secret_key.*signature''']

[[allowlists]]
description = "RC21 release provenance names an exact public python-keepkey git commit"
targetRules = ["generic-api-key"]
condition = "AND"
regexTarget = "line"
paths = ['''^docs/security/7\.15\.0-rc21-clearsign-release-control\.md$''']
regexes = ['''^[[:space:]]*-[[:space:]]*python-keepkey:[[:space:]]*`c406a1ba9120da410c356dbff7f4d4bd1e1758fa`\.[[:space:]]*$''']

# Converted from the deprecated singular [allowlist] to a fourth [[allowlists]]
# entry. gitleaks refuses to load a config containing both forms ("[allowlist] is
# deprecated, it cannot be used alongside [[allowlists]]"), which is what the
# alpha<-develop merge produced: alpha carried three [[allowlists]] and develop
# carried this one [allowlist]. The merge kept both and the scan died at config
# load, taking the whole build graph with it. Content is unchanged.
[[allowlists]]
description = """
Two U2F attestation artifacts are public by design and must not fail the scan.

Expand Down
8 changes: 4 additions & 4 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[submodule "deps/device-protocol"]
path = deps/device-protocol
url = https://github.com/keepkey/device-protocol.git
branch = master
url = https://github.com/keepkey/device-protocol.git
branch = up/release-protocol
[submodule "deps/trezor-firmware"]
path = deps/crypto/trezor-firmware
url = https://github.com/keepkey/trezor-firmware.git
url = https://github.com/BitHighlander/trezor-firmware.git
[submodule "googletest"]
path = deps/googletest
url = https://github.com/google/googletest.git
Expand All @@ -14,7 +14,7 @@ url = https://github.com/keepkey/code-signing-keys.git
[submodule "deps/python-keepkey"]
path = deps/python-keepkey
url = https://github.com/keepkey/python-keepkey.git
branch = master
branch = reconcile/upstream-sync
[submodule "deps/qrenc/QR-Code-generator"]
path = deps/qrenc/QR-Code-generator
url = https://github.com/keepkey/QR-Code-generator.git
Expand Down
67 changes: 61 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ endif()

project(
KeepKeyFirmware
VERSION 7.14.3
VERSION 7.15.0
LANGUAGES C CXX ASM)

set(BOOTLOADER_MAJOR_VERSION 2)
Expand All @@ -22,6 +22,16 @@ option(KK_DEBUG_LINK "Build with debug-link enabled" OFF)
option(KK_BUILD_FUZZERS "Build the fuzzers?" OFF)
option(KK_BITCOIN_ONLY "Build Bitcoin-only firmware (strip all non-BTC coins)"
OFF)
# Zcash shielded/Orchard support is part of the regular firmware. It is an
# internal compile selection, not a third release variant: bitcoin-only strips
# the Zcash coin and privacy engine; every regular device/emulator build ships
# both. The open constant-time Pallas audit finding remains a release gate for
# RC18, but it must not silently change the product being audited.
if(KK_BITCOIN_ONLY)
set(KK_ZCASH_PRIVACY OFF)
else()
set(KK_ZCASH_PRIVACY ON)
endif()

# When building the dylib, every static lib it links (kkfirmware, kkboard,
# trezorcrypto, kkrand, kktransport, qrcodegenerator, SecAESSTM32, ...) must
Expand Down Expand Up @@ -64,7 +74,7 @@ endif()
if(NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/crypto/trezor-firmware/crypto/Makefile)
message(
FATAL_ERROR
" trezor-crypto missing. Need to 'git submodule update --init --recursive"
"trezor-crypto fork missing. Run 'git submodule update --init deps/crypto/trezor-firmware'"
)
endif()

Expand All @@ -86,6 +96,13 @@ endif()
if(${KK_EMULATOR})
add_definitions(-DEMULATOR)
add_definitions(-DCONFIDENTIAL=)
# macOS/BSD declare strlcpy/strlcat in <string.h>; glibc (Linux) and MinGW
# (Windows) do not. Force-include the prototypes so the ~20 call sites build
# without -Werror=implicit-function-declaration (definitions come from
# lib/board/strlcpy.c + strlcat.c). Apple already has them in <string.h>.
if(NOT APPLE)
add_compile_options(-include ${CMAKE_SOURCE_DIR}/include/keepkey/board/bsd_compat.h)
endif()
else()
add_definitions(-DCONFIDENTIAL=__attribute__\(\(section\("confidential"\)\)\))
endif()
Expand Down Expand Up @@ -115,6 +132,13 @@ add_definitions(-DUSE_CARDANO=0)
add_definitions(-DUSE_MONERO=0)
add_definitions(-DUSE_NEM=0)

# NOT a USE_* style on/off toggle despite sitting next to them: trezor-crypto's
# rand.c tests this macro with #ifndef, so only its *definedness* matters. The
# old -D...=0 spelling read as "off" while actually meaning "on", and the
# insecure LCG random32() stayed out of the build purely by that double
# negation. Define it bare, matching upstream trezor-core's SConscript.firmware,
# so a future cleanup of an apparent "=0 means unused" define cannot silently
# compile in the LCG. lib/rand/rng.c #errors if this ever goes missing.
# trezor-crypto's rand.c tests only whether this macro is defined. A value of
# zero therefore did not disable anything; it excluded the library's insecure
# test LCG by definedness. Use the upstream spelling so that intent is clear,
Expand All @@ -137,22 +161,37 @@ add_definitions(-DBIP39_WORDLIST_PADDED=1)

add_definitions(-DAES_128=1)

# NOTE: AES table size is selected per release product below. The regular
# image includes Zcash and its Pallas curve arithmetic, so it uses the smaller
# AES tables to preserve flash headroom. Bitcoin-only keeps FOUR_TABLES AES.

if(${KK_DEBUG_LINK})
add_definitions(-DDEBUG_LINK=1)
else()
add_definitions(-DDEBUG_LINK=0)
endif()

# Always defined, 0 or 1, and always tested with `#if BITCOIN_ONLY`. Device
# builds compile with -Wundef -Werror, so an undefined identifier inside `#if`
# is a hard error rather than a silent zero -- which is what we want, because
# a silently-zero guard would ship the coin engines into the stripped image.
# Value macros: always defined, 0 or 1, and always tested with `#if FLAG`.
# Device builds compile with -Wundef -Werror, so an undefined identifier inside
# `#if` is a hard error rather than a silent zero -- which is what we want,
# because a silently-zero guard would ship the coin engines into the stripped
# image.
if(${KK_BITCOIN_ONLY})
add_definitions(-DBITCOIN_ONLY=1)
else()
add_definitions(-DBITCOIN_ONLY=0)
endif()

if(${KK_ZCASH_PRIVACY})
add_definitions(-DZCASH_PRIVACY=1)
# The Orchard engine leaves the regular image tightest on flash; shrink the
# Gladman AES lookup tables from 4KB to 1KB each (-15,360 bytes ROM,
# slightly slower AES). Bitcoin-only keeps the fast FOUR_TABLES.
add_definitions(-DAES_SMALL_TABLES)
else()
add_definitions(-DZCASH_PRIVACY=0)
endif()

if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
add_definitions(-DDEBUG_ON)
add_definitions(-DMEMORY_PROTECT=0)
Expand Down Expand Up @@ -185,6 +224,12 @@ if(NOT ${KK_EMULATOR})
link_directories(${LIBOPENCM3_PATH}/lib)
include_directories(${LIBOPENCM3_PATH}/include)

# Emit per-function stack-frame sizes (.su files) on device builds. CI's
# SRAM budget gate (tools/check_sram_budget.py) reports the largest frames
# and fails when the linker-asserted stack reserve minus the largest frame
# leaves less than the configured margin. See tools/firmware/keepkey.ld.
add_compile_options(-fstack-usage)

# Dummy empty libraries for stack smashing protection support, since we
# implement __stack_chk_guard and __stack_chk_fail ourselves.
file(WRITE ${CMAKE_BINARY_DIR}/ssp.c "")
Expand Down Expand Up @@ -213,6 +258,10 @@ if(${KK_EMULATOR})
add_test(test-firmware ${CMAKE_BINARY_DIR}/bin/firmware-unit)
add_test(test-board ${CMAKE_BINARY_DIR}/bin/board-unit)
add_test(test-crypto ${CMAKE_BINARY_DIR}/bin/crypto-unit)
if(${KK_ZCASH_PRIVACY})
add_test(test-pallas-ct ${CMAKE_BINARY_DIR}/bin/pallas-ct-unit)
add_test(test-zcash-crypto ${CMAKE_BINARY_DIR}/bin/zcash-crypto-unit)
endif()

add_custom_target(
xunit
Expand All @@ -223,4 +272,10 @@ if(${KK_EMULATOR})
COMMAND ${CMAKE_BINARY_DIR}/bin/crypto-unit
--gtest_output=xml:${CMAKE_BINARY_DIR}/unittests/crypto.xml)

if(${KK_ZCASH_PRIVACY})
add_custom_command(TARGET xunit POST_BUILD
COMMAND ${CMAKE_BINARY_DIR}/bin/pallas-ct-unit
--gtest_output=xml:${CMAKE_BINARY_DIR}/unittests/pallas-ct.xml)
endif()

endif()
42 changes: 42 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Convenience targets — mirrors CI jobs so failures are caught locally.
#
# CI pins clang-format-20. Use that version if available, otherwise fall back.
# To install: brew install llvm@20 or apt-get install clang-format-20
CLANG_FORMAT ?= $(shell command -v clang-format-20 2>/dev/null || echo clang-format)

# Directories and exclusions must match .github/workflows/ci.yml lint-format job.
LINT_DIRS := include/keepkey lib/firmware lib/board lib/transport/src
LINT_SOURCES := $(shell find $(LINT_DIRS) -name '*.c' -o -name '*.h' 2>/dev/null \
| grep -v generated | grep -v '\.pb\.')

.PHONY: lint format help

## lint: Check formatting (same rules as CI). Exits non-zero on any violation.
lint:
@echo "clang-format version: $$($(CLANG_FORMAT) --version)"
@FAILED=0; \
for f in $(LINT_SOURCES); do \
if ! $(CLANG_FORMAT) --style=file --dry-run --Werror "$$f" 2>/dev/null; then \
echo " NEEDS FORMAT: $$f"; \
FAILED=1; \
fi; \
done; \
if [ "$$FAILED" = "1" ]; then \
echo ""; \
echo "Run 'make format' to fix all files."; \
exit 1; \
else \
echo "All files pass clang-format check."; \
fi

## format: Auto-fix formatting in-place for all source files.
format:
@echo "Formatting $(LINT_DIRS)..."
@for f in $(LINT_SOURCES); do \
$(CLANG_FORMAT) --style=file -i "$$f"; \
done
@echo "Done. Review changes with: git diff"

## help: List available targets.
help:
@grep -E '^## ' $(MAKEFILE_LIST) | sed 's/^## / make /'
9 changes: 8 additions & 1 deletion cmake/caches/device.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,14 @@ set(WARN_FLAGS
-Werror")


set(KK_C_FLAGS "${ARCH_FLAGS} -std=gnu99 ${WARN_FLAGS}" CACHE STRING "")
# Newlib's snprintf unconditionally links the float engine (_svfprintf_r,
# _dtoa_r, soft-double libgcc, malloc) — ~22 KB of ROM with zero %f users in
# the firmware. Route all callers to the integer-only siprintf family instead.
# %lld/%llu still work (this toolchain's libc.a compiles the integer engine
# with long-long support). Device builds only; host/emulator keep libc printf.
set(PRINTF_FLAGS "-Dsnprintf=sniprintf -Dvsnprintf=vsniprintf")

set(KK_C_FLAGS "${ARCH_FLAGS} -std=gnu99 ${WARN_FLAGS} ${PRINTF_FLAGS}" CACHE STRING "")
set(KK_CXX_FLAGS "${ARCH_FLAGS} -std=gnu++11 ${WARN_FLAGS} \
-fno-exceptions \
-fno-rtti \
Expand Down
40 changes: 40 additions & 0 deletions cmake/toolchains/mingw-w64-x86_64.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# MinGW-w64 cross-compile toolchain for the Windows emulator DLL (libkkemu.dll,
# x86_64). Lets us cross-build the Windows DLL from the existing macOS/Linux
# emulator build host — no Windows runner required.
#
# Usage:
# cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64-x86_64.cmake \
# -DKK_EMULATOR=ON -DKK_BUILD_DYLIB=ON -DKK_DEBUG_LINK=ON ...
# cmake --build <dir> --target kkemulator_dylib
#
# Install MinGW: `brew install mingw-w64` (macOS) / `apt-get install mingw-w64`.
#
# Only the kkemulator_dylib target is meant to cross-compile. The standalone
# UDP `kkemu` binary is gated out on Windows (tools/emulator/CMakeLists.txt).

set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

set(TOOLCHAIN_PREFIX x86_64-w64-mingw32)
find_program(CMAKE_C_COMPILER NAMES ${TOOLCHAIN_PREFIX}-gcc)
find_program(CMAKE_CXX_COMPILER NAMES ${TOOLCHAIN_PREFIX}-g++)
find_program(CMAKE_RC_COMPILER NAMES ${TOOLCHAIN_PREFIX}-windres)

if(NOT CMAKE_C_COMPILER)
message(FATAL_ERROR
"${TOOLCHAIN_PREFIX}-gcc not found. Install MinGW-w64 "
"(brew install mingw-w64 / apt-get install mingw-w64).")
endif()

# Derive the target sysroot from the compiler location so this works across
# Homebrew versions and Linux package layouts.
get_filename_component(_kk_cc "${CMAKE_C_COMPILER}" REALPATH)
get_filename_component(_kk_bin "${_kk_cc}" DIRECTORY)
get_filename_component(_kk_root "${_kk_bin}/.." ABSOLUTE)
set(CMAKE_FIND_ROOT_PATH "${_kk_root}/${TOOLCHAIN_PREFIX}")

# Find host programs on the host; libraries/headers in the target sysroot.
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
13 changes: 12 additions & 1 deletion deps/crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ set(sources
#trezor-firmware/crypto/aes/aestst.c
trezor-firmware/crypto/aes/aestab.c)

# Pallas/Orchard curve arithmetic (~2.4k LOC) -- only the Zcash shielded engine
# uses it. Excluded from the default and bitcoin-only images.
if(${KK_ZCASH_PRIVACY})
list(APPEND sources
trezor-firmware/crypto/pallas.c
trezor-firmware/crypto/pallas_ct.c
trezor-firmware/crypto/pallas_sinsemilla.c
trezor-firmware/crypto/pallas_swu.c
trezor-firmware/crypto/redpallas.c
trezor-firmware/crypto/zcash_zip316.c)
endif()

# Clang 5.0 in the docker image (kktech/firmware:v7) is missing
# <xmmintrin.h>, which breaks these. Until they're needed, we'll just elide
# them.
Expand All @@ -69,7 +81,6 @@ set(sources

include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/trezor-firmware/crypto
${CMAKE_CURRENT_SOURCE_DIR}/trezor-firmware/
${CMAKE_CURRENT_SOURCE_DIR}/trezor-firmware/crypto/ed25519-donna
${OPENSSL_INCLUDE_DIR})

Expand Down
17 changes: 17 additions & 0 deletions docs/Build.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,20 @@ Running the tests
$ cd build
$ make all test
```

Release products
-----------------

Two release products, no separate `zcash-privacy` artifact:

| Product | Contents |
| --- | --- |
| Regular (`full`) | Every supported chain, including Zcash shielded/Orchard |
| Bitcoin-only | Bitcoin only; all non-Bitcoin coins and Zcash privacy code removed |

An unflagged CMake build is the regular product (`BITCOIN_ONLY=0`,
`ZCASH_PRIVACY=1`). `-DKK_BITCOIN_ONLY=ON` sets `BITCOIN_ONLY=1` and
`ZCASH_PRIVACY=0`. Zcash privacy is part of the regular firmware and cannot be
disabled as a release choice; the internal `ZCASH_PRIVACY` value exists only so
bitcoin-only can compile the privacy sources out. Device, emulator, unit-test,
SRAM, and tagged-release CI matrices cover only these two products.
12 changes: 2 additions & 10 deletions docs/DiceEntropy.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
# Dice Entropy

On-device dice rolls, folded into the seed at creation time. Available from
firmware v7.14.3 (bitcoin-only line) and v7.15.0 (`ResetDevice.dice_entropy`).

One difference from 7.15 in this line: the legacy `display_random` entropy
screen still exists here, because already-shipped 7.14 hosts request it. The
two are mutually exclusive — `ResetDevice` with both `display_random` and
`dice_entropy` set is refused with a SyntaxError, since the screen shows the
POST-mix internal entropy and honoring both would hand a host the seed
pre-image and make the dice fold-in worthless.
firmware v7.15.0 (`ResetDevice.dice_entropy`).

## What happens

Expand Down Expand Up @@ -53,8 +46,7 @@ described it as a verifiable commitment; that was strictly worse. A host that
supplies `ext_entropy` and reads that screen once computes
`SHA256(shown || ext_entropy)` — the seed pre-image. Dice change nothing about
that attack, because the displayed value is already post-mix. Unverifiable
mixing beats a verifiable seed pre-image. See the comment above the
`dice_entropy` block in `reset.c:reset_init()`.
mixing beats a verifiable seed pre-image. See the comment at `reset.c:136`.

The roll digest is safe by contrast because it hashes the user's own input, not
seed material.
Expand Down
Loading
Loading