UX decision: match upstream wherever the crypto allows, and where it does not #8
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?
Settling the shape of the extension before building more of it. Referenced by #3, #4, #5 and #6.
The rule
So: a password unlocks signing. The mnemonic is shown once at creation and never stored. Identical to upstream, and the ML-DSA key material supports it directly — signing needs the secret key, which is exactly what the encrypted PKCS8 blob holds.
Verified, not assumed: upstream's
encodePairtakes{ publicKey, secretKey }only, andaccountsCreateSuridrops the suri onceaddUrihas derived from it. Nothing inextension-base/src/stores/persists a seed. We match that.One caveat to carry:
pairToJsonputsmetaoutside the encrypted field, so account metadata is plaintext at rest. Nothing sensitive goes there today and nothing sensitive should start.What is unchanged from upstream
Every one of these works with ML-DSA with no UX difference at all:
createFromUri, verified againstquantus-clifor both schemesseedValidatealready accepts a 256-bit hex seed; this is how dev-genesis accounts moveWhat the crypto forces to change
Exactly one thing: deriving a further account.
Upstream derives a child from the parent's key material, so an unlocked parent is enough. ML-DSA keys are not derivable from one another — the Quantus tree derives every account from the mnemonic independently — so
pair.derive()refuses and cannot do otherwise (quantus/common#4).Since we deliberately do not store the mnemonic, an unlocked account is not sufficient to produce account n+1. That is a genuine consequence of the two decisions together, not an implementation gap.
The UX that stays closest to upstream:
The
Popup/Derive/*flow keyed on a parent account goes away — it has no ML-DSA meaning.canDerive.tsshould returnfalsefor the ML-DSA types, and that is a permanent answer rather than a placeholder.Alternative considered and rejected: offering to create accounts 0..n in one go at creation time, so the phrase is only needed once. It front-loads a decision the user cannot yet make and does not help anyone importing an existing wallet.
CLI import is a must-have
Agreed, and my earlier recommendation against it was wrong — see quantus/common#3, where I withdrew the reasoning. Filed as #8.
It converts at the edge: decrypt the CLI container, take the key material, re-encrypt as the extension's own PKCS8 under a password the user chooses. The keyring stays single-format, and the mnemonic inside the CLI file is derived-and-discarded, never stored — same posture as every other import.
Confirmed buildable with what is already on hand:
@noble/hashes/argon2is an existingutil-cryptodependency and WebCrypto covers AES-GCM. A real CLI wallet has been decrypted this way already.The export gap, which is a UX problem
quantus-clicannot read the extension's JSON, and the extension will not write the CLI's. So an extension JSON backup is recoverable only through polkadot-js-format tooling.The backup flow must therefore present the recovery phrase as the primary backup and the JSON as a convenience, saying plainly that the phrase works in every Quantus tool and the file does not. This is the one place where being less like upstream is right: upstream can treat its JSON as a universal backup because the whole ecosystem reads it, and ours cannot.
Accounts imported from a raw seed have no phrase to show. That flow must not claim otherwise.
Correction to the body: the CLI-import work is quantus/common#7, not
#8— that number is this issue. The conversion lives incommonbecause it is keyring-adjacent and testable there; the extension side is the import screen that calls it.