Skip to content

About

The real SeedSigner firmware running in a browser tab: a simulator you can try before buying the device, and rebuild yourself to check it is the pinned upstream release.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

7 stars

Watchers

0 watching

Forks

Repository files navigation

SeedSigner simulator

try it live smartcard fork stock reproducible-build tests upstream tests release licence

Real SeedSigner firmware, the actual Python off the device, running in a browser tab. The screen is a canvas, the buttons are your keyboard, the camera is your webcam.

Insecure by design

Never type in a seed phrase you rely on. Use a published test seed.

Nothing makes this safe: not running it offline, not a private window, not a clean laptop. See below.

The simulator running the wallet's home screen

Why it cannot be made safe

A SeedSigner is a signing device because of what surrounds the software. None of that is here. Only the software is.

A real SeedSigner This page
No wifi, no bluetooth, no port A computer that has all three
Runs one program and nothing else Runs beside extensions, other tabs, devtools, every process on the machine
Loses everything at power-off A JavaScript heap, copied by the garbage collector into swap and hibernation files
A real smartcard holds the key A JavaScript object pretending to be a smartcard
A screen only you see A canvas any script or screen recorder can read

Offline does not help. It changes one row and leaves the rest. The seed is still typed into a general-purpose computer with an OS, a clipboard, a swap file and a network connection it will use again later. Anything there that can read memory or the screen can read the seed. Having no network is a property of the page, not of the computer, and a tab is a sandbox against other websites, not against the machine it runs on.

Mainnet works, which is the dangerous part. The page boots on Testnet, but Mainnet is still in Settings, deriving real keys and signing real transactions (test/test_mainnet.py checks them against BIP32, BIP143 and ECDSA). Treat everything it shows you as public: seeds, passphrases, xpubs, descriptors, signatures.

If you already entered a real seed here, move the funds to a seed generated on a device you trust. SECURITY.md.

Good for learning the menus, rehearsing a flow, teaching multisig, testing a fork's screens. Anything involving money belongs on hardware.

Three firmwares

On the page What it is URL
SeedSigner Stock, what a plain SeedSigner runs. The default ?firmware=stock
ShieldSigner 3rdIteration/seedsigner, adds SeedKeeper and Satochip ?firmware=smartcard
Doomsigner Our fork of that fork: BIP-352 silent payments, and DOOM in front ?firmware=doomsigner

The Firmware control under the device switches. DOOM is off on the first two unless ?doom is on the URL; on Doomsigner it always boots into the game, and five taps on the top side button reach the wallet.

Try it

git clone https://github.com/bitsagarob/seedsigner-simulator.git
cd seedsigner-simulator
./build/fetch-assets.sh                 # Pyodide, pinned and hash-checked (~26 MB, once)
./build/build-wallet-zip.sh smartcard   # wallet-smartcard.zip, from the pinned commit
./build/build-wallet-zip.sh stock       # wallet-stock.zip, from the pinned commit
python3 test/serve.py --port 8770 src/web src/shims build/out

Then open http://127.0.0.1:8770/. Use test/serve.py, not python3 -m http.server: without the two isolation headers it sends, the wallet never starts. Nothing fetched is committed, so what you run is provably the pinned commit, and both steps verify what they download.

Arrow keys move, Enter selects, 1 2 3 are the side buttons, and the drawn buttons work too. The screen is not one of them: a SeedSigner has no touchscreen.

To run your own fork, SS_REPO and SS_COMMIT override the pin for one build (CONTRIBUTING.md).

What you can verify

  • It is the firmware, not a re-creation. wallet-<firmware>.zip holds that firmware's upstream Python tree and its own Controller.start() runs it. Menus, seed handling, PSBT parsing, QR encoders: all theirs, unmodified.
  • Nothing patches the wallet. Hardware is replaced from outside, by src/shims/. Even Testnet-at-boot is a value in the settings.json the device reads, not an edit.
  • Pinned to release tags, not branch tips (UPSTREAM, one section per firmware). The fork's tag is the one the official pi0-smartcard device image is built from, so this and that device run the same code.
  • Rebuild and compare. build/build-wallet-zip.sh reproduces a zip byte for byte. CI re-derives the hashes on every push on a clean runner and GitHub signs the result: gh attestation verify wallet-smartcard.zip --repo bitsagarob/seedsigner-simulator.
  • Upstream's own tests run against our pins (upstream-tests.yml): about 2,500 from the fork's suite, all of stock's. About 200 of the fork's skip themselves, most for want of a jcardsim build, pyscard or SeedSigner OS's own tools.
  • The webcam really is the camera. Same DecodeQR, same SeedQR / CompactSeedQR / PSBT / UR parsing; only the decoder is the browser's.
  • One host. No backend, and the CSP names one other origin, signet.bitsaga.be, for the faucet and read-only lookups.

The walkthroughs

Three guided ceremonies done in the page. The resting page is a plain SeedSigner, so none of them starts on its own: open the wallet drawer and the picker offers whichever ones the running firmware can do, or name one on the URL as ?tutorial=single, ?tutorial=multi or ?tutorial=musig.

Walkthrough Firmwares What it does
Single sig SeedSigner, ShieldSigner, DoomSigner makes a seed, backs it up, exports the key, funds it, signs and sends. A checkbox adds a passphrase
Multisig SeedSigner, ShieldSigner, DoomSigner a 2 of 3, from three cards on the forks and by QR on stock
MuSig2 DoomSigner a 2 of 3 MuSig2, nonces exchanged and both cards signing. DoomSigner boots into DOOM, so five taps on the top side button open the wallet first

Three published test seeds, the real PIN ceremony where there are cards, the faucet on Bitsaga Signet pays the wallet, and the spend confirms. Those coins are not real bitcoin. Press play and it narrates itself, or take over and press the buttons.

The coordinator is drawn as a phone beside the device, because that is what it is: signet-coordinator.js builds the descriptor, addresses, PSBT and final transaction in the browser, and test/test_tutorial.py checks every value against the wallet's own embit. The QR transfers are real modules read off real pixels by the wallet's own decoder, so your webcam is never involved.

Holding three keys on one device is fine for a demo and wrong for real funds.

What works, and what does not

Cards are the fork's, so under stock there is no card tray at all.

Works, any firmware. The full menu tree, seed loading by QR or by hand, passphrases, xpub export, PSBT signing, SeedQR backup, settings, every QR screen.

Works, ShieldSigner and Doomsigner. Three card slots, each a SeedKeeper or a Satochip, both PIN-checked. Seed onto a SeedKeeper and back off. A 2 of 3 descriptor onto a SeedKeeper. Satochip holds a real BIP32 master key and authentikey, and pysatochip verifies every answer it signs.

Does not, any firmware. No microSD, so settings reset on reload and firmware update is gone. Nothing on a background thread: no spinner, no scrolling text, no pulsing border, and thread-based work such as brute-force address verification never completes (camera preview and animated QR are pumped by hand). No timing, so no wipe timer, screensaver or battery reading.

Does not, the fork. Card-to-card secret copy (refused rather than done in the clear), card signing, PIN change, 2FA, factory reset, and Satodime: the fork lists it under Smartcard Tools, and the tray has no Satodime to offer. Cards forget on reload, deliberately.

How it works

The wallet's Python runs under Pyodide (CPython on WebAssembly) in a Web Worker. Four hardware seams are replaced, three under stock.

Seam Replaced by Why
Display browser_display.py Swaps the panel driver under SeedSigner's unmodified Renderer; RGB frames go to a canvas.
Buttons wallet-worker.js The worker is blocked in the wallet's main loop and can never answer a postMessage, so keys cross on a SharedArrayBuffer.
Camera + QR browser_camera.py + wallet-camera.js pyzbar has no WebAssembly build, so the browser decodes and hands bytes to the unmodified decoder.
Smartcard src/smartcard/ Browsers have no smartcard API, so simulated cards answer real APDUs and pysatochip runs unchanged.

A fifth, browser_qr.py, draws the QR screens, whose drawing lives in a thread this environment cannot run.

That one constraint, a permanently blocked worker, explains most of the architecture. docs/ARCHITECTURE.md is the long version, including why BarcodeDetector is never trusted with a payload.

Self-hosting

Static files. Two things trip up every first attempt (docs/SELF-HOSTING.md):

  1. Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp are mandatory, or SharedArrayBuffer does not exist and the wallet never starts.
  2. The camera needs https or localhost. Plain http on a LAN has no camera API to ask.

Development

python3 test/run.py builds what is missing and runs everything in a real browser. CONTRIBUTING.md covers the rest, test/README.md says what each test proves, and ?debug=1 traces every screen, thread and keypress. Security reports: SECURITY.md.

Licence and credits

MIT, see LICENSE. Almost none of this code was written here: the wallet is upstream SeedSigner (MIT, Copyright (c) 2021 SeedSigner), the smartcard build is pinned to 3rdIteration/seedsigner, and the browser side rests on Pyodide and jsQR. THIRD-PARTY.md lists every dependency and how to check it.

Independent project, not affiliated with or endorsed by SeedSigner. Running it proves nothing about a real device.

About

The real SeedSigner firmware running in a browser tab: a simulator you can try before buying the device, and rebuild yourself to check it is the pinned upstream release.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages