bitcoin-kernel / browser-node β€” a Bitcoin testnet4 node in a tab

Bootstrap a UTXO set from a torrented or Bitcoin Core assumeUTXO snapshot, validate blocks forward, follow the chain, and live-sync headers β€” all in this tab, on the bitcoin-kernel engine.

πŸ“– How it works Β· βœ“ Verify the UTXO set (SwiftSync, 32 bytes) Β· β–Ά Run the node Β· from genesis Β· or the acts below.

Acts β‘’ β‘£ β‘₯ work here on GitHub Pages. Acts β‘  (WebTorrent seeding) and β‘€ (the live p2p bridge) need a local server β€” see the README.

Log

β‘  Bootstrap β€” coin view from torrented snapshot

β€” not loaded yet β€”

β‘‘ Forward block validation

Validate the next real testnet4 block (#26000) forward against its coin view β€” full consensus: prevout resolution, scripts/signatures (BIP143), fees, coinbase amount, maturity, witness commitment. The bitcoin-kernel engine runs in this tab; the coin view holds the 28 real prevouts the block spends.

β€” not validated yet β€”

β‘’ Follow the chain β€” multi-block

Validate a run of consecutive real blocks (26000β†’26020), applying each to the UTXO set so the next block can spend its outputs, and verifying linkage block-to-block. This is a node's inner loop β€” the step from "accepts a block" to "follows the chain." This range is a consolidation cascade: most inputs spend coins created earlier in the run, so it only works if the apply step is correct.

β€” not run yet β€”

β‘£ Live feed β€” follow the network over a WS↔TCP bridge

A browser can't open raw TCP, so the tab speaks the Bitcoin p2p protocol over a WebSocket to a thin local bridge that relays frames to a real testnet4 peer (here, our own synced node). The tab does the version/verack handshake, syncs the whole header chain from genesis, and fully validates every header (PoW, BIP94 difficulty, linkage, most-work reorg) β€” then tails the live tip. The bridge is untrusted: it can't forge a valid header. The chain is persisted to OPFS, so a reload resumes from disk instead of re-syncing from genesis.

β€” not connected β€”

β‘€ assumeUTXO β€” parse Core's dumptxoutset snapshot

Parse the real Bitcoin Core snapshot we torrented (utxo-testnet4-120000.dat, the v2 compressed format) directly in the tab, then forward-validate the first post-snapshot block (#120001) against the coin view it produces. This is the bridge from a Core assumeUTXO snapshot to a usable, validating node β€” and the ultimate parser proof: real signatures only verify if every decompressed amount and script is exactly right.

β€” not run yet β€”

β‘₯ Persist the UTXO set β€” OPFS checkpoint

The coin view lives in RAM (fast synchronous lookups), and is checkpointed to OPFS β€” the same model Bitcoin Core uses (flush the chainstate periodically, not every block). Build a coin view from the Core snapshot, checkpoint it, then resume it from disk on reload β€” no re-parsing. This is UTXO-set persistence; at full 14M-coin scale the same code moves into a Web Worker with sync access handles (see README).

β€” not run yet β€”

⑦ WASM signature verification

Signature verification is the bottleneck for inscription-flood blocks (~14k sigs each). The engine's default is pure-JS secp256k1; this swaps in a WASM libsecp256k1 backend via the engine's setVerifyBackend hook. Gated by a consensus check: WASM must agree with pure-JS on every verdict β€” you don't swap consensus crypto on faith.

β€” not run yet β€”

β‘§ Run the node in a Web Worker β€” scale

At full 14M-coin scale, validating inscription-flood blocks (~14k sigs) and writing multi-GB UTXO checkpoints would freeze the tab. The fix: run the engine, the WASM secp backend, the UTXO set, and OPFS sync access handles (Worker-only, the fast I/O path) inside a Web Worker. Below: the same validation runs in the Worker (UI stays responsive) and on the main thread (UI freezes) β€” measured by the largest gap between animation ticks.

β€” not run yet β€”

⑨ SwiftSync β€” stateless validation (past the memory ceiling)

The full UTXO set is ~25 GB β€” too big for a tab. SwiftSync removes the need to hold it: since all outputs βˆ’ all inputs = the UTXO set, you feed every created output (+) and spent input (βˆ’) into a constant-size accumulator. With the start + terminal snapshots, a valid chain cancels to zero β€” proving no double-spends or fabricated coins, with 32 bytes of state instead of 25 GB. This is the repo's own SwiftSync accumulator (verified across all of testnet4), run here over a real block range.

β€” not run yet β€”

β‘© SwiftSync at scale β€” the full UTXO set in 32 bytes

The proof that this breaks the ceiling: streaming the real full testnet4 UTXO set (14,129,063 coins, 826 MB) through the accumulator commits it with RSS flat at ~0.9 GB (the input file) β€” the set-state never leaves 32 bytes, vs ~25 GB to hold it (tools/swiftsync-commit.mjs, commitment af37b01d…). Below, the same accumulator streams millions of outpoints in the worker (UI stays responsive) to show the 32-byte state holds at scale, in the tab.

β€” not run yet β€”

β‘ͺ SwiftSync hints β€” build the UTXO set from a tiny file

The full SwiftSync flow: generate a compact hints file (which outputs survive), reconstruct the UTXO set from blocks + hints with no spend processing (fast, parallelizable), and verify it with the 32-byte accumulator. The hints are tiny (~25 bytes/block β€” the whole testnet4 chain β‰ˆ a few MB), and they carry no trust: wrong hints just fail the accumulator check.

β€” not run yet β€”