Skip to content

security: audit and harden 7.14.3 Bitcoin-only + Dice - #604

Merged
BitHighlander merged 10 commits into
developfrom
release/7.14.3-bitcoin-only
Aug 26, 2026
Merged

security: audit and harden 7.14.3 Bitcoin-only + Dice#604
BitHighlander merged 10 commits into
developfrom
release/7.14.3-bitcoin-only

Conversation

@BitHighlander

@BitHighlander BitHighlander commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Fork target and immutable identity

This PR is entirely on the BitHighlander fork:

  • repository: BitHighlander/keepkey-firmware
  • base: develop
  • head: release/7.14.3-bitcoin-only
  • audit baseline: 2c4b7021041bd14667dd6d9c1ac282974e24d6c0
  • proposed head: 1d446ccbc9cf32a6e499990e65bcf7b358f1325b
  • product: firmware 7.14.3 Bitcoin-only + on-device Dice entropy
  • pinned Python test head: e353ce5ef05d96e8f85568bd2a7a7420b11713c4

The PR remains a draft while the final-head CI and the remaining SOP gates are
audited. It is not a certification of the regular multi-coin product.

Normative SOP and definition of “line review”

The in-tree, versioned audit contract is
docs/security/7.14.3-bitcoin-only-dice-audit-sop.md.

For this audit, line review means reading every executable line at the
recorded commit in each unit below, including error paths and compile-time
branches. File/function anchors are authoritative; mutable line numbers are
only navigation aids. The SOP defines the procedure, evidence contract,
auditor-of-auditor controls, exclusions, and merge gates so the claimed surface
cannot silently drift away from the reviewed tree.

Audit surface, unit by unit

Surface Complete line-review unit Property being proved
Product boundary top-level CMakeLists.txt; lib/firmware/CMakeLists.txt; coins.def; coins.h; coins.c BITCOIN_ONLY is binary-valued; excluded coin engines and tables are absent rather than UI-hidden; only Bitcoin and Testnet remain reachable.
Wire reachability lib/firmware/messagemap.def; fsm.c; fsm_msg_coin.h; Bitcoin-reachable fsm_msg_common.h Compiled message handlers match the advertised product; excluded signing protocols cannot be invoked through protobuf dispatch or GetCoinTable.
Bitcoin signing signing.c; transaction.c; txin_check.c; signing portions of crypto.c Every input/output/script/quorum/change/fee/Taproot/OP_RETURN field is validated, hashed, serialized, and approved consistently; state does not leak between requests.
User disclosure app_confirm.c; app_layout.c Size-delimited bytes never become unbounded C strings; pagination cannot omit a signed suffix; unsupported semantic payloads disclose their complete raw bytes.
Dice setup dice_input.c; reset.c; dice_input.h Exact roll alphabet/count; rolls mix before host entropy; cancel/re-entry disarms and wipes; Dice cannot be neutralized or confused with internal-entropy display.
RNG lib/rand/rng.c; rng_health.c; their headers; every random_buffer_checked caller Shipping ARM selects hardware RNG; health failure latches; failed draws are wiped; key-material draws fail closed.
Recovery/storage reset.c; recovery_cipher.c; storage.c; public headers Seed/settings commit atomically; failure paths wipe; the Bitcoin-only storage version band cannot be downgraded or opened by the wrong product.
Session secrets authenticator.c; storage session functions; lock/wipe/initialize/session-clear handlers; signing caches Losing authorization clears seed-derived nodes, PIN material, signing state, authenticator material, and Dice state.
Parser/schema include/pb.h; lib/transport/pb_decode.c; generated descriptors; Bitcoin/setup .options Host-controlled lengths, counts, oneofs, and repeated fields are rejected before capacity or semantic use.
Firmware authenticity lib/board/signatures.c; bootloader validation; code-signing-keys gitlink; release workflow and manifests Quorum/key identity fail closed; promoted bytes and variant hashes are immutable and attributable.
Test trust .github/workflows/ci.yml; scripts/emulator/*; native manifests; pinned Python test/report selection Both product variants build; the shipping Bitcoin-only emulator/native tests actually execute; Dice executes; failures propagate nonzero; reports bind to the same head.
Dependency identity .gitmodules and every recursive gitlink A clean checkout resolves the exact reviewed objects from committed remotes; no branch tip or local URL substitutes for a pin.

The regular multi-coin image is a regression-build boundary only because the
release workflow publishes both variants. Its non-Bitcoin transaction semantics
are out of certification scope and require their own audit record.

Confirmed findings and fixes in this PR

The #610 issue comment also records a rejected first fix: the complete product
suite caught unexpected prompts in both many-output regressions. That candidate
was replaced and retested before this branch was published.

Candidate evidence

Pinned Docker base:
kktech/firmware@sha256:7438e53933d47d53157ed6d96d864cb208597e62dce26235ace09d1063427fa2
(AMD64 image executed on an ARM64 host).

  • native firmware: 56 passed
  • native board: 9 passed
  • native crypto: 18 passed
  • Bitcoin-only Python product lane: 253 passed, 415 skipped, 0 failed
  • Dice reset device flow: executed and passed
  • Bitcoin-only variant/table/handler checks: executed and passed
  • OP_RETURN carry-over regression and same-transaction control: executed and
    passed
  • structured and raw many-output controls: executed and passed
  • unsupported 220-byte Omni multi-page disclosure: native regression passed
  • git diff --check: passed
  • gitleaks event range: 4 audit commits scanned, no leaks found
  • actionlint: no finding in the modified matrix/scanner steps; the workflow retains
    pre-existing shellcheck findings outside this change

The local evidence authorizes this draft and does not replace final-head CI.
Artifacts, skips, firmware hashes, and signing provenance must be reconciled in
the final attestation before this PR is made ready.

Companion fork PR

The test harness change is also entirely on the fork:
BitHighlander/python-keepkey#62.
It lowers the Bitcoin-only product-test gate specifically to 7.14.3 and marks
the Mayachain memo test as full-feature-only. This firmware PR pins the exact
companion commit rather than following its branch tip.

Auditor-of-auditor / merge gates

The independent check must reconcile claims against source, fork issue state,
immutable commits, JUnit, screenshots, firmware hashes, and CI artifacts. A
green aggregate badge, elapsed waiting time, or this prose alone is not
evidence. In particular it must prove tests executed rather than skipped and
that every resolved issue has a reachable fix plus regression.

Do not merge until the SOP checklist is complete, including the clean regular
regression build, final-head variant tests, failure-propagation positive
control, static/format/submodule/secret gates, release manifest provenance, and
closure of every in-scope finding.

Closes #446
Closes #449
Closes #528
Closes #531
Closes #597
Closes #608
Closes #609
Closes #610
Closes #544

Backported from release/7.15, where the collection UI and the physical
short-press/long-hold classification are hardware-verified. Eight files
arrive byte-identical to that tree (dice_input.{c,h}, rng_health.{c,h},
rng.{c,h}, u2f.c, keepkey_flash.c) along with both unit-test suites; the
reset.c / fsm_msg_common.h / fsm_msg_debug.h integration is ported
surgically and diffs clean against 7.15 except for the one deliberate
divergence below.

ResetDevice.dice_entropy collects 50/75/99 rolls on the device (short press
advances 1-6/undo, 800 ms hold commits) and folds
SHA256(int_entropy || rolls) into the internal entropy BEFORE
EntropyRequest, so the host contribution arrives strictly after the device
has committed to its own. The completion screen shows the first 8 bytes of
SHA256(rolls) so the user can recompute it from their written rolls; the
same digest is served over DebugLink for test parity. docs/DiceEntropy.md
covers what the digest proves and what it deliberately does not.

Divergence from 7.15, on purpose: display_random is RETAINED, because
already-shipped 7.14-line hosts request the Internal Entropy screen and the
pinned python suite asserts it below 7.15.0. The two are mutually exclusive
by refusal -- ResetDevice with both flags fails with a SyntaxError, since
the screen shows the POST-mix value, which is the seed pre-image once
ext_entropy is known.

The seed-time RNG gate covers every call site on rng_health.h's documented
list, so the header's COMPLETE LIST claim is true in this tree: reset_init
(fail with FirmwareError), the storage encryption and wipe-code keys and
the V1-upgrade PIN-KDF salt (storage_drawKeyMaterial, halt), U2F key-handle
derivation (refuse registration), and the one-shot OTP randomness block
(skip the write, leave the block claimable by a healthy boot).

Pins: deps/device-protocol advances to 8545cd5b -- the exact pin
release/7.15 builds against; every bytes/string field new to the compiled
protos verified to carry nanopb sizing (the options files here were already
pre-staged for it, plus three Solana LUT lines). deps/python-keepkey
advances to 9aaaa84, which moves the dice end-to-end test, the ceremony
re-entry regression, and report Section K from a 7.15.0 gate to 7.14.3 --
exact for the fleet, since no firmware between 7.14.3 and 7.15.0 exists
without dice. Those tests previously SKIPPED against this branch and CI
went green with zero device-level dice coverage.

ci: build-arm-firmware compiles both release variants on every PR, so a
change that only breaks the bitcoin-only image can no longer go green.

Verified in CI-parity docker (same base image digest): bitcoin-only ARM
links at .text 372,928 and full at 523,600; firmware-unit 103/103 including
Dice.* (5) and RngHealth.* (15); the full python-integration compose suite
passes 449/449 selected with test_reset_device_dice and
test_reset_reentry_disarms_entropy_ack RUNNING and green; clang-format-20
clean on every touched file.
@BitHighlander BitHighlander changed the title hold: 7.14.3 bitcoin-only + dice entropy backport security: audit and harden 7.14.3 Bitcoin-only + Dice Aug 26, 2026
@BitHighlander

Copy link
Copy Markdown
Owner Author

Exact-head audit attestation

Audited candidate: 1d446ccbc9cf32a6e499990e65bcf7b358f1325b

Exact-head CI: https://github.com/BitHighlander/keepkey-firmware/actions/runs/32945400323

All required jobs and the aggregate CI gate completed successfully, including full-feature and Bitcoin-only ARM builds, both integration-test lanes, native firmware/board/crypto tests, secret scanning, cppcheck, clang-format, emulator, and dylib checks.

Reconciled test evidence

Lane Suite Result
Full Python integration 449 passed, 219 skipped, 0 failed/errors
Bitcoin-only Python integration 253 passed, 415 skipped, 0 failed/errors
Full Screenshot scenarios 96 executed, 19 skipped, 0 failed/errors
Bitcoin-only Screenshot scenarios 41 executed, 74 skipped, 0 failed/errors
Full Native firmware 104 passed, 0 failed/errors
Bitcoin-only Native firmware 56 passed, 0 failed/errors
Both variants Native board 9 passed per variant, 0 failed/errors
Both variants Native crypto 18 passed per variant, 0 failed/errors
Dylib Native suite 4 passed, 0 failed/errors

Shipping artifact identity

Variant Size SHA-256
Full 527,156 bytes c8c89248d2b1fe336d05af788ae54621d00a800476d30fe9ade61bec27493793
Bitcoin-only 354,764 bytes ff17c7220295e712f243d6cb6ac901f35a95caf7b2e3fc6589ed10591c18609c

Artifacts: firmware-v7.14.3-1d446cc-full and firmware-v7.14.3-1d446cc-bitcoin-only.

Auditor-of-auditor evidence

The first proposed remediation for #610 made the targeted OP_RETURN regression pass but caused unexpected signing confirmations in both structured and raw many-output tests. That remediation was rejected. The corrected lifecycle fix resets only the current signing digest at signing start while preserving the prior completed-transaction warning, and the entire Bitcoin-only lane was rerun successfully. This negative result and correction are recorded on #610.

The audit also found that the repository-wide secret scan could ingest disconnected fork refs and block CI (#544). The fix binds scanning to the event revision range, pins and verifies the scanner download, and introduces no allowlist or suppression.

This remains a draft until the SOP's human line-review attestations and release-signing/provenance gates are explicitly completed. Green CI is evidence for the tested candidate; it is not, by itself, a claim that every manual security-review gate is complete.

@BitHighlander

Copy link
Copy Markdown
Owner Author

Adversarial exact-head audit — NOT APPROVED

Audited product: firmware 7.14.3, Bitcoin-only + Dice
Audited head: 1d446ccbc9cf32a6e499990e65bcf7b358f1325b
Comparison: release/7.14.3-bitcoin-only → fork develop
SOP: docs/security/7.14.3-bitcoin-only-dice-audit-sop.md

I completed the SOP line-review units, reconciled the linked issues and PR discussion, reproduced both shipping builds, inspected generated parser descriptors and the Bitcoin-only link surface, and ran the required negative control. This head is not certifiable for merge or release because the audit found six new defects, four rated high.

New fork findings

Severity Surface Finding
High RNG boundary #613 RNG persistent-error reset clears hardware evidence without latching the fault
High Session secrets / authenticator #614 Authenticator commands continue after passphrase rejection against a stale decrypted cache
Low Bitcoin signing / test equivalence #615 BTC checksum hashes pointer-size bytes, so ARM and emulator validate different data
High Protocol/session boundary #616 ClearSession does not abort Bitcoin signing or fully scrub signer state
Medium Setup/recovery/storage #617 Reset/recovery abort paths retain mnemonic material and armed state
High Bitcoin signing/change #618 Invalid output multisig quorum can lock change to an unsatisfiable script

All six issues are on the BitHighlander fork tracker; nothing was filed upstream.

Pre-existing fork issues confirmed live in the audited product

The audit also reconfirmed related open findings rather than duplicating them: #399 (retained transaction.c derived node), #401 (release signature verification gate), #426 (Actions not pinned to commit SHAs), #427 (tag/provenance and manifest ordering), #450 (authdata wallet binding and plaintext/key cleanup), #454 (storage cipher IV/key residue), #456 (signature-check fault hardening), and #542 (authenticator decoded-secret cleanup).

The fixes claimed for #446, #449, #528, #531, #544, #597, #608, #609, and #610 are present at this exact head and their targeted regression evidence is consistent. That does not offset the new blockers above; those issues should remain merge-coupled as planned.

Surface disposition and review accounting

Model attestation: OpenAI Codex, GPT-5 model family. The runtime does not expose exact inference-token telemetry per audit section. I will not fabricate it. The deterministic accounting below is the exact reviewed source footprint and ceil(source bytes / 4) as a reproducible source-token approximation; it is not billing/inference-token usage. Files shared by multiple security surfaces are intentionally counted in each applicable surface.

SOP surface Files Lines Bytes Approx. source tokens Disposition
Variant selection 2 302 10,204 2,551 No finding; excluded engines absent from Bitcoin-only link
Protocol reachability 4 1,646 62,410 15,603 #616
Bitcoin signing 4 3,815 135,945 33,987 #615, #616, #618
Confirmation renderer 2 1,342 40,253 10,064 No new finding
Dice ceremony 3 972 35,015 8,754 No Dice-input/mixing finding; reset cleanup is #617
RNG boundary and checked call sites 8 4,678 160,677 40,170 #613
Setup/recovery/storage 6 3,843 136,694 34,174 #617; existing #450/#454 also live
Session secrets 4 4,151 144,647 36,162 #614, #616; existing #399/#450/#542 also live
Wire parser 6 2,999 117,118 29,280 No finding; static capacities/repeated counts checked
Firmware authenticity 7 1,644 52,354 13,089 No new issue; existing #401/#426/#427/#456 remain
Test trust 15 1,497 57,905 14,477 No finding after positive control
Dependency identity 3 manifests plus every recursive gitlink 60 manifest lines 2,218 555 No finding; exact objects resolved from committed public URLs

Gross reviewed-footprint sum (with cross-surface duplication): 26,949 lines, 955,440 bytes, approximately 238,866 source tokens.

Reproduction and evidence

  • PR discussion reviewed: there were no inline reviews or review comments; the existing owner attestation comment was reconciled against the downloaded artifacts and JUnit XML.
  • Exact-head CI run 32945400323 is green, including 253 passing Bitcoin-only Python integration tests.
  • Downloaded XML reconciles to: full Python 449 pass / 219 skip; Bitcoin-only Python 253 pass / 415 skip; native firmware 104 full and 56 Bitcoin-only; board 9 and crypto 18 per variant; dylib 4; zero failures/errors.
  • Both ARM variants were rebuilt locally with kktech/firmware@sha256:7438e53933d47d53157ed6d96d864cb208597e62dce26235ace09d1063427fa2; outputs match CI byte-for-byte:
    • full: c8c89248d2b1fe336d05af788ae54621d00a800476d30fe9ade61bec27493793
    • Bitcoin-only: ff17c7220295e712f243d6cb6ac901f35a95caf7b2e3fc6589ed10591c18609c
  • Bitcoin-only ELF inspection found no selected excluded-engine handler/signing symbols; CMake and link inputs agree with the product boundary.
  • Native Bitcoin-only emulator suite reproduced locally: firmware 56, board 9, crypto 18, all passing.
  • Positive control: injected one disposable EXPECT_TRUE(false) into a Dice test. Result was 55 pass / 1 fail, wrapper exit 2, preserved JUnit/status 2; feeding a failed dependency into the aggregate-gate logic exited 1. The exact audited checkout remained clean.
  • Generated nanopb review found exact static caps: paths 8, multisig pubkeys/signatures 15, prevhash 32, scriptSig 1650, OP_RETURN 220, streamed transaction arrays 1, extra data 1024. Static repeated-field decode rejects overflow.
  • Companion fork PR python-keepkey#62 was reviewed at gitlink e353ce5ef05d96e8f85568bd2a7a7420b11713c4; no companion finding was identified.
  • Recursive submodules were clean and resolved at their committed public objects. The PR head remained exact and the fork develop tree remained identical to the recorded merge-base tree at final verification.

Certification decision

Do not merge, tag, sign, or promote this head. Resolve #613#618, rerun the exact SOP and positive control on the new immutable head, then complete the still-pending independent human line-review and release-signing/provenance attestations. Green CI here certifies the tested mechanics and artifacts only; it does not certify the security properties that failed this review.

@BitHighlander

Copy link
Copy Markdown
Owner Author

Security re-review attestation — exact head d842f1e19b98dd1b67873d38d60201c18bfa0daa

Disposition: the six fork-only findings reported against 1d446ccbc9cf32a6e499990e65bcf7b358f1325b are remediated. I found no unresolved security blocker in the re-reviewed delta and approve merge only at this exact head, subject to the repository's normal human/branch controls. Any head change invalidates this disposition and requires another review.

This attestation does not authorize firmware signing or release, and I did not merge, sign, publish, close issues, or change draft state.

Finding disposition

Primary remediation: 373137e64f7303c08594b1a89d0652b47f093407
Final atomicity correction: d842f1e19b98dd1b67873d38d60201c18bfa0daa
Pinned companion gate: BitHighlander/python-keepkey#62 at 758f20c2c2288fe30cbf192f927fc966de44adc1

Exact-head evidence

  • Push CI: 32956606737 — success.
  • Pull-request CI: 32956612123 — success.
  • In both runs: format, secret scan, submodule validation, static analysis, full and Bitcoin-only ARM builds, emulator build, dylib tests, unit tests, full integration, Bitcoin-only integration, reports, and aggregate CI gate passed. Emulator publication was intentionally skipped for a draft/non-release run.
  • Companion integration rerun: 32955382103 — 671 tests: 452 passed, 219 skipped by declared version/variant gates, 0 failed; 102 subtests passed.
  • Exact final-delta local checks: SetupCeremony.* 7/7; recovery-cipher device suite 7 passed and 1 declared version-gated skip; git diff --check clean.
  • Broader remediation validation before the final one-line persistence correction: full native firmware 114/114, board 9/9, crypto 18/18; Bitcoin-only firmware 66/66, board 9/9, crypto 18/18; targeted authenticator/session/multisig wire tests 3/3; full and Bitcoin-only ARM links; cppcheck over 109 translation units with an empty report. Exact-head CI repeated the full build/test gates after d842f1e1.

Re-review surface and repository state

  • Re-reviewed range: 1d446ccbc9cf32a6e499990e65bcf7b358f1325b..d842f1e19b98dd1b67873d38d60201c18bfa0daa.
  • Exact accounting: 4 commits, 23 changed paths, 715 inserted lines, 127 deleted lines, 58,466-byte reproducible textual diff.
  • PR remains draft, open, and mergeable at the exact reviewed head. The audited worktree and companion checkout are clean.

Review performed by GPT-5 Codex. Exact per-section inference-token telemetry is unavailable; the reproducible source/diff accounting and test evidence above are reported instead.

@BitHighlander

Copy link
Copy Markdown
Owner Author

Finding #616 — ClearSession leaves Bitcoin signing active: resolved

At exact head d842f1e, ClearSession reaches the centralized Bitcoin signing abort and scrubs signer statics, private/root/derived nodes, and digests. Device-wire tests interrupt every TxRequest stage and confirm signing cannot continue. Both exact-head CI gates pass.

Issue: #616

@BitHighlander

Copy link
Copy Markdown
Owner Author

Finding #617 — Recovery/reset retains mnemonic state: resolved

At exact head d842f1e, reset/recovery aborts wipe the BIP39 workspace and all recovery fragments, and invalid word counts disarm the ceremony. Re-review also found and corrected a hidden U2F-counter commit that aborted recovery before mnemonic storage. The complete recovery-cipher suite and both exact-head CI matrices pass.

Issue: #617

@BitHighlander

Copy link
Copy Markdown
Owner Author

Finding #614 — Authenticator passphrase-rejection bypass: resolved

At exact head d842f1e, passphrase rejection returns before authenticator dispatch, cancellation/rejection propagates to the caller, and plaintext passphrase, HMAC, OTP, and temporary secret buffers are cleared. Native and device-wire regressions pass, as do both exact-head CI gates.

Issue: #614

@BitHighlander

Copy link
Copy Markdown
Owner Author

Finding #615 — ARM/emulator checksum divergence: resolved

At exact head d842f1e, both checksum passes hash script type using a canonical four-byte little-endian representation, removing host-width dependence. Checksum regression coverage, full and Bitcoin-only ARM builds, emulator builds, and both exact-head CI gates pass.

Issue: #615

@BitHighlander

Copy link
Copy Markdown
Owner Author

Finding #613 — RNG fault latch bypass: resolved

At exact head d842f1e, RNG hardware and continuous-health failures latch across subsequent cached checks. Transient and persistent hardware faults, degenerate output, and wipe-on-failure behavior have regression coverage. Both exact-head CI gates pass.

Issue: #613

@BitHighlander

Copy link
Copy Markdown
Owner Author

Finding #618 — Invalid multisig change can lock funds: resolved

At exact head d842f1e, signing output validation and both multisig compilers require has_m and 1 <= m <= n <= 15. Invalid external and change outputs cannot serialize or sign. Native and device-wire negative tests and both exact-head CI gates pass.

Issue: #618

@BitHighlander
BitHighlander marked this pull request as ready for review August 26, 2026 18:31
@BitHighlander
BitHighlander merged commit d866fa3 into develop Aug 26, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment