The extension has to build a signing payload, assemble an extrinsic and decode
a call well enough to show a user what they are approving. The obvious route was
@polkadot/api's codec. That is closed, and quantus/api#1 carries the tested
evidence:
- @polkadot/types caps fixed arrays at 2048 bytes, and ML-DSA signatures are
[u8;5261] and [u8;7219], so every Quantus extrinsic trips it
- api.rpc.chain.getBlock throws 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
- it *guesses* that signed extensions it does not recognise contribute nothing
to the signed payload
The third is why this is a package rather than a patch. The guess is right
today — the registry says ReversibleTransactionExtension and
WormholeProofRecorderExtension are empty on both halves — and it is right only
by luck. This chain's encoding has changed between runtimes, transactionVersion
has gone 2 -> 3 -> 6 across four upgrades, and when the guess stops holding the
wallet keeps signing: valid signatures over a payload missing bytes the runtime
put there, reported by the chain as BadProof, which is also what it reports for
a wrong key.
So nothing here names a pallet, a call, an extension or a signature scheme.
Every type id is read from metadata the node produced by running
Metadata_metadata against the runtime WASM in a given block's state, the same
oracle blackbeard.observer has been decoding against across four upgrade
boundaries. encode_extensions walks the declared extensions in order and refuses
to build a payload when one that encodes to something has no value supplied —
a wallet that cannot sign is a bug report, one that signs the wrong bytes is a
support case nobody diagnoses.
Proven end to end on Heisenberg at spec 148: a balances.transfer_keep_alive
built entirely here, signed by @quantus/crypto under QUANTUS_EXTRINSIC, included
at block 1050475 and read back from that block — inherent at index 0 included,
which is the block @polkadot/api cannot decode at all.
Two notes carried over from @quantus/crypto, both load-bearing: decode_checked
walks with scale_decode's IgnoreVisitor before scale_value touches the bytes,
because scale_value sizes a Vec from the length prefix before decoding an item
and an aborted allocation leaves no Err to catch; and the build needs binaryen
123, since 105 silently corrupts the output.
Closes #3
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
101 lines
4.4 KiB
TypeScript
101 lines
4.4 KiB
TypeScript
/* tslint:disable */
|
|
/* eslint-disable */
|
|
|
|
/**
|
|
* A loaded runtime description, held across calls so the metadata is parsed
|
|
* once per spec version rather than once per signature.
|
|
*/
|
|
export class QuantusRuntime {
|
|
free(): void;
|
|
[Symbol.dispose](): void;
|
|
/**
|
|
* Decode a bare call — what an approval screen shows the user.
|
|
*/
|
|
decodeCall(bytes: Uint8Array): string;
|
|
/**
|
|
* Decode one extrinsic as this runtime describes it, as JSON.
|
|
*/
|
|
decodeExtrinsic(blob: Uint8Array): string;
|
|
/**
|
|
* Encode a call by name. `args` is a JSON object keyed by argument name.
|
|
*/
|
|
encodeCall(pallet: string, call: string, args: string): Uint8Array;
|
|
/**
|
|
* The `extra` alone, which the extrinsic carries and the payload repeats.
|
|
*/
|
|
encodeExtra(extensions: string): Uint8Array;
|
|
/**
|
|
* Assemble a signed extrinsic, ready for `author_submitAndWatchExtrinsic`.
|
|
*/
|
|
encodeExtrinsic(address: string, signature: Uint8Array, extra: Uint8Array, call: Uint8Array): Uint8Array;
|
|
/**
|
|
* The extrinsic format version this runtime declares.
|
|
*/
|
|
extrinsicVersion(): number;
|
|
/**
|
|
* Parse metadata as `state_getMetadata` returns it.
|
|
*/
|
|
constructor(metadata: Uint8Array);
|
|
/**
|
|
* Every signed extension, in order, as
|
|
* `[{ identifier, needsExtra, needsAdditional }]`.
|
|
*
|
|
* The two booleans are what a caller has to satisfy, read from the
|
|
* registry. A caller that ignores them gets an error rather than a short
|
|
* payload.
|
|
*/
|
|
signedExtensions(): string;
|
|
/**
|
|
* The bytes to sign, given an encoded call and the extension values.
|
|
*
|
|
* `extensions` is a JSON object keyed by extension identifier, each value
|
|
* `{ extra?, additional? }`. Omitting one the runtime declares as non-empty
|
|
* is an error — see [`Runtime::encode_extensions`].
|
|
*/
|
|
signerPayload(call: Uint8Array, extensions: string): Uint8Array;
|
|
}
|
|
|
|
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
|
|
export interface InitOutput {
|
|
readonly memory: WebAssembly.Memory;
|
|
readonly __wbg_quantusruntime_free: (a: number, b: number) => void;
|
|
readonly quantusruntime_decodeCall: (a: number, b: number, c: number) => [number, number, number, number];
|
|
readonly quantusruntime_decodeExtrinsic: (a: number, b: number, c: number) => [number, number, number, number];
|
|
readonly quantusruntime_encodeCall: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number, number];
|
|
readonly quantusruntime_encodeExtra: (a: number, b: number, c: number) => [number, number, number, number];
|
|
readonly quantusruntime_encodeExtrinsic: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => [number, number, number, number];
|
|
readonly quantusruntime_extrinsicVersion: (a: number) => number;
|
|
readonly quantusruntime_new: (a: number, b: number) => [number, number, number];
|
|
readonly quantusruntime_signedExtensions: (a: number) => [number, number, number, number];
|
|
readonly quantusruntime_signerPayload: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
readonly __wbindgen_externrefs: WebAssembly.Table;
|
|
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
|
readonly __externref_table_dealloc: (a: number) => void;
|
|
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
|
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
readonly __wbindgen_start: () => void;
|
|
}
|
|
|
|
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
|
|
|
/**
|
|
* Instantiates the given `module`, which can either be bytes or
|
|
* a precompiled `WebAssembly.Module`.
|
|
*
|
|
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
|
*
|
|
* @returns {InitOutput}
|
|
*/
|
|
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
|
|
|
/**
|
|
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
|
* for everything else, calls `WebAssembly.instantiate` directly.
|
|
*
|
|
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
|
*
|
|
* @returns {Promise<InitOutput>}
|
|
*/
|
|
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|