Skip to content

[Feature] Add snarkVerify and snarkVerifyBatch for standalone SNARK proof verification#1216

Closed
marshacb wants to merge 13 commits intomainnetfrom
feat/snark-verify
Closed

[Feature] Add snarkVerify and snarkVerifyBatch for standalone SNARK proof verification#1216
marshacb wants to merge 13 commits intomainnetfrom
feat/snark-verify

Conversation

@marshacb
Copy link
Copy Markdown
Collaborator

@marshacb marshacb commented Mar 2, 2026

Motivation

The snark.verify opcode is being added to snarkVM alongside dynamic dispatch, enabling Leo programs to verify SNARK proofs from programs that are not deployed on-chain. This PR exposes the same capability to the SDK, allowing client-side proof verification via snarkVerify and snarkVerifyBatch. These functions take a verifying key, public inputs, and a proof, and return whether the proof is valid, matching the opcode's interface for JS/TS consumers.

Test Plan

  • Rust unit tests (wasm-pack test --node): Proof type roundtrip (string and bytes), native VerifyingKey::verify with valid/invalid inputs, proof fixture roundtrip
  • TypeScript SDK tests (npm test): Proof.fromString/toBytes roundtrip, snarkVerify with valid inputs returns true, snarkVerify with wrong inputs returns false, snarkVerifyBatch with valid batch returns true, snarkVerifyBatch with mismatched key/input counts throws
  • All tests run on both testnet and mainnet network targets (fixtures are network-agnostic)
  • create-leo-app templates (template-node-ts, template-react-ts) updated with working examples

Note

Medium Risk
Introduces new WASM-exposed SNARK verification primitives and new ProgramManager verification entrypoints; while mostly additive, it touches cryptographic verification surfaces and input parsing/field conversion paths.

Overview
Adds standalone SNARK proof verification support to the SDK by exposing WASM functions snarkVerify/snarkVerifyBatch plus a new Proof type, and re-exporting them through the browser SDK surface.

Extends ProgramManager with verifyProof and verifyBatchProof (and exported VerificationOptions types), including automatic conversion of Aleo-typed public inputs to field elements via Plaintext.toFields().

Updates create-leo-app Node/React templates to demonstrate proof verification (including a React UI + worker method), and adds SDK/wasm tests and fixtures to validate valid/invalid and batch verification behavior.

Written by Cursor Bugbot for commit 369140c. This will update automatically on new commits. Configure here.

@marshacb
Copy link
Copy Markdown
Collaborator Author

marshacb commented Mar 2, 2026

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

@marshacb marshacb marked this pull request as ready for review March 2, 2026 19:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds standalone SNARK proof verification to the SDK, exposing snarkVerify and snarkVerifyBatch functions for client-side verification of Varuna proofs that may not be from on-chain programs.

Changes:

  • New Proof WASM type with serialization/deserialization support
  • New snarkVerify and snarkVerifyBatch WASM-bound functions in execution.rs, with convenience wrappers on ProgramManager
  • Updated create-leo-app templates with working examples of proof verification

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
wasm/src/types/native/mod.rs Adds ProofNative type alias for the native Proof<CurrentNetwork>
wasm/src/programs/proof.rs New Proof WASM type with fromBytes, fromString, toBytes, toString methods
wasm/src/programs/mod.rs Registers the new proof module
wasm/src/programs/execution.rs Adds snarkVerify, snarkVerifyBatch, and parse_field_inputs functions
wasm/Cargo.toml Adds snarkvm-synthesizer-snark dev-dependency with test-helpers feature
sdk/src/wasm.ts Re-exports Proof, snarkVerify, snarkVerifyBatch
sdk/src/browser.ts Re-exports Proof, snarkVerify, snarkVerifyBatch
sdk/src/program-manager.ts Adds verifyProof and verifyBatchProof convenience methods
sdk/tests/program-manager.test.ts Adds tests for Proof type and SNARK verification functions
sdk/tests/data/snark-verify.ts New test fixture file with sample verifying key, proof, and inputs
create-leo-app/template-react-ts/src/workers/worker.ts Adds verifyProof worker method using sample fixtures
create-leo-app/template-react-ts/src/App.tsx Adds UI button to trigger verifyProof in the React template
create-leo-app/template-node-ts/src/index.ts Adds snarkProofVerification demo function to the Node template

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread sdk/src/program-manager.ts Outdated
Comment on lines +3111 to +3118
verifyProof(verifyingKey: VerifyingKey, inputs: string[], proof: Proof): boolean {
try {
return snarkVerify(verifyingKey, inputs, proof);
} catch (e) {
console.warn(`Proof verification failed: ${e}`);
return false;
}
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

verifyProof silently swallows all exceptions and returns false, including errors caused by invalid inputs (e.g. malformed field strings, wrong number of inputs). This makes it impossible for callers to distinguish a legitimate "proof is invalid" result from an error in their own inputs. Consider either re-throwing non-verification errors (e.g. parse errors) or at minimum documenting clearly that any exception results in a false return. The same issue applies to verifyBatchProof.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed the try/catch. Input errors (e.g. malformed field strings) now propagate to the caller instead of being silently returned as false. Invalid proofs still return false since VerifyingKey::verify returns a bool, not a Result

Comment thread wasm/src/programs/execution.rs
Comment thread wasm/src/programs/execution.rs
Comment thread wasm/src/programs/proof.rs
Copy link
Copy Markdown
Member

@iamalwaysuncomfortable iamalwaysuncomfortable left a comment

Choose a reason for hiding this comment

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

This is great! A couple of nits + a few main change requests:

  • On the JS side, users should be able to specify either
    • The field representation of the inputs (as this is what leo/aleo functions take to do snark.verify) for people who know how to handle this.
    • However we should also be able accept the string serialization representation of the inputs (i.e. ["1u32", "2u32"]) and perform the conversion on behalf of the user for users who don't know how to handle it.
  • We likely don't need the proof object exported.
  • The create-leo-app example should allow people to paste in their own proofs and inputs.

Thanks for putting this together, I'd say this is almost over the line.

// Demonstrate standalone SNARK proof verification. This verifies a proof from a circuit that is
// not necessarily an Aleo program execution. Useful for verifying proofs received from external
// sources (e.g. another party proved a computation and you want to verify it).
function snarkProofVerification() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we make a version of this for the react example that allows people to put their own proof and verifying key in?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Great idea! Updated the React template to accept user provided verifying key, proof, and public inputs via text fields. Pre-filled with sample fixtures so it works out of the box.

Comment thread sdk/src/program-manager.ts Outdated
}

/**
* Verify a SNARK proof against a verifying key and public inputs.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
* Verify a SNARK proof against a verifying key and public inputs.
* Verify an Aleo zkSnark proof against a verifying key and public inputs.

Comment thread wasm/src/programs/execution.rs Outdated
pub fn snark_verify_batch(verifying_keys: Array, inputs: Array, proof: &Proof) -> Result<bool, String> {
if verifying_keys.length() != inputs.length() {
return Err(format!(
"Mismatch: {} verifying keys but {} input groups provided",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
"Mismatch: {} verifying keys but {} input groups provided",
"Mismatch: {} verifying keys but {} input groups provided. # of input groups must match # of verifying keys.",

Comment thread sdk/src/program-manager.ts Outdated
* It directly invokes the Varuna proof verification from snarkVM.
*
* @param {VerifyingKey} verifyingKey The verifying key for the circuit
* @param {string[]} inputs Array of field element strings representing public inputs (e.g. ["1field", "2field"])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So although we DO want to support fields, we also want to support passing the string representations of the types here in the SDK instead of the field representations, then internally we can call toFields() on the inputs.

So this means the string representation of normal aleo types like:

  • ["1u32", "2u32"]
  • ["{ is_open: true, commision: 20u8}"]

We can then parse to see if they're correct aleo types and call to_fields() internally. Users should have the option to pass both the Field representation of the inputs AND the serialized aleo type representations. We can do this via a parameter object.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We can then parse to see if they're correct aleo types and call to_fields() internally. Users should have the option to pass both the Field representation of the inputs AND the serialized aleo type representations. We can do this via a parameter object.

Technically we could do it in this JS side because we have the toFields() tooling to do it here.

Comment thread sdk/src/program-manager.ts Outdated
* const proof = Proof.fromString("proof1...");
* const isValid = programManager.verifyProof(verifyingKey, ["1field", "2field"], proof);
*/
verifyProof(verifyingKey: VerifyingKey, inputs: string[], proof: Proof): boolean {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We want to move to object parameters in general in new JS methods, so here we'd do this.

Suggested change
verifyProof(verifyingKey: VerifyingKey, inputs: string[], proof: Proof): boolean {
verifyProof(VerificationOptions): boolean {

Comment thread sdk/src/program-manager.ts Outdated
* Each verifying key is paired with one or more sets of public inputs (instances).
*
* @param {string[]} verifyingKeys Array of verifying key strings, one per circuit
* @param {string[][][]} inputs 3D array of field element strings [circuit_idx][instance_idx][field_idx]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same comment here, we want users to be able to put it in the plaintext string representation of the inputs.

/// @param {Proof} proof The batch proof to verify
/// @returns {boolean} True if the batch proof is valid, false otherwise
#[wasm_bindgen(js_name = "snarkVerifyBatch")]
pub fn snark_verify_batch(verifying_keys: Array, inputs: Array, proof: &Proof) -> Result<bool, String> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We might consider giving the user the option to give either field inputs OR the string serialization.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Handled at the SDK layer. ProgramManager.verifyProof() and verifyBatchProof() will now both run inputsToFields() which accepts either raw field strings or Aleo type strings (e.g. "1u32", "true", structs) and automatically converts them to fields via Plaintext.toFields(). The wasm functions are kept as thin wrappers over the snarkVM native API, while ProgramManager provides the user-friendly interface.

Comment thread sdk/src/program-manager.ts Outdated
*
* @param {VerifyingKey} verifyingKey The verifying key for the circuit
* @param {string[]} inputs Array of field element strings representing public inputs (e.g. ["1field", "2field"])
* @param {Proof} proof The proof to verify
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should likely just be the proof string and not the wasm object.

Suggested change
* @param {Proof} proof The proof to verify
* @param {string} proof The proof to verify

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good call! Updated to accept proof and verifyingKey as plain strings. The methods now handle Proof.fromString() / VerifyingKey.fromString() conversion internally so users don't need to import or construct wasm objects.

/// SNARK proof for verification of program execution
#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct Proof(ProofNative);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think we need to export this object as it doesn't add much. The main reason for exporting an object to wasm is when it provides computations done in Rust that would be hard/error-prone/hard-to-maintain if re-implemented in JS/TS. In methods that use proofs we can just construct the Rust object via the string representation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed! Updated to accept proof: string (and verifyingKey: string). The ProgramManager methods handle Proof.fromString() / VerifyingKey.fromString() internally, so users never need to import or construct the Proof wasm object.

Comment thread sdk/src/program-manager.ts
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread create-leo-app/template-react-ts/src/App.tsx Outdated
iamalwaysuncomfortable and others added 3 commits March 24, 2026 22:22
* Add WASM support for dynamic dispatch variant types (DynamicRecord, DynamicFuture, RecordWithDynamicID, ExternalRecordWithDynamicID)

* fmt

* fix(wasm): normalize dynamic dispatch type strings to match snarkVM serialization and add missing doc comments

* add JS/TS fixture tests for dynamic dispatch variant types (record_dynamic, record_with_dynamic_id, external_record_with_dynamic_id)
iamalwaysuncomfortable and others added 5 commits March 24, 2026 22:22
* Handle Consensus V14 in deployments

* Remove redundant .map_err in latest_stateroot call

* Update getOrInitConsensusVersionTestHeights tests and doc comments to handle all currently active test version heights

* Deallocate deployment cost tuple
* Add stringToField utility function for converting program and function names to fields for dynamic dispatch

* Add exports of the stringToField utility in the js SDK
* Add the dynamic record type

* Add JS side tests for the DynamicRecord type

* Check visibility on record conversion roundtrip

---------

Signed-off-by: Mike Turner <mike@provable.com>
* Handle Consensus V14 in deployments (#1230)

* Handle Consensus V14 in deployments

* Remove redundant .map_err in latest_stateroot call

* Update getOrInitConsensusVersionTestHeights tests and doc comments to handle all currently active test version heights

* Deallocate deployment cost tuple

* Updates to make AMM tests works

* Also handle upgrade

* Address feedback

* Cleanup

* Cargo fmt lints

* Revert .gitignore changes unrelated to this PR

* Change core rev to the testnet 4.6.0 candidate

* Bump version update to v0.9.18

* Remove unecessary dependencies in the workspace root

---------

Co-authored-by: Mike Turner <mike@provable.com>
…NARK proof verification

Add support for verifying SNARK proofs from programs not deployed on chain,
wrapping snarkVM's VerifyingKey::verify and VerifyingKey::verify_batch APIs.
@iamalwaysuncomfortable iamalwaysuncomfortable force-pushed the feat/dynamic-dispatch branch 2 times, most recently from 6c5a46a to 486b002 Compare March 26, 2026 07:29
Base automatically changed from feat/dynamic-dispatch to testnet March 26, 2026 07:33
Base automatically changed from testnet to mainnet March 31, 2026 16:29
@iamalwaysuncomfortable
Copy link
Copy Markdown
Member

This feature has been merged via #1263 therefore closing this!

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.

4 participants