Conversation
…, settlement The wormhole for JS as a separate, optional package; @quantus-network/wasm stays signing-only and its README points to the companion package. wormhole/core is pure Rust: deposit keys (address, HD derivation, nullifier), ZK-leaf decoding and Merkle path preparation, leaf (layer 0) and private-batch (layer 1) proving, public-batch (layer 2) proving behind the public-batch feature, verification and parsing of all three layers with the verifier artifacts the runtime embeds, and the unsigned verify_private_batch / verify_public_batch extrinsics plus the UsedNullifiers storage key. build.rs generates the artifacts with the same generator and sizing as pallet-wormhole and, with the feature, proves the layer-2 padding batch. wormhole/native is the napi-rs addon over it with a typed ESM wrapper, published as @quantus-network/wormhole with per-platform binaries as optional dependencies. Proving runs off the event loop on every core. Circuit crates are pinned to the versions the runtime verifies against. Native rather than wasm: wasm32 cannot address the 7 GB a public batch needs, wasm threads needed a nightly toolchain and custom worker glue, and a private batch still took 18 s against 4 s natively. Tests prove and aggregate the circuits repo's reference deposit, verify the runtime's private_batch.hex / public_batch.hex fixtures, aggregate a public batch (opt-in), decode the extrinsics as wormhole calls, and check storage keys and hashes against polkadot.js. The Native addon workflow builds Linux x64/arm64/musl and macOS arm64/x64 and publishes on release.
A standalone project depending on @quantus-network/wasm and @quantus-network/wormhole (file: links here, npm for users). Against a node it derives ML-DSA-87 and ML-DSA-65 accounts from one mnemonic, funds both and sends from each so both schemes verify on chain, deposits from each into the wormhole, proves both deposits, aggregates them into one private batch with two exits, settles it, and checks the nullifiers and balances. --offline runs the derivation, signing and fixture-verification parts only.
Tag wormhole releases wormhole-vX.Y.Z so publish.yml (wasm, vX.Y.Z) and native.yml (wormhole) no longer both fire on every GitHub Release; each workflow checks the tag prefix and native.yml verifies the tag against wormhole/native/package.json. napi prepublish already publishes the platform packages, so drop the extra publish loop that would have failed on the second attempt. wormhole/native/scripts/create-release.sh bumps package.json, the platform packages and optionalDependencies, commits, tags, pushes and creates the release; CREATE_RELEASE.md covers the npm Trusted Publishing setup and the manual first publish each new package name needs.
They point at packages that do not exist on npm until the first release,
so npm install left them out of the lock file and npm ci then refused
to install ("Missing ... from lock file"), failing every CI job at
setup. napi prepublish rebuilds optionalDependencies from the targets
and writes them into package.json right before npm publish, so the
committed manifest does not need them.
n13
left a comment
There was a problem hiding this comment.
Reviewer model: GPT-6 Astra
Verdict (advisory): Request changes
Reviewed head 058db6f2f2957b6bfb1a5823446397c9c1d286d7 against base c459d40da40041935ef047d030f82efe959edee0.
-
[P1] Replace the retired Intel macOS runner —
.github/workflows/native.yml:37-38. GitHub retiredmacos-13in December 2025 (official notice). This matrix entry cannot run on a supported hosted runner; it is still queued in this PR's workflow while the other four platform builds have passed. Becausepublishhasneeds: build, it also blocks publication of every platform. Use a supported Intel image such asmacos-15-intel, retaining the x64 build and smoke test. -
[P2] Reject invalid amounts before N-API coerces them to u32 —
wormhole/native/js/index.mts:249-254. Amounts and fee rates pass directly into Rustu32fields, so N-API silently truncates/wraps them. Reproduced through the public API:dequantize(-1)returns42949672950000000000n,outputAfterFee(10000, 2 ** 32)returns10000, andproveLeaf({ ...fixture, outputAmount1: NaN, volumeFeeBps: 4 })succeeds. Aggregating that leaf produces a cryptographically valid private batch with the deposit's nullifier and zero total payout. A bad caller calculation can therefore become a spend proof for a different amount instead of an error. Add a shared finite-integer/u32-range guard before the native boundary, apply it to both output amounts and the other u32 parameters (including the amount helpers), and test NaN, fractions, negative values, and overflow.
Validation:
git diff --check, locked Cargo metadata, TypeScript compilation, release-script syntax, and native-package tarball contents passed.cargo test --manifest-path wormhole/Cargo.toml --locked --release -p quantus-wormhole-core: 11 passed.cargo build --manifest-path wormhole/Cargo.toml --locked --release -p quantus-wormhole-native: passed; the locally built addon also reproduced the amount-coercion issue.WORMHOLE_TEST_PUBLIC_BATCH=1 node --test test/wormhole.test.mjs: 10 passed, none skipped, using the macOS arm64 CI artifact associated with this head.- Additional API probes confirmed the numeric-coercion issue; malformed/oversized proof blobs and an inconsistent header were rejected.
Live-chain settlement was not exercised; the root CI test job was still running at the final status check.
…cos-13 runner N-API converts JS numbers to u32 with ToUint32 semantics, so NaN became 0, -1 became 4294967295 and 2**32 became 0. A caller's bad output amount could turn into a valid proof for a different amount (NaN -> a zero payout that still spends the nullifier). Every u32 crossing into the addon is now checked to be an integer in range, and Merkle positions to be 0-3, with tests for NaN, fractions, negatives and overflow. GitHub retired macos-13 in December 2025; the x86_64-apple-darwin build moves to macos-15-intel.
n13
left a comment
There was a problem hiding this comment.
Reviewer model: GPT-6 Astra
Verdict (advisory): Request changes
Reviewed head e66c84da520ca198dada13f604f40c85f88a131b against base c459d40da40041935ef047d030f82efe959edee0. The earlier numeric-coercion and retired-macOS-runner findings are addressed; one release-flow issue remains.
[P2] Push the release tag before creating the GitHub Release — wormhole/native/scripts/create-release.sh:23-25. With default Git settings, git tag "$tag" creates a lightweight tag, whereas git push --follow-tags only adds annotated tags to the refs being pushed. Consequently, this script pushes the version commit but leaves the new wormhole-vX.Y.Z tag local, and the following gh release create --verify-tag aborts because the tag is absent from GitHub. The documented release command therefore cannot trigger native-package publication. Create an annotated tag with an explicit message, or explicitly push the named tag before creating the release; the script should work without relying on a maintainer's tag-signing configuration.
Validation:
cargo test --manifest-path wormhole/Cargo.toml --locked --release -p quantus-wormhole-core: 11 passed.cargo build --manifest-path wormhole/Cargo.toml --locked --release -p quantus-wormhole-native: passed on macOS arm64.WORMHOLE_TEST_PUBLIC_BATCH=1 node --test test/wormhole.test.mjs, using that locally built addon: 11 passed, none skipped, including all three proving layers and the new numeric-input regression.npm ci --ignore-scripts, native TypeScript compilation, locked Cargo metadata, native-package tarball contents,git diff --check, and shell/JS syntax checks passed.
Release behavior was assessed statically against the Git/gh command semantics; no release script, publication, or live-chain settlement was executed.
CI at the final head check: all three Linux builds and the Intel macOS build passed; the macOS arm64 build and root CI test job were still running.
What
The wormhole for JS as a separate, optional package;
@quantus-network/wasmstays signing-only.@quantus-network/wormhole(wormhole/native): a native Node.js addon (napi-rs) with a typed ESM API. Deposit keys (wormholeAddress,wormholeFromMnemonic,nullifier),decodeZkLeaf,merklePositions, amount quantisation and fee, proving for all three layers (proveLeaf,aggregatePrivateBatch,aggregatePublicBatch, all async and off the event loop),parse*/verify*for every layer with the verifier artifacts the runtime embeds,encodeVerifyPrivateBatch/encodeVerifyPublicBatch, andusedNullifierStorageKey/extrinsicHashfor settlement checks with onestate_getStoragecall. Prebuilt binaries as optional dependencies@quantus-network/wormhole-<platform>for Linux x64 (glibc, musl), Linux arm64, macOS arm64/x64.@quantus-network/wasm: unchanged API. README points at the companion package; the two meet atsignTransfer(seed, { recipient: key.address }).examples/consumer: a standalone project using both packages like an application (see below).Why native, why separate
wasm32 cannot address the 7 GB a public batch needs; wasm threads required a nightly toolchain,
build-std, and customworker_threadsglue, and a private batch still took 18 s against 4 s natively. A separate package keeps the wasm package pure and free of a peer dependency, and lets the wormhole version track the chain's circuit releases on its own.Layout
wormhole/core: pure Rust (keys, leaves, verify, extrinsics, proving). Layer 2 behind thepublic-batchfeature; the build script generates the verifier artifacts with the same generator andQP_NUM_*sizing aspallet-wormholeand, with the feature, proves the layer-2 padding batch.wormhole/native: napi bindings (src/lib.rs, thin: hex/SS58 at the boundary,Uint8Arrayfor blobs) + the typed wrapper (js/index.mts) over the generatedbinding.js;npm/holds the platform packages;README.mdis the package's documentation..github/workflows/native.yml: builds the five targets, smoke-tests each, and publishes the platform packages and the package on a GitHub Release via Trusted Publishing.Circuit crates stay pinned to what the runtime verifies against (
qp-wormhole-* =4.3.0,qp-plonky2 =1.5.5). Rebased onto main after #4 (ML-DSA-65) and #8; squashed to one commit plus the example.Tests
wormhole/core/tests, 12): the circuits repo's reference deposit is proved, aggregated and verified; the runtime'sprivate_batch.hex/public_batch.hexverify with the embedded verifiers; extrinsics decode asWormhole(20)::verify_private_batch(2)/verify_public_batch(3); leaf SCALE layout; Merkle sorting; storage-key layout. Layer-2 aggregation opt-in viaWORMHOLE_TEST_PUBLIC_BATCH=1(passes: 23 s, verified with the embedded public-batch verifier).test/wormhole.test.mjs, 9 + 1 opt-in): the same through the published API (imported as@quantus-network/wormholevia thefile:dev dependency), plus storage keys and extrinsic hashes against@polkadot/util-crypto.examples/consumeronquantus-node --dev: ML-DSA-87 and ML-DSA-65 accounts from one mnemonic; both funded; a transfer sent from each (7301-byte and 5343-byte extrinsics, both included); 5 DEV deposited from each into the wormhole; both deposits proved (38 ms, 27 ms) and aggregated into one private batch with two exits (2.6 s);verify_private_batchaccepted; both nullifiers used; each account credited 4.99 DEV.Follow-ups
Example tests
