Skip to content

S2 support for browsers#67

Open
Scratch-net wants to merge 3 commits intomainfrom
s2
Open

S2 support for browsers#67
Scratch-net wants to merge 3 commits intomainfrom
s2

Conversation

@Scratch-net
Copy link
Copy Markdown
Contributor

@Scratch-net Scratch-net commented Mar 30, 2026

Summary by CodeRabbit

  • New Features

    • Added support for STWO zero-knowledge proof engine as a new proving method option.
  • Updates

    • Updated cryptographic dependency to the latest patch version.
    • Default zero-knowledge engine in the CLI now uses STWO instead of SNARKJS.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3d213416-047e-40e0-8c03-26472039b001

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds support for a new STWO (stow) ZK proof engine alongside existing GNARK and SNARKJS engines. Changes include proto enum extension, TypeScript code generation updates, a new browser-based STWO operator implementation with WASM initialization and witness serialization, build system configuration updates, and integration across client and utility code to enable engine selection.

Changes

Cohort / File(s) Summary
Proto and Type Definitions
proto/api.proto, src/proto/api.ts
Added ZK_ENGINE_STWO = 2 enum member to ZKProofEngine, updated JSON serialization/deserialization handlers to recognize and convert the new engine value.
ZK Engine Integration
src/utils/zk.ts, src/client/create-claim.ts, src/server/utils/assert-valid-claim-request.ts
Extended getEngineString() and getEngineProto() helpers to translate between STWO engine and proto/string representations; refactored client and server code to use helper-based engine conversion instead of inline conditionals.
STWO Implementation
src/scripts/fallbacks/stwo.ts
New browser-native STWO operator with base64 encoding/decoding, witness serialization (validating counters, mapping input.out to plaintext and input.in to ciphertext), dynamic WASM initialization via s2circuits, and algorithm-specific proof generation/verification (chacha20, aes-128-ctr, aes-256-ctr).
Build System Configuration
src/scripts/build-browser.ts, src/scripts/build-jsc.ts, src/scripts/build-browser.sh
Added esbuild alias mappings to redirect @reclaimprotocol/zk-symmetric-crypto/stwo to local fallback; enhanced build cleanup and added resource presence checks for STWO WASM files with warning emissions.
Browser and Dependencies
browser/index.html, package.json
Added browser-rpc/resources/stwo/s2circuits.js script tag to HTML; bumped @reclaimprotocol/zk-symmetric-crypto from ^5.0.4 to ^5.0.9.
CLI Update
src/scripts/generate-receipt.ts
Changed default ZK engine selection from snarkjs to stwo when --zk flag is not explicitly "gnark".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • adiwajshing

Poem

🐰 A new engine hops into view,
STWO circuits making proofs true,
With WASM bounds and witness care,
Gnark and Snarkjs now find a peer fair! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'S2 support for browsers' directly aligns with the main objective of this pull request, which adds STWO (S2) ZK proof engine support for browser environments.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch s2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utils/zk.ts (1)

689-697: ⚠️ Potential issue | 🔴 Critical

Add OPRF operator support for STWO engine.

STWO is selectable as a ZK engine (e.g., src/scripts/generate-receipt.ts), but OPRF_OPERATOR_MAKERS only includes a maker for 'gnark'. If STWO is selected as the zkEngine and TOPRF operations are required, makeDefaultOPRFOperator will throw "No OPRF operator maker for stwo" at runtime.

Either add makeStwoOPRFOperator to OPRF_OPERATOR_MAKERS, or prevent STWO from being selected when OPRF is required.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/zk.ts` around lines 689 - 697, OPRF_OPERATOR_MAKERS currently only
maps 'gnark' and lacks a maker for the 'stwo' ZK engine, causing
makeDefaultOPRFOperator to throw when zkEngine == 'stwo'; fix by adding the STWO
OPRF maker to the mapping (add an entry 'stwo': makeStwoOPRFOperator) or, if
STWO cannot support OPRF, add a guard where makeDefaultOPRFOperator is invoked
to prevent selecting 'stwo' when OPRF is required and surface a clear error;
update the OPRF_OPERATOR_MAKERS constant or the selection logic referencing
OPRF_OPERATOR_MAKERS and makeDefaultOPRFOperator accordingly, using the
makeStwoOPRFOperator symbol if available.
🧹 Nitpick comments (2)
src/scripts/build-browser.sh (1)

8-14: Consider failing the build when STWO resources are missing.

The script emits warnings but continues when s2circuits.js or s2circuits_bg.wasm are absent. Given that generate-receipt.ts now defaults to STWO (instead of SnarkJS), a build missing these resources will fail at runtime. Consider making this a fatal error:

♻️ Proposed fix to fail on missing resources
 # ensure stwo resources exist (s2circuits.js + s2circuits_bg.wasm)
+STWO_MISSING=0
 if [ ! -f ./browser/resources/stwo/s2circuits.js ]; then
-    echo "Warning: stwo/s2circuits.js not found in resources"
+    echo "Error: stwo/s2circuits.js not found in resources"
+    STWO_MISSING=1
 fi
 if [ ! -f ./browser/resources/stwo/s2circuits_bg.wasm ]; then
-    echo "Warning: stwo/s2circuits_bg.wasm not found in resources"
+    echo "Error: stwo/s2circuits_bg.wasm not found in resources"
+    STWO_MISSING=1
 fi
+if [ $STWO_MISSING -eq 1 ]; then
+    echo "STWO resources missing. Please ensure `@reclaimprotocol/zk-symmetric-crypto` includes stwo resources."
+    exit 1
+fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scripts/build-browser.sh` around lines 8 - 14, The build script currently
only warns when STWO resources s2circuits.js and s2circuits_bg.wasm are missing;
change it to fail the build by printing an error and exiting non‑zero. Update
the checks in build-browser.sh that look for
./browser/resources/stwo/s2circuits.js and
./browser/resources/stwo/s2circuits_bg.wasm to emit an error (to stderr) and
call exit 1 when either file is absent (or set a missing flag and exit 1 after
both checks); this ensures generate-receipt.ts's default STWO path won't break
at runtime.
src/scripts/fallbacks/stwo.ts (1)

16-34: Consider using more efficient string building for large arrays.

The fromUint8Array function uses string concatenation in a loop, which has O(n²) complexity due to string immutability. For large ciphertext blocks, this could impact performance.

♻️ Optional performance improvement
 const Base64 = {
 	fromUint8Array(arr: Uint8Array): string {
-		let binary = ''
-		for(const element of arr) {
-			binary += String.fromCharCode(element)
-		}
-
-		return btoa(binary)
+		// Use apply with chunks to avoid call stack limits while maintaining O(n) complexity
+		const CHUNK_SIZE = 0x8000
+		const chunks: string[] = []
+		for(let i = 0; i < arr.length; i += CHUNK_SIZE) {
+			chunks.push(String.fromCharCode.apply(null, arr.subarray(i, i + CHUNK_SIZE) as unknown as number[]))
+		}
+		return btoa(chunks.join(''))
 	},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scripts/fallbacks/stwo.ts` around lines 16 - 34, Base64.fromUint8Array
currently concatenates characters in a loop causing O(n²) behavior for large
arrays; replace the per-byte string concatenation with chunked batch conversion
using String.fromCharCode on slices (e.g., 32k–64k sized chunks) and push each
chunk into an array then join once and call btoa on the result to avoid
quadratic concatenation and argument-length limits; keep Base64.toUint8Array
unchanged. Ensure you update the Base64.fromUint8Array function accordingly and
reference it by name when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/scripts/fallbacks/stwo.ts`:
- Around line 96-106: The code currently uses non-null assertions (e.g.,
s2.initSync!, s2.generate_chacha20_proof!) which can throw cryptic runtime
errors if the WASM glue (getS2Circuits) doesn't expose those methods; update
initPromise and groth16Prove to perform explicit runtime checks: call
getS2Circuits(), verify that required functions (s2.initSync,
s2.generate_chacha20_proof, and any other methods used later) exist and typeof
=== 'function' before invoking them, and if any are missing throw a clear,
descriptive Error (e.g., "s2circuits missing initSync") so the failure is
explicit and avoid using non-null assertions anywhere in initPromise,
groth16Prove, and the code paths around lines 156-168.
- Around line 84-109: The module-level WASM state (wasmInitialized, initPromise)
and the release() behavior cause shared global state across all
makeStwoZkOperator instances; change ensureWasmInitialized and release handling
to use instance-aware reference counting or scoped initialization: implement a
shared counter incremented when makeStwoZkOperator (or the function that calls
ensureWasmInitialized) initializes WASM and decremented by each instance's
release(), and only actually reset wasmInitialized/initPromise and free WASM
when the counter reaches zero; update ensureWasmInitialized to increment the
refcount on successful init and ensure release() decrements the refcount and
only clears module-level flags when refcount == 0, keeping function names
ensureWasmInitialized, release, wasmInitialized, and initPromise as the
integration points.
- Around line 182-220: In groth16Verify, wrap the verification flow in a
try/catch and return false on any thrown error; specifically, first safely guard
access to publicSignals.noncesAndCounters (avoid direct [0] when
noncesAndCounters can be undefined), then call ensureWasmInitialized and
getS2Circuits as before but wrap assertU32Counter, TextDecoder.decode, the calls
to s2.verify_chacha20_proof! / s2.verify_aes_ctr_proof!, and JSON.parse in the
try block so any RangeError, decoding error, WASM error, or parse error is
caught; in catch log the error via logger?.warn and return false to ensure the
function never throws.
- Around line 145-180: In groth16Prove (function in
src/scripts/fallbacks/stwo.ts) change the returned object to include proofData
and plaintext to match downstream expectations: return the generated proof as
proofData (use result.proof) and include the plaintext buffer/value you created
earlier (plaintext derived from data.plaintext), so the function returns {
proofData: result.proof, plaintext: plaintext } instead of { proof: result.proof
}; ensure the return type aligns with ProveResult/consumer code.

In `@src/scripts/generate-receipt.ts`:
- Line 60: The current zk engine selection (const zkEngine =
getCliArgument('zk') === 'gnark' ? 'gnark' : 'stwo') causes unintended STWO
selection and will crash in Node because fallbacks/stwo.ts's getS2Circuits()
accesses window directly; update selection logic to explicitly support
'snarkjs', 'gnark', and 'stwo' (e.g., use the CLI arg value and default to
'snarkjs' in Node), and modify getS2Circuits() in fallbacks/stwo.ts to guard
browser globals (check typeof window !== 'undefined' and return a safe
failure/throw or fallback) so the CLI doesn't hit window.s2circuits in Node.
Ensure you reference getCliArgument, zkEngine, and getS2Circuits when making the
changes.

---

Outside diff comments:
In `@src/utils/zk.ts`:
- Around line 689-697: OPRF_OPERATOR_MAKERS currently only maps 'gnark' and
lacks a maker for the 'stwo' ZK engine, causing makeDefaultOPRFOperator to throw
when zkEngine == 'stwo'; fix by adding the STWO OPRF maker to the mapping (add
an entry 'stwo': makeStwoOPRFOperator) or, if STWO cannot support OPRF, add a
guard where makeDefaultOPRFOperator is invoked to prevent selecting 'stwo' when
OPRF is required and surface a clear error; update the OPRF_OPERATOR_MAKERS
constant or the selection logic referencing OPRF_OPERATOR_MAKERS and
makeDefaultOPRFOperator accordingly, using the makeStwoOPRFOperator symbol if
available.

---

Nitpick comments:
In `@src/scripts/build-browser.sh`:
- Around line 8-14: The build script currently only warns when STWO resources
s2circuits.js and s2circuits_bg.wasm are missing; change it to fail the build by
printing an error and exiting non‑zero. Update the checks in build-browser.sh
that look for ./browser/resources/stwo/s2circuits.js and
./browser/resources/stwo/s2circuits_bg.wasm to emit an error (to stderr) and
call exit 1 when either file is absent (or set a missing flag and exit 1 after
both checks); this ensures generate-receipt.ts's default STWO path won't break
at runtime.

In `@src/scripts/fallbacks/stwo.ts`:
- Around line 16-34: Base64.fromUint8Array currently concatenates characters in
a loop causing O(n²) behavior for large arrays; replace the per-byte string
concatenation with chunked batch conversion using String.fromCharCode on slices
(e.g., 32k–64k sized chunks) and push each chunk into an array then join once
and call btoa on the result to avoid quadratic concatenation and argument-length
limits; keep Base64.toUint8Array unchanged. Ensure you update the
Base64.fromUint8Array function accordingly and reference it by name when making
the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 11e65824-59f1-4768-84ba-2ed59fd70275

📥 Commits

Reviewing files that changed from the base of the PR and between c036b80 and f4346ec.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • browser/index.html
  • package.json
  • proto/api.proto
  • src/client/create-claim.ts
  • src/proto/api.ts
  • src/scripts/build-browser.sh
  • src/scripts/build-browser.ts
  • src/scripts/build-jsc.ts
  • src/scripts/fallbacks/stwo.ts
  • src/scripts/generate-receipt.ts
  • src/server/utils/assert-valid-claim-request.ts
  • src/utils/zk.ts

Comment thread src/scripts/fallbacks/stwo.ts
Comment thread src/scripts/fallbacks/stwo.ts
Comment thread src/scripts/fallbacks/stwo.ts
Comment thread src/scripts/fallbacks/stwo.ts
Comment thread src/scripts/generate-receipt.ts
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.

1 participant