From 6d4dbc31cd58d28521a3d003ada305ebe9597943 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sun, 20 Sep 2026 21:41:24 +0200 Subject: [PATCH] docs: README with Arch/Omarchy setup and troubleshooting Co-Authored-By: Claude Fable 5.1 --- README.md | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..56ee18d --- /dev/null +++ b/README.md @@ -0,0 +1,209 @@ +# boltcard-writer + +Write, verify and wipe **Bolt Cards** (NXP NTAG 424 DNA) from a Linux PC with a +USB NFC reader, straight from the LNbits **Bolt Cards** extension's auth link. +No phone app needed. + +Tested target: **ACS ACR1252U** reader, Arch Linux / Omarchy. Any PC/SC (CCID) +reader that supports ISO 14443-4 should work. + +What it does is exactly what the official +[Bolt Card programmer app](https://github.com/boltcard/bolt-card-programmer) +does with the same link: write the `lnurlw://` URL, enable SDM mirroring +(UID + tap counter encrypted with k1, CMAC with k2), lock the file to k0, set +keys 1–4 then 0. After writing it reads the card back and checks the mirrored +`p`/`c` values with k1/k2 — the same check LNbits does on every tap. + +## Setup (Arch Linux / Omarchy) + +```sh +sudo pacman -S --needed pcsclite ccid uv git +sudo systemctl enable --now pcscd.socket + +git clone https://git.atitlan.io/aiolabs/boltcard-writer.git +cd boltcard-writer +uv run boltcard-writer readers +``` + +`uv run` creates a virtualenv and installs the two Python dependencies on +first use (needs internet once). With the ACR1252U plugged in you should see +two readers, the starred one is used: + +``` +* ACS ACR1252 1S CL Reader PICC 00 00 + ACS ACR1252 1S CL Reader SAM 00 01 +``` + +Smoke test with any NTAG 424 DNA card (blank or not): + +```sh +uv run boltcard-writer read +``` + +``` +Tap the card on: ACS ACR1252 1S CL Reader PICC 00 00 +Card: + UID: 04A1B2C3D4E5F6 + chip: NTAG 424 DNA (HW 30.00, SW 01.00, storage 0x11) + ATR: 3B8180018080 + key versions: k0=0 k1=0 k2=0 k3=0 k4=0 + NDEF file: SDM off, comm plain, read=free write=free change=key0 + NDEF URL: (empty) + state: blank / factory +``` + +## Writing a card + +1. In LNbits open the **Bolt Cards** extension and create the card as usual + (name, limits, **card UID**: get it with `read` above, keys auto-generated). +2. Click the QR icon on the card row, then **Keys / Auth link**: this copies + `https:///boltcards/api/v1/auth?a=` to the clipboard. + Back up the keys from that dialog somewhere safe. +3. Put a blank card on the reader and run: + +```sh +uv run boltcard-writer write 'https:///boltcards/api/v1/auth?a=' +``` + +``` +Card: my card +LNURLw: lnurlw://lnbits.example.com/boltcards/api/v1/scan/9f2c… +Keys backed up to /home/you/.local/share/boltcard-writer/backups/20260920-181200-write-my_card.json +Tap the card on: ACS ACR1252 1S CL Reader PICC 00 00 + UID: 04A1B2C3D4E5F6 + … + state: blank / factory +Write this card now? Keep it still on the reader until done [y/N]: y + + … writing NDEF URL (119 chars, p@88 c@124) + … authenticating with key 0 + … enabling SDM mirroring on the NDEF file + … changing key 1 + … changing key 2 + … changing key 3 + … changing key 4 + … changing key 0 + … reading back and verifying p/c + … verified: uid 04A1B2C3D4E5F6 counter 1 + +Done. Bolt Card 04A1B2C3D4E5F6 written and verified (counter 1). +``` + +**Do not move the card until "Done"** — the whole write takes about a second. + +The auth link is **single-use**: LNbits rotates the code every time it is +fetched. The keys are saved to a backup file *before* the card is touched, so +if something fails mid-way you can retry with the backup instead of a new link: + +```sh +uv run boltcard-writer write ~/.local/share/boltcard-writer/backups/.json +``` + +The `boltcard://program?url=…` deep link behind the QR code is accepted too, +and `-` reads a keys JSON from stdin. + +## Checking a written card + +```sh +uv run boltcard-writer read --keys ~/.local/share/boltcard-writer/backups/.json +``` + +reads the live `p`/`c` the card mirrors and verifies them with k1/k2, printing +the tap counter. Or just tap it on any Lightning POS / wallet that speaks +LNURL-withdraw. + +## Wiping a card + +Resets keys to zero, disables SDM and clears the NDEF so the card is blank +again (delete it in LNbits afterwards). + +1. In LNbits, QR icon on the card row → **Wipe** → copy the JSON (`WIPE DATA`). +2. Save it to a file, or pipe it: + +```sh +uv run boltcard-writer wipe wipe.json +# or +pbpaste | uv run boltcard-writer wipe - # wl-paste on Wayland, xclip -o on X11 +``` + +A backup file from `write` or the card's auth link work as well. The JSON's +`uid` is checked against the card on the reader so you cannot wipe the wrong +card by mistake. + +## Options + +``` +boltcard-writer [-r READER] [--debug] [--timeout SECS] COMMAND + + readers list readers + read [--keys FILE] inspect the card, optionally verify p/c + write SOURCE [-y] [--force] [--no-verify] [--backup-dir DIR] + wipe SOURCE [-y] [--backup-dir DIR] +``` + +`--debug` prints every APDU exchanged with the card to stderr. **Include that +output when reporting a problem.** Key material is never in the trace (key +changes travel encrypted). + +## Troubleshooting + +| Symptom | Fix | +|---|---| +| `libpcsclite.so.1 not found` | `sudo pacman -S pcsclite` (NixOS: use `nix-shell`) | +| `SCARD_E_NO_SERVICE` | `sudo systemctl enable --now pcscd.socket` | +| `SCARD_E_NO_READERS_AVAILABLE` | reader unplugged, or the `ccid` package is missing. `lsusb` should list `072f:223b Advanced Card Systems`; `pcsc_scan` (package `pcsc-tools`) should show it | +| `SCARD_E_SHARING_VIOLATION` | close `pcsc_scan` or the other program using the reader | +| Waits forever at "Tap the card" | card not detected: put it flat on the reader's centre; a different card type (NTAG 213, credit card) is rejected on first command instead | +| `AUTHENTICATION_ERROR` / `RndA' mismatch` on write | the card is not factory-fresh; its k0 is not zero. Wipe it with the keys it was written with | +| `AUTHENTICATION_ERROR` on wipe | wrong keys for this card | +| `AUTHENTICATION_DELAY` | too many bad auths; leave the card on the reader ~1 s and retry | +| `card already looks provisioned` | it has SDM on or non-zero key versions; wipe first (`--force` only if you know k0 is zero) | +| `this is not an NTAG 424 DNA` | wrong tag; Bolt Cards need NTAG 424 DNA | + +If the user cannot access the reader (permission errors from pcscd), Arch's +pcsclite uses polkit: the session must be a local active login, or add a rule +for `org.debian.pcsc-lite.access_pcsc` / `access_card`. + +## NixOS + +```nix +services.pcscd.enable = true; # ships the ccid driver +``` + +then in the repo `nix-shell` (provides `uv` + `libpcsclite`) and use +`uv run boltcard-writer …` as above. + +## Development + +```sh +uv run pytest # AN12196 vectors + a software NTAG 424 simulator +uv run ruff check +``` + +No hardware is needed for the tests. `tests/test_vectors.py` replays the +worked examples from NXP application note AN12196; `tests/simcard.py` is a +minimal software card that verifies MACs, decrypts, changes keys and renders +SDM mirroring, so the write/wipe flows run end to end. + +## How it maps to the card + +| Step | Command | Comm mode | +|---|---|---| +| NDEF URL with `p=`/`c=` placeholders | ISOSelectFile + ISOUpdateBinary | plain (factory file is free-write) | +| Session | AuthenticateEV2First key 0 | AES, LRP not used | +| SDM on, offsets, write locked to k0 | ChangeFileSettings file 02 | full (`40 00E0 C1 FF12 …`) | +| k1..k4 then k0, key version 1 | ChangeKey | full | +| Verify | ISOReadBinary, decrypt `p` with k1, CMAC with k2 | plain | + +LNbits stores `k3 = k1` and `k4 = k2`, as the phone app does. Random-UID mode +(`uid_privacy`) is not used by LNbits and not implemented. + +## Limitations + +- Linux only (ctypes bindings to `libpcsclite`; no compiled extension). +- Assumes factory keys (all zero) when writing; a card written with other + keys must be wiped with them first. + +## License + +MIT