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.
| Screen | Emitted line |
|---|---|
| PIN entry | PIN 125 >7 — digits entered, then the selected digit (>OK = confirm tick) |
| Credential (site) | 3: google.com |
| Credential fields | 3: user, 3: password, 3: 2FA |
| Menu | Menu: <selected item> |
| Confirm page | <title> Hold=OK Left=No |
>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
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.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.Source map
| Concern | Where |
|---|---|
| Emitter, echo line, credential reveal | ZerokeyOS/zerokey-utils.cpp |
| 10 s activation gesture | ZerokeyOS/zerokey-io.cpp |
| Persistent “Reader: On/Off” toggle | ZerokeyOS/zerokey-menu.cpp |
| Runtime & persistent flag, EEPROM address | ZerokeyOS/zerokey-globals.{h,cpp} |
User guide
Activate and use the mode with a dead screen.
Bitcoin signer
Why the seed is never exposed, even here.