wasm: add BIP39 passphrase support to make_default_account_privkey - #2113
Merged
Merged
Conversation
erubboli
force-pushed
the
feat/wasm-bip39-passphrase
branch
2 times, most recently
from
September 15, 2026 10:34
90e78f7 to
fe37fdd
Compare
Add an optional BIP39 passphrase parameter (Option<String>, optional in JS) used when converting the mnemonic to a seed (salt = "mnemonic" + passphrase, PBKDF2-HMAC-SHA512, 2048 iterations via the bip39 crate). Passing None / undefined / null / "" preserves the legacy behavior byte-for-byte; the passphrase and derived seed are wrapped in Zeroizing on the Rust side. Also bumps the workspace (and npm package) version to 1.5.0 with changelog, README and generated WASM-API.md updates, regression tests pinning the pre-change output (empty passphrase, mainnet/testnet, receiving addresses 0/1), official Trezor BIP39 vectors, non-ASCII normalization vectors, a wasm-vs-Core key-chain parity test, and JS bindings tests.
- Remove the accidentally committed local wrangler cache and ignore .wrangler/ - Disable default features of the wallet dev-dependency (avoids pulling the trezor/ledger device stacks into the wasm-wrappers test build) - Best-effort wording for the zeroization comment - Strict equality in the JS bindings test - Pin non-ASCII BIP39 passphrase normalization (NFKD) with vectors verified by an independent implementation
erubboli
force-pushed
the
feat/wasm-bip39-passphrase
branch
from
September 15, 2026 13:49
fe37fdd to
cdd136b
Compare
anyxem
approved these changes
Sep 15, 2026
|
✅ OpenCodeReview: Review complete: 0 finding(s) across 5 selected item(s). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the Mojito mobile wallet blocker (High, CWE-325): the wasm API had no way to pass a BIP39 passphrase to key derivation.
Stacked on #2112 (supply-chain/lockfile context needed for cargo-deny/vet).
API shape
Chose the single-entry-point form over a new export:
MasterKeyChain::mnemonic_to_root_key(mnemonic, Option<&str>)signaturepassphrase?: string | nullin the generated TS bindings — an optional third argument, so all existing 2-argument JS callers keep working unchangedSemantics
seed = PBKDF2-HMAC-SHA512(mnemonic, salt = "mnemonic" + passphrase, 2048 iterations, 64 bytes)viabip39'sto_seed(which performs the required NFKD normalization) — same call pattern as Core desktop (to_seed(passphrase.unwrap_or(""))).None/undefined/null/""are all byte-identical to the old behavior. Passphrase and seed are wrapped inZeroizing(best-effort; see comment in code).Backward compatibility (regression-tested)
Pinned vectors captured from the pre-change implementation and asserted in
legacy_derivations_unchanged: empty-passphrase derivations on mainnet and testnet, plus receiving keys at index 0 and 1 for both networks. The existing JSpredefined_address_test(2-argument call, pinned address) continues to pass unchanged.Tests
bip39_trezor_test_vectors— 5 official BIP39 Trezor vectors ("TREZOR" passphrase), cross-checked against an independent PBKDF2 implementationnon_ascii_passphrase_normalization— NFKD normalization pinned with independently computed vectors (non-ASCII/Unicode-separator passphrases), the interop concern for arbitrary JS stringsdifferent_passphrases_produce_different_keys— same mnemonic, different passphrases → different extended keys and different receiving addresseswasm_matches_core_key_chain— acceptance gate:MasterKeyChain::mnemonic_to_root_key(Core desktop key-management) and the wasm export derive identical account keys and identical addresses for the same mnemonic + passphraseCall-path audit (requirement 5)
make_receiving_address/make_change_address/make_receiving_address_public_key/make_change_address_public_key: consume the already-extended account key — no change needed ✅encode_witness/sign_message_for_spending: consume raw private keys derived downstream — no mnemonic involved ✅sign_challenge/verify_challenge: consume a private key / address, no mnemonic ✅wallet'sMasterKeyChain::mnemonic_to_root_key, which already takes the passphrase (and is the interop reference for this PR) ✅Versioning
This PR is version-neutral (no workspace/npm version change); the changelog entry lives under
[Unreleased]. The version upgrade to 1.5.0 will be coordinated separately in a dedicated release PR. README documents the new parameter; generatedWASM-API.mdandwasm_wrappers.d.tsupdated (passphrase?: string | null).Compatibility note
Affected wallets: only wallets created from a mnemonic with a user passphrase (currently impossible via wasm — that's the bug). Legacy wallets (no passphrase) must keep deriving with
None/undefined; mixing them up produces different keys. Migration for the mobile wallet: re-vendor the wasm package; for new wallets pass the user's passphrase; for existing (legacy) wallets pass nothing.Verification
./do_checks.shgreen locally under the CI toolchain (1.92.0): fmt, cargo-deny, cargo-vet, clippy (all targets), codecheck, wasm-doc checkcargo test --workspace(all 202 test binaries) green locally — includes the node-daemon / wallet-rpc-daemon RPC doc expect-testscargo test -p wasm-wrappers: 7/7 pass; full JS bindings suite via wasm-pack build + node: all pass