@quantus/codec: encode and decode against the runtime's own metadata, not a hand-written codec #3
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?
Sibling package to
@quantus/crypto(#1), same shape: a Rust crate compiled to WASM with a thin TypeScript surface, published to the Gitea registry.Why
The extension needs to build a signing payload, build a signed extrinsic, and decode a call well enough to show a user what they are about to approve. The obvious route was
@polkadot/api's codec. That is closed — see quantus/api#1 for the tested evidence. Three things in summary:@polkadot/typescaps fixed arrays at 2048; Quantus signatures are[u8;5261]and[u8;7219].api.rpc.chain.getBlockthrows on every block of this chain, at the timestamp inherent, because it reads the extrinsic preamble byte as a version when the top two bits are a type tag.Unknown signed extensions ReversibleTransactionExtension, WormholeProofRecorderExtension found, treating them as no-effectand guesses zero bytes for theirextraand their implicit.That last one is the reason this package exists rather than a patch. The guess is right today — both extensions are
PhantomDatawithtype Implicit = ()— and it is right only by luck. This chain's encoding has already changed between runtimes, andtransactionVersionhas gone 2 → 3 → 6 across four upgrades; each is an extrinsic-format change. A signer that assumes an extension contributes nothing produces a cryptographically valid signature over the wrong bytes, which the chain reports asBadProof— indistinguishable from a wrong key, and it will happen on an upgrade rather than at build time.The metadata declares every one of those types. Reading them is not more work than guessing; it is the same work done correctly.
The oracle
state_getMetadatatakes a block hash and makes the node runMetadata_metadataagainst the runtime code in that block's state, so the runtime WASM describes itself and the node executes it for us.blackbeard.observerhas been doing this across four upgrade boundaries on Heisenberg (v126, v131, v136, v148, each a different blob) —crates/blackbeard-core/src/runtime.rsis the reference implementation and carries the hard-won notes, including the preamble-byte one above.Surface
Metadata in, bytes out. Nothing in this package names a pallet, a call or an extension.
loadMetadata(bytes)— SCALE-decode prefixed metadata; v14, erroring loudly on anything else rather than mis-reading itencodeCall(pallet, call, args)— by name, against the registryencodeSignerPayload({ call, era, nonce, tip, specVersion, transactionVersion, genesisHash, blockHash, … })— walksextrinsic.signed_extensionsin order, encoding each extension'styinto the extra and eachadditional_signedinto the implicit, from values supplied by identifier. An extension whose declared type is non-empty and for which no value was supplied is an error, not a zero-length write. That rule is the whole point.encodeExtrinsic({ address, signature, extra, call })— preamble0x84,MultiAddress::Id, fixed-size signature with no compact length prefixdecodeExtrinsic(bytes)/decodeCall(bytes)— for the approval screen, and for the tier-1 harness to read back what it submittedAcceptance
The BadProof in quantus/api#1 resolves: a
balances.transfer_keep_alivebuilt entirely through this package, signed by@quantus/cryptounderQUANTUS_EXTRINSIC, is accepted by Heisenberg and the recipient's balance moves. That is quantus/extension#7 tier 1, and it is the first end-to-end proof that the fork can spend.Secondary:
decodeExtrinsicreads a block that@polkadot/apicannot — the timestamp inherent at index 0 — which is the regression test for the preamble byte.Notes
wasm-crypto(toolchain),initSyncover base64+zlib for the MV3 CSP, wasm-bindgen's own glue, binaryen 123 (105 silently corrupts the output).spec_version— one fetch per runtime version, ever — asblackbeard-datadoes; decoding then survives the node pruning the block's state.