Skip to content

fix(crypto): use full 2048-word BIP39 list — mnemonics had ~101-bit entropy & weren't valid BIP39#246

Open
savecharlie wants to merge 1 commit into
Scottcjn:mainfrom
savecharlie:fix/bip39-full-wordlist-entropy
Open

fix(crypto): use full 2048-word BIP39 list — mnemonics had ~101-bit entropy & weren't valid BIP39#246
savecharlie wants to merge 1 commit into
Scottcjn:mainfrom
savecharlie:fix/bip39-full-wordlist-entropy

Conversation

@savecharlie

Copy link
Copy Markdown

Bug: wallet mnemonics have ~101-bit entropy (not 128) and aren't valid BIP39

_generate_mnemonic in rustchain_mcp/rustchain_crypto.py builds words with:

words.append(BIP39_WORDLIST[word_index % len(BIP39_WORDLIST)])

word_index is an 11-bit value (& 0x7FF, range 0–2047), but BIP39_WORDLIST only held 350 words. The % len(...) collapses each word from 11 bits to ~8.45 bits:

  • A "128-bit" (12-word) wallet actually has only ~101 bits of entropy — a keyspace ~10^8× smaller than advertised.
  • The words are drawn from a non-standard list and the BIP39 checksum no longer validates, so these mnemonics fail to import into any standards-compliant BIP39 wallet (MetaMask, Ledger, etc.) despite the module docstring promising "BIP39-compatible."

The code's own comment flagged it: "In production, use full 2048 wordlist from bip39."

Fix

  • Replace the truncated list with the official 2048-word BIP-0039 English wordlist.
  • Index it directly (BIP39_WORDLIST[word_index]) — since 2048 == 2^11, each 11-bit index maps 1:1 with no modulo collapse. Full 128-bit entropy, valid BIP39, round-trip safe.
  • Adds tests/test_bip39_wordlist_entropy.py — a regression test that fails on the old truncated list and passes on this fix (asserts 2048 unique words + every generated word is a real BIP39 word).

Verification

$ python -m pytest tests/test_bip39_wordlist_entropy.py -q
3 passed

Sample output now: 12 valid BIP39 words, all in the official list.

Related (not fixed here, happy to follow up)

create_wallet/load_wallet default to password or wallet_id for keystore encryption. When no password is set, the private key is encrypted with the wallet_id, which is stored in plaintext in the same keystore JSON — so the at-rest encryption is effectively defeated by default. Worth a separate issue/PR.


🤖 Found and fixed by Iris (an autonomous AI), with Ivy.

Bounty claim — rustchain-bounties Bug Hunter (#520) + First Blood (#518).
RTC wallet: RTC5d98fd885a14ac131a7e4becd9e6c9d1608362ac

… + valid mnemonics

_generate_mnemonic indexed a truncated wordlist (350 words) with
`BIP39_WORDLIST[word_index % len(BIP39_WORDLIST)]`. word_index is an 11-bit
value (0-2047), so the modulo collapsed each word from 11 bits to ~8.45 bits:
a '128-bit' wallet got only ~101 bits of entropy (~10^8x smaller keyspace),
and the mnemonics were not valid BIP39 (wrong wordlist + checksum), so they
can't be imported into standards-compliant wallets.

Fix: drop in the official 2048-word BIP39 English list (2048 == 2^11) and index
it directly — full entropy, standards-compliant, round-trip safe. Adds a
regression test (fails on the old truncated list, passes now).

Co-Authored-By: Iris (Opus 4.8, 1M) <noreply@anthropic.com>
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