Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
388 changes: 330 additions & 58 deletions groth16_verifier/Cargo.lock

Large diffs are not rendered by default.

26 changes: 9 additions & 17 deletions groth16_verifier/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
[package]
name = "soroban-groth16-verifier-contract"
version = "0.0.0"
edition = "2021"
publish = false
rust-version = "1.89.0"
[workspace]
members = ["bls12-381", "bn254"]
resolver = "2"

[lib]
crate-type = ["cdylib"]
doctest = false
[workspace.package]
rust-version = "1.89.0"

[dependencies]
soroban-sdk = { version = "23.0.1" }
[workspace.dependencies]
soroban-sdk = { version = "25.0.2" }

[dev-dependencies]
soroban-sdk = { version = "23.0.1", features = ["testutils"] }
ark-bls12-381 = { version = "0.4.0"}
ark-serialize = { version = "0.4.2"}
ark-ff = { version = "0.4.2"}
ark-ec = { version = "0.4.2"}
[workspace.dev-dependencies]
soroban-sdk = { version = "25.0.2", features = ["testutils"] }

[profile.release]
opt-level = "z"
Expand Down
15 changes: 11 additions & 4 deletions groth16_verifier/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ default: build
all: test

test: build
cargo test
cargo test --workspace

build:
stellar contract build
@ls -l target/wasm32v1-none/release/*.wasm

stellar contract build --package soroban-groth16-verifier-bls12-381
stellar contract build --package soroban-groth16-verifier-bn254
mkdir -p opt/
stellar contract optimize \
--wasm target/wasm32v1-none/release/soroban_groth16_verifier_bls12_381.wasm \
--wasm-out opt/soroban_groth16_verifier_bls12_381.wasm
stellar contract optimize \
--wasm target/wasm32v1-none/release/soroban_groth16_verifier_bn254.wasm \
--wasm-out opt/soroban_groth16_verifier_bn254.wasm
@ls -l opt/*.wasm
fmt:
cargo fmt --all

Expand Down
22 changes: 5 additions & 17 deletions groth16_verifier/README
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
# Groth16 Verifier Contract
# Groth16 Verifier Examples

A demonstration of a Groth16 zero-knowledge proof verifier implemented as a Soroban smart contract.
This directory contains two Groth16 verifier contracts:

The proof and verification key are generated following the Circom2 (circom compiler 2.2.1) [getting-started guide](https://docs.circom.io/getting-started/installation/).
- BLS12-381 implementation: [bls12-381](bls12-381)
- BN254 implementation: [bn254](bn254)

The computation demonstrates a simple multiplication circuit: `a * b = c`, where:
- `a` and `b` are private inputs
- `c` is the public output

The `./data` directory contains all input files (circuit definition, inputs) and generated outputs. For proof verification, three key files are required:
- [proof.json](./data/proof.json) - Contains the zero-knowledge proof
- [verification_key.json](./data/verification_key.json) - Contains the verification key
- [public.json](./data/public.json) - Contains the public inputs/outputs

Other intermediate artifacts, including witness generation code and outputs from the "Powers of Tau" ceremony, are included in `./data/auxiliary` for reproducibility.

The [contract implementation](./src/lib.rs) is translated from the auto-generated [Solidity contract](./data/multiplier2_js/verifier.sol). The [test suite](./src/test.rs) demonstrates off-chain parsing of the proof and verification key, along with successful contract execution.

This example was presented at the [Stellar Developer Meeting - 12/19/2024](https://www.youtube.com/watch?v=51SitOUZySk&list=PLmr3tp_7-7Gg5IAsJ0VlgfMoh-aTmbQmh&index=4) to demonstrate the BLS12-381 features.
Each subpackage includes its own contract, tests, and data artifacts. Shared Rust toolchain configuration remains at the workspace root.

## ⚠️ WARNING: Demonstration Use Only

Expand Down
21 changes: 21 additions & 0 deletions groth16_verifier/bls12-381/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "soroban-groth16-verifier-bls12-381"
version = "0.0.0"
edition = "2021"
publish = false
rust-version.workspace = true

[lib]
crate-type = ["cdylib"]
doctest = false

[dependencies]
soroban-sdk = { workspace = true }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
ark-bls12-381 = { version = "0.4.0" }
ark-serialize = { version = "0.4.2" }
ark-ff = { version = "0.4.2" }
ark-ec = { version = "0.4.2" }

16 changes: 16 additions & 0 deletions groth16_verifier/bls12-381/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
default: build

all: test

test: build
cargo test

build:
stellar contract build
@ls -l ../target/wasm32v1-none/release/*.wasm

fmt:
cargo fmt --all

clean:
cargo clean
28 changes: 28 additions & 0 deletions groth16_verifier/bls12-381/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Groth16 Verifier Contract (BLS12-381)

A demonstration of a Groth16 zero-knowledge proof verifier implemented as a Soroban smart contract.

The proof and verification key are generated following the Circom2 (circom compiler 2.2.1) [getting-started guide](https://docs.circom.io/getting-started/installation/).

The computation demonstrates a simple multiplication circuit: `a * b = c`, where:
- `a` and `b` are private inputs
- `c` is the public output

The `./data` directory contains all input files (circuit definition, inputs) and generated outputs. For proof verification, three key files are required:
- [proof.json](data/proof.json) - Contains the zero-knowledge proof
- [verification_key.json](data/verification_key.json) - Contains the verification key
- [public.json](data/public.json) - Contains the public inputs/outputs

Other intermediate artifacts, including witness generation code and outputs from the "Powers of Tau" ceremony, are included in [data/auxiliary](data/auxiliary) for reproducibility.

The contract implementation in [src/lib.rs](src/lib.rs) is translated from the auto-generated Solidity contract. The test suite in [src/test.rs](src/test.rs) demonstrates off-chain parsing of the proof and verification key, along with successful contract execution.

This example was presented at the [Stellar Developer Meeting - 12/19/2024](https://www.youtube.com/watch?v=51SitOUZySk&list=PLmr3tp_7-7Gg5IAsJ0VlgfMoh-aTmbQmh&index=4) to demonstrate the BLS12-381 features.

## ⚠️ WARNING: Demonstration Use Only

**This project is for demonstration purposes only.**
- It has **not** undergone security auditing
- It is **not** safe for use in production environments

**Use at your own risk.**
File renamed without changes.
196 changes: 196 additions & 0 deletions groth16_verifier/bls12-381/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#![cfg(test)]
extern crate std;

use ark_bls12_381::{Fq, Fq2};
use ark_serialize::CanonicalSerialize;
use core::str::FromStr;
use soroban_sdk::{
crypto::bls12_381::{Fr, G1Affine, G2Affine, G1_SERIALIZED_SIZE, G2_SERIALIZED_SIZE},
Env, Vec, U256,
};

use crate::{Groth16Verifier, Groth16VerifierClient, Proof, VerificationKey};

const ALPHA_X: &str = "851850525556173310373115880154698084608631105506432893865500290442025919078535925294035153152030470398262539759609";
const ALPHA_Y: &str = "2637289349983507610125993281171282870664683328789064436670091381805667870657250691837988574635646688089951719927247";

const BETA_X1: &str = "1312620381151154625549413690218290437739613987001512553647554932245743783919690104921577716179019375920325686841943";
const BETA_X2: &str = "1853421227732662200477195678252233549930451033531229987959164216695698667330234953033341200627605777603511819497457";
const BETA_Y1: &str = "3215807833988244618006117550809420301978856703407297742347804415291049013404133666905173282837707341742014140541018";
const BETA_Y2: &str = "812366606879346135498483310623227330050424196838294715759414425317592599094348477520229174120664109186562798527696";

const GAMMA_X1: &str = "352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160";
const GAMMA_X2: &str = "3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758";
const GAMMA_Y1: &str = "1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905";
const GAMMA_Y2: &str = "927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582";

const DELTA_X1: &str = "2981843938988033214458466658185878126396080429969635248100956025957789319926032198626745120548947333202362392267114";
const DELTA_X2: &str = "2236695112259305382987038341098587500598216646308901956168137697892380899086228863246537938263638056666003066263342";
const DELTA_Y1: &str = "717163810166643254871951856655865822196000925757284470845197358532703820821048809982340614428800986999944933231635";
const DELTA_Y2: &str = "3496058064578305387608803828034117220735807855182872031001942587835768203820179263722136810383631418598310938506798";

const IC0_X: &str = "829685638389803071404995253486571779300247099942205634643821309129201420207693030476756893332812706176564514055395";
const IC0_Y: &str = "3455508165409829148751617737772894557887792278044850553785496869183933597103951941805834639972489587640583544390358";
const IC1_X: &str = "2645559270376031734407122278942646687260452979296081924477586893972449945444985371392950465676350735694002713633589";
const IC1_Y: &str = "2241039659097418315097403108596818813895651201896886552939297756980670248638746432560267634304593609165964274111037";

const PI_A_X: &str = "314442236668110257304682488877371582255161413673331360366570443799415414639292047869143313601702131653514009114222";
const PI_A_Y: &str = "2384632327855835824635705027009217874826122107057894594162233214798350178691568018290025994699762298534539543934607";
const PI_B_X1: &str = "428844167033934720609657613212495751617651348480870890908850335525890280786532876634895457032623422366474694342656";
const PI_B_X2: &str = "3083139526360252775789959298805261067575555607578161553873977966165446991459924053189383038704105379290158793353905";
const PI_B_Y1: &str = "1590919422794657666432683000821892403620510405626533455397042191265963587891653562867091397248216891852168698286910";
const PI_B_Y2: &str = "3617931039814164588401589536353142503544155307022467123698224064329647390280346725086550997337076315487486714327146";
const PI_C_X: &str = "3052934797502613468327963344215392478880720823583493172692775426011388142569325036386650708808320216973179639719187";
const PI_C_Y: &str = "2028185281516938724429867827057869371578022471499780916652824405212207527699373814371051328341613972789943854539597";

fn g1_from_coords(env: &Env, x: &str, y: &str) -> G1Affine {
let ark_g1 = ark_bls12_381::G1Affine::new(Fq::from_str(x).unwrap(), Fq::from_str(y).unwrap());
let mut buf = [0u8; G1_SERIALIZED_SIZE];
ark_g1.serialize_uncompressed(&mut buf[..]).unwrap();
G1Affine::from_array(env, &buf)
}

fn g2_from_coords(env: &Env, x1: &str, x2: &str, y1: &str, y2: &str) -> G2Affine {
let x = Fq2::new(Fq::from_str(x1).unwrap(), Fq::from_str(x2).unwrap());
let y = Fq2::new(Fq::from_str(y1).unwrap(), Fq::from_str(y2).unwrap());
let ark_g2 = ark_bls12_381::G2Affine::new(x, y);
let mut buf = [0u8; G2_SERIALIZED_SIZE];
ark_g2.serialize_uncompressed(&mut buf[..]).unwrap();
G2Affine::from_array(env, &buf)
}

fn create_client(e: &Env) -> Groth16VerifierClient<'_> {
Groth16VerifierClient::new(e, &e.register(Groth16Verifier {}, ()))
}

mod groth16_verifier_contract {
soroban_sdk::contractimport!(file = "../opt/soroban_groth16_verifier_bls12_381.wasm");
}

fn build_vk(env: &Env) -> VerificationKey {
VerificationKey {
alpha: g1_from_coords(env, ALPHA_X, ALPHA_Y),
beta: g2_from_coords(env, BETA_X1, BETA_X2, BETA_Y1, BETA_Y2),
gamma: g2_from_coords(env, GAMMA_X1, GAMMA_X2, GAMMA_Y1, GAMMA_Y2),
delta: g2_from_coords(env, DELTA_X1, DELTA_X2, DELTA_Y1, DELTA_Y2),
ic: Vec::from_array(
env,
[
g1_from_coords(env, IC0_X, IC0_Y),
g1_from_coords(env, IC1_X, IC1_Y),
],
),
}
}

fn build_proof(env: &Env) -> Proof {
Proof {
a: g1_from_coords(env, PI_A_X, PI_A_Y),
b: g2_from_coords(env, PI_B_X1, PI_B_X2, PI_B_Y1, PI_B_Y2),
c: g1_from_coords(env, PI_C_X, PI_C_Y),
}
}

fn public_output(env: &Env, value: u32) -> Vec<Fr> {
Vec::from_array(env, [Fr::from_u256(U256::from_u32(env, value))])
}

fn build_vk_wasm(env: &Env) -> groth16_verifier_contract::VerificationKey {
groth16_verifier_contract::VerificationKey {
alpha: g1_from_coords(env, ALPHA_X, ALPHA_Y).into(),
beta: g2_from_coords(env, BETA_X1, BETA_X2, BETA_Y1, BETA_Y2).into(),
gamma: g2_from_coords(env, GAMMA_X1, GAMMA_X2, GAMMA_Y1, GAMMA_Y2).into(),
delta: g2_from_coords(env, DELTA_X1, DELTA_X2, DELTA_Y1, DELTA_Y2).into(),
ic: Vec::from_array(
env,
[
g1_from_coords(env, IC0_X, IC0_Y).into(),
g1_from_coords(env, IC1_X, IC1_Y).into(),
],
),
}
}

fn build_proof_wasm(env: &Env) -> groth16_verifier_contract::Proof {
groth16_verifier_contract::Proof {
a: g1_from_coords(env, PI_A_X, PI_A_Y).into(),
b: g2_from_coords(env, PI_B_X1, PI_B_X2, PI_B_Y1, PI_B_Y2).into(),
c: g1_from_coords(env, PI_C_X, PI_C_Y).into(),
}
}

fn public_output_wasm(env: &Env, value: u32) -> Vec<U256> {
Vec::from_array(env, [U256::from_u32(env, value)])
}

#[test]
fn test() {
// Initialize the test environment
let env = Env::default();

// Load verification key components (copied from `data/verification_key.json`)
// These values are pre-computed for the circuit that verifies a*b = c
// where a=3, b=11, c=33 and only c is public.
let vk = build_vk(&env);
let proof = build_proof(&env);

// Create the contract client
let client = create_client(&env);

// Test Case 1: Verify the proof with the correct public output (33, copied from `data/public.json`)
let output = public_output(&env, 33);
let res = client.verify_proof(&vk, &proof, &output);
assert_eq!(res, true);

// Print out the budget report showing CPU and memory cost breakdown for
// different operations (zero-value operations omitted for brevity)
env.cost_estimate().budget().print();
/*
=================================================================
Cpu limit: 100000000; used: 40968821
Mem limit: 41943040; used: 297494
=================================================================
CostType cpu_insns mem_bytes
MemAlloc 12089 3401
MemCpy 3091 0
MemCmp 928 0
VisitObject 5917 0
ComputeSha256Hash 3738 0
Bls12381EncodeFp 2644 0
Bls12381DecodeFp 29550 0
Bls12381G1CheckPointOnCurve 13538 0
Bls12381G1CheckPointInSubgroup 3652550 0
Bls12381G2CheckPointOnCurve 23684 0
Bls12381G2CheckPointInSubgroup 4231288 0
Bls12381G1ProjectiveToAffine 185284 0
Bls12381G1Add 7689 0
Bls12381G1Mul 2458985 0
Bls12381Pairing 30335852 294093
Bls12381FrFromU256 1994 0
// ... zero-value rows omitted ...
=================================================================
*/

// Test Case 2: Verify the proof with an incorrect public output (22)
let output = public_output(&env, 22);
let res = client.verify_proof(&vk, &proof, &output);
assert_eq!(res, false);
}

#[test]
fn test_running_contract_as_wasm() {
let env = Env::default();
env.cost_estimate().budget().reset_unlimited();

let contract_id = env.register(groth16_verifier_contract::WASM, ());
let client = groth16_verifier_contract::Client::new(&env, &contract_id);

let vk = build_vk_wasm(&env);
let proof = build_proof_wasm(&env);

let output = public_output_wasm(&env, 33);
let res = client.verify_proof(&vk, &proof, &output);
assert_eq!(res, true);

env.cost_estimate().budget().print();
}
21 changes: 21 additions & 0 deletions groth16_verifier/bn254/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "soroban-groth16-verifier-bn254"
version = "0.0.0"
edition = "2021"
publish = false
rust-version.workspace = true

[lib]
crate-type = ["cdylib"]
doctest = false

[dependencies]
soroban-sdk = { workspace = true }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
ark-bn254 = { version = "0.5.0" }
ark-serialize = { version = "0.5.0" }
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

The BN254 package uses ark-serialize version 0.5.0, while soroban-env-host uses ark-serialize version 0.4.2. This version mismatch could lead to compatibility issues. Consider using ark-serialize version 0.4.2 to match the runtime dependency.

Suggested change
ark-serialize = { version = "0.5.0" }
ark-serialize = { version = "0.4.2" }

Copilot uses AI. Check for mistakes.
ark-ff = { version = "0.5.0" }
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

The BN254 package uses ark-ff version 0.5.0, while soroban-env-host uses ark-ff version 0.4.2. This version mismatch could lead to compatibility issues. Consider using ark-ff version 0.4.2 to match the runtime dependency.

Suggested change
ark-ff = { version = "0.5.0" }
ark-ff = { version = "0.4.2" }

Copilot uses AI. Check for mistakes.
ark-ec = { version = "0.5.0" }
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

The BN254 package uses ark-ec version 0.5.0, while soroban-env-host uses ark-ec version 0.4.2. This version mismatch could lead to compatibility issues. Consider using ark-ec version 0.4.2 to match the runtime dependency.

Suggested change
ark-ec = { version = "0.5.0" }
ark-ec = { version = "0.4.2" }

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +20
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

The BN254 package uses ark-bn254 version 0.5.0, while soroban-env-host (a dependency of soroban-sdk) uses ark-bn254 version 0.4.0. This version mismatch could lead to subtle compatibility issues or unexpected behavior. Consider using ark-bn254 version 0.4.0 to match the runtime dependency, or verify that version 0.5.0 is intentionally required for test-only functionality.

Suggested change
ark-bn254 = { version = "0.5.0" }
ark-serialize = { version = "0.5.0" }
ark-ff = { version = "0.5.0" }
ark-ec = { version = "0.5.0" }
ark-bn254 = { version = "0.4.0" }
ark-serialize = { version = "0.4.0" }
ark-ff = { version = "0.4.0" }
ark-ec = { version = "0.4.0" }

Copilot uses AI. Check for mistakes.

16 changes: 16 additions & 0 deletions groth16_verifier/bn254/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
default: build

all: test

test: build
cargo test

build:
stellar contract build
@ls -l ../target/wasm32v1-none/release/*.wasm

fmt:
cargo fmt --all

clean:
cargo clean
Loading
Loading