feat(emulator): screen-first confirm gating (reactive, Approach B)#250
Merged
Conversation
The emulator now renders the real firmware OLED confirm frame and HOLDS it BEFORE the user approves — gating like a physical KeepKey — instead of auto-approving and replaying the captured frames afterward. Driven by the dylib poll thread (firmware submodule bump): confirm_helper blocks in C while the Bun event loop stays live, so the confirm becomes reactive — one DebugLinkDecision per firmware ButtonRequest, gated on the user's click (interactive) or auto-pressed (setup ops). hdwallet's own ButtonAck flows through; nothing is pre-written. - emulator.ts: the dylib thread owns the poll (kkemu_start); flash snapshot under kkemu_lock; drop the JS poll timer + pause/resume/emuPollOnce machinery. - emulator-transport.ts: onButtonRequest hook in readChunk + writeDecision; remove ButtonAck suppression and prewrite/cancel. - emulator-window.ts: emuGatedConfirm (interactive | auto) replaces the pre-write confirm dance; remove CONFIRM_COUNTS/getConfirmCount. - index.ts / engine-controller.ts: both emuConfirmOp wrappers delegate to emuGatedConfirm (auto mode). - tests: 07-reactive-confirm.test.ts drives the thread + reactive DLD end to end (loadDevice + showDisplay address; result matches the silent path). - Makefile: ad-hoc re-sign the rebuilt dylib (linker-signed adhoc is rejected by standalone bun on Sequoia). Net -170 lines in the vault. confirm_sm.c untouched and libkkemu.c is dylib-only, so real-hardware signing is byte-identical.
BitHighlander
force-pushed
the
feat/emu-reactive-confirm
branch
3 times, most recently
from
June 16, 2026 23:51
a82da00 to
e3f1859
Compare
Host-side half of the thread-safety fixes (firmware: BitHighlander keepkey-firmware#252; submodule bump a40a7ae1 -> f322fc40). - saveEmulatorState is now async and takes the firmware lock via kkemu_trylock + an event-loop yield instead of a blocking kkemu_lock — the poll thread holds the lock across a pending confirm, so a blocking acquire from the bridge-bearing event loop would deadlock against the click that releases it. Callers (emuGatedConfirm, emulatorSave RPC) await it. - Drop emuGetDisplay + its FFI binding (kkemu_get_display retired in firmware): zero callers, and the underlying canvas read was unsynchronized w.r.t. the poll thread. - tests/emulator/08-thread-safety.test.ts: proves save-during-confirm does NOT deadlock (it waits through a held confirm then acquires), and drains the frame ring cross-thread under load with no malformed frames.
BitHighlander
force-pushed
the
feat/emu-reactive-confirm
branch
from
June 17, 2026 00:02
e3f1859 to
99aee2d
Compare
This was referenced Jun 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Emulator: screen-first confirm gating (reactive, Approach B)
The emulator now renders the real firmware OLED confirm frame and holds it before the user approves — gating like a physical KeepKey — instead of auto-approving and replaying the captured frames afterward.
This PR has two commits:
How (gating)
Driven by a dylib poll thread (firmware, merged to
alpha):confirm_helperblocks in C while the Bun event loop stays live, so the confirm becomes reactive — oneDebugLinkDecisionper firmwareButtonRequest, gated on the user's click (interactive) or auto-pressed (setup ops). hdwallet'sButtonAckflows through; nothing is pre-written.emulator.ts: dylib thread owns the poll (kkemu_start); drop the JS poll timer + pause/pre-poll machinery.emulator-transport.ts:onButtonRequesthook inreadChunk+writeDecision; remove ButtonAck suppression and prewrite/cancel.emulator-window.ts:emuGatedConfirm(interactive | auto) replaces the pre-write dance; removeCONFIRM_COUNTS. Unifies the three confirm wrappers.Concurrency review fixes (2nd commit)
A thread-safety review of the poll-thread change found three real races, all fixed (firmware: BitHighlander/keepkey-firmware #251 + #252, both merged to
alpha):g_poll_runningvolatile→_Atomic(acquire/release).saveEmulatorStateuses non-blockingkkemu_trylock+ event-loop yield (proven: waits through a held confirm then acquires, no freeze).emuGetDisplaybinding (unsafe in thread mode).Submodule
modules/keepkey-firmware455ac104→f322fc40(poll thread #251 + thread-safety fixes #252, both merged toalpha).Test
make test-emu→ 30/30 (24 host-driven +07-reactive-confirm+08-thread-safetycovering the thread + reactive DLD + the no-deadlock save path).tsc --noEmitclean. Manual: EVM sends gate correctly with the held frame.Review focus (sensitive signing path)
libkkemu.c+emulator.ts.emuGatedConfirm/onButtonRequest.