refactor(quantus-crypto): drop the @polkadot/wasm-util dependency

It cost more than it saved. Two problems, the second only visible once
quantus/common tried to consume this package:

Its index re-exports packageDetect, whose only job is a side effect registering
with @polkadot/util — a peer dependency inherited for nothing. Deep imports
(/base64, /fflate) avoided that.

But it is a workspace package, so a symlinked consumer resolves its dependencies
through *this* repo's node_modules, where @polkadot/wasm-util points at the
package source rather than its build and carries no exports map. Node follows
symlinks to their realpath, so `@polkadot/wasm-util/base64` failed to resolve
from quantus/common no matter which yarn protocol was used — portal: and link:
behave the same once the realpath is taken.

So: fflate directly for zlib inflate, and fifteen lines for base64 rather than a
dependency at all. Deliberately not atob or Buffer.from — the first is
browser-only, the second node-only, and this runs in an MV3 service worker, a
Worker, node tests and a bundled extension page.

The package is now self-contained apart from fflate, which resolves normally from
any checkout. Size is unchanged at 234,292 raw / 109,649 zlib / 146,200 base64.

Refs quantus/wasm#1, quantus/common#2

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
This commit is contained in:
rob thijssen
2026-09-10 18:39:54 +03:00
co-authored by Claude Opus 5
parent 8323e442d9
commit a6d3685c59
7 changed files with 71 additions and 25 deletions
+8
View File
@@ -28,6 +28,14 @@ wasm-bindgen 0.2.79's JS-heap ABI, while this crate builds with 0.2.128, which
uses externref tables. wasm-bindgen's own generated glue plus `initSync` is both
smaller and correct.
The only runtime dependency is `fflate`, for zlib inflate. Base64 decoding is
fifteen lines here rather than a dependency. Both were originally taken from
`@polkadot/wasm-util`, which turned out to cost more than it saved: its index
re-exports `packageDetect`, dragging in a `@polkadot/util` peer dependency for a
side effect we do not want, and being a workspace package it resolved through its
*own* repo's node_modules when this package was consumed by symlink from another
checkout — which is exactly how `quantus/common` consumes it during development.
## Scheme selector
`Scheme.MlDsa87 = 0`, `Scheme.MlDsa65 = 1` — these are the chain's own
+1 -1
View File
@@ -18,7 +18,7 @@
"version": "0.1.0",
"main": "index.js",
"dependencies": {
"@polkadot/wasm-util": "7.5.4",
"fflate": "^0.8.2",
"tslib": "^2.7.0"
}
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
// An array indexer rather than a Map: the input is ASCII by construction, so it
// cannot overflow, and array access is measurably faster on the hot loop.
const MAP = new Array<number>(256);
for (let i = 0; i < CHARS.length; i++) {
MAP[CHARS.charCodeAt(i)] = i;
}
/**
* Decode base64 into a caller-supplied buffer.
*
* Deliberately not `atob` or `Buffer.from`: the first is browser-only, the second
* node-only, and this runs in an MV3 service worker, a Worker, node tests and a
* bundled extension page. The output length is known at build time, so the
* caller provides the buffer and there is no growth or reallocation.
*
* This is a reimplementation of `@polkadot/wasm-util`'s base64Decode, which was
* the dependency it replaced. That package's index re-exports `packageDetect`,
* dragging in a `@polkadot/util` peer dependency for a side effect we do not
* want, and being a workspace package it resolved through its own repo's
* node_modules when consumed by symlink from another checkout. Fifteen lines is
* cheaper than either problem.
*/
export function base64Decode (data: string, out: Uint8Array): Uint8Array {
let byte = 0;
let bits = 0;
let pos = 0;
for (let i = 0; i < data.length && pos < out.length; i++) {
const value = MAP[data.charCodeAt(i)];
if (value === undefined) {
continue;
}
byte = (byte << 6) | value;
bits += 6;
if (bits >= 8) {
bits -= 8;
out[pos++] = (byte >>> bits) & 0xff;
}
}
return out;
}
+3 -6
View File
@@ -1,12 +1,9 @@
// Copyright 2026 @quantus/crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Deep imports, not the package index. `@polkadot/wasm-util`'s index re-exports
// `packageDetect`, whose only job is a side effect registering the package with
// `@polkadot/util` — a peer dependency we would otherwise inherit for nothing.
// These two subpaths are pure functions with no dependencies at all.
import { base64Decode } from '@polkadot/wasm-util/base64';
import { unzlibSync } from '@polkadot/wasm-util/fflate';
import { unzlibSync } from 'fflate';
import { base64Decode } from './base64.js';
import { bytes, lenOut } from './bytes.js';
import { initSync } from './generated/quantus_crypto.js';
@@ -6,14 +6,6 @@
"declaration": true,
"outDir": "./build",
"rootDir": "./src",
"paths": {
"@polkadot/wasm-util/base64": [
"wasm-util/build/base64.d.ts"
],
"@polkadot/wasm-util/fflate": [
"wasm-util/build/fflate.d.ts"
]
},
"emitDeclarationOnly": false
},
"exclude": [
+7 -9
View File
@@ -4,13 +4,12 @@
# Runs the consumer test against a staged node_modules rather than in-place.
#
# In this repo `node_modules/@polkadot/wasm-util` symlinks to the package *source*
# directory, whose package.json carries no `exports` map — only the build output
# does. So a deep import like `@polkadot/wasm-util/base64` resolves for a real
# consumer and fails here, which would make the in-place test wrong in a way that
# has nothing to do with our package. Staging the built artifacts the way an
# install lays them out tests module resolution too, which is half of what can
# break in a published package.
# Staging the built artifact the way an install lays it out tests module
# resolution too, which is half of what can break in a published package — it is
# what caught binaryen 105 producing a wasm that fails to instantiate.
#
# `fflate`, the one runtime dependency, resolves by node walking up to the repo's
# own node_modules, which is exactly what it would do inside a real install.
set -e
@@ -18,10 +17,9 @@ PKG=packages/quantus-crypto
STAGE=$PKG/build-test
rm -rf $STAGE
mkdir -p $STAGE/node_modules/@quantus $STAGE/node_modules/@polkadot
mkdir -p $STAGE/node_modules/@quantus
cp -r $PKG/build $STAGE/node_modules/@quantus/crypto
cp -r packages/wasm-util/build $STAGE/node_modules/@polkadot/wasm-util
echo '{ "name": "quantus-crypto-consumer-test", "type": "module", "version": "0.0.0" }' > $STAGE/package.json
cp $PKG/test/consumer.mjs $STAGE/consumer.mjs
+1 -1
View File
@@ -614,7 +614,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@quantus/crypto@workspace:packages/quantus-crypto"
dependencies:
"@polkadot/wasm-util": "npm:7.5.4"
fflate: "npm:^0.8.2"
tslib: "npm:^2.7.0"
languageName: unknown
linkType: soft