Adds the canonical wallet-style backup form (12 or 24 BIP-39 English
words) to both implementations. Wire-compatible — bit-identical seed
derivation across Rust and Node.
Semantics:
• 24 words ↔ 32 bytes of entropy ↔ Ed25519 seed (bijection).
Phrase ↔ seed round-trips exactly.
• 12 words → 16 bytes of entropy → seed via
SHA-256("kez-bip39-12-v1" || entropy). Deterministic but one-way;
you can't recover a 12-word phrase from a seed.
The 12-word case is KEZ-specific (not interoperable with hardware-
wallet BIP-32 derivations). The 24-word case is. Both use the BIP-39
English wordlist so users can paper-back-up alongside other wallets.
We deliberately do NOT use BIP-39's PBKDF2 to_seed(passphrase) — that
produces a 64-byte seed for BIP-32 hierarchical derivation, which is
the wrong primitive for KEZ's single-identity-per-phrase model.
Rust (kez-core):
• New mod mnemonic with MnemonicWords, generate_mnemonic,
seed_from_mnemonic, mnemonic_from_seed_24.
• Ed25519Secret::{from_mnemonic, generate_with_mnemonic}.
• Dep: bip39 v2.0 with the `rand` feature for OS-RNG generation.
• 9 unit tests, all green.
Rust (kez-cli):
• `identity new --key-type ed25519` now also prints a 24-word phrase
(default), with --mnemonic-words 12 to use 12 instead.
• `identity mnemonic [--words 12|24]` — print a fresh phrase only.
• `identity from-mnemonic "<phrase>"` — derive the key from a phrase.
• `--mnemonic <phrase>` is now accepted everywhere `--ed25519-seed
<hex>` was (claim create/dns, sigchain add/revoke/show/export/
publish), mutually exclusive with --ed25519-seed and --nsec via
clap conflicts_with_all.
Node (@kez/core):
• New mnemonic.ts with the parallel API:
generateMnemonic, seedFromMnemonic, mnemonicFromSeed24,
ed25519FromMnemonic, generateEd25519WithMnemonic.
• Dep: @scure/bip39 v2.x (note: import path is
"@scure/bip39/wordlists/english.js" with the .js suffix in v2).
• 8 vitest cases mirroring the Rust tests, all green.
Node (@kez/cli):
• Same CLI surface added: identity new --mnemonic-words 12|24,
identity mnemonic --words 12|24, identity from-mnemonic "<phrase>".
• --mnemonic flag accepted alongside --nsec / --ed25519-seed in the
flag parser, with mutex enforcement; loadSigner dispatches it.
Verified cross-implementation interop:
• Same 24-word phrase → identical Ed25519 pubkey in Rust and Node.
• Same 12-word phrase → identical pubkey (proves the SHA-256
domain-tagged derivation matches byte-for-byte).
• A claim signed in Rust with --mnemonic verifies in Node (Status:
valid).
Tests: 114 Rust + 99 Node total, zero regressions.
TUTORIAL.md updated in both rust/ and nodejs/ with the new section in
"Pick your primary key" plus a callout that --mnemonic can substitute
for --ed25519-seed throughout the rest of the tutorial.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>