keyring: replace suri junction derivation with the Quantus BIP44 path for ML-DSA pairs #4
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Depends on #1. Context in quantus/extension#1.
Lattice keys have no soft derivation
createFromUriparses a suri into{ phrase, password, path, derivePath }and runskeyFromPath(pair, path, type), which walks sr25519/ed25519 junctions —//hardand/soft. Neither concept exists for ML-DSA. The chain says so directly, inPair::derive:and the hdwallet crate rejects any unhardened path outright, with a test pinning that contract (
non_hardened_path_rejected). The README's reasoning is that lattice keys are not publicly derivable, so an unhardened level cannot mean what BIP-32 implies.What replaces it
m/44'/189189'/<account>'/0'/<0 for ML-DSA-87 | 1 for ML-DSA-65>'The account index sits at the third level and the trailing hardened index carries the scheme — an unusual layout, but it is what
quantus-cliand the mobile wallet both already use (quantus-apps:quantus_sdk/lib/src/services/hd_wallet_service.dart,pathForIndex). Do not improve on it; anything else means addresses that no other Quantus tool can find.There is a second tree at
m/44'/189189189'/…for wormhole addresses. Out of scope here — note it only so nobody assumes the coin type is a typo.Two things to decide
createFromUriseeding. The non-ethereum branch callsmnemonicToMiniSecret, which is the Substrate variant. Quantus needs the 64-byte BIP39 legacy seed, then the"Dilithium seed"HMAC chain. Wrong seeding here yields a valid-looking key for an account nobody owns.//0reads naturally as "account 0" but collides with the hard-junction meaning, andcreateFromUrialso silently substitutesDEV_PHRASEfor a suri starting with//. Either accept a fullm/44'/…path as the derive part, or map a small index syntax ontopathForIndex— but make an unsupported junction an error, never a silently different key. Whatever is chosen has to match what the extension's derive UI sends (quantus/extension#3).Acceptance
quantus-cliderives for the same mnemonic and indexderive()on an already-derived pair does something defensible, or refuses — the extension's "derive from account" flow depends on the answerDone, on
quantus-keypair-types(f7dc72e32). 12 specs.Suri syntax, settled
This issue left the choice open. What landed:
quantus-clidefaults to//<n>//m/44'/189189'/…//Alicethrows,/0throws, and an unhardened level inside an explicit path throws. Quietly reinterpreting any of them would hand back an address no other Quantus tool derives and no seed phrase obviously recovers — which is the failure this issue asked to avoid, and it is worth more than the convenience of accepting the input.quantus/extension#3 should send
//<n>, or an index it turns into one.Seeding, which is the part that fails silently
The ML-DSA branch sits before the existing seeding, not after. That existing code calls
mnemonicToMiniSecret, Substrate's variant and the default reach in this file; using it would produce a perfectly well-formed key for an account nobody owns, with nothing to indicate anything went wrong. The ML-DSA path goes mnemonic → 64-byte BIP39 seed → HMAC-SHA512 keyed with"Dilithium seed", via the hdwallet crate.Raw seeds
Taken underived, since that is how the dev-genesis accounts are defined —
0x0000…00gives crystal_alice. Combining a raw seed with a derivation path is refused rather than guessed: whether such a seed is the master or already derived is unanswerable, and picking one silently would be the same class of mistake as the seeding above.derive()on an existing pairThe last question here — "does something defensible, or refuses". It refuses, with its own message rather than
keyFromPath's generic one, because the reason is specific and actionable: a child would have to come from this pair's key material, and ML-DSA keys are not derivable from one another at all. The Quantus tree derives each account from the mnemonic independently. So the error namescreateFromUriand the mnemonic.That makes quantus/extension#3's
canDerivedecision easy: derive-from-account cannot work, and the UI should offer an account index at creation instead.Acceptance
quantus-clifor both schemes from the public dev phrasederive()refuses, with a message saying what to do instead//Alice,//Bobpinned)Not done: golden vectors from quantus/wasm#2 — these fixtures are CLI-derived, which is an independent implementation but not the crate's own published vectors.
Closing. The goal is met; the one unmet criterion moves to quantus/wasm#2.
Met on
main(f7dc72e32):quantus-clifor a given mnemonic and index (dilithiumDerive.spec.ts);derive()on an existing pair refuses with a message that says why.In the extension, account indices are added from a wallet's menu (quantus/extension#14), using the same
//<n>→m/44'/189189'/n'/0'/scheme'path.Moved: "matches the hdwallet crate's own golden vectors" needs the shared fixture that quantus/wasm#2 has not produced yet. It is tracked there (item 5) rather than keeping this open.