Skip to content

2.3.1: seed safety fixes, smaller bundle, CI - #80

Merged
BitcoinQnA merged 1 commit into
mainfrom
fix/high-severity-findings
Sep 10, 2026
Merged

BitcoinQnA merged 1 commit into
mainfrom
fix/high-severity-findings

Conversation

@BitcoinQnA

Copy link
Copy Markdown
Owner

Summary

Fixes the places where the Seed Tool could show a wrong seed as if it were right, or leave seed material on the page after the user thought it was gone. Also shrinks the bundle by 2.3 MB, adds a Content Security Policy, and adds CI. Version 2.3.1.

Wrong-but-valid output

  • Seed XOR now XORs the entropy directly and gives the same results as COLDCARD's own firmware test vectors (12, 18 and 24 words). A mistyped word, a bad checksum or a share of another length gets a visible error and the result is blanked. Before, all three produced a checksummed mnemonic that looked valid. Shares you typed are no longer replaced with random ones when the seed changes.
  • One Time Pad refuses unknown words, damaged keys and keys for another length, and names the problem. A decryption that fails the BIP39 checksum shows nothing instead of a plausible wrong seed. Encrypt no longer writes the text "undefined".
  • BIP85: the Compact SeedQR is shown only for BIP39 children. For WIF, xprv and hex, it used to encode a different secret from the one displayed.

Seed material left behind

  • Clear seed now also wipes the derived address list with WIF keys, the account xprv and xpub, the Ypub and Zpub, the XOR shares and result, the BIP85 password, the single key input, the Shamir and recovery fields, and the BIP85 parent seeds held in memory.
  • Changing the seed no longer leaves the previous seed's account keys, Ypub and Zpub, BIP85 password or XOR result on screen.

Seed replaced without asking

  • Typing entropy, BIP85 "Load Child" and Learn's "Try another seed" now ask before replacing a loaded seed.
  • The entropy box is no longer pre-filled with the BIP39 seed hex, which turned into an unrelated seed as soon as you typed into it.
  • Emptying the entropy box keeps what you entered on other panels (the same bug as FIX: clearing stale entropy output discarded the user's own input (#76) #78, on another path).

Smaller fixes

  • The passphrase tester escapes the pasted address before putting it in the page.
  • PayNym avatars are only fetched from an explicit button. The label now says that this sends the payment codes to paynym.rs.
  • Diceware rolls use rejection sampling. Before, taking random bytes modulo 6 favoured faces 1 to 4 by about 1.6%.
  • Foundation Shard: the copy no longer claims the output is "byte-for-byte compatible". Generated shares show a note that they aren't signed by a Passport. KeyOS rejects them from a KeyCard because it checks the HMAC, device ID and seed fingerprint (os/keycard/src/implementation.rs), and the tool leaves all three at zero.
  • Fixed the second window.onclick overwriting the first, so clicking outside the info modal closes it again.

Build and release

  • dist/index.html is rebuilt. The signed 2.3.0 artifact was built before FIX: pad raw entropy hex to preserve leading zero bits (#76) #77 and FIX: clearing stale entropy output discarded the user's own input (#76) #78 and doesn't contain their fixes.
  • The built page carries a Content Security Policy. Nothing can load or connect except the opt-in PayNym images and BIP-353 DNS-over-HTTPS lookups. dev.html has no policy, so hot reload keeps working.
  • The Silent Payments bundle no longer includes the 2 MB Node crypto polyfill (a 40-line shim covers SHA-256, HMAC-SHA256 and random bytes) or the unused non-English wordlists. sp.js goes from 3.05 MB to 0.73 MB, and dist/index.html from 13.1 MB to 10.8 MB. No minification was added to build.js, so the page's own code stays readable.
  • The README names the current signing key with its fingerprint, and a new section explains how to reproduce a release from its tag. Rebuilding tag 2.3.0 reproduces its signed hash exactly.
  • package-lock.json is committed, plus an .nvmrc, npm run release:check, and a CI workflow that runs the tests and fails if dist/index.html doesn't match the source.
  • Removed the unused @bcts/uniform-resources dependency, qrScanner.js, slip39.js and its build script, and zxcvbn.js.map.

Test plan

  • npm test: 3 suites. test/seed-safety.test.js is new, with 19 tests: COLDCARD and BIP39 vectors for Seed XOR, OTP round trips and rejections, clearing, PayNym, diceware distribution and passphrase escaping. One test was added to the entropy suite. The shared vm harness moved to test/harness.js.
  • sp.js checked against the previous bundle on 850 random cases (addresses, decoding, sender outputs for p2wpkh, p2tr, p2pkh and p2sh-p2wpkh, tagged hashes): 0 differences.
  • The built dist/index.html, exercised in a browser:
    • XOR and OTP errors
    • Clear seed (16 filled secret fields and 20 addresses before, none after)
    • the replace-seed dialog on all three paths, Cancel and Replace
    • the BIP85 QR for WIF and BIP39
    • the PayNym button
    • the Foundation note
    • the Silent Payments address
    • 0 CSP violations and 0 console errors
  • npm run release:check passes. The build is deterministic.

Before publishing

  • Sign the new dist/index.html and publish it as 2.3.1. It still needs to be signed with the release key; the old dist/signature.txt belongs to 2.3.0 and isn't included.
  • Not in this PR: gzip/brotli and Cache-Control on bitcoiner.guide. That's nginx config, and it would take this file from 10.8 MB to about 1.5 MB on the wire.

🤖 Generated with Claude Code

Fixes tools that could show a wrong seed as if it were right, or leave
seed material on the page:

- Seed XOR: XOR the entropy (matches COLDCARD's own test vectors) and
  refuse bad words, bad checksums and shares of another length with a
  visible error instead of a valid looking wrong result. Typed shares are
  no longer overwritten when the seed changes.
- One Time Pad: refuse unknown words and damaged or mismatched keys; a
  decryption that fails the checksum shows nothing. Reading the pad no
  longer depends on host byte order.
- Clear seed wipes every tool's input, derived keys and the BIP85 parent
  chain. Seed changes no longer leave the old account keys, Ypub/Zpub,
  BIP85 password or XOR result on screen.
- BIP85: the Compact SeedQR is only shown for BIP39 children; for WIF,
  xprv and hex it encoded a different secret from the one displayed.
- Ask before replacing a loaded seed (typing entropy, BIP85 Load Child,
  Learn's "Try another seed"). The entropy box is no longer filled with
  the BIP39 seed hex, and emptying it keeps other panels' input.
- Passphrase tester escapes the pasted address before showing it.
- PayNym avatars are only fetched from an explicit button.
- Diceware rolls use rejection sampling instead of a biased modulo.
- Foundation Shard: state that generated shares are not device-signed,
  so a Passport will not load them from a KeyCard.

Release and build:

- dist/index.html rebuilt, including the #76 fixes that the signed 2.3.0
  artifact is missing.
- Content-Security-Policy in the built page: no network access except the
  opt-in PayNym images and BIP-353 DNS-over-HTTPS lookups.
- Silent Payments bundle built without the Node crypto polyfill and the
  unused wordlists: sp.js 3.05 MB to 0.73 MB, dist 13.1 MB to 10.8 MB.
  Checked against the previous bundle on 850 random cases, no differences.
- README: correct signing key and fingerprint, and how to reproduce a
  release from its tag.
- Commit package-lock.json, add .nvmrc, `npm run release:check`, and a CI
  workflow that runs the tests and checks dist matches the source.
- Remove the unused @bcts/uniform-resources dependency, qrScanner.js,
  slip39.js and its build script, and zxcvbn.js.map.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@BitcoinQnA
BitcoinQnA merged commit 091d000 into main Sep 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant