Account creation, derivation and default keypair type #3

Open
opened 2026-09-10 10:14:31 +00:00 by grenade · 2 comments
Owner

Part of #1. Depends on #2 and on quantus/common#1, #2 and #4.

Default type

Two places pick sr25519 today:

  • packages/extension/src/background.ts:103keyring.loadAll({ store: new AccountsStore(), type: 'sr25519' })
  • packages/extension-ui/src/util/defaultType.tsexport const DEFAULT_TYPE: KeypairType = 'sr25519'

Both become dilithium65. ML-DSA-87 must remain importable — it is what accounts created before the scheme was recorded use — but never offered as a choice for a new account.

Derivation UI

packages/extension-ui/src/Popup/Derive/* is built around substrate suri junctions, and SelectParent.tsx already gates on parent?.type === 'sr25519' with the message "Soft derivation is only allowed for sr25519 accounts". None of that vocabulary survives: Quantus derivation is a hardened BIP44 path where the account index sits at the third level, m/44'/189189'/<account>'/0'/1'.

The honest UI is probably an account index, not a free-text path — a user typing //alice should get an error, not a different account. Whatever the input, it has to produce exactly what quantus/common#4 accepts; settle the suri syntax there first and follow it here.

packages/extension-base/src/utils/canDerive.ts whitelists ['ed25519', 'sr25519', 'ecdsa', 'ethereum'] and will silently return false for the new types, hiding the derive action entirely. That is a reasonable temporary state — better than a broken derive — but decide deliberately rather than by omission.

Seed handling

Extension.ts has getSuri() and an ETH_DERIVE_DEFAULT constant that appends a derivation path for ethereum accounts. Quantus needs the same treatment with its own default path, and seedValidate needs to accept what the Quantus path produces. Its existing checks — 12/15/18/21/24 words, mnemonicValidate — are plain BIP39 and stay correct.

Acceptance

  • a new account created from a generated mnemonic has the address quantus-cli derives for that mnemonic at index 0
  • importing an existing mnemonic from the mobile wallet finds the same accounts it shows
  • an ML-DSA-87 account imports and signs
  • derive produces index n and matches quantus-cli for the same n
Part of #1. Depends on #2 and on quantus/common#1, #2 and #4. ## Default type Two places pick `sr25519` today: - `packages/extension/src/background.ts:103` — `keyring.loadAll({ store: new AccountsStore(), type: 'sr25519' })` - `packages/extension-ui/src/util/defaultType.ts` — `export const DEFAULT_TYPE: KeypairType = 'sr25519'` Both become `dilithium65`. ML-DSA-87 must remain **importable** — it is what accounts created before the scheme was recorded use — but never offered as a choice for a new account. ## Derivation UI `packages/extension-ui/src/Popup/Derive/*` is built around substrate suri junctions, and `SelectParent.tsx` already gates on `parent?.type === 'sr25519'` with the message *"Soft derivation is only allowed for sr25519 accounts"*. None of that vocabulary survives: Quantus derivation is a hardened BIP44 path where the account index sits at the third level, `m/44'/189189'/<account>'/0'/1'`. The honest UI is probably an account **index**, not a free-text path — a user typing `//alice` should get an error, not a different account. Whatever the input, it has to produce exactly what quantus/common#4 accepts; settle the suri syntax there first and follow it here. `packages/extension-base/src/utils/canDerive.ts` whitelists `['ed25519', 'sr25519', 'ecdsa', 'ethereum']` and will silently return `false` for the new types, hiding the derive action entirely. That is a reasonable *temporary* state — better than a broken derive — but decide deliberately rather than by omission. ## Seed handling `Extension.ts` has `getSuri()` and an `ETH_DERIVE_DEFAULT` constant that appends a derivation path for ethereum accounts. Quantus needs the same treatment with its own default path, and `seedValidate` needs to accept what the Quantus path produces. Its existing checks — 12/15/18/21/24 words, `mnemonicValidate` — are plain BIP39 and stay correct. ## Acceptance - [ ] a new account created from a generated mnemonic has the address `quantus-cli` derives for that mnemonic at index 0 - [ ] importing an existing mnemonic from the mobile wallet finds the same accounts it shows - [ ] an ML-DSA-87 account imports and signs - [ ] derive produces index *n* and matches `quantus-cli` for the same *n*
Author
Owner

Two constraints for this issue, both settled in common and both changing what the UI should do.

The mnemonic is the only portable backup

quantus-cli writes an entirely different wallet format — Argon2id + AES-256-GCM around a JSON payload, where polkadot-js uses scrypt + NaCl secretbox around PKCS8. Details in quantus/common#3.

Neither tool can read the other's file. Import is fine, because a CLI wallet's mnemonic moves it and that path is verified against real CLI wallets. Export is the problem: a backup this extension writes is readable only by polkadot-js-format tooling, so a user whose only backup is our JSON, with the extension unavailable, cannot recover through quantus-cli or the mobile wallet.

So the backup flow must present the mnemonic at least as prominently as the JSON file, and should say plainly that the JSON is extension-specific while the mnemonic works everywhere. That is a UI decision, not a format one — implementing CLI-format export was considered and rejected, since the format is mid-flight (encryption_version: 2 with empty kyber_* fields) and the mnemonic already covers it.

One exception worth handling: dev-genesis wallets have no mnemonic (derivation_path: "m/", raw seed). They move as a hex seed. seedValidate already accepts a 256-bit hex seed, so the import side works; anything offering "back up your mnemonic" needs to not claim one exists for those accounts.

Derivation: offer an index at creation, not a derive action

quantus/common#4 landed the suri syntax:

form meaning
(nothing) account 0
//<n> account index n
//m/44'/189189'/… explicit path
anything else throws

Send //<n>. //Alice throws, /0 throws, and an unhardened level in an explicit path throws — deliberately, since reinterpreting any of them would produce an address no other Quantus tool derives.

More importantly for the UI: pair.derive() refuses, and cannot do otherwise. ML-DSA keys are not derivable from one another — the Quantus tree derives each account from the mnemonic independently — so "derive a child from this account" is not an unimplemented feature, it is an impossible one. The error names createFromUri and the mnemonic.

That settles the canDerive.ts question this issue left open: it is not a temporary omission to revisit. The derive-from-parent flow in Popup/Derive/* has no ML-DSA meaning at all, and what replaces it is an account index chosen at creation or import time, against the mnemonic the user already has. Worth doing as part of the create/import screens rather than as a separate action that only ever fails.

Two constraints for this issue, both settled in `common` and both changing what the UI should do. ## The mnemonic is the only portable backup `quantus-cli` writes an entirely different wallet format — Argon2id + AES-256-GCM around a JSON payload, where polkadot-js uses scrypt + NaCl secretbox around PKCS8. Details in quantus/common#3. Neither tool can read the other's file. Import is fine, because a CLI wallet's mnemonic moves it and that path is verified against real CLI wallets. **Export is the problem**: a backup this extension writes is readable only by polkadot-js-format tooling, so a user whose only backup is our JSON, with the extension unavailable, cannot recover through `quantus-cli` or the mobile wallet. So the backup flow must present the **mnemonic** at least as prominently as the JSON file, and should say plainly that the JSON is extension-specific while the mnemonic works everywhere. That is a UI decision, not a format one — implementing CLI-format export was considered and rejected, since the format is mid-flight (`encryption_version: 2` with empty `kyber_*` fields) and the mnemonic already covers it. One exception worth handling: dev-genesis wallets have **no mnemonic** (`derivation_path: "m/"`, raw seed). They move as a hex seed. `seedValidate` already accepts a 256-bit hex seed, so the import side works; anything offering "back up your mnemonic" needs to not claim one exists for those accounts. ## Derivation: offer an index at creation, not a derive action quantus/common#4 landed the suri syntax: | form | meaning | |---|---| | *(nothing)* | account 0 | | `//<n>` | account index *n* | | `//m/44'/189189'/…` | explicit path | | anything else | **throws** | Send `//<n>`. `//Alice` throws, `/0` throws, and an unhardened level in an explicit path throws — deliberately, since reinterpreting any of them would produce an address no other Quantus tool derives. More importantly for the UI: **`pair.derive()` refuses, and cannot do otherwise.** ML-DSA keys are not derivable from one another — the Quantus tree derives each account from the mnemonic independently — so "derive a child from this account" is not an unimplemented feature, it is an impossible one. The error names `createFromUri` and the mnemonic. That settles the `canDerive.ts` question this issue left open: it is not a temporary omission to revisit. The derive-from-parent flow in `Popup/Derive/*` has no ML-DSA meaning at all, and what replaces it is an **account index chosen at creation or import time**, against the mnemonic the user already has. Worth doing as part of the create/import screens rather than as a separate action that only ever fails.
Author
Owner

Checked against main. Most of this is superseded by the wallet model in #14. Two concrete items remain.

Met:

  • Default type: dilithium65 (background.ts, util/defaultType.ts).
  • Matches quantus-cli: the index-0 accounts of a mnemonic, both schemes (Extension.spec.ts wallets block, and common's dilithiumDerive.spec.ts).
  • Mobile-wallet phrases: the chain node's TEST_ADDRESS_HD_0 and TEST_WORMHOLE_ADDRESS, both also pinned by the mobile SDK, were checked in the running extension.
  • ML-DSA-87: imports and signs.
  • Derive: a deliberate decision. canDerive is false for ML-DSA, and wallets add account indices instead.
  • Superseded: "ML-DSA-87 importable but not offered for new accounts". A wallet shows both schemes per index, which is the point of #14.

Remaining:

  1. Account index n > 0 against quantus-cli. Today the path is only correct by construction (//<n>m/44'/189189'/n'/0'/scheme'), and the tests show only that index 1 differs from index 0. Add a CLI-derived address for an index ≥ 1 as a vector.
  2. Backup wording (from the comment above). Export.tsx and ExportAll.tsx are upstream's. They should put the recovery phrase ahead of the JSON file, and say the JSON restores only into this extension.
Checked against `main`. Most of this is superseded by the wallet model in #14. Two concrete items remain. **Met:** - **Default type:** `dilithium65` (`background.ts`, `util/defaultType.ts`). - **Matches `quantus-cli`:** the index-0 accounts of a mnemonic, both schemes (`Extension.spec.ts` wallets block, and common's `dilithiumDerive.spec.ts`). - **Mobile-wallet phrases:** the chain node's `TEST_ADDRESS_HD_0` and `TEST_WORMHOLE_ADDRESS`, both also pinned by the mobile SDK, were checked in the running extension. - **ML-DSA-87:** imports and signs. - **Derive:** a deliberate decision. `canDerive` is false for ML-DSA, and wallets add account indices instead. - **Superseded:** "ML-DSA-87 importable but not offered for new accounts". A wallet shows both schemes per index, which is the point of #14. **Remaining:** 1. **Account index n > 0 against `quantus-cli`.** Today the path is only correct by construction (`//<n>` → `m/44'/189189'/n'/0'/scheme'`), and the tests show only that index 1 differs from index 0. Add a CLI-derived address for an index ≥ 1 as a vector. 2. **Backup wording (from the comment above).** `Export.tsx` and `ExportAll.tsx` are upstream's. They should put the recovery phrase ahead of the JSON file, and say the JSON restores only into this extension.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: quantus/extension#3