Wraps the crate for JS consumers and adds the build that produces it.
Init deliberately avoids fetch and avoids @polkadot/wasm-bridge. The consumer is
an MV3 service worker under `script-src 'self' 'wasm-unsafe-eval'`, which can
compile WASM but not usefully fetch it, and which can be cold-started between any
two messages; callers like pair.sign() are synchronous and have no await to give.
So the WASM is zlib-compressed and base64'd into bytes.js at build time and
instantiated with wasm-bindgen's initSync. Bridge is not usable here regardless:
it implements the 0.2.79 JS-heap ABI and this crate builds with 0.2.128, which
uses externref tables.
build-quantus.sh is separate from build-wasm.sh rather than folded into it,
because that script drives the nightly-2022-06-24 + xargo build wasm-crypto
needs. install-build-deps.sh gains a second wasm-bindgen for the same reason —
the two ABIs cannot share a binary. No asm.js step: wasm2js over ML-DSA would be
enormous and slow, and every context we ship into permits wasm.
bytes.js is emitted in both module systems, with the CJS copy under a directory
carrying its own {"type":"commonjs"} — the package is "type": "module" and node
otherwise refuses to load an exports.-style file from it.
Proven end to end against the real build output: base64 -> inflate -> initSync
with no fetch, crystal_alice's account id matching the CLI through the JS path,
sig||pk matching the runtime's fixed-array size, and JsError surfacing as a JS
exception across the boundary.
Sizes are read from the crate rather than exposed as constants to copy. They are
consensus-critical and a drifted JS constant would re-frame every byte after the
signature while looking entirely healthy.
Refs quantus/wasm#1
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
47 lines
1.7 KiB
TypeScript
47 lines
1.7 KiB
TypeScript
// Copyright 2026 @quantus/crypto authors & contributors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
/**
|
|
* Which ML-DSA parameter set a call refers to.
|
|
*
|
|
* These are the chain's own `DilithiumSignatureScheme` variant indices, not a
|
|
* private numbering — the value here is the byte a signed extrinsic carries on
|
|
* the wire. Keeping them identical means the keyring's `TYPE_PREFIX` is the
|
|
* identity function on a scheme, with no table to get backwards.
|
|
*/
|
|
export enum Scheme {
|
|
/** ML-DSA-87. Legacy: accounts created before the scheme was recorded, and the dev-genesis accounts. */
|
|
MlDsa87 = 0,
|
|
/** ML-DSA-65. What new accounts use. */
|
|
MlDsa65 = 1
|
|
}
|
|
|
|
/**
|
|
* The name each scheme is stored under, matching `quantus-cli` and the mobile
|
|
* wallet so a wallet exported from one tool imports into another.
|
|
*/
|
|
export const SCHEME_NAME: Record<Scheme, string> = {
|
|
[Scheme.MlDsa87]: 'ml-dsa-87',
|
|
[Scheme.MlDsa65]: 'ml-dsa-65'
|
|
};
|
|
|
|
/**
|
|
* FIPS 204 context for on-chain extrinsic signatures.
|
|
*
|
|
* Only from spec 148 onward — earlier runtimes verify under the empty context,
|
|
* and a signature made under the wrong one is valid, rejected by the chain, and
|
|
* indistinguishable locally. Callers pass the spec version and get the right
|
|
* answer from {@link contextForSpec}; nothing here guesses.
|
|
*/
|
|
export const EXTRINSIC_CONTEXT = new TextEncoder().encode('QUANTUS_EXTRINSIC');
|
|
|
|
/** First spec version that verifies extrinsics under {@link EXTRINSIC_CONTEXT}. */
|
|
export const EXTRINSIC_MIN_SPEC = 148;
|
|
|
|
/** The signing context a runtime at `specVersion` expects. */
|
|
export function contextForSpec (specVersion: number): Uint8Array {
|
|
return specVersion >= EXTRINSIC_MIN_SPEC
|
|
? EXTRINSIC_CONTEXT
|
|
: new Uint8Array();
|
|
}
|