ZerokeyOS/zerokey-utils.cpp (emitter), zerokey-io.cpp (gesture) and
zerokey-menu.cpp (persistent toggle).
State & activation
- Runtime flag
screenReaderMode(zerokey-globals.cpp, defaultfalse). - Persistent flag in EEPROM at
EEPROM_SCREEN_READER_ADDR = 0x0003(1= boot straight into reader mode).initScreenReaderMode()reads and applies it at boot; Settings → “Reader: On/Off” (setScreenReaderPersistent) writes it and flips the runtime flag so the change takes effect immediately. - Session gesture: hold Center for 10 s (
SCREEN_READER_HOLD_MS = 10000) while the PIN prompt is shown (PIN_SCREEN/EDITPIN). A border animation fills over the full 10 s and togglesscreenReaderModeexactly on completion.
The gesture is deliberately restricted to the PIN screen so it is reachable
before unlocking — the whole point is to rescue a device whose screen died.
Releasing before 10 s does nothing.
What it emits (echo‑line mechanics)
The device keeps one logical line on the host.announceCurrentScreen():
- caches the text in
g_lastEchoand the length ing_hidEchoLen; - on a state change, backspaces the previous echo and types the new one (unchanged states are not retyped → no flicker);
- sets
g_suppressNextAnnounceto skip the one automatic announce that would otherwise double up right after a real credential was typed.
hidTapKey): press, 12 ms, releaseAll,
18 ms; \n→KEY_RETURN, \t→KEY_TAB.
Blind PIN entry: Up/Down change the selected digit (watch
>n), Right adds
it, Left deletes, then select >OK and Right/Center to unlock — the typed line
mirrors what the OLED would show.
Revealing a value: pressing Center on a credential wipes the echo
(typeCredential backspaces g_hidEchoLen) and types the real value
(user/password) exactly as normal HID typing would — so a password can be read
blind into a focused text box.
Data‑exposure boundary (the audit‑relevant part)
Two properties matter for an audit:- No new egress channel. The reader only ever emits (a) the single status
line the OLED would show, or (b) — on an explicit Center — the same value
typeCredentialalready types during normal use. It exposes nothing that a sighted user couldn’t already get over HID. - The Bitcoin seed is never typed. The seed viewer (
btcDisplaySeed) draws to the OLED only and has no HID path; the reader has no branch that emits seed words or entropy. A broken‑screen unit therefore still cannot leak the Bitcoin seed over USB. (See Bitcoin signer.)
How to audit it yourself
1
Enumerate the emitters
Grep for
announceCurrentScreen and hidTypeText/hidTapKey in
zerokey-utils.cpp. Confirm every call site corresponds to a screen the OLED
already shows, and that none of them reads the ZKBW wallet page or the seed
words.2
Confirm the gesture scope
In
zerokey-io.cpp, verify the 10 s toggle is gated on
programPosition == PIN_SCREEN || EDITPIN and SCREEN_READER_HOLD_MS.3
Confirm persistence
Verify the persistent flag is a single byte at
EEPROM_SCREEN_READER_ADDR (0x0003) and that it only toggles the same runtime behaviour.Source map
User guide
Activate and use the mode with a dead screen.
Bitcoin signer
Why the seed is never exposed, even here.