keyring: teach createPair the ML-DSA arms, and stop assuming address == public key #2
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?
Depends on #1. Context in quantus/extension#1.
There is no plugin point
packages/keyring/src/pair/index.tsholds four module-private const maps and acreatePairthat closes over them:Nothing is injectable, which is the whole reason this repo is a fork rather than a wrapper. Add arms; do not restructure the maps (see the rebasability convention in quantus/extension#1).
TYPE_PREFIXis already the right shapeA happy accident worth not breaking:
pair.sign(msg, { withType: true })prependsTYPE_PREFIX[type], and the chain'sDilithiumSignatureSchemevariant byte is exactly that —dilithium87: [0],dilithium65: [1]. WithTYPE_SIGNATUREreturningsig ‖ pk, the bytes@polkadot/typeshands to a dapp are already the correct SCALE encoding of the runtime's signature enum, with no special-casing anywhere upstream of here.TYPE_ADDRESSis the interesting oneEvery existing arm is either identity or a cheap re-encoding, because on Substrate the address is the public key. For Quantus it is
Poseidon2(publicKey)— 1952 or 2592 bytes in, 32 bytes out, one-way.That breaks an invariant
createPaircurrently relies on without stating it: a locked pair can still report its address, because the public key is in memory even when the secret is not. For Quantus that still holds — the public key is enough to compute the address — but the moment the public key isn't available, it does not. Which is exactly the situation #3 is about, and the two issues should be designed together:createPairwill likely need to accept a precomputed account id for pairs restored from JSON.Also
decodePkcs8branches ondecoded.secretKey.length === 64to decide "secret key" vs "seed". A 4032- or 4896-byte ML-DSA secret takes the wrong branch and gets fed toTYPE_FROM_SEEDas if it were entropy. Make the check type-aware rather than length-magic.derive()is #4.pair.sign()needs a signing context — #5.Acceptance
quantus-clireports for itpair.sign(payload, { withType: true })produces bytes that the runtime'sVerify::verifyacceptsVerified against
main(7f23d0a60), closing.createPair: landed in2bffbc62band506b77351.quantus-cli: for a known mnemonic, both schemes (dilithiumDerive.spec.ts); the raw-seed crystal_alice is checked indilithium.spec.ts.sign(…, { withType: true })carries the chain's variant byte and length, and verifies withdilithiumVerify. Chain acceptance was then shown through quantus/extension#7 tier 1.pair/index.spec.tsis untouched, the curve sign functions are wrapped so a context can't reach them, and sr25519 is pinned.decodePkcs8now decides by key type rather than by key length.The classical arms themselves go in quantus/common#6.