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
60 lines
2.2 KiB
Bash
Executable File
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"
|