Skip to content

feature: integrate 7.14.1 firmware updates#424

Closed
BitHighlander wants to merge 50 commits into
keepkey:developfrom
BitHighlander:feature-7141
Closed

feature: integrate 7.14.1 firmware updates#424
BitHighlander wants to merge 50 commits into
keepkey:developfrom
BitHighlander:feature-7141

Conversation

@BitHighlander
Copy link
Copy Markdown
Collaborator

Summary

Integrates the 7.14.1 firmware work onto upstream develop from a non-release branch name (feature-7141) so CI does not apply release-branch behavior.

Includes:

Notes:

  • This PR is intentionally Zcash-free.
  • The python-keepkey/device-protocol pins can be moved back to their canonical branches after those upstream PRs merge.

Validation

  • git diff --check keepkey/develop..HEAD

BitHighlander and others added 30 commits April 26, 2026 23:00
Adds an in-process emulator library (libkkemu, .dylib on macOS / .so on
linux) so a host process can drive firmware logic via FFI instead of
spinning up a separate kkemu binary and talking over UDP. Used by vault
v11 to host the emulator inside the Bun process for the wallet onboarding
+ Zcash testing flows; also lets researchers fuzz / drive firmware
behavior from arbitrary host code without the network hop.

Build:
  cmake -DKK_EMULATOR=1 -DKK_BUILD_DYLIB=1 -B build-emu .
  cmake --build build-emu --target kkemulator_dylib

The standalone `kkemu` UDP-driven binary continues to build with
KK_EMULATOR=1 alone, byte-identical to before.

Files:
  - include/keepkey/emulator/libkkemu.h, lib/emulator/libkkemu.c
      C API exposed to host: init(seed), step(), screenshot capture
      (display_refresh frames packed 8bpp->1bpp into a 256x64x1 buffer),
      ringbuf-backed message I/O.
  - lib/emulator/ringbuf.{c,h}
      Lock-free single-producer/single-consumer byte ring used by
      libkkemu's host I/O paths (replaces the UDP sockets in dylib mode).
  - lib/emulator/setup.c
      setup_urandom_only() — RNG init for libkkemu callers that
      provide their own flash buffer (no flash mmap).
  - lib/emulator/udp.c
      Behind #ifdef KKEMU_DYLIB: emulatorSocketInit/Read/Write become
      thin trampolines to the libkkemu ring buffers. Standalone build is
      unchanged. Adds optional KEEPKEY_UDP_PORT env override for the
      kkemu binary so multiple emulators can share a host.
  - lib/emulator/CMakeLists.txt, tools/emulator/CMakeLists.txt
      Define the kkemulator_dylib target and refactor kkemu's link
      command to share FIRMWARE_LIBS between standalone and dylib builds.
  - CMakeLists.txt
      cmake_policy(SET CMP0079 NEW) — required so tools/emulator can
      link kkemulator_dylib which is defined in lib/emulator. KK_BUILD_DYLIB
      option (default OFF) guards the whole dylib path; absent the flag,
      the build is identical to fork develop.
  - include/keepkey/emulator/setup.h
      Forward decl for setup_urandom_only.

Out of scope (separate concerns even though they touch nearby files):
  - Version bump 7.14.0 -> 7.15.0
  - BITCOIN_ONLY define (BTC-only firmware variant)
  - DebugLink screenshot canvas semantics (next commit)
  - CI screenshot pipeline / zoo workflows
…t capture

fsm_msgDebugLinkGetState previously called force_animation_start() +
animate() before capturing the canvas (or wrapped both in
`if (is_animating())`). Both forms produce the same bug for non-animated
screens (warning_static, address displays, etc.):

  - The confirm() loop already calls animate() before sending the
    ButtonRequest that triggers DebugLinkGetState.
  - When the layout is static (no animation queued) and we still call
    force_animation_start() + animate() in the DebugLink handler, we
    overwrite the static canvas with a stale animation frame OR a no-op,
    depending on queue state — either way, the screenshot the host
    captures is not what the user is actually seeing.

Replace with a single display_refresh() call. The canvas already holds
the right pixels by the time DebugLinkGetState fires; we just need the
framebuffer synced before the host reads it.

Production firmware (compiled with KK_DEBUG_LINK=0) is unaffected — the
function is excluded from non-debug builds entirely.

Was the missing piece for the CI screenshot pipeline correctly capturing
warning + confirmation screens with their as-rendered text instead of
animation frames or empty buffers.
… response

Bumps RINGBUF_CAPACITY from 32 to 128. The previous value left effective
room for 31 HID reports between writer and reader; DebugLinkGetState now
serializes a 2048-byte `layout` plus the rest of DebugLinkState (~2.7 KB
total payload, ~44 reports after the per-report sync prefix + continuation
byte). Pushes past slot 31 returned 0 from emulatorSocketWrite(), but
upstream lib/board/usb.c::msg_debug_write() ignores the return — so the
host saw a silently-truncated screenshot capture instead of a clean
failure.

128 gives ~3x headroom on the worst current response, accommodates
DebugLinkFlashDumpResponse (1024-byte chunks) plus future field growth,
and stays a power of two so the modulo in ringbuf_push/pop is a cheap
mask. Memory cost: 4 rings × 128 × 64 = 32 KB. Trivial next to the
existing 128 KB frame_ring.

Out of scope: making msg_debug_write() propagate emulatorSocketWrite()
failures is the right long-term fix and belongs in a separate PR (it's
upstream code, not introduced by libkkemu). Until then, sizing the ring
so it never overflows on a legitimate response is the pragmatic guard.
…ILD_DYLIB

The previous CMakeLists.txt only marked kkemulator_dylib itself as PIC,
but it links static archives (kkfirmware, kkboard, kkrand, kktransport,
trezorcrypto, qrcodegenerator, SecAESSTM32, kkemulator, kkvariant.*)
defined in sibling directories. macOS happens to be lenient and produces
a working .dylib regardless, but Linux's link step against a non-PIC
archive fails with "recompile with -fPIC".

Set CMAKE_POSITION_INDEPENDENT_CODE=ON globally when KK_BUILD_DYLIB is
on, BEFORE add_subdirectory(lib) below — every static target picks it up
at definition time. CMP0079 (already set above) lets the dylib reach
across directories to link them.

When KK_BUILD_DYLIB=0 (the default), nothing changes — static libs build
without -fPIC as before.
…down

Two related hardening changes in libkkemu's lifecycle:

1. mlock() failure logged

   kkemu_init() previously called mlock() on the host flash buffer and
   ignored the return. Many platforms cap unprivileged mlock at a few MB
   (RLIMIT_MEMLOCK), and silent failure means the seed / FVK / PIN
   derivation state in the buffer can be swapped to disk without anyone
   knowing.

   Now we check the return and log to stderr on failure ("flash buffer
   may be swapped to disk; do not load production secrets"). Init still
   succeeds — a dev/CI environment that hits the rlimit shouldn't be
   blocked from running the emulator. Production hosts of libkkemu are
   expected to treat the logged failure as a security warning and refuse
   to load secrets.

2. Zero sensitive static buffers on kkemu_shutdown()

   In dylib mode the library lives inside a long-running host process
   (e.g. the Bun runtime in vault). Static rings, the frame ring, the
   last-packed dedup buffer, and the display-pack scratch all have
   static storage duration — they outlive the emulator session and are
   visible to the host's memory image (core dumps, ptrace, GC roots).

   They retain:
     - rb_main_*:   PIN, passphrase, signing inputs/outputs
     - rb_debug_*:  mnemonic + recovery-cipher state in DEBUG_LINK builds
     - frame_ring + last_packed + display_packed_scratch: rendered OLED
       bytes including PIN matrix, recovery words, address confirms,
       signing summaries

   Promoted the previously function-static `packed[2048]` in
   kkemu_get_display() to a file-scope `display_packed_scratch` so
   shutdown can reach it. All five buffers now go through trezor-crypto's
   memzero() (compiler-can't-optimize-out) before munlock — the same
   primitive the firmware uses everywhere it clears key material.

   The host-owned flash buffer is still left for the caller to zero
   (documented contract — host may want post-mortem inspection).
Bumps deps/python-keepkey to bithighlander/master 73f2a2d (the merge
commit of BitHighlander/python-keepkey#14), which adds the
DylibTransport for in-process libkkemu testing plus four screenshot
regression tests targeting this PR's own changes:

  - test_layout_round_trip_fits_through_ring  → covers RINGBUF_CAPACITY
  - test_layout_repeated_reads_no_truncation
  - test_layout_stable_across_idle_reads      → covers fsm_msg_debug
                                                 animate→display_refresh
  - test_layout_features_dont_corrupt_capture → iface separation

Adds a new CI job, python-dylib-tests, that builds libkkemu natively
(NOT in the existing Docker emulator image — different artifact, different
toolchain) and runs the four tests against it. Joins publish-emulator's
gate so a Docker push can't ship without the dylib path being green.

Pinned tooling (matches the firmware's own .python-version + the
.proto generator the repo uses):
  Python 3.10, protobuf 3.20.3, nanopb 0.3.9.4.post3, protoc 3.21.12
  KK_DEBUG_LINK=ON at cmake time (default OFF — without it,
  fsm_msgDebugLinkGetState is excluded and read_layout() hangs).

generate-test-report's needs list is intentionally NOT updated — that
job's PDF aggregation only knows about the existing JUnit + screenshot
schema. The dylib JUnit uploads as its own artifact instead. Folding
into the report is a follow-up.
The first python-dylib-tests run failed in 20s on the protoc download:
unzip reported "End-of-central-directory signature not found" because
the URL 404'd and curl wrote the HTML error page to /tmp/protoc.zip.

Root cause: protobuf renamed releases from v3.21.x → v21.x at this
release ("protoc version reset" — the project decoupled protoc's
version from the C++ runtime's). The tag is `v21.12`, not `v3.21.12`,
and the file inside is `protoc-21.12-linux-x86_64.zip`, not
`protoc-3.21.12-...`.

Also added `curl -f` so the next typo fails the step on the HTTP error
instead of letting unzip belly-flop on a 404 page, plus a `file`
sanity-check on the downloaded archive.
Top-level CMakeLists.txt requires googletest unconditionally — the
add_subdirectory(deps/googletest) call runs at configure time, before
any target selection. Even though we only build kkemulator_dylib,
configure fails with 'googletest missing' if the submodule isn't
init'd.

Caught by the second CI run on PR #217: protoc URL fix landed (config
got past stage 1 of cmake), then died on the gtest check at line 57.
Pip-installed nanopb 0.3.9.4.post3 ships nanopb.proto + plugin.proto
but NOT the generated nanopb_pb2.py / plugin_pb2.py files. Without
them, nanopb_generator.py fails at import with:

  ImportError: attempted relative import with no known parent package

at line 37 (`from .proto import nanopb_pb2, plugin_pb2`).

The fix is to regenerate them — but with the PINNED protoc 3.21, NOT
the github-runner's system protoc (which is too new and produces .pb2
files that require a protobuf runtime newer than the 3.20.3 we pinned,
manifesting as 'Descriptors cannot be created directly').

Same trap I hit locally before this CI work landed; documented inline
so the next person doesn't have to rediscover it.
…tor/)

Two failures from the third dylib CI run:

1. ModuleNotFoundError: requests
   ethereum_tokens.def build step fetches token lists at compile time
   via Python; needs requests pip-installed.

2. ImportError: attempted relative import with no known parent package
   The protoc-gen-nanopb that protoc was finding via PATH was the COPY
   in <site-packages>/nanopb/generator/ — a raw .py file that fails
   when invoked as a top-level script. The pip install's
   console-script wrapper at <pyenv>/bin/protoc-gen-nanopb loads
   nanopb_generator as a module, so relative imports resolve.

   The previous PATH override prepended the broken generator/ dir
   ahead of bin/. Removed both PATH manipulations — setup-python
   already puts the python bin dir on PATH, which is where the
   working wrapper lives. Added `which protoc-gen-nanopb` to surface
   the resolved path in CI logs for any future debugging.

The pb2 regeneration step still runs (the *_pb2.py files don't ship
with the pip install), but the regenerated files now get loaded via
the correct module path.
cmake CMakeLists.txt:78 does find_program for nanopb_generator.py
(specifically with the .py extension), which only exists in the pip
package's <site-packages>/nanopb/generator/ directory — not in the
console-script bin/ dir.

Last fix removed generator/ from PATH entirely to avoid its broken
protoc-gen-nanopb winning over the working bin/ wrapper. That flipped
the failure: cmake configure now reports 'Must install nanopb 0.3.9.4,
and put nanopb-nanopb-0.3.9.4/generator on your PATH'.

Right answer: APPEND generator/ to PATH, so:
  - bin/ comes first → bin/protoc-gen-nanopb (working wrapper) wins
  - generator/ comes last → only place nanopb_generator.py exists,
    cmake's find_program resolves it there

Added `which protoc-gen-nanopb` and `which nanopb_generator.py` to
the configure step so future CI logs make the resolution unambiguous.
Linux build hits a duplicate-symbol link error: __stack_chk_guard is
defined by both lib/board/keepkey_board.c (uintptr_t) and
lib/emulator/setup.c (uint32_t). Apple's linker silently picks one
(matches local macos arm64 builds that work); GNU ld is strict and
fails with 'multiple definition'.

Fixing requires deduping the symbol — out of scope for the current PR
which is about the libkkemu runtime + screenshot tests, not the
firmware's stack-canary plumbing.

This commit:
  - runs-on: ubuntu-latest → macos-latest (arm64)
  - protoc download URL: linux-x86_64 → osx-aarch_64
  - drops apt-get install (macos pre-installs cmake + clang)
  - replaces nproc with sysctl -n hw.ncpu
  - find prefers libkkemu.dylib, .so as fallback for future cross-plat
  - added inline comment + TODO documenting the Linux follow-up

Once the __stack_chk_guard symbol is deduped (likely just remove
emulator/setup.c's redefinition since lib/board's already provides
the on-device version that emulator inherits), this job can flip back
to a matrix that runs both ubuntu-latest + macos-latest.
Job hit "No module named pytest" on macos runner. The existing
python-integration-tests job runs in a Docker image with pytest baked
in; this new dylib job is on a vanilla macos-latest runner so it needs
explicit install.

Pin pytest-timeout too — even though it can't actually break the
dylib's C busy-loop (per the long rationale in
test_dylib_confirm_flow.py's @unittest.skip), it's used transitively
by some pytest features.
The build step was producing libkkemu.dylib but only test results were
being uploaded. Add an upload-artifact step so reviewers / vault devs /
external auditors can download the dylib straight from the PR's CI run
without rebuilding the toolchain locally.

  Artifact name:  libkkemu-<full-sha>   (avoids overwrite across PR pushes)
  Path:           env DYLIB_PATH (set by the build step)
  Retention:      30 days
  Trigger:        always() so a downstream test failure doesn't lose
                  the binary that just built successfully

if-no-files-found: error catches a future build-step regression that
silently drops the artifact.
…ccess-list

fix(eth): hash EIP-1559 access list AFTER all data chunks
Repoint deps/python-keepkey from upstream keepkey/python-keepkey to
the BitHighlander fork. Fork master is now a strict superset of
upstream master:

  upstream/master (a6c6602)
  + feat(transport): add DylibTransport for in-process libkkemu testing
  + test(dylib): screenshot regression for ringbuf capacity + canvas semantics
  + fix(dylib): address PR #14 review

DylibTransport enables in-process testing against libkkemu without an
external transport, used by upcoming integration tests. CI on the firmware
fork will pull from BitHighlander/python-keepkey going forward.
chore(deps): pin python-keepkey to fork master
… proto

Bumps deps/device-protocol to BitHighlander/device-protocol@feat/message-signing-parity
which adds (among others) TronSignMessage / TronMessageSignature / TronVerifyMessage
proto definitions used by the next commit.

Per the existing fork pattern (cf. deps/python-keepkey), submodule URL is
switched from upstream to the BitHighlander fork so the new branch is
fetchable. Kept on a fork branch until firmware integration is validated;
upstream device-protocol PR will follow.
Implements TIP-191 personal_sign for TRON, mirroring the Ethereum
implementation in lib/firmware/ethereum.c — only the prefix differs:

  hash = keccak256("\x19TRON Signed Message:\n" || ASCII(len) || message)

The signature shape is identical to Ethereum's: 65 bytes (r || s || v)
where v = 27 + recovery_id, secp256k1 over the keccak digest.

Adds:
- tron_message_sign() / tron_message_verify() in tron.c
- fsm_msgTronSignMessage() / fsm_msgTronVerifyMessage() in fsm_msg_tron.h
- MSG_IN/MSG_OUT entries in messagemap.def

UX:
- Sign: PIN check, path validation (m/44'/195'/...), printable/hex display
  on the OLED (truncated to 38×3 chars), user confirm before signing.
- Verify: address + signature recovered host-style, confirms signer
  address then displays the verified message. Returns Success or Failure.

Verification recovers the secp256k1 pubkey from (sig, hash), derives the
canonical TRON address (keccak256 of uncompressed pubkey, last 20 bytes,
prefixed with 0x41, Base58Check-encoded), and compares against the claim.

The TIP-712 typed-data signing variant (TronSignTypedHash, IDs 1407/1408)
is left for a follow-up PR.
Fork device-protocol master was just synced with upstream (PR-equivalent
merge of keepkey/master into BitHighlander/master) and then absorbed
the feat/message-signing-parity branch via a no-ff merge.

Bumps submodule pin to fork master tip (e0bf5a4) so this branch tracks
the canonical fork state. Also switches the .gitmodules tracking branch
from feat/message-signing-parity to master — that branch is now folded
into master and no longer needs separate tracking.
The ecdsa_sign_digest() canonical-check contract: non-zero = accept,
zero = retry with fresh nonce. The previous implementation returned 0
unconditionally, which caused ecdsa_sign_digest to retry forever and
ultimately fail every TronSignMessage request.

Match ethereum_is_canonic exactly: return (v & 2) == 0, which accepts
v in {0, 1} and rejects v in {2, 3}. Verifiers across the TRON/EVM
ecosystem expect v in {0,1,27,28}; restricting to canonical recovery
IDs at the producer side avoids interop issues.

Reported in PR #221 review.
…ning tests

Pins deps/python-keepkey to BitHighlander/python-keepkey@feat/tron-signmessage
(ca1063eb), which adds:
- Regenerated proto bindings from device-protocol fork master (incl.
  TronSignMessage/MessageSignature/VerifyMessage/SignTypedHash IDs)
- tron_sign_message() / tron_verify_message() client methods
- tests/test_msg_tron_signmessage.py with round-trip + rejection cases

This wires up the test suite that exercises the TIP-191 implementation
introduced in this PR.
Three line-wrap adjustments to satisfy lint-format CI gate:
- tron.c: ecdsa_recover_pub_from_sig argument wrapping
- fsm_msg_tron.h: TRON_MSG_DISPLAY_MAX macro continuation
- fsm_msg_tron.h: fsm_sendFailure line break

No semantic changes.
Bumps deps/device-protocol to BitHighlander/device-protocol@master (e0bf5a4),
which contains TronSignTypedHash / TronTypedDataSignature (IDs 1407/1408)
proto definitions used by the next commit.
Implements TIP-712 typed-data signing via the host-pre-hashed mode,
mirroring EthereumSignTypedHash exactly — TIP-712 uses the identical
'\x19\x01' prefix as EIP-712:

  digest = keccak256('\x19\x01' || domain_separator_hash || message_hash)
  sig    = secp256k1_sign(digest)  → 65 bytes (r || s || 27+v)

The host computes domain + message hashes per the TIP-712 spec; the
device assembles the digest, displays both hashes for user confirmation
on the OLED, and signs.

Adds:
- tron_typed_hash() / tron_typed_hash_sign() in tron.c
- fsm_msgTronSignTypedHash() in fsm_msg_tron.h
- MSG_IN/MSG_OUT entries in messagemap.def

UX (mirrors fsm_msgEthereumSignTypedHash):
- Confirm Base58Check signer address
- Confirm 64-char hex domain separator hash
- Confirm 64-char hex message hash (or 'No message' for domain-only typed data)

Out of scope: full on-device typed-data walker (Tron712TypesValues
analog of Ethereum712TypesValues). Hash mode covers the dapp use case
where the host renders the typed data; full mode would let the device
walk the JSON and is significantly more work — separate PR if needed.
Bumps deps/device-protocol to BitHighlander/device-protocol@master (e0bf5a4),
which contains TonSignMessage / TonMessageSignature (IDs 1504/1505) used
by the next commit.
Implements TON arbitrary-message Ed25519 signing. The handler fences
behind storage_isPolicyEnabled('AdvancedMode'), mirroring the
SolanaSignMessage pattern in fsm_msg_solana.h:461 — bare Ed25519 over
message bytes lacks domain separation and is indistinguishable on the
wire from a signed transaction.

The proper domain-separated path is TON Connect's ton_proof envelope,
which deserves its own proto + handler. This primitive exists so dapps
that already drive TonSignMessage on Trezor / Ledger have parity here,
but the policy gate blocks naive use.

Adds:
- ton_message_sign() in lib/firmware/ton.c (raw ed25519_sign over
  msg->message; returns signature + Ed25519 public_key)
- fsm_msgTonSignMessage() in fsm_msg_ton.h with:
    * PIN check, path validation (m/44'/607'/...)
    * AdvancedMode policy gate
    * Printable text vs hex preview confirm dialog (mirrors Solana)
- MSG_IN/MSG_OUT entries in messagemap.def

UX:
- AdvancedMode disabled → review banner + Failure_ActionCancelled
- AdvancedMode enabled → 'Sign TON Message' (printable) or
  'Sign TON Bytes' (hex preview, truncated to 32 bytes + length suffix)
…nMessage proto

Bumps deps/device-protocol to BitHighlander/device-protocol@master (e0bf5a4),
which contains SolanaSignOffchainMessage / SolanaOffchainMessageSignature
(IDs 756/757) used by the next commit.
Implements the Solana off-chain message spec
(https://docs.solana.com/wallet-guide/sign-offchain-message). Device
signs over:

  '\xff' || 'solana offchain' || version:u8 || format:u8
        || length:u16 LE || message bytes

The 0xFF lead byte is invalid as a Solana transaction prefix, providing
the domain separation that bare SolanaSignMessage (754/755) lacks. Per
fsm_msg_solana.h:461 the existing SignMessage path is gated behind the
AdvancedMode policy because of that gap; this primitive carries its own
domain separator, so NO AdvancedMode gate is required.

Adds:
- solana_offchain_message_sign() in lib/firmware/solana.c (envelope
  construction + Ed25519 sign, allocates 1232-byte stack buffer worst case)
- fsm_msgSolanaSignOffchainMessage() in fsm_msg_solana.h with:
    * PIN check, path warning for non-standard derivation
    * Format validation (0=ASCII, 1=UTF-8 limited; format 2 rejected)
    * Version validation (only 0 defined)
    * Length validation (1212-byte spec ceiling)
    * Printable text vs hex preview confirm dialog
- MSG_IN/MSG_OUT entries in messagemap.def

Format support:
- 0 = restricted ASCII (printable-only) — fully renderable on OLED
- 1 = UTF-8 limited — printable bytes render, non-printable falls
      through to hex preview
- 2 = UTF-8 extended (Ledger-only blind sign) — explicitly rejected;
      device requires renderable content.

Out of scope: deprecating bare SolanaSignMessage. Once dapps migrate
to the offchain envelope, the AdvancedMode gate on SignMessage can be
tightened further or the handler removed entirely. Separate PR.
The firmware build copies .options from include/keepkey/transport/ —
NOT from the device-protocol submodule. After bumping the submodule to
fork master (which adds TIP-191/TIP-712/TON SignMessage/Solana offchain
protos), nanopb saw new bytes/string/repeated fields with no max_size
caps and emitted them as pb_callback_t. The transport sanity check
('! grep -r pb_callback_t' in lib/transport/CMakeLists.txt) failed the
build.

Adds local options entries covering every new field across all four
chains. Each PR pins to the same fork master (e0bf5a4) so all four
chains' protos are generated even when a given PR only adds firmware
code for one — the options additions must be the union.

Verified locally with kktech/firmware:v15 — make kktransport.pb passes.
The firmware build copies .options from include/keepkey/transport/ —
NOT from the device-protocol submodule. After bumping the submodule to
fork master (which adds TIP-191/TIP-712/TON SignMessage/Solana offchain
protos), nanopb saw new bytes/string/repeated fields with no max_size
caps and emitted them as pb_callback_t. The transport sanity check
('! grep -r pb_callback_t' in lib/transport/CMakeLists.txt) failed the
build.

Adds local options entries covering every new field across all four
chains. Each PR pins to the same fork master (e0bf5a4) so all four
chains' protos are generated even when a given PR only adds firmware
code for one — the options additions must be the union.

Verified locally with kktech/firmware:v15 — make kktransport.pb passes.
The firmware build copies .options from include/keepkey/transport/ —
NOT from the device-protocol submodule. After bumping the submodule to
fork master (which adds TIP-191/TIP-712/TON SignMessage/Solana offchain
protos), nanopb saw new bytes/string/repeated fields with no max_size
caps and emitted them as pb_callback_t. The transport sanity check
('! grep -r pb_callback_t' in lib/transport/CMakeLists.txt) failed the
build.

Adds local options entries covering every new field across all four
chains. Each PR pins to the same fork master (e0bf5a4) so all four
chains' protos are generated even when a given PR only adds firmware
code for one — the options additions must be the union.

Verified locally with kktech/firmware:v15 — make kktransport.pb passes.
The firmware build copies .options from include/keepkey/transport/ —
NOT from the device-protocol submodule. After bumping the submodule to
fork master (which adds TIP-191/TIP-712/TON SignMessage/Solana offchain
protos), nanopb saw new bytes/string/repeated fields with no max_size
caps and emitted them as pb_callback_t. The transport sanity check
('! grep -r pb_callback_t' in lib/transport/CMakeLists.txt) failed the
build.

Adds local options entries covering every new field across all four
chains. Each PR pins to the same fork master (e0bf5a4) so all four
chains' protos are generated even when a given PR only adds firmware
code for one — the options additions must be the union.

Verified locally with kktech/firmware:v15 — make kktransport.pb passes.
messagemap.def is included by translation units that don't see fsm_msg_tron.h
(which is only #include'd from fsm.c). Without forward decls in fsm.h, the
MSG_IN entries referencing fsm_msgTronSignMessage / fsm_msgTronVerifyMessage
trigger 'undeclared here (not in a function)' errors at -Werror.

Mirrors the existing pattern for fsm_msgTronSignTx / fsm_msgSolanaSignMessage
(fsm.h:122,127). Verified locally: kkfirmware target builds clean.
messagemap.def is included by translation units that don't see fsm_msg_tron.h.
Without a forward decl in fsm.h, the MSG_IN entry triggers
'fsm_msgTronSignTypedHash undeclared' at -Werror. Mirrors fsm.h:122 pattern.
messagemap.def is included by translation units that don't see fsm_msg_ton.h.
Without a forward decl in fsm.h, the MSG_IN entry triggers
'fsm_msgTonSignMessage undeclared' at -Werror. Mirrors fsm.h:124 pattern.
messagemap.def is included by translation units that don't see fsm_msg_solana.h.
Without a forward decl in fsm.h, the MSG_IN entry triggers
'fsm_msgSolanaSignOffchainMessage undeclared' at -Werror. Mirrors fsm.h:127 pattern.
Pulls in scripts/generate-test-report.py SECTIONS additions for the
message-signing parity work, plus client methods + tests for all four
chains (T6-T18 TRON, N8-N13 TON, S13-S19 Solana). Adds KK_BUILD_LABEL
env to the test-report job so per-PR PDFs are no longer byte-identical.

Addresses 5 issues found in the previous CI test reports:
1. New tests silently absent (no SECTIONS entry) — fixed
2. PR PDFs byte-identical — KK_BUILD_LABEL fixes
3. Firmware version stuck at 7.14.0 — partially fixed via build label
4. SECTIONS hardcoded inventory — entries added for new tests
5. Section descriptions stale — TRON/TON/Solana descriptions updated
Pulls in scripts/generate-test-report.py SECTIONS additions for the
message-signing parity work, plus client methods + tests for all four
chains (T6-T18 TRON, N8-N13 TON, S13-S19 Solana). Adds KK_BUILD_LABEL
env to the test-report job so per-PR PDFs are no longer byte-identical.

Addresses 5 issues found in the previous CI test reports:
1. New tests silently absent (no SECTIONS entry) — fixed
2. PR PDFs byte-identical — KK_BUILD_LABEL fixes
3. Firmware version stuck at 7.14.0 — partially fixed via build label
4. SECTIONS hardcoded inventory — entries added for new tests
5. Section descriptions stale — TRON/TON/Solana descriptions updated
Pulls in scripts/generate-test-report.py SECTIONS additions for the
message-signing parity work, plus client methods + tests for all four
chains (T6-T18 TRON, N8-N13 TON, S13-S19 Solana). Adds KK_BUILD_LABEL
env to the test-report job so per-PR PDFs are no longer byte-identical.

Addresses 5 issues found in the previous CI test reports:
1. New tests silently absent (no SECTIONS entry) — fixed
2. PR PDFs byte-identical — KK_BUILD_LABEL fixes
3. Firmware version stuck at 7.14.0 — partially fixed via build label
4. SECTIONS hardcoded inventory — entries added for new tests
5. Section descriptions stale — TRON/TON/Solana descriptions updated
Pulls in scripts/generate-test-report.py SECTIONS additions for the
message-signing parity work, plus client methods + tests for all four
chains (T6-T18 TRON, N8-N13 TON, S13-S19 Solana). Adds KK_BUILD_LABEL
env to the test-report job so per-PR PDFs are no longer byte-identical.

Addresses 5 issues found in the previous CI test reports:
1. New tests silently absent (no SECTIONS entry) — fixed
2. PR PDFs byte-identical — KK_BUILD_LABEL fixes
3. Firmware version stuck at 7.14.0 — partially fixed via build label
4. SECTIONS hardcoded inventory — entries added for new tests
5. Section descriptions stale — TRON/TON/Solana descriptions updated
# Conflicts:
#	include/keepkey/firmware/fsm.h
#	include/keepkey/firmware/tron.h
#	lib/firmware/fsm_msg_tron.h
#	lib/firmware/messagemap.def
#	lib/firmware/tron.c
Practice release integrating:
- PR #217: libkkemu shared library + native macOS build
- PR #221: TRON TIP-191 SignMessage + VerifyMessage
- PR #222: TRON TIP-712 SignTypedHash
- PR #223: TON Ed25519 SignMessage (AdvancedMode-gated)
- PR #224: Solana SignOffchainMessage (domain-separated envelope)

PR #215 (CI artifact / kitchen-sink) deferred — not on critical path
for this practice run.

This release branch is for fork-only testing; original feature
branches remain untouched for upstream PRs.
PR #217's libkkemu/* files weren't pre-formatted; my PR #222 merge
resolution appended TIP-712 handler with a stray trailing line in
fsm_msg_tron.h; zxappliquid.c is pre-existing debt that the strict CI
gate now catches at -Werror.

No semantic changes — pure formatting normalization needed to pass
the lint-format CI gate on integration branches that combine multiple
feature PRs.
@BitHighlander
Copy link
Copy Markdown
Collaborator Author

Superseded by #425, which uses the upstream keepkey/keepkey-firmware release-7141 branch into develop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant