Security
Disclosure, and the threat model.
Bitcoin Witness defends against five threat classes through five orthogonal boundaries.
Disclosure contact
If you've found a vulnerability in Bitcoin Witness or in the free xpubverify tool:
- Email:
security (at) bitcoinwitness.org - Download the public key directly: /keys/bitcoinwitness-pub.asc. After importing, confirm the fingerprint below matches before trusting any signature.
Encrypt to the fingerprint above (gpg --recipient 014B4C54914E2D6F3AC5D601D8AE5F9CA285E35D) instead of a particular email address.
For day-to-day questions that aren't vulnerabilities, use GitHub Discussions.
What to report
- Memory-safety bugs in
xpubverifyor any product script - Address-derivation incorrectness: any case where Bitcoin Witness produces an address that differs from a known-correct reference
- Air-gap bypass: any way the station could touch a network
- Supply-chain tampering: anything that lets a bundled component differ from the fingerprint or checksum it was pinned to
- Dark Skippy or covert-channel signature leakage: anything that lets a signature carry seed material
- PSBT or seed handling that exposes secret material
Out of scope for disclosure
- Issues in third-party software bundled by Bitcoin Witness (Electrum, Sparrow, KeePassXC, Ian Coleman's BIP39 tool). Report to the respective projects
- Hardware tampering with a device you've voluntarily handed over to a third party
- Social engineering of the user
- Physical seed-plate theft
Threat model
We start by acknowledging that there is no perfect security. Please don't leave your Rolex on the counter.
That said, if you do, there are a few things we include that can soften the impact.
Installation
Pinned supply chain
GPG-pinned tarballs, a digest-pinned base image, version-pinned wheels. Re-checked on the Pi.
Not: a compromised preparation machine.
Every workflow
Air gap
Four independent layers, nine checks before anything else runs. A failure halts the workflow; continuing needs a typed override.
Not: anything that never needed a network.
Powered off
LUKS at rest
Five kinds of persistent secret behind AES-XTS. Optional at install.
Not: integrity. It hides secrets. It does not prove the box is unaltered.
At signing
Nonce security and privacy audit
Dark Skippy and Privacy audits
Not: a signature made elsewhere. That nonce was never ours to reproduce.
Any later boot
Trust-base integrity
Hashes the code that has to sit in plaintext to boot. You compare it off the device.
Not: remote attestation, and not proof. Evidence.
What all five rest on
Plaintext scripts the users audit themselves or via AI. We ship no binaries of our own, none of it is a black box.
1. Air gap (network exposure)
The station has no inbound or outbound network path. WiFi and Bluetooth are disabled at four independent layers: hardware overlays, kernel module blacklist, firmware deletion, and active rfkill enforcement. Every workflow runs the air-gap check before it does anything else. It asks nine questions of the running machine: no interfaces up, no default routes, no external nameservers, no listening sockets, no wireless interfaces, no wireless kernel modules, all radios rfkill-blocked, no surveillance daemons running, and no wireless firmware left under /lib/firmware. A failure stops the workflow there, with the failing check named on screen; it continues only if you type an explicit override, and the report records that. The last of those reads the disk rather than the running kernel, and it is deliberately scoped as a warning: firmware without its blacklisted driver is inert. Read a full air-gap check, all nine checks with their output →
| Layer | Mechanism | What it stops | Where you can see it |
|---|---|---|---|
| Layer 4 OS | rfkill soft-block · masked network services |
Refuses to bring a radio up even if the three below it were undone. | Checks 6 · 8 |
| Layer 3 Firmware | brcmfmac* blobs removed from /lib/firmware |
Nothing left on disk for a driver to load, short of a reinstall. | Check 9 · warning reads the disk, not the running kernel |
| Layer 2 Driver | 30 modules blacklisted · sentinel-blacklist.conf |
Even a powered radio has no driver loaded to talk to it. | Check 7 · authoritative the one that carries the weight |
| Layer 1 Hardware | dtoverlay=disable-wifi · disable-bt |
The SoC firmware never initialises the radio. The chip is present and not powered on. | Checks 1 · 5 |
↑ A signal would have to clear all four ↑
The nine checks, in the order the station asks them
1
Network interfaces: eth0 and wlan0 down or not up
Layer 1
2
Default routes: none, IPv4 or IPv6
Runtime assertion
3
DNS resolvers: no nameserver entries
Runtime assertion
4
Listening sockets: loopback only
Runtime assertion
5
Wireless interfaces: no wireless devices, no extensions
Layer 1
6
rfkill status: all interfaces soft-blocked
Layer 4
7
Wireless kernel modules: none loaded
Layer 2 · authoritative
8
Surveillance daemons: none running
Layer 4 · masked units
9
Wireless firmware: none under /lib/firmware
Layer 3 · warning
Six of the nine make a layer visible. The other three assert the consequence, that nothing is talking, whatever the mechanism. Check 1 belongs to both, since it asserts every interface is down, not only the wireless one. The checks are additive, and a single failure gives AIR-GAP: VIOLATED. Check 9 is the exception: a warning, which an installed station, where strict mode is the default, escalates to a violation.
2. LUKS at rest (device theft)
When you enable it, the operator's home directory is a LUKS2 / AES-XTS-plain64 encrypted volume unlocked by passphrase at boot. Five kinds of persistent secret live behind it: wallet files, the GPG keyring, signed PSBT exports, the KeePassXC database if you installed it, and the operational logs.
LUKS protects against device theft, not against passphrase loss.
The secrets that matter most, a mnemonic being verified, a seed derived from it, a private key about to sign, are never written to a disk-backed path at all. They live in a workspace on /dev/shm, a filesystem that is memory instead of permanent storage. An exit trap overwrites the variables that held them and shreds that workspace on every path out of a workflow, whether it finished or you interrupted it. Nor can that memory be paged out behind your back: the default swapfile is turned off at install and replaced by compressed swap that stays in RAM (zram), so a signing session has no plaintext swap to leak into. Because none of it is storage, nothing survives power-off and nothing was ever written to the card. This holds with or without LUKS. It is a different axis from encryption at rest.
Memory · /dev/shm
Never written down
- A mnemonic being verified
- A seed derived from it
- A private key about to sign
An exit trap overwrites the variables and shreds the workspace on every path out, finished or interrupted. Swap is off. zram keeps compressed swap in RAM.
Secrets survive power-off: no
Encrypted · LUKS2 / AES-XTS
Five kinds of persistent secret
- Wallet files
- The GPG keyring
- Signed PSBT exports
- The KeePassXC database
- Operational logs
Optional at install. Decline it and this column is an ordinary folder. Confidentiality, not integrity.
Secrets guarded by: passphrase
Plaintext · has to be, to boot
The trust base
- The kernel
- The boot partition
- The launchers
- The air-gap configuration
- The signer
LUKS cannot reach any of it. Physical custody of the card is the guard, and boundary 5 is the check.
Secrets guarded by: user + boundary 5
Two smaller measures are an additional defense in depth. Kernel core dumps are discarded outright, the core pattern is piped to /bin/false and set-user-ID dumps are refused, so a workflow that crashes mid-signing cannot spill decrypted seed or key pages into a core file sitting outside the encrypted home. Alongside it, a sysctl hardening block restricts kernel-pointer and dmesg exposure, tightens ptrace scope, keeps address-space randomisation on, and enforces protected symlinks and hardlinks. Neither is a line of defense on its own. However, both shrink what a bug on the box could reach.
Two important caveats. First, LUKS gives confidentiality, not integrity. It hides your secrets from a thief, but it does not prove the appliance is unaltered. The executable trust base, the kernel and boot partition has to stay in plaintext to boot, so it is protected by physical custody of the Pi, not by the passphrase. Second, LUKS is an optional module. If you decline it at install, that home directory is an ordinary folder, every secret above sits unencrypted, and device theft means full disclosure. On a box without LUKS, physical control of the device is your only protection at rest.
3. Supply-chain verification (tampered binaries)
The upstream tarballs (Electrum, Sparrow, the Temurin JDK, the BIP39 HTML tool) are GPG-verified against 40-character fingerprints pinned by hand in the preparation script. The system packages ride a digest-pinned base image and a SHA-256 manifest rather than a GPG chain. The Python wheels are version-pinned, not hash-pinned. All code and download links are in plaintext script for auditability.
All of the verification and assembly takes place via a plaintext script on your preparation machine, and at the moment the transfer bundle is built. What crosses to the Pi afterward is the payload plus that manifest. The manifest can be regenerated by anyone who edits the files. The preparation run prints one more value, the composition hash, a SHA-256 of the manifest itself. Per-file checksums prove the files match the manifest. The composition hash proves the manifest is the one your own machine produced.
Preparation machine
GPG-verifies upstream tarballs against 40-character fingerprints pinned by hand. Builds the payload and its SHA-256 manifest.
prints: composition hash
USB ────▶
payload + manifest
never auto-mounts · ro, noexec, nosuid, nodev
Paper ────▶
the composition hash, written down and carried across the gap by hand
The station
Checks per-file sums against the manifest, prints the composition hash; you compare it with the number on paper, and re-runs the GPG verification itself against fingerprints baked into the install scripts.
a tampered USB cannot rewrite a value that never travelled on it
Per-file checksums prove the files match the manifest. The composition hash proves the manifest is the one your own machine produced. The Pi-side GPG re-check is defense in depth on top of both.
What you are verifying
Plaintext bash and Python scripts the users audit themselves or via AI. The upstream apps are third-party compiled binaries based on open-source code, which is why each one is pinned to a fingerprint and checked twice.
The Pi re-runs the GPG verification independently. The pinned 40-character fingerprints are baked into the install scripts, not only the preparation host. Every upstream signature is checked a second time on the Pi, against fingerprints a tampered transfer USB cannot substitute. The preparation machine remains the primary anchor, the composition hash (on paper is optional) carries its result across the gap. The Pi-side re-check is independent defense in depth on top of both.
USB media never auto-mounts on the station, a polkit rule denies the file manager that path, so a drive is only ever mounted through the curated import workflow. That workflow mounts it read-only and with noexec, nosuid, and nodev, so nothing on a crafted stick can execute, gain privilege through a set-user-ID bit, or present a rogue device node. This is media hardening at the one edge where an outside object meets an air-gapped box, distinct from the noexec on the station's own temporary filesystems, which on a single-user, console-only box adds friction rather than a boundary.
4. Input validation + Dark Skippy nonce verification
The station bounds what it will attempt to sign. A PSBT larger than 16 MB is refused for security. Input and output counts then climb an escalating ladder, past 64 the station warns, past 128 it requires you to type a confirmation, and past 256 inputs it refuses the file and preserves it for security. The user can sign it independently in the bundled software, Electrum or Sparrow. Inputs could be refused this way because each one adds a diminishing verification cost. Outputs only warn, never refuse and never truncate. A legitimate batch payout can carry hundreds of destinations and you need to see every one.
Before it reads anything: what it will attempt
Inputs
0–64
Signs
> 64
Warns
> 128
Typed confirmation
> 256
Refuses, preserves the file
Outputs
0–64
Signs
> 64 · no ceiling
Warns only. Never refused, never truncated.
> 16 MB · refused outright
Input counts can be refused because each one adds verification cost. Output counts only ever warn, because a legitimate batch payout can carry hundreds of destinations and you need to see every one.
Then, the two readings that are about more than arithmetic
Privacy observations
Heuristics, not guarantees, and scoped to this one transaction: an air-gapped box has no chain history. All eight flags it can raise, and the line it prints when none do:
This transaction has 2 inputs. Spending them together publicly asserts they share one owner (standard for most transactions). Informational.
This transaction consolidates 12 inputs into 1 output(s). That permanently links all 12 input addresses as one owner on-chain. Common for fee management. A privacy trade-off to be aware of.
Output #1 pays a round amount (0.05 BTC). Round payments make the remaining change output easy for an observer to spot as yours. Informational: many legitimate payments are round.
Output #1 pays a scriptPubKey also spent as an input in this transaction (within-PSBT address reuse). Only reuse inside the same PSBT is visible. No chain history.
Privacy observations, continued
The change output (#1) is larger than every payment — unusual, and a distinguishing footprint.
The change output is at position #0, not last. Many wallets place change last; a non-terminal change position is a wallet fingerprint.
The change output is p2pkh while the inputs are p2wpkh. Honest wallets return change to their own script type; a mismatch fingerprints and can indicate a mis-built tx.
This transaction spends 2 script types (p2tr/p2wpkh). Mixing input types links those coins as one owner and reveals wallet details. Informational.
No privacy heuristics fired for this transaction.
Dark Skippy audit
For each signature the station produced itself, it rederives the deterministic nonce and refuses to export any that deviates. Four outcomes, two of which the report names in words of its own:
The transaction was not signed here, so there is no nonce of ours to recompute.
The station rederives the deterministic nonce for a signature it produced, RFC 6979 for ECDSA and BIP 340 for Schnorr, and it agrees.
Export is refused and the file is renamed *.SUSPECT-DO-NOT-BROADCAST.psbt, or *.SUSPECT-DO-NOT-COMBINE.psbt for multisig. Nothing is deleted.
The signature arrived already made, from a hardware wallet or a cosigner. Its nonce was never ours to reproduce.
The station flags and stops there: it is not a mixer and does not alter the transaction. The run shown above is an unsigned review, which is why its nonce line reads not evaluated rather than a clean verdict.
Standards in this workflow
This is our answer to Dark Skippy, the covert-channel key-exfiltration technique disclosed in August 2024, in which a compromised signer leaks secret key material through the nonces it chooses. After Electrum or Sparrow signs a PSBT on the station, the station rederives the deterministic nonce for each signature it produced itself, RFC 6979 for ECDSA and BIP 340 for Schnorr/Taproot, and refuses to export any signature whose nonce deviates. We have not found another consumer signer that ships this check. If you know of one, tell us and we will name it here.
Scope note: this is a nonce-integrity check, not a complete anti-exfiltration guarantee. It closes the nonce channel Dark Skippy uses, and operator review plus the signed-artifact diff cover output order, amounts, and change selection. However, a compromised signer can still hide a few bits in a field it legitimately controls, such as the ordering of key-value pairs inside a PSBT. It is a user responsibility to maintain the device encrypted, password protected and safe from evil-maid attacks.
Dark Skippy is an attack on the device that signs. We defend the signing we do. Therefore Bitcoin Witness can only recompute a nonce it generated itself. A signature that arrives already made, from a hardware wallet or a cosigner, is checked against the key it claims to come from, but its nonce is never ours to reproduce.
A signature that fails the check is not destroyed. The file is renamed, export is refused, and nothing is deleted.
5. Trust-base integrity (appliance self-attestation)
Trust-base integrity is the fifth boundary, it defends the integrity of the code itself after install day. The kernel and the boot partition all have to sit in plaintext on the card to boot, so LUKS cannot protect them. As mentioned above, it gives confidentiality, not integrity. On its own, physical custody of the Raspberry Pi SD card cannot tell you whether that trust base has been altered.
Boundary 5 is a way to confirm the appliance is still running what you verified during install. It detects persistent tampering, an evil-maid edit or a swapped card or on-box persistence, by hashing the trust base and comparing it, off the device, against a value you recorded at install or a value we will publish on this website per each version of the scripts once they are released.
No secure element, by design
Bitcoin Witness is a set of bash and python scripts that run on user-supplied, general-purpose hardware. The defenses and the threat model are described above. Raspberry Pi has no secure element by design.
Why is it important?
The value of an independent verification layer like Bitcoin Witness comes from being 100% inspectable and generic, so we chose hardware you can audit over hardware you must take mostly on faith. By design, all operations with seed words are performed in RAM memory only, after airgap hardening, and are erased immediately after finishing an operation. If you decide to keep any secret material on the device permanently, make sure you use LUKS and other encryption methods the station offers out of the gate.
A chip of a hardware wallet, by comparison, protects secrets that the user chose to store on the device in exchange for a convenience of signing transactions quickly. Although it can be used for this purpose, we do not recommend using Bitcoin Witness as an everyday wallet. In either case, always follow best practices and have multiple backups.
The corollary is this: a hardware wallet's secure element earns its keep, which is the reason Bitcoin Witness sits alongside it in a separate layer instead of replacing it.
TYPICAL ATTACK VECTORS
A secure element
Raspberry Pi
01Physical key extraction
Someone holds the device and goes after the key inside it.
A sealed die, hardened against probing and glitching. This is the problem it was built for.
If you choose to store any sensitive information on Raspberry Pi, seeds or inheritance documents, encrypt it with GPG, AES, LUKS or any combination of them.
02Robbery
Self-explanatory
The hardware wallet announces what it is, and therefore that you hold something worth taking, potentially exposing the owner to coercion.
Secrets like seeds exist in RAM for the length of a workflow and are gone at power-off. What persists is secret-free data behind encryption. A stolen Pi is a stolen Pi. Most importantly, Pi does not look like a hardware wallet with sensitive information on it.
03Software vulnerability
A flaw, or a planted change, in the code the device runs.
In most cases at least part of the internals are hidden by design, which is the same property that makes them unverifiable. You trust the hardware executes the code correctly.
Similar exposure to bugs, but more transparency. Plaintext scripts a user himself clicks on. No binaries of our own code, no black boxes. A pinned, verified supply chain and a self-attestation hash you re-compute on a machine you already trust.
04Exfiltration over the network
A secret leaves the device over a wire or a radio.
The chip has no network. The host it plugs into may have one.
Four independent air-gap layers and nine checks before a workflow runs. Any secret information lives in RAM memory only, for a short period of time and only if the user typed it in himself.
05Purchase exposure
Buying the thing is what tells someone you are worth robbing.
A branded device ships to an address, and a retailer keeps that record.
Generic parts from general retail, nothing branded on the box. You still bought a computer from a reputable retailer.
06Coercion
The attacker skips the technology and asks you.
A sealed chip does not help when the attacker asks you to open it, and neither does an air gap. Nothing on this page defends against it. That is a question for how your seed is split (if it is), if you use multisig, timelock, and most importantly, people knowing you have something valuable.
Read the two columns as complements, they are not a score.
Why the objective decides the trade
When the chip is right
The objective is to hold a key that spends often, and the realistic attacker is someone holding your device. Then hiding the secret behind a hardware chip designed to protect it adds value to your stack.
When generic hardware is right
The objective is to produce an independent verdict about something else: this seed matches that xpub, the addresses are derived correctly and correspond to the shown seed words, this descriptor is what your wallet shows, this transaction pays who it says. Plus additional functions of a normal hardware wallet if one needs them. The output is a claim. A verdict from a hardware wallet about its own work asks you to trust the verifier.
Bitcoin Witness sits alongside a hardware wallet rather than replacing it, and the split above is why: the wallet keeps the key, the station checks the claim.
What is not defended against
- An attacker with sustained physical access to both the device and the LUKS passphrase
- Compromised silicon (the user is asked to source the Pi from a reputable retailer)
- A compromised host machine during the host-side preparation step. The user must use a reasonably-trusted machine for that step
- The user's broader operational security (seed storage, inheritance planning, multisig signers)
Verification
Every shipped boundary on this page is verifiable.
- Air-gap claim: run the free Air-Gap Security Checklist against your station.
- Supply-chain claim: the product code and the installer are plaintext scripts one can easily verify
- Dark Skippy defense: the nonce-verification code (
sign-psbt.shandpsbt_handler.py) ships in the product bundle as auditable plaintext. Read it line by line or verify it with your preferred AI product. It is not part of the free xpubverify tool, which contains no signing or nonce code. - Trust-base integrity. At install the station prints a hash of its own code. Record it, and on any later boot compare the printed hash against that value by hand, on a separate machine. A mismatch means the card was altered.
None of the cryptography is reinvented. Bitcoin Witness contributes the workflow wrapped around it in plaintext bash and python form.
Keep reading
Every boundary above can be checked.
Each claim on this site is paired with a way to verify it on hardware you own, with no account and no vendor in the loop.