Epic: a Quantus-capable browser extension that signs real extrinsics #1
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?
Tracking issue for post-quantum support across this fork and the five upstream forks it depends on. Everything below is scoped as an issue on the repo that owns the code.
Done means: a user installs this extension, creates or restores a Quantus account from a mnemonic, sees it at SS58 prefix 189, and approves a
balances.transfer_keep_alivefrom a dapp that the node accepts and includes in a block. That is #7.What Quantus actually changes
Only four things differ from stock Substrate. Everything else — extrinsic format v4 signed (
0x84),MultiAddress::Id, a standardTxExtensiontuple, metadata from an unmodified vendoredframe-metadata— is upstream-identical.MultiSignature, 64/65 BDilithiumSignatureSchemeenum —Dilithium87= variant byte0,Dilithium65= variant byte1; payload issig ‖ pubkeyas a fixed array, no compact lengthPoseidon2-over-Goldilocks(pubkey)— one-way, so the public key must ride along inside every signature//hard,/soft)"Dilithium seed", hardened-only, pathm/44'/189189'/<account>'/0'/<0 for 87 | 1 for 65>'"QUANTUS_EXTRINSIC"(spec ≥ 148; empty context before that)Sizes, which several issues turn on:
sig ‖ pkSS58 prefix is 189. ML-DSA-65 is the scheme new accounts use; ML-DSA-87 is legacy (accounts created before the scheme was recorded).
Sources of truth, in preference order — do not re-derive any of this from prose:
chain:primitives/dilithium-crypto/src/scheme_macro.rs— key generation, signing,IdentifyAccount, thesig ‖ pklayoutchain:primitives/dilithium-crypto/src/signing_context.rs— the context stringchain:runtime/src/lib.rs—Signature,Address,TxExtension,UncheckedExtrinsicquantus-apps:quantus_sdk— a working non-Rust implementation of all of this against a forked polkadart.lib/src/resonance_extrinsic_payload.dart,lib/src/extensions/dilithium_scheme_extension.dartandrust/src/api/crypto.rsare the closest thing to a reference port that exists. Read them before writing anything.Decode from metadata, never from a name
Standing rule, learned elsewhere and paid for elsewhere. Metadata v14+ carries a complete
scale-inforegistry describing the structure of every call, event and storage item, andstate_getMetadataat a block hash makes the node runMetadata_metadataagainst the runtime wasm in that block's state — the runtime is the oracle and the node executes it for us. blackbeard.observer'sblackbeard-core/src/runtime.rsheader records why the alternative failed:Quantus is a moving target. Wherever a choice exists between reading a type from metadata and pinning it by name, read it from metadata — and cache metadata by
spec_version, not by block hash, so decoding survives pruning. This is the deep reason papi needs one patch where polkadot-js needs a fork chain.The work
quantus/wasm — the crypto itself, compiled from the chain's own crates. Critical path: nothing downstream is testable until it ships something callable.
@polkadot/wasm-cryptoquantus/common —
@polkadot/keyringand@polkadot/util-crypto. The bulk of the work.KeypairTypeunion; register prefix 189 in@polkadot/networkscreatePair: the ML-DSA arms, and address ≠ public keypair.sign()quantus/ui —
@polkadot/ui-keyring.restoreAccountcannot rebuild a public key from an address; same problem as common#3, solve it there firstquantus/papi-console — the test console. Independent of the polkadot-js stack entirely, which is what makes it worth having.
quantus/api —
@polkadot/types. Needed for polkadot-js/apps and the wider@polkadot/apiecosystem, but not for signing a first real extrinsic.ExtrinsicSignaturetype; note the comment ruling out thesignedTransactionworkaroundquantus/extension (here)
sig ‖ pkfor raw bytesStanding convention for all six forks
We are not trying to upstream this. Post-quantum PRs to polkadot-js/Parity have been attempted and did not land, and the working assumption is that they never will — this fork only starts mattering to anyone else if and when non-PQ Substrate becomes a victim of a cryptographically relevant quantum computer.
That has a concrete consequence, and it is the opposite of the usual advice: do not contort changes to look upstreamable, but do keep them mechanically rebasable. Prefer additive changes over rewrites, keep Quantus branches in
switch/map lookups next to the existing arms rather than restructuring them, and leave the upstream code path byte-identical where a Quantus account is not involved. When we pull a new upstream tag, agit rebaseshould be boring. Each fork's default branch ismainand tracks upstream'smaster(papi-console is alreadymain); upstream tags are pushed and can be rebased onto.papi-console is the exception worth stating: it is AGPL-3.0 where the rest are Apache-2.0/MIT, and our change there should stay a pnpm patch rather than a source edit, precisely so that rebasing stays trivial.
Order
Note what moved: signing a first real extrinsic no longer waits on api#1. papi assembles Quantus extrinsics correctly today, so the console gets there first and the
@polkadot/apiwork becomes about ecosystem reach rather than about proving the thing works.