Skip to main content
This page documents the implementation of the HID screen‑reader so its data exposure can be audited. For the user recovery guide see Broken / No‑screen mode. The mode makes the device type the current on‑screen state over USB HID (keyboard), one line at a time, so a unit with a dead OLED can still be unlocked and operated blind. Implementation lives in ZerokeyOS/zerokey-utils.cpp (emitter), zerokey-io.cpp (gesture) and zerokey-menu.cpp (persistent toggle).

State & activation

  • Runtime flag screenReaderMode (zerokey-globals.cpp, default false).
  • 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 toggles screenReaderMode exactly 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_lastEcho and the length in g_hidEchoLen;
  • on a state change, backspaces the previous echo and types the new one (unchanged states are not retyped → no flicker);
  • sets g_suppressNextAnnounce to skip the one automatic announce that would otherwise double up right after a real credential was typed.
HID output uses a fixed cadence (hidTapKey): press, 12 ms, releaseAll, 18 ms; \nKEY_RETURN, \tKEY_TAB.
ScreenEmitted line
PIN entryPIN 125 >7 — digits entered, then the selected digit (>OK = confirm tick)
Credential (site)3: google.com
Credential fields3: user, 3: password, 3: 2FA
MenuMenu: <selected item>
Confirm page<title> Hold=OK Left=No
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)

The reader types PIN digits and, on an explicit Center, passwords into whatever field is focused. Use it only into a private text field you control and clear that field afterwards.
Two properties matter for an audit:
  1. 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 typeCredential already types during normal use. It exposes nothing that a sighted user couldn’t already get over HID.
  2. 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

ConcernWhere
Emitter, echo line, credential revealZerokeyOS/zerokey-utils.cpp
10 s activation gestureZerokeyOS/zerokey-io.cpp
Persistent “Reader: On/Off” toggleZerokeyOS/zerokey-menu.cpp
Runtime & persistent flag, EEPROM addressZerokeyOS/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.