Exclude quantum-unsafe keypair types, rather than adding ML-DSA alongside them #6
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?
A policy change that inverts part of #1 and lands with #2. Recorded here rather than buried in a commit because it changes what several other issues are for.
The decision
KeypairTypebecomes'dilithium65' | 'dilithium87'. Not ML-DSA added to ed25519/sr25519/ecdsa/ethereum — ML-DSA instead of them.Why this is the better posture
#1 opened the union, which made the extension able to hold post-quantum keys. It also left it able to hold quantum-vulnerable ones, in a tool whose entire reason for existing is that those are not safe. A wallet that will happily generate an sr25519 account is a wallet that invites someone to store funds under a key Shor's algorithm recovers, while displaying the same reassuring branding around it.
It also shrinks the fork.
assertSubstrateType, added in #1 to express "no post-quantum answer here yet", stops being needed the moment there is no non-post-quantum answer either — theTYPE_*maps get ML-DSA arms and nothing else, and every narrowing call site disappears rather than being rewritten.Two levels, and which one this is
Type-level exclusion —
KeypairTypecarries only the ML-DSA types, so the keyring cannot construct, import or sign with anything else. The primitives (ed25519Sign,sr25519PairFromSeed,secp256k1…) stay inutil-cryptoas exported functions, unreferenced by the keyring.Ripping out the primitives — deleting
util-crypto/src/{ed25519,sr25519,secp256k1,ethereum}, some 48 source files plus specs.Do the first. The security property comes entirely from the first: nothing can reach those functions through the keyring once the type is gone. The second is a very large diff with a permanent rebase cost, and it buys defence against a caller who imports
ed25519Signdirectly — which a TypeScript type never prevented anyway, so the real answer there is a runtime guard at the data-entry points, not a deletion.Runtime guards are the part that matters and must not be skipped:
keyring.createFromJsonmust reject a classicalencoding.content[1]with a message saying why, not throw a type error or, worse, construct somethingkeyring.addFromJson/ui-keyring.restoreAccountinherit thatSequencing
This cannot land before #2. Removing the classical types from the
TYPE_*maps while the ML-DSA arms are unimplemented leaves the keyring with no valid arms at all — it would build and be inert. The order is: #2 wires@quantus/cryptointo the maps, then the classical arms and the classical types go in the same change.Already done
The network half shipped on
quantus-keypair-types(commitc616a0d94):availableNetworksandselectableNetworksnow exclude any chain whosestandardAccountis a discrete-log scheme, viaisQuantumSafe().allNetworksis deliberately not filtered. Gating what we offer must not gate what we can read — a pasted Polkadot address has to render as a Polkadot address so it can be recognised and refused. A wallet that cannot decode an address it will not accept can only say "invalid", which reads as a typo rather than a warning.Five upstream tests asserted the shape of a 150-entry list and were rewritten to assert the rule that displaced them, including an explicit test that non-PQ chains stay decodable — that distinction is the design, not an accident.
Knock-on
keyFromPathand the whole junction mechanism have no caller. Consider deleting the call rather than making it throw.canDerive.ts's['ed25519','sr25519','ecdsa','ethereum']whitelist becomes empty of anything reachable; it should list the ML-DSA types or go.restoreAccountdefaultscryptoTypeto'ed25519'when the JSON does not say. That default is now unrepresentable and must become a refusal.Deferred, deliberately. Both halves of this — the type narrowing and the primitive deletion — wait until post-quantum signing is proven end to end.
Why the ordering matters more than it looks
The work this issue describes produces no post-quantum capability whatsoever. Every hour of it is spent fixing code that has nothing to do with ML-DSA — 30 spec files, the
hw-ledgerpackages, whatever else falls out — while the question that actually carries risk stays unanswered.And that question is genuinely open. quantus/wasm#1 proved the crypto agrees with the chain, but nothing has yet proved that a signature built through the keyring, wrapped by
@polkadot/types, and submitted to a node is accepted. The failure modes there are the nasty ones: a valid signature under the wrong FIPS-204 context, or a framing error that re-frames every byte after the signature. Those are found by quantus/extension#7 tier 1, and nothing about deleting sr25519 brings them forward.Worse, doing the removal first would blur the diagnosis. If the first real extrinsic is rejected, the useful question is "is our ML-DSA wrong, or our extrinsic assembly wrong". A branch that has simultaneously torn out half of
util-cryptoadds a third candidate that is pure noise.Revised order
KeypairType, deleteutil-crypto/src/{ed25519,sr25519,secp256k1,ethereum}, and add the runtime refusals at the JSON entry points.The intent is unchanged — the forks end up post-quantum only, primitives deleted rather than merely unreachable. What changed is that it happens once there is something working to protect, rather than as a prerequisite to having one.
Consequence for #1's scaffolding
assertSubstrateTypenow has a clearer job than "not implemented yet": it is the seam that lets the two schemes coexist through the proving phase. Each call site is replaced by a real ML-DSA arm in #2, and the helper itself is deleted here, when the classical arms go. Its remaining call sites are the progress bar.Done. Merged to
mainin all three repos, fast-forwarded frompq-only:0480bfcbc:KeypairType = 'dilithium65' | 'dilithium87'. The classical primitives are deleted:ed25519,sr25519,secp256k1,signature, the ethereum and ledger HD paths,hdkdandmnemonicToMiniSecret.keyring.createFromJsonandcreateFromUrirefuse classical types with "<type>keys are not quantum-safe and cannot be held here…". Published as14.0.3-quantus.3.160d97c1:ui-keyring.restoreAccountrefuses version-0 JSON and classical types. Published as3.16.7-quantus.3.29033e8b: depends on both. The derive-from-parent flow,extension-compat-metamaskand the Ethereum address branches are removed. The restore screen now shows the keyring's refusal reason instead of "Invalid Json file".Verification:
.spec.tsxfiles still don't run; see earlier comments.Address decoding is untouched, so a pasted Polkadot address still renders and can be recognised.