EntropyLab is a self-contained Bitcoin key and wallet calculator designed for offline, air-gapped use. It converts user-supplied entropy, seed phrases, and private keys into wallet recovery information without intentionally sending sensitive data to a server.
Current version: v1.0.0
Official website: entropylab.online
When a multi-worker Vanity search stops, its next counter is the first unfinished counter, not the sum of candidates processed across workers. Resuming can repeat work from later buckets, but will not skip an unfinished gap in the requested range.
CI builds and tests the Rust/WASM modules before compiling the site, then
shares those exact modules with downstream tests and artifact publication.
For local testing after Rust changes, run npm run build:wasm before
npm run build && npm test; the checked-in modules may predate the changes.
Clearing a Key or Multisig station cancels its pending derivation. Leaving the page clears rendered seed-word copies and prevents a late derivation or import from restoring cleared secrets. Locking the journal also invalidates pending notebook and Key Manager imports. See SECURITY.md for the limits of browser-memory cleanup.
- Accepts dice rolls, coin flips, playing-card transcripts, number-base transcripts (binary through base64), hexadecimal entropy, BIP39 seed phrases, extended keys, WIF keys, raw private keys, and Casascius mini private keys. Optional live chi-squared fairness analysis flags biased dice as rolls are entered for hashed-dice and BitBox input. D++ omits the panel because a complete transcript cannot reach the panel's Pearson threshold. All five BIP39 phrase lengths (12, 15, 18, 21, and 24 words) are supported for every entropy entry method. A separate Brain wallet — lab mode hashes exact UTF-8 text with SHA-256 and uses the 32-byte digest as 256-bit BIP39 entropy (24 words). That is not a BIP39 passphrase, not a Bitcoin Core hdseed or address-key backup, and not the private-key brain-wallet path (which treats the same hash as a secp256k1 scalar). Strength is the entropy of the text, not the word count. Derive Key is required; the lab does not preview the mnemonic while typing.
- Derives BIP39 seeds, BIP32 extended keys, wallet fingerprints, addresses, and Bitcoin Core-compatible descriptors. Each master fingerprint is shown next to its deterministic LifeHash icon so two keys can be told apart at a glance. The icon hashes the raw fingerprint bytes, so it matches the image Sparrow Wallet shows for the same key. Every row of the address tables has a QR button that opens that address as a scannable QR code, so any derived address — not just the first — can be verified on a signing device without retyping it.
- Supports legacy, nested SegWit, native SegWit, and Taproot single-signature
address types. Derivation-scheme presets cover the BIP44, BIP49, BIP84,
BIP86, and six-level BIP48 layouts and label each path level accordingly.
A custom mode accepts an arbitrary-depth BIP32 account path, keeps Bitcoin
network selection explicit, and appends the selected branch and address
ranges. The default derives receive and change branches
{0-1}and address indexes{0-9}, displayed as a full BIP-88 path template. Typinghor'after a preset index enables its Harden control. - Supports numeric coin-type and account indexes for single-signature and multisignature derivation. Purpose, coin type, and account indexes are hardened by default; the starting address index is unhardened by default. Each can be changed independently. Coin type 0 uses Bitcoin Mainnet, coin type 1 uses Bitcoin Testnet, and custom indexes retain Mainnet address serialization. Hardened address children require private key material and therefore cannot be derived from multisig co-signer xpubs. A network picker in the header (the Bitcoin-orange coin next to the network name) offers Bitcoin, Testnet, Signet, and Regtest; it shows the network every tool is set to and switches it — address formats, extended key versions, WIF prefixes, and coin-type defaults all follow, and each menu entry spells out the checks its choice implies. The PSBT tools read the picker's choice directly and have no network control of their own. Nothing connects anywhere: the choice only picks formats, and a tool's own advanced fields can still override it. Every load starts on Mainnet again.
- Derives watch-only multisignature wallets from extended public keys without requiring private keys. Multisig script type and purpose are separate as well; conventional script choices restore their standard purpose, while pasted co-signer origins auto-detect and must agree with the selected path indexes and hardening choices. An exported wallet descriptor can also be pasted to repopulate the station — quorum, script type, key order, and co-signers; descriptors carrying private keys are refused. Addresses are derived from the exported output descriptor itself by rust-miniscript (in the WASM crate), so the two cannot drift.
- Inspects PSBT v0 transactions (paste, or upload a binary .psbt / hex or base64 text export; the loaded bytes download as .psbt or .txn), reports PSBT-provided amounts and fees, checks for repeated ECDSA nonces from the same public key — including signatures carried by finalized scriptSig/witness fields, which are decoded and analyzed rather than skipped — verifies optional Jade anti-exfil (sign-to-contract) transcripts without a key, and can compare supported SegWit v0 SIGHASH_ALL signatures with RFC 6979, including Bitcoin Core-style low-r grinding, in a temporary session. Every input's declared sighash policy and each signature's appended sighash byte are decoded without a key; anything other than exact SIGHASH_ALL (or Taproot's equivalent SIGHASH_DEFAULT) is a blocking warning. Finalized signatures that cannot be decoded or associated with a key block any clean nonce verdict. The report gives each check a completed, problem, or incomplete state and gives an overall incomplete result whenever required data or support is missing. “Completed” describes only that check against the data in the file; it is not a claim that the transaction is safe or that PSBT-provided data is true.
- Accepts a fully signed raw Bitcoin transaction (hex or base64) in the same inspector: outputs, extracted ECDSA nonces, and inscription-envelope hints. Fee and RFC 6979 cannot be checked without previous outputs.
- Keeps an optional cross-session ECDSA nonce history in page memory and lets
the user download or upload it as versioned JSON. The file contains only
the check time, master fingerprint when available, raw
r, a domain-separated SHA-256 tag for the exact signing key, a tagged message or source identity, and a verification flag — never the PSBT, transaction, signature, public key, or message digest. The exact-key tag prevents child keys sharing a master fingerprint from being conflated. It can confirm reuse only when the same key/rpair has different verified message digests; incomplete records produce a warning instead. The file is correlation-sensitive metadata and should remain offline. Nothing persists unless it is downloaded. - With a session seed, root xprv, WIF, or hex key, labels each output as change, receive, or not in this wallet (accounts 0–2, 50 receive + 50 change, all four script types). A two-or-more-output transaction with no matching change is a blocking warning. OP_RETURN outputs are decoded for size and a text/hex preview; the tool does not create data-carrier outputs.
- Scans PSBT tap-leaf scripts and finalized witnesses for inscription envelopes
(
OP_FALSE OP_IF "ord"). Reports content-type, size, and text previews; does not number sats, fetch chain data, create inscriptions, or render images. - Edits PSBT v0 and v2 (BIP-370) files field by field (a bip174.org-style
editor backed by
rust-bitcoin compiled to WebAssembly): a mempool.space-style
transaction-flow diagram draws one box per input and output (claimed
amount, address or script template, signing status) with per-column totals
and bezier connectors into the unsigned transaction; selecting a box opens
that key-value map for editing in a panel under the diagram, the
transaction box opens the version/locktime fields, and output amounts edit
directly in their boxes. Every key-value pair of the global,
per-input, and per-output maps is decoded (BIP-174 and BIP-371 taproot
fields), editable as raw hex, and removable, new pairs can be added, and the
unsigned transaction's version, locktime, input prevouts/sequences, and
output amounts/scripts get structured fields. Fields longer than 64
characters (a whole previous transaction, a large script) collapse to a
truncated preview with a length label; clicking the cell opens the full
text in an editor window, where values stay editable. Re-serialization is
validated by rust-bitcoin before the edited PSBT is shown. A second PSBT can
be pasted for a semantic comparison against the editor's: the underlying
transaction, the signing state, and the PSBT metadata are diffed separately
on the decoded contents, so reordered map serialization is not reported as
a change. The comparison reports differences only; it does not judge
whether a change is safe. An edited PSBT too large for a static QR code is
shown as an animated
ur:crypto-psbtfragment sequence for air-gapped scanning, and an Insane editing switch lifts the consensus-layer checks for deliberately broken files while still listing their problems. The editor never signs anything. - Derives BIP-85 child entropy from the active key's BIP32 root (or a pasted root xprv): English BIP-39 mnemonics (12–24 words), HD-seed WIF, XPRV, HEX, and Base64/Base85 passwords. Same parent, application, and index always reproduce the same child — this is a calculator, not a generator. Children follow the published BIP-85 vectors and match COLDCARD, including derivation from a passphrase-extended root when a BIP-39 passphrase is in effect. Unremoved BIP-39 and XPRV children can be selected as HD roots in BIP-85, Silent Payments, Vanity, PSBT/Nonce inspection, and multisig; WIF children can be selected for PSBT/Nonce inspection. Their pickers show the parent LifeHash, an arrow, then the child LifeHash and fingerprint. Deleting a child removes its picker options and wipes any private sessions loaded from it. Vanity matches from an immutable child can be searched and copied, but not written back to that child.
- Derives BIP-352 Silent Payment addresses (
sp1q…/tsp1q…) from a seed or root xprv, including labeled codes, BIP-392spscan/spspenddescriptors, sender taproot outputs from pasted vin JSON, and receiver verification of pasted x-only outputs. Prints a BIP-321bitcoin:?sp=URI and the BIP-353 DNS TXT to paste on a domain you control; paste that URI back on Send. Hybrid URIs with an ordinary Bitcoin fallback address still use Silent Payments; the results identify the fallback address as not used. This is a calculator: it does not scan the chain or resolve names. - Grinds vanity addresses for a Key Station key or a compatible BIP-85 child
(Vanity tab), picked through the same chip picker as BIP-85 and Silent
Payments. Two methods: the passphrase grind extends the key's BIP39
passphrase with base-62 odometer counter characters, the derivation grind
keeps the passphrase and steps through BIP32 account indexes. Every
candidate is derived the standard way (PBKDF2 seed, BIP32 path — the key's
own purpose, account, branch, and address index) in a dedicated WebAssembly module, one Web Worker
per CPU core, and its mainnet address of the selected type (legacy, nested
SegWit, native SegWit, Taproot, or a BIP-352 Silent Payment code) is checked
against the chosen prefix. For Silent Payment codes, the first custom
character is limited to
g f 2 t v d w 0; later characters can use the full Bech32 alphabet. A short timing sample on tab entry (fixed published constants, never the session's keys) turns the odds into an expected time to a match, and Stop on first find halts the grind at the first hit. Same key and counter always reproduce the same address, so nothing is invented. For a Key Station source, Update key writes a found passphrase or account index back to the key and re-derives it, so the Keys tab, its exports, and the Journal show the vanity wallet. BIP-85 children stay unchanged. Found passphrases stay in page memory, are masked until revealed, and are wiped with the session. - Derives Lightning node identity public keys (a Lightning tab held back from
release navigation while its UI is polished) from a seed
phrase: an LND 24-word aezeed cipher seed is deciphered in WebAssembly
(scrypt key derivation, AEZ v5, CRC-32C checksum; a wrong passphrase is
detected, unlike BIP39) and its entropy derives the node key at LND's
m/1017'/coinType'/6'/0/0keychain path, while a BIP-39 phrase follows the ldk-node convention (BIP39 seed, master key, re-seeded second BIP32 tree, node secret atm/0'). The decoded cipher seed's internal version and wallet birthday are shown alongside the node key; the decoded entropy and salt and the BIP32 root xprv (whatchantools showrootkeyprints) sit behind a reveal toggle. Decoding only: the tab never creates seeds. - A session Journal (temporarily hidden from release navigation while its
backup and restore flow is polished) holds an Entropy
Journal notebook, a notepad stamped with this computer's date and time,
a Key Manager, a live summary of everything derived in this sitting, and a debug log
of tool switches and derives (fingerprints, not seeds). Its introduction
remains above the Journal controls. Notepad, Key manager, Session state, and Session log
stay visible but disabled until the user creates a journal, with or without
a password, or successfully opens an existing journal; the create/open gate
then disappears and the Journal starts on Notepad. The create form reports
whether password protection is enabled and checks confirmation matches live
without exposing what was typed. A blank password is accepted and provides
no access protection: anyone holding the resulting file can open it by
leaving the password blank. Journal-wide Download journal and Clear journal actions stay
below the introduction once a journal is unlocked; clearing wipes the
entries, notepad, session snapshot, and log from page memory and
returns to the create/open gate. The notebook keeps
entropy the user already produced — dice, coins, hex, brain-wallet text, or
a seed — under AES-256-GCM; the key is PBKDF2-SHA-256 (600,000 rounds) of the
optional password the user chooses, with the salt derived from that password
and the IV HMAC-SHA-256 of the plaintext, so the file is a pure function of
password and entries and no CSPRNG is ever called. One JSON file the user
downloads and loads back. Nothing is stored in the browser; download a file
to keep it. Closing the page discards the sitting. The notebook is a
calculator companion, not a password manager: it only stores material the
user generated themselves.
The Key manager tab packages selected Key Station source inputs and
settings, including ignored entries, into an
.elkeysfile. It reuses the unlocked Journal's password setting, so it adds no password prompt, random salt, or random nonce. Imports are unverified inputs, not ready-to-use wallets. Choose Load inputs to derive, review the inputs/settings, then derive each key in Key Station. Cached addresses, private outputs, and fingerprints from legacy files are discarded; only fresh derivation can match an existing key. Add all to Key Station applies only to already-derived keys, leaving unverified and ignored entries untouched. New files use input-only format version 2 (requires this updated reader); version 1 files remain importable. Cached-output-only files cannot replace missing source inputs. Deleting a Key Station tab while a Journal is open likewise removes it from the station without discarding it from Key Manager. The Journal also includes a paged notepad. Pages use the Key Station's numbered naming convention, can be added or removed with the +/− controls, and can be renamed by activating the selected page again or pressing F2. Default names such asPage 1shorten toP1in narrow windows. A responsive control row below the editor sets each page's typeface, text size, and line spacing. Its key picker lists the currently derived Key Station keys with their LifeHashes; choosing one inserts a public inline reference with a line-height LifeHash and master fingerprint. Notepad, Session state, and Session log downloads use the unlocked Journal's file encryption by default. If the Journal has a password, that password protects the downloads; without one, the encoded downloads have no access protection. Their matching checkboxes stay synchronized, so one change applies to Notepad, Session state, and Session log; unchecking exports the original plain JSON or text. Notepad can upload either its plain notebook JSON or its Journal-format export while that journal is unlocked. Each page opens with a live local timestamp and freezes it when note text is entered. Delete the note back to its timestamp to return to the live new-note prompt. Press Enter after a note to open the next live timestamp and prompt. Press Enter on that empty prompt to leave an unstamped blank line and move a fresh live prompt to the line below. Delete at the empty prompt moves it back through those blank lines; once it reaches a completed note, the empty timestamp and prompt disappear and the caret returns to that note's end. Clicking any blank line moves an untouched live note there, or inserts a new live note there if the last note is already written; dragging across blank lines only selects them. Deleting a selected bottom section opens a live prompt on the blank line left at the caret. Clicking a written note cancels an untouched pending note and keeps the caret where clicked. Moving the pointer over the editor briefly reveals its clipboard button at the right edge; the same control is reachable from the keyboard and copies the active page as readable text. Download notebook writes versioned UTF-8 JSON containing page names, styles, and structured text/key runs; it stores only the key's display name and public fingerprint, then regenerates the LifeHash locally when that file is uploaded in a later session. Older.txtnotes can also be uploaded as a single page. The Journal also holds a read-only summary that updates as the sitting changes and a debug log of meaningful dashboard actions: tool and station changes, calculations and failures, safe setting changes, copies, imports, downloads, clears, and PSBT structural edits. The log records public fingerprints where useful, but never secret-field values, filenames, note bodies, PSBT bytes, or individual entry keystrokes. It can be copied from its output field or downloaded. Nothing is stored in the browser; download a file to keep it. Closing the page discards the sitting. - Runs a quick barrage of startup sanity checks on the host browser (secure context, CSPRNG, BigInt, UTF-8 encoding, NFKD, and WebAssembly). If any check fails, the page is replaced with a failure report listing the failed checks, because wallet output from a broken host cannot be trusted. iPhone/iPad/Mac Lockdown Mode blocks WebAssembly (the secp256k1 engine): exclude the site in Safari's page menu, or open the saved HTML in Firefox on an air-gapped computer. There is no JavaScript secp256k1 fallback.
- Before any input is wired, runs published test vectors (BIP39 encoding and seed, BIP32 private and public derivation, the BIP44/49/84/86 first receive addresses, and an RFC 6979 ECDSA signature and its verification) through the same WebAssembly engine the calculator uses, and kills the page the same way if this browser computes any of them wrong. The separate PSBT module gets the same treatment once it loads: BIP-174 and BIP-370 decodes, a byte-exact rebuild through the editor's own path, rejection of an invalid file, and the BIP-341 Taproot sighash-byte rule. It catches a miscompiling engine or a corrupted copy; it cannot catch an exploit aimed at the vectors themselves, so for anything that matters, compare the master fingerprint and first address against a second browser or signing device too.
- Produces recovery information that can be saved or printed for offline use.
- Exports a Bitcoin Core
wallet.dat(SQLite descriptor wallet) with every derived output descriptor already imported — receive and change for each script type, active and ready for address generation. The default download is watch-only; while private recovery material is shown on screen, the export becomes the spending variant (account xprvs as descriptor keys) and the button says so. The descriptor birthday defaults to genesis so recovered keys are discovered by Bitcoin Core's initial scan; choose the "New keys" birthday only for entropy created at that moment. If a loaded wallet looks empty, repair it withrescanblockchain 0in Bitcoin Core. Generated database files match Bitcoin Core's own record layout byte-for-byte (verified against Bitcoin Core v28.3.0). - A derived multisig can export a watch-only Bitcoin Core
wallet.dat(same SQLite descriptor wallet as Key Station, never with private keys) and theimportdescriptorsJSON for a blankdisable_private_keyswallet. Drop the.datinwallets/<name>/andloadwallet, or feed the JSON tobitcoin-cli importdescriptors. A derived multisig also prints and saves a one-page watch-only policy sheet (network,m-of-n, co-signer fingerprints, descriptor checksum, receive address 0) so every signer can verify the policy before funding, and a BIP 388 wallet policy (template + xpubs) for wallets that register policies. Calculator export, not a generator. - On a derived key, Edit Input loads its inputs into Key Station. Derive New Key creates a separate tab, including for identical inputs. The secondary action, Update Existing Key, replaces only that source tab after successful derivation. Custom tab names survive updates; a tab still named for its fingerprint follows the key if that fingerprint changes.
- MS Station keeps a session key selected when its co-signer derivation path changes. Clicking that selected key removes it; using it on another co-signer still requires opting into key reuse and distinct derivation paths. On a derived multisig, Edit Input loads its inputs into MS Station. Derive New Multisig creates a separate tab and preserves existing multisigs, even when the inputs are identical. The secondary action, Update Existing Multisig, replaces that source tab after successful derivation. Multisig tabs display their saved names, including renames; updating an existing multisig preserves its name, and the edit note identifies it by its current tab name.
- An optional Sync entropy across methods checkbox (off by default) keeps direct dice, card, number-base, seed-word, and private-key representations in sync while input is entered. Each destination waits for enough bits to emit its next complete character. Hashed inputs update the non-hashed methods in one direction; edits to non-hashed methods never overwrite hashed inputs. With sync off, every dice-roll method retains its own independent transcript.
- SLIP-132 extended-key display is a prefix swap only (same payload, new
version bytes and checksum). Import/derive shows the key as pasted, the
account xpub/tpub, any account private-key export, and the descriptor
(script in the descriptor, not the prefix). The master xpub/tpub is at
m; the account xpub/tpub is at the selected account path. A generic xprv is re-prefixed only when the path/script match: x = legacy, y = nested BIP49, z = native BIP84, Y = nested BIP48 multisig, Z = native BIP48 native-msig. Testnet uses t / u / v / U / V. There is no Taproot SLIP prefix. - Gates each release behind a two-step disclaimer: the first step warns that the software is experimental, and the second names what the browser cannot protect (keys paged to disk, clipboard history, memory surviving close) and unlocks only after the reader types a proof digit — derived from how long the warning was read, never from a CSPRNG. Acceptance is remembered per version, so each new release asks again.
- Guards the session with just-in-time warnings: deriving from less entropy than recommended requires an explicit confirmation, and a newly derived key whose master fingerprint matches another open key asks before it is kept. While the page is offline, external reference links open as QR codes to scan with an online device instead of navigating.
- Offers optional on-screen keyboards for mouse-only entry of seed words, passphrases, private keys, and Base64/Base32 text, and a footer toggle between dark and light themes.
- Speaks five languages: a header selector switches the interface between English, German, Spanish, French, and Portuguese, with untranslated strings falling back to English.
Download the self-contained entropylab.html from the
official website or the
releases page, transfer it to a trusted
computer, disconnect that computer from all networks, and open the file in a
modern browser. For sensitive wallet material, use a dedicated air-gapped
machine and verify important addresses and descriptors with an independent
wallet or signing device before receiving funds.
To build the HTML file yourself, see Building from source.
On iPhone, open entropylab.online in Safari, then choose Share → Add to Home Screen → Open as Web App → Add. Keep the page open until its first load completes. The hosted app stores the self-contained calculator in a versioned browser cache so the Home Screen app can reopen when the phone has no network connection.
Before entering any seed phrase, private key, or other secret wallet material, disconnect every network available to the phone, reopen EntropyLab from the Home Screen, and confirm that the header reports Offline. A cached page is not proof of an air gap: the status is based on browser network signals, and the hosted worker checks for application updates whenever the app is opened while connected. Clearing Safari website data or removing the Home Screen app may remove the cached copy.
For sensitive or long-term use, the recommended path remains the downloaded,
verified entropylab.html on a dedicated air-gapped computer. The downloaded
file is still self-contained and never registers the hosted service worker.
Every merge to rock publishes a SHA256SUMS.txt checksum manifest for
entropylab.html (committed next to it in this repository), a matching
CID.txt (CIDv1 raw sha2-256 of those same bytes), a
GitHub artifact attestation
for the exact bytes built by CI, and an OpenTimestamps proof
(entropylab.html.ots) of that same digest. After downloading, verify:
sha256sum -c SHA256SUMS.txt
gh attestation verify entropylab.html -R OogaBoogaX/entropylabThe three WASM modules embedded in the HTML are committed as
src/js/*-wasm-b64.js; their digests are in WASM-SHA256SUMS.txt, committed
next to SHA256SUMS.txt. From a checkout of the release commit:
sha256sum -c WASM-SHA256SUMS.txtThe build is also reproducible: entropylab.html rebuilds byte-for-byte
from the source commit stamped in its footer, so "CI built these bytes from
that source" can be checked instead of trusted. Docker is the only
prerequisite — the pinned development image is the canonical build
environment (fixed Node, Rust, and clang):
git checkout <stamped-commit> # the commit shown in the file's footer
docker compose run --rm dev bash -c "npm ci --ignore-scripts && npm run build"
sha256sum entropylab.html # compare with SHA256SUMS.txtThis rebuilds the HTML from source using the committed WASM modules as fixed
inputs. Rebuilding those modules (npm run build:wasm inside the same image)
uses the image's pinned clang. CI requires two such builds to match the
modules it publishes: the artifact commit and the Pages deploy wait for that
check. A host clang build is not those bytes. Rebuilds on other machines that
matched the published hashes, the page and the modules alike, are recorded in
docs/Reproductions.md, with how to add one.
The checksum detects accidental corruption. The attestation (Sigstore) says
this repository's CI built those bytes. OpenTimestamps says the digest
existed as of a Bitcoin block, independently of GitHub. A newly committed
.ots is a pending calendar receipt, not a timestamp — do not call it
stamped until ots info entropylab.html.ots shows a Bitcoin attestation.
Then, on a machine with Bitcoin Core (a pruned node is fine):
ots verify entropylab.htmlOn Windows, opentimestamps-client's python-bitcoinlib dependency
looks for OpenSSL as libeay32.dll, which stock Windows does not provide
(LoadLibrary() argument 1 must be str, not None). No full OpenSSL install
is needed: Git for Windows already ships it. Copy
libcrypto-3-x64.dll (and libssl-3-x64.dll) from
C:\Program Files\Git\mingw64\bin\ into any PATH directory as
libeay32.dll and verification runs natively.
EntropyLab itself never stamps, upgrades, or verifies .ots files and never
talks to a calendar. The HTML stays air-gappable.
The CID is a self-describing name for the SHA-256, not a second hash. The
calculator never talks to IPFS. To store or fetch the file on a local
node without GitHub or entropylab.online DNS:
ipfs block put --cid-codec=raw --allow-big-block entropylab.html # pin the bytes you already verified
ipfs get -o entropylab.html "$(cut -d' ' -f1 CID.txt)" # retrieve by CID
sha256sum -c SHA256SUMS.txtipfs add (UnixFS chunking) produces a different CID; that is expected.
Public gateways may refuse a multi-megabyte raw block — a local node is the
intended path. Never open a gateway URL as the wallet origin. Do not put
seeds or other private material on IPFS.
The attestation is keyless (Sigstore) and bound to this repository's release
workflow, so it authenticates the artifact independently of the hosting
account. The checksum manifest alone only detects accidental corruption —
always pair it with the attestation or reproduce the build from reviewed
source. For a given Git revision, npm run build deterministically assembles
entropylab.html from committed inputs, including the committed WASM modules;
the revision to check out is stamped in the generated file. Rebuilding those
modules from their Rust/C sources (npm run build:wasm) is separate: CI
asserts it inside the pinned image, and
docs/Reproductions.md records the rebuilds on other
machines. CI also runs the WASM binding tests against the fresh build (see
Building from source).
An online version is available at entropylab.online for convenient access. Do not enter seed phrases, private keys, or other secret wallet material into an internet-connected device; use the downloaded HTML on a trusted air-gapped computer for sensitive operations.
EntropyLab does not generate wallet entropy, and it does not use browser randomness anywhere. The BitBox and D++ coin-flip steps record a fixed equivalent die face: any face in the Heads range derives the same word as any other, so that choice never changes the resulting entropy. Wallet security still depends on the quality and secrecy of the entropy, seed phrase, passphrase, or private key supplied by the user.
No. EntropyLab deterministically transforms entropy or key material that you supply. It does not create secret wallet entropy. BIP-85 children are derived from the parent root you provide and are reproducible from that same root, application, and index.
Do not enter wallet secrets on an internet-connected device. Download the self-contained HTML, verify it, transfer it to a trusted air-gapped computer, and open it there. Keep backups and verify important results independently before receiving funds.
No. EntropyLab is a calculator and verification tool. It can derive recovery information, construct watch-only wallet data, and inspect supported PSBT details, but it is not intended to be a transaction signer or broadcaster. Use a separately verified wallet or signing device when spending bitcoin.
Derive the same wallet with an independent implementation or signing device and compare the address, derivation path, fingerprint, and descriptor. Do not rely on matching only a shortened value or a visual icon.
Follow Verifying the download. Check the SHA-256 manifest together with the GitHub artifact attestation, or build the file from the reviewed source. A checksum by itself detects changed bytes but does not authenticate who produced them.
Short inputs are useful for deterministic tests, demonstrations, and recovery experiments, so they are accepted with a warning. Hashing a short transcript does not add entropy. Never secure funds with an input below the displayed recommendation.
Do not open a public issue for a suspected vulnerability involving incorrect derivations, secret exposure, injected code, unexpected network access, or possible loss of funds. Follow the private reporting instructions in SECURITY.md.
The build bundles the application with esbuild and inlines the result into a
single self-contained HTML file. The wallet cryptographic primitives run in
WebAssembly from the pinned Rust crates (below). The JavaScript bundle also
includes uqr (QR rendering) and @scure/btc-signer through
src/js/script-builder.js, which the PSBT editor uses to convert supplied
addresses to output scripts. Its bundled dependencies are part of the
production audit surface, even though the package is listed under
devDependencies. The @noble/@scure packages also serve as differential
test oracles; they are not exclusively test-only. package-lock.json pins
the complete dependency tree and the integrity hash of every downloaded
package.
EntropyLab's cryptography — secp256k1 curve operations (public-key
derivation, ECDSA signing and verification in PSBT inspection, curve point
math), hashes (SHA-256, SHA-512, RIPEMD-160, HMAC-SHA-512,
PBKDF2-HMAC-SHA-512), BIP32 extended-key derivation, BIP39 mnemonics,
Base58Check and bech32m encoding, output descriptor evaluation
(BIP380-386; taproot sortedmulti_a is layered on top), and
address/script construction
(p2pkh/p2sh/p2wpkh/p2tr, bare and taproot multisig) — runs on rust-bitcoin's
secp256k1 crate (libsecp256k1 v0.4.1 vendored by secp256k1-sys 0.10.1),
bitcoin, bitcoin_hashes, base58ck, bech32, bip39, and
miniscript, compiled to
WebAssembly from the pinned Rust crate in entropylab-wasm/ (exact crate
versions in entropylab-wasm/Cargo.lock, toolchain pinned by
rust-toolchain.toml) via the facades in src/js/secp256k1.js,
src/js/hashes.js, src/js/hdkey.js, src/js/bip39.js, src/js/base58.js,
src/js/addresses.js, and src/js/bech32.js. The PSBT editor and flow
visualizer render script addresses through the WASM addressFromScript
facade; the script builder's address-to-script conversion is the JavaScript
exception described above. The compiled artifact is committed as src/js/entropylab-wasm-b64.js, so building the
site needs only Node.js. CI
rebuilds it inside the pinned dev image, runs its test suite against the
fresh build, and commits that copy back to rock after each merge (the
same flow as the site artifact). The image pins clang; a host clang build
is not that copy. Build-host paths are remapped out of the binary.
docs/Reproductions.md records the rebuilds of the
published hashes in that image on other machines.
PSBT parsing, typed field decoding, and re-serialization in the PSBT editor
run on rust-bitcoin 0.32.102 compiled to WebAssembly from the pinned crate in
psbt-wasm/ (same pinning rules), exposed through src/js/psbt-wasm.js and
committed as src/js/psbt-wasm-b64.js. The WASM sees only PSBT bytes and
UTF-8 JSON; it holds no keys and generates no randomness.
Requirements: Node.js 20.19 or newer.
npm ci
npm run buildTo modify the Rust bindings (entropylab-wasm/, psbt-wasm/), Rust (with the
wasm32-unknown-unknown target, installed automatically by rustup) is also
required; regenerate the committed artifacts with npm run build:wasm.
Build output (generated; CI rebuilds it for every run and commits it back to
rock after each merge so the file stays downloadable from the repository):
entropylab.html— the self-contained application (open this file)
The version is declared once in package.json and substituted into the
output at build time. The generated file is gitignored locally; CI builds
it before every test run and commits it back to rock after each merge.
To remove generated files, run npm run clean.
├── assets/ Static assets (logo, favicon, social card)
├── scripts/
│ ├── build.mjs Locked-dependency esbuild and HTML assembly
│ ├── build-wasm.mjs crypto WASM rebuild (npm run build:wasm)
│ ├── cid.mjs CIDv1 raw sha2-256 name for the release HTML
│ └── verify-site.mjs Site artifact verification (npm run verify)
├── entropylab-wasm/ Pinned Rust crate: rust-bitcoin + rust-miniscript -> WebAssembly bindings
├── test/
│ ├── browser-instrumentation.html In-page browser test hooks
│ ├── browser-suite.html In-page browser test suite
│ ├── browser.test.mjs Headless-Firefox integration harness
│ ├── browser-check.test.mjs Tests for the startup browser sanity checks
│ ├── self-test-wasm.test.mjs Tests for the boot self-test (oracle-checked vectors)
│ ├── network-check.test.mjs Tests for the network-check module
│ ├── sqlite-writer.test.mjs Tests for the SQLite writer (verified with real SQLite)
│ ├── ui-defaults.test.mjs UI defaults and markup invariants
│ ├── validate.test.mjs Source and security invariants
│ ├── wallet-export-reference.mjs Bitcoin Core wallet.dat ground-truth fixture
│ └── wallet-export.test.mjs Tests for the wallet.dat export module
├── src/
│ ├── index.html HTML template (markup and document head)
│ ├── assets/ Header logos, inlined as data URIs at build time
│ ├── css/styles.css Application styles
│ └── js/
│ ├── app.js Application logic and explicit package imports
│ ├── journal.js Encrypted entropy notebook, session notepad, snapshot, and debug log
│ ├── secp256k1.js Curve facade over the WASM module (noble-shaped API)
│ ├── entropylab-wasm.js Shared WASM module loader
│ ├── entropylab-wasm-b64.js Generated WASM artifact (committed; build:wasm)
│ ├── hashes.js Hash facade over the WASM module (noble-shaped API)
│ ├── hdkey.js BIP32 HDKey facade over the WASM module (scure-shaped API)
│ ├── bip39.js BIP39 mnemonic facade over the WASM module (scure-shaped API)
│ ├── bip39-english.js Canonical 2048-word English list (UI data)
│ ├── base58.js Base58Check facade over the WASM module
│ ├── addresses.js Script/address/descriptor facade over the WASM module
│ ├── bech32.js bech32m facade over the WASM module (BIP352)
│ ├── coders.js hex/base64 byte coders (no cryptography)
│ ├── sqlite-writer.js Minimal SQLite database file writer
│ ├── wallet-export.js Bitcoin Core wallet.dat descriptor export
│ ├── online.js Hosted-site online warning
│ ├── network-check.js Network adapter detection and warning
│ ├── browser-check.js Startup browser sanity checks and kill-screen
│ ├── self-test.js Known-answer vectors run through the WASM before boot
│ ├── enhanced-inputs.js
│ └── repeat-inputs.js
├── entropylab.html Compiled application (generated, CI-committed)
└── versions/archived/ Historical releases excluded from the picker
The toolchain is npm and Node.js (>=20.19). Install the exact dependency tree
with npm ci; every local and CI operation is exposed as an npm script:
npm test # run all tests, including the headless-Firefox suite
npm run test:ci # the CI subset: network-check, ui-defaults, source invariants
npm run test:validate # validate source and security invariants
npm run test:browser # test crypto, sanitization, networking, exports in headless Firefox
npm run build # compile src/ into the generated root files
npm run build:wasm # rebuild the committed crypto WASM artifact (needs Rust)
npm run verify # verify the site artifact (entropylab.html, assets)
npm run ci # run the CI test subset, build, and verify in orderGitHub Actions builds the site first, then runs the same test steps for pull
requests and pushes to rock, stages the verified site (entropylab.html,
assets/) and deploys it to GitHub Pages. After a merge to
rock, a final job commits the rebuilt entropylab.html
back to the repository so the file stays downloadable; pull requests never
carry the generated output, so they stop conflicting on it. The staging step
copies the verified entropylab.html to a deployment-only index.html,
allowing both the site root and /entropylab.html to serve the same
application without committing a second application artifact. CI runs the
test suites that need no browser; the headless-Firefox suite runs locally
where a Firefox binary is available. Local checks and CI/CD use the same
commands; the workflow contains no separate build implementation.
The browser suite runs the assembled application in headless Firefox against a local Node.js HTTP server. It feeds hostile markup and event-handler strings through user-facing fields, verifies the application makes no network requests at runtime, exercises the hosted warning and assets, derives a known wallet through the UI, and inspects both watch-only and private recovery-sheet exports. It also runs the BIP39 and BIP32 published vectors directly against the application code. It is the only part of the toolchain that needs a browser; the server, build, and test harness are dependency-free Node.js.
Bitcoin private keys and seed phrases control funds. Review the code, test the tool with known vectors, keep secret material offline, and maintain verified backups. This software is provided without warranty; use it at your own risk.
EntropyLab is released into the public domain under The Ooga Booga License — a caveman-speak dedication of the software to the public domain, with the same meaning as The Unlicense: free to copy, modify, publish, use, compile, sell, or distribute, in source or binary form, for any purpose and by any means, with no warranty of any kind. Any and all copyright interest in the software is dedicated to the public at large.