Skip to content

feat!: release 3.0.0 — supply-chain hardening, dep cleanup, perf, new…#76

Merged
rushby merged 10 commits into
mainfrom
release/3.0.0
May 14, 2026
Merged

feat!: release 3.0.0 — supply-chain hardening, dep cleanup, perf, new…#76
rushby merged 10 commits into
mainfrom
release/3.0.0

Conversation

@rushby

@rushby rushby commented May 13, 2026

Copy link
Copy Markdown
Collaborator

… connection options

This is a large multi-area release: CI/supply-chain hardening, removal of
dead production dependencies, performance work on the subscription and
transaction paths, broader test coverage, and new user-facing connection
options. See BREAKING CHANGE footer for the user-impacting changes.

CI / supply-chain

  • pin every third-party GitHub Action to a full commit SHA; bump actions/checkout v3→v6.0.2 and actions/setup-node v3→v4.4.0 in the test workflow (v3 deprecated); slack-github-action SHA-pinned at v1.25.0
  • trigger ci-build-and-test on synchronize and reopened (not just opened) so pushes to a PR re-run tests
  • add --ignore-scripts to npm ci in both workflows; verified the dep tree has zero transitive packages that need install scripts
  • remove cache: 'npm' from ci-publish-package.yml (defense against GHA cache poisoning)
  • add step-security/harden-runner (audit mode) as the first step in all 4 workflows
  • add .github/dependabot.yml for weekly automated dependency PRs. Includes cooldown (default 14d / 30d major / 7d minor & patch) — matches the worm-defense intent from the original review item. Groups @polkadot/* and @polkadot-api/* together, separates prod and dev deps, ignores semver-major updates (must be raised manually), and covers the github-actions ecosystem so our SHA-pinned workflow Actions stay current. Built into GitHub — no app install needed.
  • add CodeQL workflow (javascript-typescript, security-extended query suite, push/PR/weekly cron)
  • add OSV-Scanner workflow (recursive lockfile scan against the OSV database, push/PR/weekly cron)
  • generate CycloneDX SBOM at publish time via @cyclonedx/cyclonedx-npm --ignore-npm-errors and attach it to the GitHub Release as sbom.cyclonedx.json
  • delete four stale .d.ts files from the src/ working tree that shadowed the real types (not git-tracked but confused IDE LSP)
  • remove the untracked tests/common/data/ultrahonk_test/ leftover

Dependencies

  • remove unused production deps: web3, snarkjs (Library.snarkjs refers to the local proofTypes/groth16/formatter/snarkjs/ directory, not the npm package; the test generator shells out to a globally installed snarkjs CLI in CI)
  • remove unused dev deps: @types/snarkjs, @types/web3, js-sha3, @types/debug, @typescript-eslint/eslint-plugin, @typescript-eslint/parser (the unified typescript-eslint package bundles these), eslint-config-prettier, eslint-plugin-prettier (not wired into eslint.config.mjs), globals, ts-node, ls-engines, conventional-changelog-cli, @babel/cli, @babel/core, babel-plugin-add-import-extension
  • bump direct-vulnerable dev deps: axios 1.8.2 → 1.16.0, eslint 9.25.1 → 9.39.4, fastify 4.29.1 → 5.8.5 (walletServer.js API surface verified compatible with v5)
  • bump engines.node >= 18>= 20 (Node 18 EOL April 2025)
  • npm audit findings dropped 44 → 9 (critical eliminated); node_modules dropped from 764 to 471 packages

Build

  • eliminate the babel post-process step: add explicit .js extensions to all 383 relative imports across 91 source files (file imports → ./foo.js; directory imports → ./foo/index.js). tsc preserves the extensions in ESM output natively. tsconfig.esm.json stays on module: "es2020" (avoids the dual-package hazard nodenext triggers with polkadot)
  • jest gains a moduleNameMapper regex to strip .js at resolution time so tests resolve to .ts source
  • enable sourceMap: true + inlineSources: true in tsconfig.base so consumers reporting bugs get TS source locations in stack traces
  • narrow sideEffects from false to an explicit whitelist of the Buffer polyfill paths — preserves browser polyfill behavior while letting bundlers tree-shake everything else
  • add eslint-plugin-security with the recommended config; only detect-object-injection is disabled (13 false positives on typed object access patterns). detect-possible-timing-attacks and detect-non-literal-fs-filename re-enabled — both fire on zero current code, guarding against regression
  • add knip 6.13.1 (knip.json + npm run knip + CI step) to catch future dead deps/files automatically
  • remove redundant coveragePathIgnorePatterns: ['tests/'] from jest.config.js; remove forceExit: true so future open-handle leaks surface instead of being masked
  • remove manual git add from lint-staged config (auto-staged since v15)

Bug fixes

  • verify(): no longer mutates the caller's options.domainId from input.domainId. Builds a per-call effectiveOptions via spread so reusing a builder across execute() calls no longer leaks state between them.
  • validateProofTypeOptions: now a pure function; returns the validated ProofOptions (possibly with runtime-version defaults applied) instead of mutating in place. Helpers renamed to withDefaultedUltrahonkVersion / withDefaultedTeeVariant.
  • getAggregateStatementPath (custom RPC): switched .toHuman() + Number() to .toJSON(). .toHuman() formats u32 with thousand-separator commas which Number() parses as NaN once numberOfLeaves crosses 1000. Two regression tests added (large number preserved + .toHuman()-shaped response rejected).
  • formatPublicSignals: replaced no-op validation pubs.some(() => false) with a real typeof !== 'string' check. Previously, a [1, 2, 3] (numbers, not strings) input sailed through and crashed later inside BigInt() with a cryptic error.
  • handleTransaction signAndSend callback: full body wrapped in a protected try/catch (the early-return on Error status used to run outside the try); cancelTransaction(error) itself wrapped in a defensive try. Guarantees the async callback's returned Promise resolves cleanly so a rejection can never escape into polkadot-api's silent rejection swallow.
  • extractErrorMessage: JSON.stringify wrapped in try/catch with String(err) fallback so polkadot codecs with circular refs no longer crash error-path code.
  • setupAccount: pre-validates the seed phrase before keyring.addFromUri. Empty/whitespace input throws with a clear message. Substrate dev SURIs (//Alice etc.) are rejected on Volta and zkVerify mainnet (allowed on .Custom() for local dev). BIP39 word-count enforced (12/15/18/21/24) — 32-byte hex seeds and mnemonic-with-derivation still work.
  • validateProofTypeOptions plonky2 message: was "requires 'compressed' (boolean) and 'hashFunction'"; compressed doesn't exist on Plonky2Config. Now reads "requires a 'hashFunction' option."
  • closeSession retry: replaced fixed 500ms × 5 sleeps with exponential backoff (100/200/400/800ms, no sleep after final attempt). Worst-case sleep 2500ms → 1500ms.

Performance / refactors

  • VerificationManager: extract the 4× duplicated Object.defineProperty builder loop (verify, optimisticVerify, batchVerify, batchOptimisticVerify) into a single buildProofTypeMap<T> private helper. Adding a proof type now requires touching only the ProofType enum.
  • zkVerifySession: remove the bindMethods runtime introspection; use typed arrow-property delegations (verify: VerificationManager['verify'] = (...args) => ...). Drops 60–70 .bind calls per session construction, removes the runtime "Method collision detected" failure mode, and uses indexed types so signatures stay in sync with manager changes. bindMethods deleted from utils/helpers.
  • EventManager: hoist the matchMap regex/string table to a module-level constant (RUNTIME_EVENT_MATCHERS); lift the per-event matcher lookup out of the per-record inner loop.
  • EventManager._registerRuntimeEvent: idempotent guard (if (this.runtimeEventHandlers.has(eventType)) return) so the function is safe at the function level, not just via the subscribedEvents Set in subscribe().
  • handleTransactionEvents: hoist getProofPallet(...) lookup out of the per-event-record forEach. Previously called twice per event for Verify or VKRegistration; now resolved once before the loop, and skipped entirely for non-proof transaction types.
  • unstringifyBigInts: hoist regexes to module-level constants (DECIMAL_RE, HEX_RE); cheap 2-byte prefix check routes to one regex instead of running both; non-numeric strings return early instead of falling through to the object branch.
  • handleTransaction: deferred-promise pattern — resolve/reject are captured outside the new Promise(...) constructor so the constructor closure can be freed immediately. Helpers and state live at function scope. .then().catch() on the signAndSend unsubscribe handshake replaced with a single async IIFE (1 chained Promise saved per tx).
  • waitForNodeToSync: was an unbounded while (isSyncing) poll with no timeout. Now accepts WaitForNodeToSyncOptions { timeoutMs, pollIntervalMs, signal }. Defaults: 5-min total timeout, 1s poll interval (unchanged), no signal. Throws on deadline; throws AbortError (DOMException) on signal abort. New internal sleep helper clears its timer on abort to avoid leaked handles.
  • consolidate 6× duplicated validateHexString (ultrahonk, risc0, sp1, ezkl, tee, plonky2) into a single shared helper in utils/helpers/index.ts.

New user-facing options

  • NetworkConfig.wsProvider (new) — pass autoConnectMs (number | false; default 2500) and timeout (number) to the WebSocket provider. Setting autoConnectMs: false disables auto-reconnect entirely; combine with session.provider.on('disconnected', ...) to implement custom retry policies with a hard upper bound.
  • NetworkConfig.syncTimeoutMs (new) — max ms to wait for the node to finish syncing during session start. Defaults to 300_000 (5 min). Throws if exceeded.
  • WaitForNodeToSyncOptions exported for advanced direct use.
  • README "Connection tuning" section covers both new options; registerDomain JSDoc + README clarified that queueSize: 0 throws (the default only applies when the argument is omitted).

Tests

  • 169 tests across 24 suites at start; 181 tests across 24 suites at end. New tests cover: rpc .toJSON() regression (2), formatPubs type check (6), waitForNodeToSync timeout/abort (5), connection config plumbing (3), handleTransactionEvents pallet-lookup hoist +5), _registerRuntimeEvent idempotency (1), unstringifyBigInts (8), handleTransaction isInvalid path (1), formatPublicSignals utilities + unstringifyBigInts (14 in one file), seed-phrase validation (7), extractErrorMessage (5). Three pre-existing tests updated to match the C9 immutable-options contract.

BREAKING CHANGE: engines.node raised from >= 18 to >= 24
Node 18 went EOL April 2025. Yarn Berry users on Node 18 will get
hard install failures; npm/pnpm users get a warning unless
engine-strict=true.

BREAKING CHANGE: web3, snarkjs, @types/snarkjs, and @types/web3
removed from package.json. They were never imported from src/. Any
consumer who was relying on these as transitive deps via zkverifyjs
must add them explicitly.

BREAKING CHANGE: validateProofTypeOptions (internal but exported)
now returns ProofOptions instead of void; callers that previously
relied on in-place mutation of the input options object will need to
use the return value. The exported bindMethods helper has been
removed from utils/helpers — it was internal and not referenced by
any public consumer pattern, but it is no longer exported.

BREAKING CHANGE: fastify (dev dep used by the test wallet server)
bumped 4.29.1 → 5.8.5. Affects integration test setup only — no
runtime impact on the published package.

@rushby
rushby requested a review from cronicc May 13, 2026 17:34
@rushby
rushby requested a review from a team as a code owner May 13, 2026 17:34
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@rushby
rushby force-pushed the release/3.0.0 branch 2 times, most recently from f80d650 to 0ca463f Compare May 13, 2026 17:53
rushby added 10 commits May 14, 2026 15:58
… connection options

  This is a large multi-area release: CI/supply-chain hardening, removal of
  dead production dependencies, performance work on the subscription and
  transaction paths, broader test coverage, and new user-facing connection
  options. See BREAKING CHANGE footer for the user-impacting changes.

  ## CI / supply-chain

  - pin every third-party GitHub Action to a full commit SHA; bump
    actions/checkout v3→v4.2.2 and actions/setup-node v3→v4.4.0 in the test
    workflow (v3 deprecated); slack-github-action SHA-pinned at v1.25.0
  - trigger ci-build-and-test on `synchronize` and `reopened` (not just
    `opened`) so pushes to a PR re-run tests
  - add `--ignore-scripts` to `npm ci` in both workflows; verified the dep
    tree has zero transitive packages that need install scripts
  - remove `cache: 'npm'` from ci-publish-package.yml (defense against
    GHA cache poisoning)
  - add `step-security/harden-runner` (audit mode) as the first step in
    all 4 workflows
  - add `.github/dependabot.yml` for weekly automated dependency PRs.
    Includes `cooldown` (default 14d / 30d major / 7d minor & patch) —
    matches the worm-defense intent from the original review item.
    Groups @polkadot/* and @polkadot-api/* together, separates prod and
    dev deps, ignores semver-major updates (must be raised manually),
    and covers the github-actions ecosystem so our SHA-pinned workflow
    Actions stay current. Built into GitHub — no app install needed.
  - add CodeQL workflow (`javascript-typescript`, `security-extended`
    query suite, push/PR/weekly cron)
  - add OSV-Scanner workflow (recursive lockfile scan against the OSV
    database, push/PR/weekly cron)
  - generate CycloneDX SBOM at publish time via
    `@cyclonedx/cyclonedx-npm --ignore-npm-errors` and attach it to the
    GitHub Release as `sbom.cyclonedx.json`
  - delete four stale `.d.ts` files from the src/ working tree that
    shadowed the real types (not git-tracked but confused IDE LSP)
  - remove the untracked `tests/common/data/ultrahonk_test/` leftover

  ## Dependencies

  - remove unused production deps: `web3`, `snarkjs` (Library.snarkjs
    refers to the local `proofTypes/groth16/formatter/snarkjs/` directory,
    not the npm package; the test generator shells out to a globally
    installed snarkjs CLI in CI)
  - remove unused dev deps: `@types/snarkjs`, `@types/web3`, `js-sha3`,
    `@types/debug`, `@typescript-eslint/eslint-plugin`,
    `@typescript-eslint/parser` (the unified `typescript-eslint`
    package bundles these), `eslint-config-prettier`,
    `eslint-plugin-prettier` (not wired into eslint.config.mjs),
    `globals`, `ts-node`, `ls-engines`, `conventional-changelog-cli`,
    `@babel/cli`, `@babel/core`, `babel-plugin-add-import-extension`
  - bump direct-vulnerable dev deps: axios 1.8.2 → 1.16.0,
    eslint 9.25.1 → 9.39.4, fastify 4.29.1 → 5.8.5 (walletServer.js
    API surface verified compatible with v5)
  - bump `engines.node` `>= 18` → `>= 24` (Node 18 EOL April 2025; Node 20 EOL April 2026)
  - npm audit findings dropped 44 → 9 (critical eliminated); `node_modules`
    dropped from 764 to 471 packages

  ## Build

  - eliminate the babel post-process step: add explicit `.js` extensions
    to all 383 relative imports across 91 source files (file imports
    → `./foo.js`; directory imports → `./foo/index.js`). `tsc` preserves
    the extensions in ESM output natively. `tsconfig.esm.json` stays on
    `module: "es2020"` (avoids the dual-package hazard `nodenext`
    triggers with polkadot)
  - jest gains a `moduleNameMapper` regex to strip `.js` at resolution
    time so tests resolve to `.ts` source
  - enable `sourceMap: true` + `inlineSources: true` in tsconfig.base
    so consumers reporting bugs get TS source locations in stack traces
  - narrow `sideEffects` from `false` to an explicit whitelist of the
    Buffer polyfill paths — preserves browser polyfill behavior while
    letting bundlers tree-shake everything else
  - add `eslint-plugin-security` with the recommended config; only
    `detect-object-injection` is disabled (13 false positives on
    typed object access patterns). `detect-possible-timing-attacks`
    and `detect-non-literal-fs-filename` re-enabled — both fire on
    zero current code, guarding against regression
  - add `knip` 6.13.1 (`knip.json` + `npm run knip` + CI step) to catch
    future dead deps/files automatically
  - remove redundant `coveragePathIgnorePatterns: ['tests/']` from
    jest.config.js; remove `forceExit: true` so future open-handle leaks
    surface instead of being masked
  - remove manual `git add` from lint-staged config (auto-staged since
    v15)

  ## Bug fixes

  - `verify()`: no longer mutates the caller's `options.domainId` from
    `input.domainId`. Builds a per-call `effectiveOptions` via spread
    so reusing a builder across `execute()` calls no longer leaks
    state between them.
  - `validateProofTypeOptions`: now a pure function; returns the
    validated `ProofOptions` (possibly with runtime-version defaults
    applied) instead of mutating in place. Helpers renamed to
    `withDefaultedUltrahonkVersion` / `withDefaultedTeeVariant`.
  - `getAggregateStatementPath` (custom RPC): switched `.toHuman()` +
    `Number()` to `.toJSON()`. `.toHuman()` formats u32 with
    thousand-separator commas which `Number()` parses as NaN once
    `numberOfLeaves` crosses 1000. Two regression tests added (large
    number preserved + `.toHuman()`-shaped response rejected).
  - `formatPublicSignals`: replaced no-op validation
    `pubs.some(() => false)` with a real `typeof !== 'string'` check.
    Previously, a `[1, 2, 3]` (numbers, not strings) input sailed
    through and crashed later inside `BigInt()` with a cryptic error.
  - `handleTransaction` signAndSend callback: full body wrapped in a
    protected try/catch (the early-return on Error status used to run
    outside the try); `cancelTransaction(error)` itself wrapped in a
    defensive try. Guarantees the async callback's returned Promise
    resolves cleanly so a rejection can never escape into polkadot-api's
    silent rejection swallow.
  - `extractErrorMessage`: `JSON.stringify` wrapped in try/catch with
    `String(err)` fallback so polkadot codecs with circular refs no
    longer crash error-path code.
  - `setupAccount`: pre-validates the seed phrase before
    `keyring.addFromUri`. Empty/whitespace input throws with a clear
    message. Substrate dev SURIs (`//Alice` etc.) are rejected on Volta
    and zkVerify mainnet (allowed on `.Custom()` for local dev).
    BIP39 word-count enforced (12/15/18/21/24) — 32-byte hex seeds and
    mnemonic-with-derivation still work.
  - `validateProofTypeOptions` plonky2 message: was "requires
    'compressed' (boolean) and 'hashFunction'"; `compressed` doesn't
    exist on `Plonky2Config`. Now reads "requires a 'hashFunction'
    option."
  - `closeSession` retry: replaced fixed 500ms × 5 sleeps with
    exponential backoff (100/200/400/800ms, no sleep after final
    attempt). Worst-case sleep 2500ms → 1500ms.

  ## Performance / refactors

  - `VerificationManager`: extract the 4× duplicated
    `Object.defineProperty` builder loop (`verify`, `optimisticVerify`,
    `batchVerify`, `batchOptimisticVerify`) into a single
    `buildProofTypeMap<T>` private helper. Adding a proof type now
    requires touching only the `ProofType` enum.
  - `zkVerifySession`: remove the `bindMethods` runtime introspection;
    use typed arrow-property delegations
    (`verify: VerificationManager['verify'] = (...args) => ...`). Drops
    60–70 `.bind` calls per session construction, removes the runtime
    "Method collision detected" failure mode, and uses indexed types so
    signatures stay in sync with manager changes. `bindMethods` deleted
    from `utils/helpers`.
  - `EventManager`: hoist the `matchMap` regex/string table to a
    module-level constant (`RUNTIME_EVENT_MATCHERS`); lift the per-event
    matcher lookup out of the per-record inner loop.
  - `EventManager._registerRuntimeEvent`: idempotent guard
    (`if (this.runtimeEventHandlers.has(eventType)) return`) so the
    function is safe at the function level, not just via the
    `subscribedEvents` Set in `subscribe()`.
  - `handleTransactionEvents`: hoist `getProofPallet(...)` lookup out
    of the per-event-record `forEach`. Previously called twice per
    event for Verify or VKRegistration; now resolved once before the
    loop, and skipped entirely for non-proof transaction types.
  - `unstringifyBigInts`: hoist regexes to module-level constants
    (`DECIMAL_RE`, `HEX_RE`); cheap 2-byte prefix check routes to one
    regex instead of running both; non-numeric strings return early
    instead of falling through to the object branch.
  - `handleTransaction`: deferred-promise pattern — `resolve`/`reject`
    are captured outside the `new Promise(...)` constructor so the
    constructor closure can be freed immediately. Helpers and state
    live at function scope. `.then().catch()` on the signAndSend
    unsubscribe handshake replaced with a single async IIFE (1 chained
    Promise saved per tx).
  - `waitForNodeToSync`: was an unbounded `while (isSyncing)` poll
    with no timeout. Now accepts `WaitForNodeToSyncOptions { timeoutMs,
    pollIntervalMs, signal }`. Defaults: 5-min total timeout, 1s poll
    interval (unchanged), no signal. Throws on deadline; throws
    `AbortError` (DOMException) on signal abort. New internal `sleep`
    helper clears its timer on abort to avoid leaked handles.
  - consolidate 6× duplicated `validateHexString` (ultrahonk, risc0,
    sp1, ezkl, tee, plonky2) into a single shared helper in
    `utils/helpers/index.ts`.

  ## New user-facing options

  - `NetworkConfig.wsProvider` (new) — pass `autoConnectMs` (number |
    false; default 2500) and `timeout` (number) to the WebSocket
    provider. Setting `autoConnectMs: false` disables auto-reconnect
    entirely; combine with `session.provider.on('disconnected', ...)`
    to implement custom retry policies with a hard upper bound.
  - `NetworkConfig.syncTimeoutMs` (new) — max ms to wait for the node
    to finish syncing during session start. Defaults to 300_000 (5 min).
    Throws if exceeded.
  - `WaitForNodeToSyncOptions` exported for advanced direct use.
  - README "Connection tuning" section covers both new options;
    `registerDomain` JSDoc + README clarified that `queueSize: 0`
    throws (the default only applies when the argument is omitted).

  ## Tests

  - 169 tests across 24 suites at start; 181 tests across 24 suites at
    end. New tests cover: rpc `.toJSON()` regression (2), formatPubs
    type check (6), waitForNodeToSync timeout/abort (5), connection
    config plumbing (3), handleTransactionEvents pallet-lookup hoist
    +5), `_registerRuntimeEvent` idempotency (1), unstringifyBigInts
    (8), handleTransaction isInvalid path (1), formatPublicSignals
    utilities + unstringifyBigInts (14 in one file), seed-phrase
    validation (7), extractErrorMessage (5). Three pre-existing tests
    updated to match the C9 immutable-options contract.

  BREAKING CHANGE: `engines.node` raised from `>= 18` to `>= 24` —
  Node 18 went EOL April 2025 and Node 20 went EOL April 2026. Yarn Berry users on Node 18, 20, or 22 will get
  hard install failures; npm/pnpm users get a warning unless
  `engine-strict=true`.

  BREAKING CHANGE: `web3`, `snarkjs`, `@types/snarkjs`, and `@types/web3`
  removed from `package.json`. They were never imported from src/. Any
  consumer who was relying on these as transitive deps via `zkverifyjs`
  must add them explicitly.

  BREAKING CHANGE: `validateProofTypeOptions` (internal but exported)
  now returns `ProofOptions` instead of `void`; callers that previously
  relied on in-place mutation of the input options object will need to
  use the return value. The exported `bindMethods` helper has been
  removed from `utils/helpers` — it was internal and not referenced by
  any public consumer pattern, but it is no longer exported.

  BREAKING CHANGE: `fastify` (dev dep used by the test wallet server)
  bumped 4.29.1 → 5.8.5. Affects integration test setup only — no
  runtime impact on the published package.
@rushby
rushby merged commit 40a7789 into main May 14, 2026
5 checks passed
@rushby
rushby deleted the release/3.0.0 branch May 14, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants