nullrouteSource

Air-gapped Bitcoin signer for the Raspberry Pi

Built to be checked,not to be trusted.

A Raspberry Pi and an 800x480 touchscreen, turned into a Bitcoin signing device with no network of any kind. You roll dice for the seed, carry transactions across the gap by camera, read each one on the device’s own screen, and sign offline.

Every module ships a specification the build checks, so the code, the specs and the tests cannot quietly disagree. The source is the product: build it yourself and compare the hash the device shows you.

StatusRuns today on a Mac or Linux box, and as a card image that boots under QEMU. Hardware bring-up on the Pi 4 is next, then an outside review. Keep real money off it until both are done.

specs
39
invariants
338
tests bound to invariants
977 of 1296
On the device800x480 touchscreen · Pi 4 first
The device's first screen: a verified badge, the manifest root and the system partition hash, and a button to open a wallet

The first screen, before any wallet is open, drawn with sample hashes. On a real device the top one is the manifest root of the software it is about to run. Build the source yourself, and if your number differs, do not enter your PIN. Nothing on this website runs that software.

What it is

Six screens from the real frontend, at the panel’s real 800x480. Rendered by a browser from the same gallery the layout and contrast checks measure, so this is the device rather than a picture of one.

Two hashes, before the PIN
Two hashes, before the PIN

The manifest root of the application, and the dm-verity root the kernel is checking every block of the root filesystem against, read from the live device-mapper table rather than from the card. Compare the first against your own build. The second is absent where there is no mapping, rather than shown as a zero. The values in these pictures are samples, not this build.

A hundred rolls
A hundred rolls

The entropy counter is arithmetic you can repeat. 100 rolls, not 99: 99 is 255.911 bits and this project does not round in its own favour.

The words, once
The words, once

A standard BIP-39 mnemonic. Any wallet can restore it, which is what makes walking away from this project harmless.

Read before signing
Read before signing

Every output, every amount, and which of them are provably yours. Signing stays disabled until the review has been read to the end.

An address to check
An address to check

Shown with its derivation path and its script type, so you can confirm it on a second device rather than trusting this one.

Somebody else's proof
Somebody else's proof

Checking a signature needs no key and works with the wallet locked. Verifying a stranger should not cost the passphrase to your money.

Two hashes, before the PIN
Two hashes, before the PIN

The manifest root of the application, and the dm-verity root the kernel is checking every block of the root filesystem against, read from the live device-mapper table rather than from the card. Compare the first against your own build. The second is absent where there is no mapping, rather than shown as a zero. The values in these pictures are samples, not this build.

Close
A hundred rolls
A hundred rolls

The entropy counter is arithmetic you can repeat. 100 rolls, not 99: 99 is 255.911 bits and this project does not round in its own favour.

Close
The words, once
The words, once

A standard BIP-39 mnemonic. Any wallet can restore it, which is what makes walking away from this project harmless.

Close
Read before signing
Read before signing

Every output, every amount, and which of them are provably yours. Signing stays disabled until the review has been read to the end.

Close
An address to check
An address to check

Shown with its derivation path and its script type, so you can confirm it on a second device rather than trusting this one.

Close
Somebody else's proof
Somebody else's proof

Checking a signature needs no key and works with the wallet locked. Verifying a stranger should not cost the passphrase to your money.

Close

How you check it

Every module ships a specification a machine reads. The build fails when the code, the specs and the tests stop agreeing, so the specification cannot quietly fall behind the thing it describes.

make verifypassed
nullroute verification

  ok    coverage        271 of 271 runtime exports covered
  ok    invariants      338 invariants bound to 977 tests, 1296 tests in the suite
  ok    vectors         24 of 24 vector files match their pinned hash
  ok    differential    3 modules cross-checked
  ok    integrity       368 files, root 0886433f...a9857292

  39 specs, 338 invariants
  manifest root  0886433f3c2968433144f92d467cf15e593c29438de9349f5148244aa9857292

verification passed

Check the smallest claim yourself

The device turns dice into a seed by one rule you can repeat without any of this code: SHA-256 of the rolls as ASCII digits, with no trailing newline. Here is the public example from the entropy document, 100 rolls of 123456 repeated. Paste it into a terminal. On macOS, use shasum -a 256.

$ printf '%s' '1234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234561234' | sha256sum
e56403e8522ddeae1b44a1e8148b1ba4d3b4c626ccf20980056eedcc7e0c0f35  -

Those 32 bytes are the seed’s entropy. If your terminal prints a different number, one of us is wrong and it is worth finding out which. Never use this sequence for money: it is public. The whole derivation, down to the 24 words.

The last line is the manifest root: every source file hashed, sorted under LC_ALL=C, in a format coreutils produced and coreutils can check. The device prints the same string on its lock screen. Build the source yourself and compare the two. If they differ, do not enter your PIN. How to do that.

Specified and implemented

  • Dice into a seed, checkable by hand
  • Addresses and descriptors other software restores
  • Transactions reviewed before they are signed
  • Multisig across several devices
  • Several wallets, encrypted at rest
  • Backups, labels and child seeds
  • Proving an address, and checking somebody else’s
  • Closing itself when you walk away
  • Data across the gap by camera

Each line is present because the verifier reports every spec behind it as implemented. Nothing here is typed in by hand.

Where it stands

What is built, what comes next, and what it does not protect against yet. Keep real money off it until the first two are done.

Not audited yet
One person wrote it. Before it holds real money, the code that holds keys needs to be read by somebody other than its author.
Hardware bring-up is next
The card boots under QEMU, opens its dm-verity mapping, refuses a partition with one byte changed, and starts the signing daemon. The next milestone is the same card on a Pi 4 with the 7 inch panel, which is the first run of the firmware path from power-on to the kernel and the first time the screens are drawn on the panel itself.
The boot partition is not covered
dm-verity detects modification of the system partition and does not prevent it. The boot partition holds the root hash and cannot be under the tree that hash describes, so an attacker who rewrites it supplies their own number. A signed boot chain closes that in phase 7, last on purpose, because it burns one-time fuses.
The browser is the weakest part
The screens are a Chromium kiosk, and its own sandbox needs the user namespaces that the directives protecting the daemon take away. So trust is kept out of it rather than hardened into it: the daemon holds the keys, and the screens receive only xpubs, addresses, descriptors and transactions.
It will not save you from a person
Hidden profiles and a wipe PIN are planned for phase 7. When they exist, they buy time against somebody unsophisticated and nothing more: this codebase is public, so anyone who reads it knows exactly what they do.

The full list, including what this deliberately does not defend against, is in the threat model. It is long on purpose.

Read it yourself

The device and the method behind it are both open: specifications a machine can check, invariants bound to named tests, and a build that fails when a claim stops being true.

There is no download: you build it, which is the point. It runs on a Mac or a Linux box today. The device needs a 64-bit Raspberry Pi with enough memory, an 800x480 touchscreen and a camera. The card built today targets a Pi 4 and the official 7 inch touchscreen, about $100 in parts, and other boards follow as each one is brought up. A wallet here is a BIP-39 mnemonic and a canonical BIP-380 descriptor, so Bitcoin Core restores it with none of this code involved. The most valuable thing you can do with it is find where it is wrong.

Rendered from docs/ in the repository, the same bytes a reviewer reads in the source.

github.com/Xaxis/nullroute