Skip to content
gemivnetPublic

About

Air-gapped key fob that proves the person on the other end is who they say they are, plus offline TOTP codes. Open hardware: STM32U0, e-paper, 5-way joystick, IR pairing.

Topics

Resources

Contributing

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

firmware hardware simulator case License Last Commit

IRL2FA

An air-gapped key fob that proves the person on the other end is who they say they are, and keeps your 2FA codes off your phone.

AI can now clone a voice from a few seconds of audio and write messages in anyone's style. "It's me, I lost my phone, can you send…" is no longer something you can judge by ear. IRL2FA gives two people a shared secret they set up once, in person. From then on, each device shows two words that change every 15 seconds: the word you say and the word you should hear back. If the reply matches, the other person is holding the device you paired with face to face.

It is also a standard TOTP authenticator (the 6-digit codes from Google Authenticator and similar apps) for up to 512 accounts. It has no USB data port, no radio and no network. The only control is a 5-way joystick.

IRL2FA in its 3D-printed case

▶ Try it in your browser · Build one · How pairing works · Download


How it works

Contact words screen TOTP code screen

Pair once, in person.

  1. Both people choose Pair with someone and hold their devices top edge to top edge.
  2. The devices agree a 256-bit key over infrared using X25519, with a commitment scheme that stops a man in the middle.
  3. Both screens show the same six joystick symbols. You compare them, confirm, and name the contact.

Verify any time after.

  • Open the contact. Say the SAY word; they should answer with your HEAR word.
  • Words come from HMAC-SHA256 over the current 15-second window, mapped onto the 2048-word BIP-39 list.
  • Nothing is ever transmitted again, so there is nothing to intercept.

TOTP codes.

  • Add an account by typing the base32 secret most sites show under "can't scan the QR code?".
  • RFC 6238; SHA1/SHA256/SHA512; 6 or 8 digits; 30 or 60 s.

Secrets at rest.

  • Every record, labels included, is sealed with XChaCha20-Poly1305 in the MCU's read-protected flash.
  • An optional joystick PIN wraps the key with Argon2i. Ten wrong PINs erase the device.

The full design is in docs/pairing-protocol.md and docs/ui-flows.md.

For family members

Simple mode: Susan will say: material

Setting one up for a parent, grandparent or child who should only check who is calling? Use simple mode (the docs call it vulnerable mode). The device then shows one thing: the name of the person, and in big letters the word that person will say. "If they don't say this word, hang up and call them back." Up and down go to the next person. Nothing else does anything: no menus, no codes, no settings.

  1. Pair the device in person with everyone who might call, and give each contact a name they will recognise ("Susan", "Grandpa Joe").
  2. Settings → Simple mode, then choose a family PIN of 6 or more joystick moves and enter it three times. Write it down: it cannot be reset.
  3. Give them the device. When you call, open their contact on your own device and say your SAY word.

To change anything later, hold the centre of the joystick for 10 seconds and enter the family PIN. Wrong family PINs never erase the device; they make the next try wait longer, up to an hour. Details and the security trade-offs are in docs/ui-flows.md.

Try the simulator

The firmware's portable core is compiled to WebAssembly and runs two devices side by side in your browser: pair them over a simulated IR link, compare words, add a TOTP secret and check it against your phone.

It behaves like the hardware, not just like the firmware:

  • E-paper. A full refresh flashes black and white for 3 s; a partial one takes 0.42 s and leaves faint ghosting that builds up until the firmware's next full refresh. Presses during a refresh are queued, as on the board.
  • IR link. Set the distance and angle between the devices. Bit errors and dropouts rise with distance, and bytes take their real airtime at 4800 baud on a half-duplex link; the firmware listens before it talks. Beyond about 9 cm pairing keeps retrying and gives up after a minute.
  • Battery. The panel uses the same power model as the battery budget, generated from hardware/power. Fast-forward days or years of use and watch the remaining life and the cell voltage fall.

irl2fa.com

Simulator: two devices pairing

Hardware

MCU STM32U083KCU6: Cortex-M0+, 256 KB flash, 40 KB RAM, TRNG, readout protection
Display 2.13″ e-paper, 250×122, Good Display GDEY0213B74 (SSD1680)
Clock Micro Crystal RV-3032-C7, temperature-compensated ±2.5 ppm
Input Alps SKRHABE010 5-way switch, the only control
Pairing 940 nm side-looking IR LED and phototransistor on the top edge
Power CR2450 coin cell. About 2.8 years typical, 1.6 years worst case at 20 uses a day, with the awake time and refreshes measured on the emulated firmware (calculation)
Board 77 × 31 mm, 2 layers, 0.8 mm, ENIG. JLCPCB can assemble every part except the display
Case 80.9 × 34.9 × 11.0 mm, 3D-printed in 4 parts, M2 screws

PCB front PCB back

Build one

You need a PCB, a display, a coin cell, a printed case and four screws. Everything to order is committed and kept current by CI.

  1. Order the assembled board from JLCPCB. The form has a few answers that would spoil the boards; docs/assembly-review.md explains each one.
    • Upload hardware/fab/irl2fa-gerbers.zip and keep the detected size (77 × 31 mm) and 2 layers.

    • Delivery format: Panel by JLCPCB, 1 column × 2 rows, with edge rails on the long sides, joined by stamp holes (mouse bites), not V-cut. Assembling both sides needs Standard PCBA, which only takes boards or panels of at least 70 × 70 mm.

    • PCB thickness 0.8 mm, surface finish ENIG (a bare pad is the coin cell's negative contact), via covering Tented, 1 oz copper, any colour.

    • Remove order number: Yes: the board has no spot reserved for it.

    • Turn on PCB Assembly: PCBA type Standard, assembly side Both sides (the joystick is on the front, everything else on the back), tooling holes added by JLCPCB, Confirm Parts Placement: Yes.

    • Add irl2fa-bom-jlcpcb.csv and irl2fa-cpl-jlcpcb.csv. Every part has an LCSC number. The BOM lists JLCPCB's stock, Basic/Extended parts and alternatives. The MCU and the RTC are often short, so check them before paying.

    • The placement file is already corrected for JLCPCB's own footprints. On the placement preview, check the polarised parts against the expected-view table, especially the IR LED and phototransistor (lenses at the top edge) and the coin-cell clip (open end at the bottom edge).

    • Paste the order remark into the remark box.

    • Optional: have JLCPCB flash the firmware. In the Standard PCBA options, open the advanced options and choose programming. JLCPCB asks for:

      • the MCU: STM32U083KCU6;
      • the programming interface: SWD on five test pads on the back, along the bottom edge, 1.5 mm round on a 2.54 mm grid: TP2 GND, TP1 VBAT (supply 3.0 V), TP3 SWDIO, TP4 SWCLK, TP5 NRST. Positions and a board image are in docs/pcb-design.md; attach docs/images/pcb/pcb-back.png;
      • the firmware: the factory image irl2fa-factory-<version>.bin from the latest release (bootloader + signed firmware in one file), written from the start of flash (0x08000000). Do not send the application's .elf alone: it does not start without the bootloader. The chip is left unprotected; readout protection is only ever turned on by its owner, from Settings.

      It costs about $8 in engineering fees plus operator time per board. JLCPCB decides per order whether it can program a part, so the option can be refused. A freshly programmed chip runs ST's bootloader, not the firmware, until it has seen a power-on reset (firmware review F4): the board must be power-cycled after the first flash. Inserting the coin cell does that; a board that is checked still on the fixture after programming looks dead, and is not.

    • To solder it yourself, use the interactive BOM.

  2. Buy the display. A Good Display GDEY0213B74 (about $5), or the GDEM0213B74WT if it has to work below 0 °C.
  3. Print the case.
  4. Flash the firmware.
    • Get the factory image irl2fa-factory-<version>.hex (bootloader + signed firmware, for the whole chip) from the latest release, or build firmware/build/stm32u0/irl2fa-factory.hex with firmware/port/stm32u0/build.sh (unsigned: a development unit).
    • Flash it through a Tag-Connect TC2030-NL cable and any ST-Link: firmware/port/stm32u0/README.md. The test pads TP1-TP5 on the back carry the same signals for a pogo-pin fixture or probe clips. Skip this step if JLCPCB programmed the boards.
    • The chip stays unprotected (anyone with a debug cable could read it) until you choose Settings → Lock debug port on a fresh cell. Do that once the device is set up; it is permanent unless the chip is erased, and updates over IR still work (details).
    • Later versions go on over infrared, no cable: see Updating.
  5. Assemble.
    • Thread the display's flex cable through the slot from the front and lock it into the connector on the back (J1), then stick the display down on the front. Done the other way round, the flex has to be bent back over the glass at its glued root to reach the slot.
    • Slide the CR2450 under its clip from the bottom edge and screw the case together. Flash first (step 4): the Tag-Connect pads are on the back, inside the case.
    • Set the time: the device asks on first start.

Built one? Please file a build report.

Status: v0.2 is a complete, simulated and checked design, but no board has been built yet. The first boards will confirm the panel orientation, the IR range and the real sleep current. Until a build report says otherwise, treat it as a prototype.

Updating

New firmware goes on over infrared, from another IRL2FA or from a computer with a ~$5 USB infrared adapter. Settings → Firmware update → Receive an update (with the PIN, if set), then send the release's .irl2fa file:

python3 firmware/tools/irl2fa_update.py --port /dev/ttyUSB0 irl2fa-v0.2.0.irl2fa

or, from a device that already runs it, Settings → Firmware update → Send it to another. About two minutes. Codes, contacts and settings stay. The bootloader accepts only firmware signed with the IRL2FA release key, never an older version, and a cell pulled at any moment leaves a device that starts. Details, the adapter, and the security model: docs/firmware-updates.md.

Every change is simulated

The hardware is open to modification, and people order straight from this repository. So every push runs:

  • ERC and DRC (with schematic parity and silkscreen checks as errors), and the IR parts facing out through the top edge
  • every footprint checked against its manufacturer's land-pattern drawing, and its pins against the schematic
  • netlist checks against the pin map shared with the firmware
  • SPICE simulations of the coin cell, e-paper booster, power switch, reverse-battery protection and IR driver
  • the battery-life budget, with the on-times measured by running the real firmware through a scripted use and a pairing in the emulator
  • a check that the committed Gerbers, BOM and placement files match the KiCad files
  • Gerber sanity checks
  • case-fit tests
  • firmware unit tests (RFC test vectors) and UI flows, with a coverage minimum
  • a power cut after every flash operation of every store write, which must leave the old state or the new one
  • fuzzing of the IR parser, pairing, the encrypted store, base32 input and the whole app
  • constant-time checks of PIN verification, MAC compares and the pairing crypto
  • static analysis (cppcheck, clang-tidy) with zero findings
  • a formal model of the pairing protocol (Tamarin): the key stays secret and a man in the middle has to commit before he sees an honest key
  • the real firmware binary running on an emulated STM32 (Renode), where two virtual devices pair

Details: docs/hardware-testing.md.

make check   # everything CI runs, locally (needs Docker)

Repository layout

firmware/core/          portable C11: crypto, TOTP, words, pairing, encrypted store, UI
firmware/port/stm32u0/  the device: HAL, SSD1680 and RV-3032 drivers, startup, linker script
firmware/renode/        emulated STM32U083 + peripheral models + Robot tests
firmware/tests/         host unit tests
sim/                    the browser simulator (WebAssembly)
hardware/               KiCad 9 project, pins.yaml, board.yaml, SPICE benches, power model, fab outputs
hardware/lib/           footprints, symbols and 3D models (drawn from datasheets)
case/                   OpenSCAD case, STL/3MF exports, fit tests
docs/                   design documents and images

Why this exists

The original idea (2025): a pocket device that does one thing well, proving that a familiar voice or a familiar account is really the person you know. It has no app, no cloud and no battery to charge every night. The 2026 restart rebuilt the design around what that needs to be safe in practice: a full-strength key exchange instead of a typed code, secrets that never leave the chip, and enough tests that strangers can change it with confidence. See docs/design-review.md.

Security notes.

  • A verification word proves possession of a paired device and a clock within about 15 s, nothing more.
  • Someone holding your unlocked device can answer for you, which is what the PIN is for.
  • Keep the device's clock right: TOTP needs it within about 30 s, and the RTC drifts at most about 80 s a year.
  • Report vulnerabilities privately via the Security tab.

Contributing

Contributions are welcome: firmware, hardware, case, docs, translations of the wordlist. Read CONTRIBUTING.md first: main must always be orderable, and CI enforces it.

License

Part Licence
Firmware and simulator AGPL-3.0-or-later
Hardware (schematic, PCB, footprints, models) CERN-OHL-S-2.0
Case CC-BY-SA-4.0

You may use, modify, build and sell all of it, as long as derivatives stay open under the same terms. Third-party components are listed in THIRD_PARTY.md.

About

Air-gapped key fob that proves the person on the other end is who they say they are, plus offline TOTP codes. Open hardware: STM32U0, e-paper, 5-way joystick, IR pairing.

Topics

Resources

Contributing

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages