ui-keyring: restoreAccount cannot rebuild a Quantus public key from an address #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?
Mirror of quantus/common#3 — the same mistake, in a second place. Context in quantus/extension#1.
The line
packages/ui-keyring/src/Keyring.ts:decodeAddressturns the SS58 string back into 32 bytes and hands them over as the public key. On Substrate that is correct — the address is the public key. On Quantus the address isPoseidon2(publicKey)and the real public key is 1952 or 2592 bytes, so this constructs a pair whose public key is a hash of the public key.Note the ordering:
decodePkcs8(password)runs on the next line and does have the real public key available inside the encrypted blob. So the fix is not to find the key from somewhere new — it is to stop pretending to know it before the blob is open, and to let the pair carry the account id as data until it is.Do not solve this twice
quantus/common#3 has to answer the identical question for
keyring.createFromJson, and it is the one that decides whatcreatePairaccepts. Settle the shape there, then apply it here — two independent answers to "how does a locked ML-DSA pair know its address" is how the two paths drift and one of them starts returning a wrong-but-plausible address.While in this file
loadAddressguards withjson.address.length !== 66to spot Ethereum-style addresses among hex. A Quantus account id is 32 bytes and hits the normal path, so this is fine as it stands — but it is exactly the kind of length-sniffing that will need revisiting if raw public keys ever end up in this store. Worth a comment rather than a change.Acceptance
Done, on
quantus-restore-account(ec1399f3). 5 specs; 46 tests pass repo-wide, build clean.The fix
Exactly as this issue framed it — solved in quantus/common#3 first, then mirrored.
restoreAccountnow passes an accountId rather than a public key it cannot have:createPairuses it for the address while locked and clears it oncedecodePkcs8supplies the real key — checking the two agree on the way. That check earns its place here for the same reason as in the keyring: the address sits outside the encrypted blob, so a tampered one decodes cleanly and yields an account displaying an address its key cannot sign for. Pinned by a test.The
'ed25519'fallback for version-0 JSON is left as-is and commented. Those files predate the crypto type being recorded and are by definition not Quantus, so the guess is safe — but quantus/common#6 removesed25519fromKeypairType, at which point it has to become a refusal.Acceptance
The cross-repo linking has run out of road
Getting this repo to see the forked
commontook four separate fixes, each for the same underlying cause: node resolves a portal/link symlink to its realpath, after which@polkadot/util-cryptoresolves inside common's tree — where it is the workspace source directory, with noexportsmap.resolutionspointing the four@polkadotpackages atcommon's build outputtsconfig.base.jsonpath mappings, with the*.d.tssubstitution first, because NodeNext will not infer an extension for a bare path and looks for atypesdirectory instead oftypes.d.tstsconfig.jsonoverriding those with the runtime.jsview, because@polkadot/dev-tsreads that file and would otherwise import a declaration file as a module — "Debug Failure. Output generation failed"--preserve-symlinksin the test script, which yarn itself warns is required for portalsAll four are dev-time scaffolding for a problem that does not exist once these packages come from a registry. This is the third repo to hit it (
common→@quantus/crypto, nowui→common), and the extension will be the fourth with more dependencies than any of them.quantus/extension#2 is now the blocking item, not a tidy-up. Publishing to the Gitea npm registry needs a token, which I cannot create — so it needs a hand. Until then every downstream repo pays this tax again.
One incidental result worth recording:
yarn linterrors dropped from 25 to 8. The tsconfig mappings resolved types that were previouslyany, removing a pile ofno-unsafe-assignment. The remaining 8 are upstream's own, inloadContractandallowGenesis, and are left alone per the rebasability convention — note thatyarn linttherefore still exits non-zero on this repo, as it does on pristine upstream.Verified against
main(2d6a9ad1), closing.ec1399f3.restoreAccountpasses the stored account id rather than inventing a public key, the same approach as common'screateFromJson. An extension-exported ML-DSA JSON restores to the same address and can sign (Keyring.dilithium.spec.ts).dilithiumJson.spec.ts, sincerestoreAccountunlocks immediately.