Bunker mode, rehearsed
A Safe owner that burns its key after every approval.
Each press approves a real transaction on a test network with a one-time key, and names the next key in the same transaction. The key that signed is dead. The next one has never been seen, so there is nothing yet to attack. If elliptic curves fall, a stolen curve key gets nowhere.
- Revealthe one-time signature: key n is spent
- Runthe Safe transaction it approves
- Rotateto key n+1: only its fingerprint goes on chain
Starting MicroPython…
Serial: every line the page and the console said
On a board these lines would cross USB. Here they cross into MicroPython. Your passkey's seeds are secret and are never shown.
The attack room
Try to use what your approvals put on chain. Each attack asks the live seat with a simulation
(eth_call): it costs nothing and moves nothing. The seat's own error comes back.
The attacks use your last approval. Make one first.
How it works
The Safe stays the multisig
An ordinary Safe 1.4.1 holds the money, keeps the owners, counts approvals and runs transactions. Nothing about it changes. Its one owner here is a seat: a small contract that approves only when two signatures check out.
- A curve signature from your passkey, P-256, checked by Safe's own passkey signer.
- A one-time signature that uses only SHA-256, and only once: Winternitz, 67 chains of 15 steps.
Every approval names the next key's fingerprint. The seat records it and accepts nothing else. The key that just signed is dead.
One passkey, two jobs, one tap
Your passkey signs c with its curve key. In the same tap, its PRF extension turns two labels
into two 32-byte seeds, from a secret that never leaves it. The console turns the seed of key n into 67
secrets and signs m; the seed of key n+1 gives the next fingerprint.
c = sha256("sign-and-burn/approve/v1" ‖ chain ‖ seat ‖ safe ‖ n ‖ safeTxHash)
m = sha256("sign-and-burn/one-time/v1" ‖ c ‖ nextKey)
The passkey signs c; key n signs m. Swap the next key and the one-time
signature no longer fits.
The console
The console is about a thousand lines of MicroPython: PicoQuorum's Safe hash and decoder, the one-time keys, and a core that answers one JSON line with another. It works out the Safe transaction hash itself, says what the transaction does, refuses what it can't explain, and keeps the guardrail.
It runs here in WebAssembly. Copy the same files to a Pico or an ESP32 and main.py answers the
same lines over USB. That hasn't been tried on a board yet.
The guardrail: one signature per key
The seat makes sure a spent key is dead. It can't make sure a key signs only once: two signatures with one key reveal enough to forge a third. So the console records every approval before it lets one out, and while key n's approval waits it signs nothing else with key n, only sends that one again. The attack room's danger case shows why.
What it protects, and what it doesn't
| Threat | Holds? | Why |
|---|---|---|
| Curves broken; the attacker sees everything public | yes | They can make curve signatures, but not the next one-time signature. |
| Copying or replaying an approval | yes | The chain, the seat, the Safe and n are all in what is signed. |
| One key signs two messages, both public, and curves are broken | no | The guardrail is the only defence. |
| A bug in the one-time code | partly | The curve signature still guards, unless curves are broken too. |
| A hostile copy of this page, or a hostile browser extension | no | The page sees the seeds. Check its fingerprint, or run your own copy. |
| A stolen, unlocked device, or a taken-over account that syncs your passkeys | no | Whoever has the passkey has both halves. |
| Lost passkey | stuck | Here the seat can't approve again. In a real multisig the other owners replace it. |
Nobody has shown that elliptic curves can be broken this way. This is a rehearsal on a test network, not a response to a known attack, and it has not been audited.
Check this page
The console fingerprint, worked out in your browser over the files it runs:
…
The README's build table names the same one, and so would a board running these files
(node tools/fingerprint.mjs).
- Save a copy.
SHA256SUMSlists every file in this folder. Fetch them, thensha256sum -c SHA256SUMS. - Rebuild it.
cd site && npm ci && npm run buildwrites the same bytes intodocs/. CI checks that on every push. - Run your copy.
python3 -m http.serverin the folder, then openlocalhost:8000. A passkey made there belongs to your copy, and this site can never ask it to sign.