walletFromSeed is pure derivation and needs no Provider, keeping this
module network-free. MnemonicKeySource is the familiar default;
PrivateKeyKeySource is strictly safer since it controls one account
rather than the whole HD tree. describe() is asserted never to leak key
material.
Verified rather than assumed: walletFromSeed's private key prefix is
ed25519e_sk1 and its address is addr1... (Base, Mainnet default) - the
brief's regex already matched. The brief's test mnemonic passes BIP39
checksum validation.
Hardened beyond the brief after an edge-case pass:
- Mnemonic case: bip39's wordlist lookup is case-sensitive, so an
uppercase or mixed-case mnemonic threw outright. Casing carries no
BIP39 meaning, so lowercasing is safe - now applied, and verified to
derive the identical address rather than merely "some" address.
- PrivateKeyKeySource validated the trimmed key/address but stored the
original untrimmed value - the same knownAddresses() bug shape as
policy.ts (validate trimmed, store padded, silently fail every later
exact-string comparison). Now stores what was validated.
- Empty mnemonic input reported "got 1" word (''.split(' ') === ['']),
not "got 0" - message accuracy only, both branches already rejected.
Documented, not fixed: PrivateKeyKeySource cannot verify that `address`
is actually controlled by `privateKeyBech32`. That needs CML, and this
module deliberately imports only @lucid-evolution/wallet, whose one
raw-private-key function requires a live Provider. The failure is
bounded if misused - signing needs no address, so a mismatch surfaces as
an on-chain rejection, and policy.ts's own-address matching only ever
over-refuses on a wrong address, never under-refuses.
29 tests beyond the brief's 10. 5 of the fixes above were mutation-tested
(each reverted and confirmed to fail red before restoring the fix).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>