Files
wasm/scripts/build-quantus-js.sh
grenadeandClaude Opus 5 3a1611a92e feat: add @quantus/codec, driving encode and decode from runtime metadata
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
2026-09-15 14:14:34 +03:00

60 lines
2.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Copyright 2026 @quantus/crypto authors & contributors
# SPDX-License-Identifier: Apache-2.0
# Builds the JS side of packages/quantus-crypto.
#
# Not `polkadot-dev-build-ts`, because that tool returns early for any package
# not named `@polkadot/*` — twice, in `buildJs` and when collecting `locals` for
# import rewriting. Renaming the package into someone else's scope to satisfy a
# string check would be worse than not using the tool.
#
# Nothing is lost by that: this package needs none of what the tool adds (a deno
# variant, a rollup bundle, cross-package import rewriting, generated exports
# maps). It is four TypeScript files and two generated artifacts. Keeping it out
# also means `yarn build:js` stays byte-identical to upstream's behaviour, which
# is the rebasability convention in quantus/extension#1.
#
# ESM only. The consumers — quantus/common and the extension — are ESM, and the
# wasm-bindgen glue is ESM-only, so a CJS variant would mean either a second
# generated glue or hand-written marshalling. See quantus/wasm#1 before adding one.
set -e
NAME=${1:-quantus-crypto}
PKG=packages/$NAME
CRATE=$(echo "$NAME" | tr '-' '_')
echo "*** Building $NAME"
rm -rf $PKG/build-tsc
yarn polkadot-exec-tsc --outDir $PKG/build-tsc --project $PKG/tsconfig.build.json
# tsc emits only what it compiles; the generated glue and the packed bytes are
# .js and have to be carried over by hand.
mkdir -p $PKG/build/generated
cp -r $PKG/build-tsc/* $PKG/build/
cp $PKG/src/generated/$CRATE.js $PKG/build/generated/$CRATE.js
cp $PKG/src/generated/$CRATE.d.ts $PKG/build/generated/$CRATE.d.ts
rm -rf $PKG/build-tsc
# package.json, with the paths rewritten for a consumer installing the build
# output rather than the source tree.
node -e "
const fs = require('node:fs');
const pkg = JSON.parse(fs.readFileSync('$PKG/package.json', 'utf-8'));
delete pkg.private;
pkg.main = './index.js';
pkg.types = './index.d.ts';
pkg.exports = {
'.': { types: './index.d.ts', default: './index.js' },
'./package.json': './package.json'
};
fs.writeFileSync('$PKG/build/package.json', JSON.stringify(pkg, null, 2) + '\n');
"
cp $PKG/README.md $PKG/build/README.md
echo "*** Built $(ls $PKG/build/*.js | wc -l) modules"