diff --git a/.gas-snapshot b/.gas-snapshot new file mode 100644 index 0000000..da12617 --- /dev/null +++ b/.gas-snapshot @@ -0,0 +1,4 @@ +Groth16Bn254VerifierTest:testGas_bench_fe_verifyProof_ok() (gas: 202676) +Groth16Bn254VerifierTest:testGas_bench_fe_verifyProof_wrongPublicInput() (gas: 202754) +Groth16Bn254VerifierTest:testGas_bench_solidity_verifyProof_ok() (gas: 214887) +Groth16Bn254VerifierTest:testGas_bench_solidity_verifyProof_wrongPublicInput() (gas: 214921) \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b64549 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/out/ +/cache/ +/broadcast/ diff --git a/README.md b/README.md index 3c30076..7c4cf89 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,14 @@ **Features** - Groth16 Verifier - BN254 Pairing Friendly Elliptic Curve -- Precompile Pairing Function \ No newline at end of file +- Precompile Pairing Function + +This repo is a **Fe v2 workspace** with two ingots: +- `crypto` (BN254 precompile wrappers) +- `verifiers` (Groth16 BN254 verifier) + +Quick checks: +- `fe check .` +- `fe test ./crypto` +- `fe build --contract Groth16Bn254Verifier ./verifiers` +- `forge test` diff --git a/V2_UPDATE.md b/V2_UPDATE.md new file mode 100644 index 0000000..350c3ea --- /dev/null +++ b/V2_UPDATE.md @@ -0,0 +1,171 @@ +# Fe v2 update (2026-01-26) + +This document describes what changed in this repo during the “Fe v2” update, what was removed, and how to migrate any downstream usage. + +## Summary + +This repo started as older Fe v1-era code that: + +- Implemented BN254 helpers using `std::precompiles` and `std::buf::MemoryBuffer`. +- Shipped a Groth16 verifier that built the pairing input via `MemoryBuffer` and called the pairing precompile. +- Included extra BN254 arithmetic (Fp/Fp2 ops, G2 arithmetic, hash-to-curve-ish helpers, and signing helpers). + +The Fe v2 update refactors the repo to: + +- Use the Fe v2 **workspace + ingot** structure. +- Use `std::evm::{alloc, ops}` and direct precompile `staticcall` wrappers for BN254 operations. +- Keep the Groth16 verifier, but express it in Fe v2 syntax and wire it to the BN254 wrappers. +- Remove artifacts that are not required to compile, test, or use the verifier library. + +## Repository layout changes + +### Before + +- Two standalone “projects” with v1-style `fe.toml` manifests: + - `crypto/` (BN254 + helpers) + - `verifiers/` (Groth16 verifier + a `.fe.template`) + +The v1 manifests were not compatible with the current Fe CLI (`fe 0.26.0`), and `fe check` failed with version parsing errors. + +### After + +- A single workspace root at `fe.toml` with two ingots: + - `crypto/` + - `verifiers/` + +Files to look at: +- Workspace root: `fe.toml` +- Ingot manifests: `crypto/fe.toml`, `verifiers/fe.toml` +- Ingot entrypoints: `crypto/src/lib.fe`, `verifiers/src/lib.fe` + +## BN254 implementation changes (`crypto` ingot) + +### What was removed + +The previous `crypto/src/curve/bn254.fe` contained far more than what the Groth16 verifier needs: + +- Fp/Fp2 arithmetic helpers implemented via `modexp` and custom formulas. +- G2 curve arithmetic implemented in Fe. +- Hash/expand-message helpers and signing helpers (not appropriate for on-chain usage in most cases). + +Those pieces were removed to keep the repo focused on on-chain verification via Ethereum precompiles. + +### What replaced it + +`crypto/src/bn254.fe` now provides **thin wrappers** around Ethereum’s BN254 precompiles: + +- `0x06` ECADD +- `0x07` ECMUL +- `0x08` ECPAIRING + +This is the same overall approach used in the `fe-verifiers` v2 codebase: + +- Allocate input/output buffers with `std::evm::alloc`. +- Populate memory using `std::evm::ops::mstore`. +- Call precompiles with `std::evm::ops::staticcall`. +- Revert on failure via `std::evm::ops::revert(0, 0)`. + +### G2 encoding detail (important) + +The BN254 pairing precompile expects Fp2 coefficients in **reversed order** compared to the usual `(c0, c1)` math representation. + +This repo stores `G2Point` as: + +``` +G2Point { x_c0, x_c1, y_c0, y_c1 } +``` + +…but when encoding for the pairing precompile, it writes: + +``` +x_c1, x_c0, y_c1, y_c0 +``` + +That is handled internally by `store_pair` inside `crypto/src/bn254.fe`. + +### Public API changes + +Old code used: + +- `std::precompiles::ec_add/ec_mul/ec_pairing` +- `std::buf::MemoryBuffer` for input packing +- Custom types like `Array` + +New code exposes: + +- `G1Point` and `G2Point` structs (Fe v2 struct syntax) +- `negate`, `ec_add`, `ec_mul` +- `pairing_prod2/3/4` helpers that build the pairing input and perform the precompile call + +`crypto/src/lib.fe` re-exports the BN254 module so consumers can `use crypto::bn254`. + +## Groth16 verifier changes (`verifiers` ingot) + +### What changed structurally + +The Groth16 verifier implementation was moved into a dedicated module: + +- `verifiers/src/groth16_bn254.fe` + +`verifiers/src/lib.fe` re-exports it (so consumers can import `verifiers::groth16_bn254::*`) and includes a simple contract wrapper (`Groth16Bn254Verifier`). + +### How the verifier works now + +The verifier now: + +- Validates the public input is `< SNARK_SCALAR_FIELD` and reverts otherwise. +- Computes `vk_x = IC[0] + IC[1] * public_input` using BN254 `ec_add`/`ec_mul`. +- Performs the Groth16 pairing product check using `bn254::pairing_prod4`. + +### Compatibility function + +`verifiers/src/groth16_bn254.fe` keeps a `verifyProof(a, b, c, input)` wrapper that matches the common SnarkJS call shape: + +- `a` is `[u256; 2]` (G1) +- `b` is `[[u256; 2]; 2]` and is interpreted as `[[x_c1, x_c0], [y_c1, y_c0]]` (pairing precompile order) +- `c` is `[u256; 2]` (G1) +- `input` is `[u256; 1]` (this demo verifier has exactly one public input) + +Internally it converts into the `Proof` struct and calls `verify`. + +If you don’t need the SnarkJS-style wrapper, you can call `verify(Proof, public_input)` directly. + +## Removals for minimalism + +The following files were removed because they are not required to compile, test, or use the verifier library: + +- `verifiers/src/main.fe` (example contract wrapper) +- `verifiers/src/groth16.fe.template` (EJS template convenience file) + +If you want template-driven SnarkJS generation again, it can be reintroduced — but the v2 approach in `fe-verifiers` is typically “generate code, then paste constants”, rather than “swap the SnarkJS template”. + +## How to validate the repo + +From the workspace root: + +- `fe check .` +- `fe test ./crypto` +- `fe build --contract Groth16Bn254Verifier ./verifiers` +- `forge test` + +## Migration notes (v1 → v2) + +If you had code importing the old v1 modules, the main changes are: + +- Import paths changed: + - From `crypto::curve::bn254::...` to `crypto::bn254::...` +- Precompile usage changed: + - From `std::precompiles::*` + `MemoryBuffer` packing to `std::evm::{alloc, ops}` wrappers. +- Static arrays use Fe v2 syntax: + - From `Array` to `[u256; N]` + +## What’s intentionally not included (yet) + +This repo currently focuses on Groth16 verification on BN254 using Ethereum precompiles. + +The “extra” cryptographic utilities from the v1 code (Fp/Fp2 arithmetic, G2 math, hash-to-curve-ish helpers, signing helpers) were removed to keep the codebase minimal and aligned with on-chain best practices. + +If you want to bring more back in Fe v2 style, the adjacent `fe-verifiers` workspace includes examples for: + +- KZG point evaluation precompile wrapper (EIP-4844) +- BLS12-381 precompile wrappers (EIP-2537) diff --git a/crypto/fe.toml b/crypto/fe.toml index e3f620e..2a04ecb 100644 --- a/crypto/fe.toml +++ b/crypto/fe.toml @@ -1,6 +1,4 @@ +# Ingot config +[ingot] name = "crypto" -version = "1.0" - -[dependencies] -# my_lib = "../my_lib" -# my_lib = { path = "../my_lib", version = "1.0" } \ No newline at end of file +version = "0.1.0" diff --git a/crypto/src/bn254.fe b/crypto/src/bn254.fe new file mode 100644 index 0000000..fef2978 --- /dev/null +++ b/crypto/src/bn254.fe @@ -0,0 +1,157 @@ +use std::evm::{alloc, ops} + +const ECADD: u256 = 0x06 +const ECMUL: u256 = 0x07 +const ECPAIRING: u256 = 0x08 + +// BN254 base field modulus (Fp). Used for point negation. +const FP_MODULUS: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 + +pub struct G1Point { pub x: u256, pub y: u256 } + +/// Fp2 element is represented as `c0 + c1 * i`. +/// +/// IMPORTANT: The EVM pairing precompile expects Fp2 coefficients in reversed order, so +/// `x_c1` / `y_c1` are encoded before `x_c0` / `y_c0`. +pub struct G2Point { pub x_c0: u256, pub x_c1: u256, pub y_c0: u256, pub y_c1: u256 } + +pub fn p1() -> G1Point { + G1Point { x: 1, y: 2 } +} + +pub fn p2() -> G2Point { + G2Point { + x_c0: 10857046999023057135944570762232829481370756359578518086990519993285655852781, + x_c1: 11559732032986387107991004021392285783925812861821192530917403151452391805634, + y_c0: 8495653923123431417604973247489272438418190587263600148770280649306958101930, + y_c1: 4082367875863433681332203403145435568316851327593401208105741076214120093531, + } +} + +pub fn negate(p: G1Point) -> G1Point { + if p.x == 0 && p.y == 0 { + return G1Point { x: 0, y: 0 } + } + + let y = p.y % FP_MODULUS + let neg_y = if y == 0 { 0 } else { FP_MODULUS - y } + G1Point { x: p.x, y: neg_y } +} + +pub fn ec_add(p1: G1Point, p2: G1Point) -> G1Point { + let in_ptr = alloc(128) + ops::mstore(in_ptr, p1.x) + ops::mstore(in_ptr + 32, p1.y) + ops::mstore(in_ptr + 64, p2.x) + ops::mstore(in_ptr + 96, p2.y) + + let out_ptr = alloc(64) + let ok = ops::staticcall( + gas: ops::gas(), + addr: ECADD, + args_offset: in_ptr, + args_len: 128, + ret_offset: out_ptr, + ret_len: 64, + ) + if ok == 0 { + ops::revert(0, 0) + } + + G1Point { + x: ops::mload(out_ptr), + y: ops::mload(out_ptr + 32), + } +} + +pub fn ec_mul(p: G1Point, s: u256) -> G1Point { + let in_ptr = alloc(96) + ops::mstore(in_ptr, p.x) + ops::mstore(in_ptr + 32, p.y) + ops::mstore(in_ptr + 64, s) + + let out_ptr = alloc(64) + let ok = ops::staticcall( + gas: ops::gas(), + addr: ECMUL, + args_offset: in_ptr, + args_len: 96, + ret_offset: out_ptr, + ret_len: 64, + ) + if ok == 0 { + ops::revert(0, 0) + } + + G1Point { + x: ops::mload(out_ptr), + y: ops::mload(out_ptr + 32), + } +} + +fn store_pair(base: u256, p1: G1Point, p2: G2Point) { + ops::mstore(base, p1.x) + ops::mstore(base + 32, p1.y) + // Reverse Fp2 coefficients for the precompile encoding. + ops::mstore(base + 64, p2.x_c1) + ops::mstore(base + 96, p2.x_c0) + ops::mstore(base + 128, p2.y_c1) + ops::mstore(base + 160, p2.y_c0) +} + +fn pairing_call(in_ptr: u256, in_len: u256) -> bool { + let out_ptr = alloc(32) + let ok = ops::staticcall( + gas: ops::gas(), + addr: ECPAIRING, + args_offset: in_ptr, + args_len: in_len, + ret_offset: out_ptr, + ret_len: 32, + ) + if ok == 0 { + ops::revert(0, 0) + } + + ops::mload(out_ptr) != 0 +} + +pub fn pairing_prod2(a1: G1Point, a2: G2Point, b1: G1Point, b2: G2Point) -> bool { + let in_ptr = alloc(384) + store_pair(in_ptr, a1, a2) + store_pair(in_ptr + 192, b1, b2) + pairing_call(in_ptr, 384) +} + +pub fn pairing_prod3( + a1: G1Point, + a2: G2Point, + b1: G1Point, + b2: G2Point, + c1: G1Point, + c2: G2Point, +) -> bool { + let in_ptr = alloc(576) + store_pair(in_ptr, a1, a2) + store_pair(in_ptr + 192, b1, b2) + store_pair(in_ptr + 384, c1, c2) + pairing_call(in_ptr, 576) +} + +pub fn pairing_prod4( + a1: G1Point, + a2: G2Point, + b1: G1Point, + b2: G2Point, + c1: G1Point, + c2: G2Point, + d1: G1Point, + d2: G2Point, +) -> bool { + let in_ptr = alloc(768) + store_pair(in_ptr, a1, a2) + store_pair(in_ptr + 192, b1, b2) + store_pair(in_ptr + 384, c1, c2) + store_pair(in_ptr + 576, d1, d2) + pairing_call(in_ptr, 768) +} diff --git a/crypto/src/curve/bn254.fe b/crypto/src/curve/bn254.fe deleted file mode 100644 index e603ee4..0000000 --- a/crypto/src/curve/bn254.fe +++ /dev/null @@ -1,619 +0,0 @@ -use std::buf::{MemoryBuffer, MemoryBufferWriter, MemoryBufferReader} -use std::precompiles - -pub fn Fp_exp(b: u256, e: u256) -> u256 { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - - let mut b_buf: MemoryBuffer = MemoryBuffer::new(len: 32) - let mut b_writer: MemoryBufferWriter = b_buf.writer() - b_writer.write(value: b) - let mut e_buf: MemoryBuffer = MemoryBuffer::new(len: 32) - let mut e_writer: MemoryBufferWriter = e_buf.writer() - e_writer.write(value: e) - let mut p_buf: MemoryBuffer = MemoryBuffer::new(len: 32) - let mut p_writer: MemoryBufferWriter = p_buf.writer() - p_writer.write(value: p) - - let mut result: MemoryBufferReader = precompiles::mod_exp(b_size: 32, e_size: 32, m_size: 32, - b: b_buf, e: e_buf, m: p_buf, - ).reader() - return result.read_u256() -} - -pub fn Fp_times(x: u256, y: u256) -> u256 { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - let four_times: u256 = Fp_exp(b: x%p + y%p,e: 2) + p - Fp_exp(b: x%p + p - y%p,e: 2) - return ((four_times + p*(four_times%4))/4)%p //works since p=3 (mod 4) -} - -pub fn Fp_is_square(x: u256) -> bool { - let pm1div2: u256 = 10944121435919637611123202872628637544348155578648911831344518947322613104291 - return Fp_exp(b: x, e: pm1div2) != 21888242871839275222246405745257275088696311157297823662689037894645226208582 //p-1 -} - -pub fn Fp_sqrt(x: u256) -> u256 { - assert Fp_is_square(x: x), "input not square" - let pp1div4: u256 = 5472060717959818805561601436314318772174077789324455915672259473661306552146 - return Fp_exp(b: x, e: pp1div4) -} - -pub fn Fq_eq(x: Array, y: Array) -> bool { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - return (x[0]%p == y[0]%p) and (x[1]%p == y[1]%p) -} - -pub fn Fq_add(x: Array, y: Array) -> Array { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - return [(x[0]%p + y[0]%p) % p, (x[1]%p + y[1]%p) % p] -} - -pub fn Fq_neg(x: Array) -> Array { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - return [(p - x[0]%p) % p, (p - x[1]%p) % p] -} - -pub fn Fq_sub(x: Array, y: Array) -> Array { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - return [(x[0]%p + p - y[0]%p) % p, (x[1]%p + p - y[1]%p) % p] -} - -pub fn Fq_times(x: Array, y: Array) -> Array { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - let z0: u256 = Fp_times(x: x[1],y: y[1]) - let nz2: u256 = p - Fp_times(x: x[0],y: y[0]) - let z1: u256 = (Fp_times(x: x[0]%p + x[1]%p, y: y[0]%p + y[1]%p) + p - z0 + nz2)%p - return [z1, (z0 + nz2) % p] -} - -pub fn Fq_square(x: Array) -> Array { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - let z0: u256 = Fp_exp(b: x[1],e: 2) - let z2: u256 = Fp_exp(b: x[0],e: 2) - let z1: u256 = Fp_times(x: x[0], y: x[1]) - return [(z1 + z1)%p, (z0 + p - z2) % p] -} - -pub fn Fq_inv(x: Array) -> Array { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - let nm: u256 = Fp_exp(b: x[0],e: 2) + Fp_exp(b: x[1],e: 2) - assert nm != 0, "cannot divide by 0" - let inv_nm: u256 = Fp_exp(b: nm, e: p - 2) - return [Fp_times(x: p - x[0]%p, y: inv_nm), Fp_times(x: x[1], y: inv_nm)] -} - -pub fn Fq_inv0(x: Array) -> Array { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - let nm: u256 = Fp_exp(b: x[0],e: 2) + Fp_exp(b: x[1],e: 2) - let inv_nm: u256 = Fp_exp(b: nm, e: p - 2) - return [Fp_times(x: p - x[0]%p, y: inv_nm), Fp_times(x: x[1], y: inv_nm)] -} - -pub fn Fq_is_square(x: Array) -> bool { - return Fp_is_square(x: Fp_exp(b: x[0],e: 2) + Fp_exp(b: x[1],e: 2)) -} - -pub fn Fq_sqrt(x: Array) -> Array { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - let half: u256 = 10944121435919637611123202872628637544348155578648911831344518947322613104292 - let mut sqrtnm: u256 = Fp_sqrt(x: Fp_exp(b: x[0],e: 2) + Fp_exp(b: x[1],e: 2)) - if(not Fp_is_square(x: x[1]%p + sqrtnm)) { - sqrtnm = p - sqrtnm - } - let val: u256 = (x[1]%p + sqrtnm)%p - if(val != 0) { - let a: u256 = Fp_sqrt(x: Fp_times(x: val, y: half)) - let b: u256 = Fp_times(x: x[0], y: Fp_exp(b: a + a, e: p - 2)) - return [b,a] - } - let b: u256 = Fp_sqrt(x: p - x[1]%p) - return [b,0] -} - - -pub struct G1Point { - pub x: u256 - pub y: u256 -} - -pub struct G2Point { - pub x: Array - pub y: Array -} - -// return the generator of G1 -pub fn G1() -> G1Point { - return G1Point(x: 1, y: 2) -} - -//return the generator of G2 -pub fn G2() -> G2Point { - return G2Point(x: [11559732032986387107991004021392285783925812861821192530917403151452391805634, - 10857046999023057135944570762232829481370756359578518086990519993285655852781], - y: [4082367875863433681332203403145435568316851327593401208105741076214120093531, - 8495653923123431417604973247489272438418190587263600148770280649306958101930]) -} - -pub fn negate(p: G1Point) -> G1Point { - let q: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - if(p.y%q == 0) { - return G1Point(x: p.x, y: 0) - } - return G1Point(x: p.x, y: q - (p.y % q)) -} - -pub fn add(p: G1Point, q: G1Point) -> G1Point { - let res: (u256, u256) = precompiles::ec_add(x1: p.x, y1: p.y, x2: q.x, y2: q.y) - return G1Point(x: res.item0, y: res.item1) -} - -pub fn double(p: G1Point) -> G1Point { - return add(p: p, q: p) -} - -pub fn scalar_mul(p: G1Point, r: u256) -> G1Point { - let res: (u256, u256) = precompiles::ec_mul(x: p.x, y: p.y, s: r) - return G1Point(x: res.item0, y: res.item1) -} - -pub fn on_G2(p: G2Point) -> bool { - if(p.x[0] == 0 and p.x[1] == 0 and p.y[0] == 0 and p.y[1] == 0) { - return true - } - let B: Array = [266929791119991161246907387137283842545076965332900288569378510910307636690, - 19485874751759354771024239261021720505790618469301721065564631296452457478373] - let x_cubed: Array = Fq_times(x: Fq_square(x: p.x), y: p.x) - return Fq_eq(x: Fq_square(x: p.y), y: Fq_add(x: x_cubed, y: B)) -} - -pub fn G2_negate(p: G2Point) -> G2Point { - let q: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - if(Fq_eq(x: p.y, y: [0,0])) { - return G2Point(x: p.x, y: [0,0]) - } - return G2Point(x: p.x, y: [(q - (p.y[0] % q))%q, (q - (p.y[1] % q))%q]) -} - -pub fn G2_double(p: G2Point) -> G2Point { - let P: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - if(p.y[0] == 0) { - if(p.y[1] == 0) { - return G2Point(x: [0,0], y: [0,0]) - } - } - let B: Array = [266929791119991161246907387137283842545076965332900288569378510910307636690, - 19485874751759354771024239261021720505790618469301721065564631296452457478373] - let neg2B: Array = [21354383289599292899752590970982707403606157226632023085550280872824610935203, - 4804736240159840902444332968471109165811385375992205194248813196385537460420] - let two_y_inv: Array = Fq_inv(x: Fq_add(x: p.y, y: p.y)) - let x_sq: Array = Fq_square(x: p.x) - let x_cubed: Array = Fq_times(x: x_sq, y: p.x) - assert Fq_eq(x: Fq_square(x: p.y), y: Fq_add(x: x_cubed, y: B)), "point not on curve" - let slope: Array = Fq_times(x: [3*x_sq[0],3*x_sq[1]], y: two_y_inv) - let x_out: Array = Fq_add(x: Fq_square(x: slope), - y: [2*(P-(p.x[0]%P)), 2*(P-(p.x[1]%P))]) - let y_out: Array = Fq_sub(x: Fq_times(x: Fq_add(x: x_cubed, y: neg2B), y: two_y_inv), - y: Fq_times(x: slope, y: x_out)) - return G2Point(x: x_out, y: y_out) -} - -// WARNING: code isn't fixed time -pub fn G2_add(p: G2Point, q: G2Point) -> G2Point { - if(p.x[0] == 0 and p.x[1] == 0 and p.y[0] == 0 and p.y[1] == 0) { - return q - } - if(q.x[0] == 0 and q.x[1] == 0 and q.y[0] == 0 and q.y[1] == 0) { - return p - } - assert on_G2(p: p), "point not on curve" - assert on_G2(p: q), "point not on curve" - if(Fq_eq(x: p.x, y: q.x)) { - if(Fq_eq(x: Fq_add(x: p.y, y: q.y),y: [0,0])) { - return G2Point(x: [0,0], y: [0,0]) - } - //assert Fq_eq(x: p.y, y: q.y), "point not on curve" - return G2_double(p: p) - } - let x_diff_inv: Array = Fq_inv(x: Fq_sub(x: q.x, y: p.x)) - let nslope: Array = Fq_times(x: Fq_sub(x: p.y, y: q.y), y: x_diff_inv) - let x_out: Array = Fq_sub(x: Fq_square(x: nslope), y: Fq_add(x: p.x, y: q.x)) - let cross_prod: Array = Fq_sub(x: Fq_times(x: p.x, y: q.y), - y: Fq_times(x: p.y, y: q.x)) - let y_out: Array = Fq_add(x: Fq_times(x: nslope, y: x_out), - y: Fq_times(x: x_diff_inv, y: cross_prod)) - return G2Point(x: x_out, y: y_out) -} - -// WARNING: code isn't fixed time -pub fn G2_scalar_mul(p: G2Point, r: u256) -> G2Point { - let mut q: G2Point = p; - let mut ans: G2Point = G2Point(x: [0,0], y: [0,0]) - let mut ind: u256 = r; - while true { - if(ind & 1 == 1) { - ans = G2_add(p: ans, q: q) - } - ind >>= 1 - if(ind == 0) { - break - } - q = G2_double(p: q) - } - return ans -} - -pub fn expand_message_xmd(msg: MemoryBuffer, DST: MemoryBuffer, len_in_bytes: u256) -> MemoryBuffer { - let ell : u256 = (len_in_bytes + 31) / 32 // ceil - let rem : u256 = len_in_bytes - 32*(ell - 1) - - let mut DST_prime : MemoryBuffer = MemoryBuffer::new(len: DST.len() + 1) - let mut DST_prime_writer : MemoryBufferWriter = DST_prime.writer() - DST_prime_writer.write_buf(buf: DST) - DST_prime_writer.write_n(value: DST.len(), len: 1) - - let mut msg_prime : MemoryBuffer = MemoryBuffer::new(len: msg.len() + DST_prime.len() + 67) - let mut msg_prime_writer : MemoryBufferWriter = msg_prime.writer() - msg_prime_writer.write(value: 0) - msg_prime_writer.write(value: 0) // 64 byte padding - msg_prime_writer.write_buf(buf: msg) - msg_prime_writer.write_n(value: len_in_bytes, len: 2) - msg_prime_writer.write_n(value: 0, len: 1) - msg_prime_writer.write_buf(buf: DST_prime) - let b0 : u256 = precompiles::sha2_256(buf : msg_prime) - - let mut ans : MemoryBuffer = MemoryBuffer::new(len: len_in_bytes) - let mut ans_writer : MemoryBufferWriter = ans.writer() - - let mut b_buf : MemoryBuffer = MemoryBuffer::new(len: DST_prime.len() + 33) - let mut b_buf_writer : MemoryBufferWriter = b_buf.writer() - b_buf_writer.write(value: b0) - b_buf_writer.write_n(value: 1, len: 1) - b_buf_writer.write_buf(buf: DST_prime) - let mut b : u256 = precompiles::sha2_256(buf : b_buf) - - let mut i : u256 = 2 - while(i <= ell) { - ans_writer.write(value: b) - - let mut bb_buf : MemoryBuffer = MemoryBuffer::new(len: DST_prime.len() + 33) - let mut bb_buf_writer : MemoryBufferWriter = bb_buf.writer() - bb_buf_writer.write(value: b0 ^ b) - bb_buf_writer.write_n(value: i, len: 1) - bb_buf_writer.write_buf(buf: DST_prime) - b = precompiles::sha2_256(buf : bb_buf) - - i += 1 - } - ans_writer.write_n(value: b >> (32 - rem), len: rem) - return ans -} - -pub fn hash_to_Fq(msg: MemoryBuffer, DST: MemoryBuffer) -> Array { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - let two_exp256: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - let bytes : MemoryBuffer = expand_message_xmd(msg: msg, DST: DST, len_in_bytes: 96) - let mut reader: MemoryBufferReader = bytes.reader() - let high1: u128 = reader.read_u128() - let low1: u256 = reader.read_u256() - let high2: u128 = reader.read_u128() - let low2: u256 = reader.read_u256() - return [(Fp_times(x: high2, y: two_exp256) + low2%p)%p, - (Fp_times(x: high1, y: two_exp256) + low1%p)%p] -} - -//doesn't reduce inputs mod p -fn Fq_sgn0(val: Array) -> bool { - let sgn0: bool = val[1]%2 != 0 - let zero0: bool = val[1] == 0 - let sgn1: bool = val[0]%2 != 0 - return sgn0 or (zero0 and sgn1) -} - -pub fn Fq_to_G2(u: Array) -> G2Point { - let B: Array = [266929791119991161246907387137283842545076965332900288569378510910307636690, - 19485874751759354771024239261021720505790618469301721065564631296452457478373] - let gZ: Array = [266929791119991161246907387137283842545076965332900288569378510910307636689, - 19485874751759354771024239261021720505790618469301721065564631296452457478373] - let negZdiv2: Array = [10944121435919637611123202872628637544348155578648911831344518947322613104291,0] - let tv: Array = Fq_times(x: Fq_square(x: u), y: gZ) - let tv2: Array = Fq_add(x: [0,1], y: tv) - let tv1: Array = Fq_sub(x: [0,1], y: tv) - let tv3: Array = Fq_inv0(x: Fq_times(x: tv1, y: tv2)) - let tv4: Array = [15403170217607925661891511707918230497750592932893890913125906786266381721360, - 8270257801618377462829664163334948115088143961679076698731296916415895764198] - let tv5: Array = Fq_times(x: u, y: Fq_times(x: tv1, y: Fq_times(x: tv3 , y: tv4))) - let tv6: Array = [355906388159988214995876516183045123393435953777200384759171347880410182252, - 18685085378399381287283517099609868978155387573303020199856495763721534568303] - let x1: Array = Fq_sub(x: negZdiv2, y: tv5) - let x2: Array = Fq_add(x: negZdiv2, y: tv5) - let x3: Array = Fq_add(x: [1,0], y: Fq_times(x: tv6, - y: Fq_square(x: Fq_times(x: Fq_square(x: tv2), y: tv3)))) - let gx1: Array = Fq_add(x: Fq_times(x: Fq_square(x: x1), y: x1), y: B) - let gx2: Array = Fq_add(x: Fq_times(x: Fq_square(x: x2), y: x2), y: B) - let gx3: Array = Fq_add(x: Fq_times(x: Fq_square(x: x3), y: x3), y: B) - let mut x: Array = [0,0] - let mut y: Array = [0,0] - if(Fq_is_square(x: gx1)) { - x = x1 - y = Fq_sqrt(x: gx1) - } else if(Fq_is_square(x: gx2)) { - x = x2 - y = Fq_sqrt(x: gx2) - } else { - x = x3 - y = Fq_sqrt(x: gx3) - } - if(Fq_sgn0(val: u) != Fq_sgn0(val: y)) { - y = Fq_neg(x: y) - } - let ans: G2Point = G2Point(x: x, y: y) - assert on_G2(p: ans) - let cofactor: u256 = 21888242871839275222246405745257275088844257914179612981679871602714643921549 - return G2_scalar_mul(p: ans, r: cofactor) -} - -pub fn hash_to_G2(msg: MemoryBuffer, DST: MemoryBuffer) -> G2Point { - return Fq_to_G2(u: hash_to_Fq(msg: msg, DST: DST)) -} - -// This function will improved after Dynamic Arrays -pub fn pairing(buf: MemoryBuffer) -> bool { - let mut output_buf: MemoryBuffer = MemoryBuffer::new(len: 32) - let mut reader: MemoryBufferReader = output_buf.reader() - precompiles::Precompile::EcPairing.call(input_buf: buf, output_buf: output_buf) - return reader.read_u256() == 1 -} - -pub fn pairing2(a1: G1Point, a2: G2Point, - b1: G1Point, b2: G2Point) -> bool { - - let input_len: u256 = 32 * 12 // all writers work now - let mut buf: MemoryBuffer = MemoryBuffer::new(len: input_len) - let mut writer: MemoryBufferWriter = buf.writer() - - writer.write(value: a1.x) - writer.write(value: a1.y) - writer.write(value: a2.x[0]) - writer.write(value: a2.x[1]) - writer.write(value: a2.y[0]) - writer.write(value: a2.y[1]) - writer.write(value: b1.x) - writer.write(value: b1.y) - writer.write(value: b2.x[0]) - writer.write(value: b2.x[1]) - writer.write(value: b2.y[0]) - writer.write(value: b2.y[1]) - - let res: bool = precompiles::ec_pairing(buf: buf) - - return res -} - - -//WARNING: don't put private keys on the blockchain -pub fn generate_priv_key(rnd_src: MemoryBuffer) -> u256 { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - let two_exp256: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - let mut reader: MemoryBufferReader = rnd_src.reader() - let high: u128 = reader.read_u128() - let low: u256 = reader.read_u256() - return(Fp_times(x: high, y: two_exp256) + low%p)%p -} - -//WARNING: don't put private keys on the blockchain -pub fn generate_pub_key(priv_key: u256) -> G1Point { - return scalar_mul(p: G1(), r: priv_key) -} - -// WARNING: don't put private keys on the blockchain -pub fn sign(priv_key: u256, msg: MemoryBuffer, DST: MemoryBuffer) -> G2Point { - return G2_scalar_mul(p: hash_to_G2(msg: msg, DST: DST), r: priv_key) -} - -pub fn verify(pub_key: G1Point, signature: G2Point, msg: MemoryBuffer, DST: MemoryBuffer) -> bool { - return pairing2(a1: negate(p: G1()), a2: signature, - b1: pub_key, b2: hash_to_G2(msg: msg, DST: DST)) -} - -// O + O = O -#test -fn test_bn254_g1_add1() { - let p: G1Point = G1Point(x: 0, y:0); - let q: G1Point = G1Point(x: 0, y:0); - - let res: G1Point = add(p: p, q: q) - - assert res.x == 0 - assert res.y == 0 -} - -// O + G = G -#test -fn test_bn254_g1_add2() { - let p: G1Point = G1Point(x: 0, y:0) - let generator: G1Point = G1() - let res: G1Point = add(p: p, q: generator) - - assert res.x == generator.x - assert res.y == generator.y -} - -// G + (-G) = 0 -#test -fn test_bn254_g1_add3() { - let generator: G1Point = G1() - let res: G1Point = add(p: generator, q: negate(p: generator)) - - assert res.x == 0 - assert res.y == 0 -} - -// G + G = 2*G -#test -fn test_g1_double() { - let generator: G1Point = G1() - let add_generators: G1Point = add(p: generator, q: generator) - let double_generator: G1Point = double(p: generator) - - assert add_generators.x == double_generator.x - assert add_generators.y == double_generator.y -} - -// 2*G + G = 3*G -#test -fn test_bn254_g1_mul1() { - let generator: G1Point = G1() - let two_generator: G1Point = double(p: generator) - let lhs: G1Point = add(p: generator, q: two_generator) - let rhs: G1Point = scalar_mul(p: generator, r: 3) - - assert lhs.x == rhs.x - assert lhs.y == rhs.y -} - -#test -fn test_Fp_exp() { - let x: u256 = G2().x[1] - let sq: u256 = Fp_exp(b: x, e: 2) - assert sq == 19174692470794073501570615532739633251048553737626673800995753633084288986374 -} - -#test -fn test_Fp_times() { - let x: Array = G2().x - let prod: u256 = Fp_times(x: x[0], y: x[1]) - assert prod == 918692451702308434640184054528912234952016508950970653484110815618164541583 -} - -#test -fn test_Fp_sqrt() { - let x: u256 = 2 - let sqrt: u256 = Fp_sqrt(x: x) - assert Fp_exp(b: sqrt, e: 2) == 2 -} - -#test -fn test_Fq_square() { - let x: Array = G2().x - let sq: Array = Fq_square(x: x) - assert sq[1] == 21740980388926906057426892996120422348616989628855161897682971020692097960281 - assert sq[0] == 1837384903404616869280368109057824469904033017901941306968221631236329083166 -} - -#test -fn test_Fq_sqrt() { - let p: u256 = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - let x: Array = [1837384903404616869280368109057824469904033017901941306968221631236329083166, - 21740980388926906057426892996120422348616989628855161897682971020692097960281] - let sqrt: Array = Fq_sqrt(x: x) - assert sqrt[1] == p - G2().x[1] - assert sqrt[0] == p - G2().x[0] -} - -#test -fn test_Fq_inv() { - let x: Array = G2().x - let inv: Array = Fq_inv(x: x) - let res: Array = Fq_times(x: x, y: inv) - assert res[1] == 1 - assert res[0] == 0 -} - -// O + O = O -#test -fn test_bn254_g2_add1() { - let p: G2Point = G2Point(x: [0,0], y:[0,0]); - let q: G2Point = G2Point(x: [0,0], y:[0,0]); - - let res: G2Point = G2_add(p: p, q: q) - - assert res.x[0] == 0 - assert res.x[1] == 0 - assert res.y[0] == 0 - assert res.y[1] == 0 -} - -// O + G = G -#test -fn test_bn254_g2_add2() { - let p: G2Point = G2Point(x: [0,0], y:[0,0]) - let generator: G2Point = G2() - let res: G2Point = G2_add(p: p, q: generator) - - assert Fq_eq(x: res.x, y: generator.x) - assert Fq_eq(x: res.y, y: generator.y) -} - -// G + (-G) = 0 -#test -fn test_bn254_g2_add3() { - let generator: G2Point = G2() - let res: G2Point = G2_add(p: generator, q: G2_negate(p: generator)) - - assert res.x[0] == 0 - assert res.x[1] == 0 - assert res.y[0] == 0 - assert res.y[1] == 0 -} - -// G + G = 2*G -#test -fn test_g2_double() { - let generator: G2Point = G2() - let add_generators: G2Point = G2_add(p: generator, q: generator) - let double_generator: G2Point = G2_double(p: generator) - - assert Fq_eq(x: add_generators.x, y: double_generator.x) - assert Fq_eq(x: add_generators.y, y: double_generator.y) - assert Fq_eq(x: add_generators.x, y: [14583779054894525174450323658765874724019480979794335525732096752006891875705, - 18029695676650738226693292988307914797657423701064905010927197838374790804409]) - assert Fq_eq(x: add_generators.y, y: [11474861747383700316476719153975578001603231366361248090558603872215261634898, - 2140229616977736810657479771656733941598412651537078903776637920509952744750]) -} - -// 2*G + G = 3*G -#test -fn test_bn254_g2_mul1() { - let generator: G2Point = G2() - let two_generator: G2Point = G2_double(p: generator) - let lhs: G2Point = G2_add(p: generator, q: two_generator) - let rhs: G2Point = G2_scalar_mul(p: generator, r: 3) - - assert Fq_eq(x: lhs.x, y: rhs.x) - assert Fq_eq(x: lhs.y, y: rhs.y) - assert Fq_eq(x: lhs.x, y: [7273165102799931111715871471550377909735733521218303035754523677688038059653, - 2725019753478801796453339367788033689375851816420509565303521482350756874229]) - assert Fq_eq(x: lhs.x, y: [7273165102799931111715871471550377909735733521218303035754523677688038059653, - 2725019753478801796453339367788033689375851816420509565303521482350756874229]) -} - -#test -fn test_Fq_to_g2() { - Fq_to_G2(u: [0,1]) - Fq_to_G2(u: [2,3]) - Fq_to_G2(u: [4,5]) - Fq_to_G2(u: [6,7]) - Fq_to_G2(u: [8,9]) - Fq_to_G2(u: [10,11]) - Fq_to_G2(u: [12,13]) - Fq_to_G2(u: [0,0]) -} - -#test -fn test_sign() { - let priv_key: u256 = 13958764668801190265554310723816298225651752288247597460990550719176869043521 - let pub_key: G1Point = generate_pub_key(priv_key) - //since we aparently can't easily put strings into a MemoryBuffer - let mut msg: MemoryBuffer = MemoryBuffer::new(len: 3) - let mut msg_writer: MemoryBufferWriter = msg.writer() - msg_writer.write_n(value: 1,len: 1) - msg_writer.write_n(value: 2,len: 1) - msg_writer.write_n(value: 3,len: 1) - //let DST: MemoryBuffer = "bn254.fe 0.1" - let mut DST: MemoryBuffer = MemoryBuffer::new(len: 3) - let mut DST_writer: MemoryBufferWriter = msg.writer() - DST_writer.write_n(value: 4,len: 1) - DST_writer.write_n(value: 5,len: 1) - DST_writer.write_n(value: 6,len: 1) - let signature: G2Point = sign(priv_key, msg, DST) - assert verify(pub_key, signature, msg, DST) -} diff --git a/crypto/src/lib.fe b/crypto/src/lib.fe index e69de29..f6cc5aa 100644 --- a/crypto/src/lib.fe +++ b/crypto/src/lib.fe @@ -0,0 +1,21 @@ +pub use bn254::{self, *} + +use std::evm::assert + +#[test] +fn test_bn254_negate_infinity() { + let p = G1Point { x: 0, y: 0 } + let q = bn254::negate(p) + assert(q.x == 0 && q.y == 0) +} + +#[test] +fn test_bn254_negate_is_involutive() { + let p = bn254::p1() + let px = p.x + let py = p.y + + let q = bn254::negate(p) + let r = bn254::negate(q) + assert(r.x == px && r.y == py) +} diff --git a/fe.toml b/fe.toml new file mode 100644 index 0000000..1677ced --- /dev/null +++ b/fe.toml @@ -0,0 +1,5 @@ +[workspace] +name = "fe-crypto" +version = "0.1.0" +members = ["crypto", "verifiers"] + diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..6038776 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,11 @@ +[profile.default] +solc = "/usr/bin/solc" +ffi = true + +[profile.default.fuzz] +runs = 1024 + +fs_permissions = [ + { access = "read", path = "./" }, + { access = "read-write", path = "./out" }, +] diff --git a/test/Groth16Bn254Verifier.t.sol b/test/Groth16Bn254Verifier.t.sol new file mode 100644 index 0000000..5b5192f --- /dev/null +++ b/test/Groth16Bn254Verifier.t.sol @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.33; + +import {Groth16Bn254VerifierReference} from "./ref/Groth16Bn254VerifierReference.sol"; +import {BN254} from "./ref/BN254.sol"; + +interface Vm { + function ffi(string[] calldata commandInput) external returns (bytes memory); + function readFile(string calldata path) external returns (string memory); + function expectRevert() external; + function assume(bool condition) external; + function pauseGasMetering() external; + function resumeGasMetering() external; +} + +interface IGroth16Bn254Verifier { + function verifyProof( + uint256[2] calldata a, + uint256[2][2] calldata b, + uint256[2] calldata c, + uint256[1] calldata input + ) external view returns (bool); +} + +contract Groth16Bn254VerifierTest { + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + // SNARK scalar field for BN254 (a.k.a. Fr modulus). + uint256 private constant SNARK_SCALAR_FIELD = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + IGroth16Bn254Verifier private verifier; + IGroth16Bn254Verifier private referenceVerifier; + + function setUp() public { + string[] memory cmd = new string[](7); + cmd[0] = "fe"; + cmd[1] = "build"; + cmd[2] = "--out-dir"; + cmd[3] = "out/fe"; + cmd[4] = "--contract"; + cmd[5] = "Groth16Bn254Verifier"; + cmd[6] = "./verifiers"; + vm.ffi(cmd); + + bytes memory deployCode = _hexStringToBytes(vm.readFile("out/fe/Groth16Bn254Verifier.bin")); + verifier = IGroth16Bn254Verifier(_deploy(deployCode)); + referenceVerifier = IGroth16Bn254Verifier(address(new Groth16Bn254VerifierReference())); + } + + function test_verifyProof_ok() public view { + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) = _demoProof(); + + bool ok = verifier.verifyProof(a, b, c, input); + assert(ok); + } + + function test_referenceVerifier_verifyProof_ok() public view { + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) = _demoProof(); + + bool ok = referenceVerifier.verifyProof(a, b, c, input); + assert(ok); + } + + function test_diff_verifyProof_matchesReference_onDemoProof() public view { + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) = _demoProof(); + + bool okFe = verifier.verifyProof(a, b, c, input); + bool okRef = referenceVerifier.verifyProof(a, b, c, input); + assert(okFe == okRef); + } + + function test_verifyProof_returnsFalse_onWrongPublicInput() public view { + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) = _demoProof(); + + input[0] = 0x22; + bool ok = verifier.verifyProof(a, b, c, input); + assert(!ok); + } + + function test_diff_verifyProof_swappedBLimbs_matchesReference() public view { + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) = _demoProof(); + + // The verifier expects `b` in precompile order `[[x_c1,x_c0],[y_c1,y_c0]]`. + // Swapping limbs converts it to the more "math-y" `[[x_c0,x_c1],[y_c0,y_c1]]` order, which + // should not verify for a real proof (it may return `false` or revert due to invalid points). + uint256[2][2] memory swapped = [[b[0][1], b[0][0]], [b[1][1], b[1][0]]]; + + (bool okFe, bool resFe) = _staticcallVerifyProof(address(verifier), a, swapped, c, input); + (bool okRef, bool resRef) = _staticcallVerifyProof(address(referenceVerifier), a, swapped, c, input); + + assert(okFe == okRef); + if (okFe) { + assert(resFe == resRef); + assert(!resFe); + } + } + + function test_verifyProof_reverts_onOutOfRangePublicInput() public { + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) = _demoProof(); + + input[0] = SNARK_SCALAR_FIELD; + vm.expectRevert(); + verifier.verifyProof(a, b, c, input); + } + + function test_bn254_pairingProd2_generator_cancels() public view { + BN254.G1Point memory g1 = BN254.P1(); + BN254.G2Point memory g2 = BN254.P2(); + bool ok = BN254.pairingProd2(g1, g2, BN254.negate(g1), g2); + assert(ok); + } + + function test_diff_verifyProof_reverts_onOutOfRangePublicInput() public view { + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) = _demoProof(); + + input[0] = SNARK_SCALAR_FIELD; + (bool okFe, ) = _staticcallVerifyProof(address(verifier), a, b, c, input); + (bool okRef, ) = _staticcallVerifyProof(address(referenceVerifier), a, b, c, input); + assert(!okFe); + assert(!okRef); + } + + function test_diff_verifyProof_maxInRangePublicInput_doesNotRevert() public view { + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) = _demoProof(); + + input[0] = SNARK_SCALAR_FIELD - 1; + + (bool okFe, bool resFe) = _staticcallVerifyProof(address(verifier), a, b, c, input); + (bool okRef, bool resRef) = _staticcallVerifyProof(address(referenceVerifier), a, b, c, input); + assert(okFe && okRef); + assert(resFe == resRef); + } + + function testFuzz_diff_verifyProof_raw( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256 publicInput + ) public view { + uint256[1] memory input = [publicInput]; + + (bool okFe, bool resFe) = _staticcallVerifyProof(address(verifier), a, b, c, input); + (bool okRef, bool resRef) = _staticcallVerifyProof(address(referenceVerifier), a, b, c, input); + + require(okFe == okRef, "DIFF: call status"); + if (okFe) { + require(resFe == resRef, "DIFF: result"); + } + } + + function testFuzz_diff_verifyProof_inRange( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256 publicInput + ) public { + vm.assume(publicInput < SNARK_SCALAR_FIELD); + uint256[1] memory input = [publicInput]; + + (bool okFe, bool resFe) = _staticcallVerifyProof(address(verifier), a, b, c, input); + (bool okRef, bool resRef) = _staticcallVerifyProof(address(referenceVerifier), a, b, c, input); + + require(okFe == okRef, "DIFF: call status"); + if (okFe) { + require(resFe == resRef, "DIFF: result"); + } + } + + function testFuzz_diff_verifyProof_validG1Points( + uint256 aScalar, + uint256 cScalar, + uint256 publicInputRaw, + uint8 bChoice + ) public view { + uint256 publicInput = publicInputRaw % SNARK_SCALAR_FIELD; + + BN254.G1Point memory A = BN254.ecMul(BN254.P1(), aScalar % SNARK_SCALAR_FIELD); + BN254.G1Point memory C = BN254.ecMul(BN254.P1(), cScalar % SNARK_SCALAR_FIELD); + + uint256[2] memory a = [A.x, A.y]; + uint256[2] memory c = [C.x, C.y]; + uint256[2][2] memory b = _validB(bChoice); + uint256[1] memory input = [publicInput]; + + (bool okFe, bool resFe) = _staticcallVerifyProof(address(verifier), a, b, c, input); + (bool okRef, bool resRef) = _staticcallVerifyProof(address(referenceVerifier), a, b, c, input); + + require(okFe && okRef, "DIFF: unexpected revert"); + require(resFe == resRef, "DIFF: result"); + } + + function testGas_bench_fe_verifyProof_ok() public { + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) = _demoProof(); + + vm.pauseGasMetering(); + bytes memory callData = abi.encodeWithSelector(IGroth16Bn254Verifier.verifyProof.selector, a, b, c, input); + vm.resumeGasMetering(); + + (bool ok, bool res) = _staticcallBool(address(verifier), callData); + + vm.pauseGasMetering(); + require(ok && res, "FE: demo proof should verify"); + } + + function testGas_bench_solidity_verifyProof_ok() public { + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) = _demoProof(); + + vm.pauseGasMetering(); + bytes memory callData = abi.encodeWithSelector(IGroth16Bn254Verifier.verifyProof.selector, a, b, c, input); + vm.resumeGasMetering(); + + (bool ok, bool res) = _staticcallBool(address(referenceVerifier), callData); + + vm.pauseGasMetering(); + require(ok && res, "Solidity: demo proof should verify"); + } + + function testGas_bench_fe_verifyProof_wrongPublicInput() public { + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) = _demoProof(); + input[0] = 0x22; + + vm.pauseGasMetering(); + bytes memory callData = abi.encodeWithSelector(IGroth16Bn254Verifier.verifyProof.selector, a, b, c, input); + vm.resumeGasMetering(); + + (bool ok, bool res) = _staticcallBool(address(verifier), callData); + + vm.pauseGasMetering(); + require(ok && !res, "FE: wrong input should not verify"); + } + + function testGas_bench_solidity_verifyProof_wrongPublicInput() public { + ( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) = _demoProof(); + input[0] = 0x22; + + vm.pauseGasMetering(); + bytes memory callData = abi.encodeWithSelector(IGroth16Bn254Verifier.verifyProof.selector, a, b, c, input); + vm.resumeGasMetering(); + + (bool ok, bool res) = _staticcallBool(address(referenceVerifier), callData); + + vm.pauseGasMetering(); + require(ok && !res, "Solidity: wrong input should not verify"); + } + + function _demoProof() + private + pure + returns (uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[1] memory input) + { + a = [ + uint256(0x28930e0aeb50e7e3b5f9a54a6abdce99978e00701914dfd4d87f8dc5ea9e1d02), + uint256(0x02c1e99774e679c144aceac4e9fdbc67dc858533d9f49c5933939c89010131b7) + ]; + + // b is provided in precompile order: [[x_c1, x_c0], [y_c1, y_c0]] + b = [ + [ + uint256(0x0684d8357689fb95e886a8251db0e142ffdda8032e314750455b9f5ff13159ca), + uint256(0x26189ebc171412019704e808c432062721db66c4a22635f20ed422a2147ad5bf) + ], + [ + uint256(0x005d309291fd34bef6248c17779114907b6d912a5a02ddae46d902dbd05e2e1c), + uint256(0x01673b4a2e94569e28e23a9ae808b9f92b4d21beca07522f79d16ec419a6e85c) + ] + ]; + + c = [ + uint256(0x25170145c09315e2df3c93d155b39df35434469607b0121a16125224190a596a), + uint256(0x05467081343913d54408694735a8d149578e7cbb3168f2b4283a7fe2861a7a42) + ]; + + input = [uint256(0x21)]; + } + + function _validB(uint8 choice) private pure returns (uint256[2][2] memory b) { + uint8 c = choice % 5; + + // 0: Use the demo proof's B. + if (c == 0) { + (, b, , ) = _demoProof(); + return b; + } + + // 1: verifying_key_beta2 (precompile order). + if (c == 1) { + b = [ + [ + uint256(9138224228407402916103338031673805059436445969293455575712717090470338056266), + uint256(18390029660978007887012397144274200115548124992262045309168725395291020532646) + ], + [ + uint256(13521623794085633987820957758529862860973247929283590310564032250223240671848), + uint256(3861004002262797311682051645188776069221901493726904234127961112799581415639) + ] + ]; + return b; + } + + // 2: verifying_key_gamma2 (precompile order). + if (c == 2) { + b = [ + [ + uint256(11559732032986387107991004021392285783925812861821192530917403151452391805634), + uint256(10857046999023057135944570762232829481370756359578518086990519993285655852781) + ], + [ + uint256(4082367875863433681332203403145435568316851327593401208105741076214120093531), + uint256(8495653923123431417604973247489272438418190587263600148770280649306958101930) + ] + ]; + return b; + } + + // 3: verifying_key_delta2 (precompile order). + if (c == 3) { + b = [ + [ + uint256(3810475614507152687978045945018363916901751186873297176897861851851025646174), + uint256(11517822973057293310299905404808144348048624735019551898183051528663733638390) + ], + [ + uint256(7205787144229011189765531580637096098035200993493430523391610777645930427053), + uint256(18901678484212115678597129685298885730350402277813795965474832351751410499421) + ] + ]; + return b; + } + + // 4: BN254 G2 generator (precompile order). + BN254.G2Point memory p2 = BN254.P2(); + b = [[p2.x[1], p2.x[0]], [p2.y[1], p2.y[0]]]; + } + + function _staticcallVerifyProof( + address target, + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[1] memory input + ) private view returns (bool ok, bool result) { + (bool success, bytes memory data) = target.staticcall( + abi.encodeWithSelector(IGroth16Bn254Verifier.verifyProof.selector, a, b, c, input) + ); + if (!success) { + return (false, false); + } + if (data.length != 32) { + return (false, false); + } + return (true, abi.decode(data, (bool))); + } + + function _staticcallBool(address target, bytes memory callData) private view returns (bool ok, bool result) { + uint256 out; + assembly ("memory-safe") { + ok := staticcall(gas(), target, add(callData, 0x20), mload(callData), 0x00, 0x20) + out := mload(0x00) + } + + return (ok, ok && out != 0); + } + + function _deploy(bytes memory creationCode) private returns (address deployed) { + assembly ("memory-safe") { + deployed := create(0, add(creationCode, 0x20), mload(creationCode)) + } + require(deployed != address(0), "DEPLOY_FAILED"); + } + + function _hexStringToBytes(string memory s) private pure returns (bytes memory) { + bytes memory strBytes = bytes(s); + uint256 start = 0; + uint256 end = strBytes.length; + + while (start < end && _isWhitespace(strBytes[start])) { + start++; + } + while (end > start && _isWhitespace(strBytes[end - 1])) { + end--; + } + + if (end >= start + 2 && strBytes[start] == 0x30 && (strBytes[start + 1] == 0x78 || strBytes[start + 1] == 0x58)) { + start += 2; + } + + uint256 hexLen = end - start; + require(hexLen % 2 == 0, "HEX_ODD_LENGTH"); + + bytes memory out = new bytes(hexLen / 2); + for (uint256 i = 0; i < out.length; i++) { + out[i] = bytes1((_fromHexChar(strBytes[start + 2 * i]) << 4) | _fromHexChar(strBytes[start + 2 * i + 1])); + } + return out; + } + + function _isWhitespace(bytes1 c) private pure returns (bool) { + return c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d; + } + + function _fromHexChar(bytes1 c) private pure returns (uint8) { + uint8 b = uint8(c); + if (b >= 48 && b <= 57) { + return b - 48; + } + if (b >= 65 && b <= 70) { + return b - 55; + } + if (b >= 97 && b <= 102) { + return b - 87; + } + revert("HEX_BAD_CHAR"); + } +} diff --git a/test/ref/BN254.sol b/test/ref/BN254.sol new file mode 100644 index 0000000..cdc8664 --- /dev/null +++ b/test/ref/BN254.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/// @title BN254 (alt_bn128) precompile wrappers for Ethereum +/// @notice Minimal, production-style wrappers around EIP-196 (ECADD/ECMUL) and EIP-197 (PAIRING). +/// @dev +/// - ECADD: address(0x06) +/// - ECMUL: address(0x07) +/// - ECPAIRING address(0x08) +/// - Input encoding for G2 points in the pairing precompile requires reversing Fp2 coefficients. +/// This library follows the common convention where G2.X/Y are stored as [c0, c1] but encoded as [c1, c0]. +library BN254 { + uint256 internal constant FIELD_MODULUS = + 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + struct G1Point { + uint256 x; + uint256 y; + } + + /// @dev Fp2 element is represented as [c0, c1] corresponding to c0 + c1 * i. + struct G2Point { + uint256[2] x; + uint256[2] y; + } + + function P1() internal pure returns (G1Point memory) { + return G1Point(1, 2); + } + + function P2() internal pure returns (G2Point memory) { + return G2Point( + [ + uint256( + 10857046999023057135944570762232829481370756359578518086990519993285655852781 + ), + uint256( + 11559732032986387107991004021392285783925812861821192530917403151452391805634 + ) + ], + [ + uint256( + 8495653923123431417604973247489272438418190587263600148770280649306958101930 + ), + uint256( + 4082367875863433681332203403145435568316851327593401208105741076214120093531 + ) + ] + ); + } + + function negate(G1Point memory p) internal pure returns (G1Point memory) { + if (p.x == 0 && p.y == 0) { + return G1Point(0, 0); + } + // y = -y mod q + uint256 y = p.y % FIELD_MODULUS; + return G1Point(p.x, y == 0 ? 0 : FIELD_MODULUS - y); + } + + function ecAdd(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { + uint256[4] memory input = [p1.x, p1.y, p2.x, p2.y]; + bool ok; + assembly { + // call ecadd precompile (0x06) + ok := staticcall(gas(), 0x06, input, 0x80, r, 0x40) + } + require(ok, "BN254: ecAdd failed"); + } + + function ecMul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { + uint256[3] memory input = [p.x, p.y, s]; + bool ok; + assembly { + // call ecmul precompile (0x07) + ok := staticcall(gas(), 0x07, input, 0x60, r, 0x40) + } + require(ok, "BN254: ecMul failed"); + } + + /// @notice Pairing check for arrays of points. + /// @dev Returns true if product_i e(p1[i], p2[i]) == 1 in Fp12. + function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) { + require(p1.length == p2.length, "BN254: length mismatch"); + uint256 elements = p1.length; + uint256 inputSize = elements * 6; + uint256[] memory input = new uint256[](inputSize); + + for (uint256 i = 0; i < elements; i++) { + uint256 o = i * 6; + input[o + 0] = p1[i].x; + input[o + 1] = p1[i].y; + // IMPORTANT: reverse Fp2 coefficients for the precompile encoding. + input[o + 2] = p2[i].x[1]; + input[o + 3] = p2[i].x[0]; + input[o + 4] = p2[i].y[1]; + input[o + 5] = p2[i].y[0]; + } + + uint256[1] memory out; + bool ok; + assembly { + // call pairing precompile (0x08) + ok := staticcall(gas(), 0x08, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) + } + require(ok, "BN254: pairing call failed"); + return out[0] != 0; + } + + function pairingProd2( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](2); + G2Point[] memory p2 = new G2Point[](2); + p1[0] = a1; + p1[1] = b1; + p2[0] = a2; + p2[1] = b2; + return pairing(p1, p2); + } + + function pairingProd3( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2, + G1Point memory c1, + G2Point memory c2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](3); + G2Point[] memory p2 = new G2Point[](3); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + return pairing(p1, p2); + } + + function pairingProd4( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2, + G1Point memory c1, + G2Point memory c2, + G1Point memory d1, + G2Point memory d2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](4); + G2Point[] memory p2 = new G2Point[](4); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p1[3] = d1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + p2[3] = d2; + return pairing(p1, p2); + } +} diff --git a/test/ref/Groth16Bn254VerifierReference.sol b/test/ref/Groth16Bn254VerifierReference.sol new file mode 100644 index 0000000..e06a768 --- /dev/null +++ b/test/ref/Groth16Bn254VerifierReference.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.33; + +import {BN254} from "./BN254.sol"; + +/// @title Groth16 BN254 reference verifier (demo VK) +/// @notice Solidity reference implementation used for differential fuzzing vs the Fe verifier. +/// @dev Verifying key constants mirror `verifiers/src/groth16_bn254.fe`. +contract Groth16Bn254VerifierReference { + // SNARK scalar field for BN254 (a.k.a. Fr modulus). + uint256 private constant SNARK_SCALAR_FIELD = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + function verifyingKeyAlpha1() internal pure returns (BN254.G1Point memory) { + return + BN254.G1Point( + 18986994054831033570197018374507745079604334824038474376810821382341658341358, + 9857034825708937828129722307559364555144967863984840417006236609097843967147 + ); + } + + function verifyingKeyBeta2() internal pure returns (BN254.G2Point memory) { + return + BN254.G2Point( + [ + uint256( + 18390029660978007887012397144274200115548124992262045309168725395291020532646 + ), + uint256( + 9138224228407402916103338031673805059436445969293455575712717090470338056266 + ) + ], + [ + uint256( + 3861004002262797311682051645188776069221901493726904234127961112799581415639 + ), + uint256( + 13521623794085633987820957758529862860973247929283590310564032250223240671848 + ) + ] + ); + } + + function verifyingKeyGamma2() internal pure returns (BN254.G2Point memory) { + return + BN254.G2Point( + [ + uint256( + 10857046999023057135944570762232829481370756359578518086990519993285655852781 + ), + uint256( + 11559732032986387107991004021392285783925812861821192530917403151452391805634 + ) + ], + [ + uint256( + 8495653923123431417604973247489272438418190587263600148770280649306958101930 + ), + uint256( + 4082367875863433681332203403145435568316851327593401208105741076214120093531 + ) + ] + ); + } + + function verifyingKeyDelta2() internal pure returns (BN254.G2Point memory) { + return + BN254.G2Point( + [ + uint256( + 11517822973057293310299905404808144348048624735019551898183051528663733638390 + ), + uint256( + 3810475614507152687978045945018363916901751186873297176897861851851025646174 + ) + ], + [ + uint256( + 18901678484212115678597129685298885730350402277813795965474832351751410499421 + ), + uint256( + 7205787144229011189765531580637096098035200993493430523391610777645930427053 + ) + ] + ); + } + + function verifyingKeyIC(uint256 i) internal pure returns (BN254.G1Point memory) { + if (i == 0) { + return + BN254.G1Point( + 67521893739652156791009141370073344821154292280368295170345470354897934196, + 17040597570089860980787757123222393819903832599488360597826421841263128988811 + ); + } + if (i == 1) { + return + BN254.G1Point( + 6621938124027639260974226744038109301855429178070752528844179929793898435285, + 2291636765942087387769041560925446553420342985960064087254817725377189124295 + ); + } + revert("Groth16: bad IC index"); + } + + /// @notice Verify a Groth16 proof against the demo verifying key. + /// @dev `b` is provided in pairing-precompile order: `[[x_c1, x_c0], [y_c1, y_c0]]`. + function verifyProof( + uint256[2] calldata a, + uint256[2][2] calldata b, + uint256[2] calldata c, + uint256[1] calldata input + ) external view returns (bool) { + if (input[0] >= SNARK_SCALAR_FIELD) { + revert("Groth16: input out of range"); + } + + BN254.G1Point memory A = BN254.G1Point(a[0], a[1]); + BN254.G2Point memory B = BN254.G2Point([b[0][1], b[0][0]], [b[1][1], b[1][0]]); + BN254.G1Point memory C = BN254.G1Point(c[0], c[1]); + + // vk_x = IC[0] + input[0] * IC[1] + BN254.G1Point memory vk_x = BN254.ecAdd(verifyingKeyIC(0), BN254.ecMul(verifyingKeyIC(1), input[0])); + + return + BN254.pairingProd4( + BN254.negate(A), + B, + verifyingKeyAlpha1(), + verifyingKeyBeta2(), + vk_x, + verifyingKeyGamma2(), + C, + verifyingKeyDelta2() + ); + } +} + diff --git a/verifiers/README.md b/verifiers/README.md index 2723ad8..634bf66 100644 --- a/verifiers/README.md +++ b/verifiers/README.md @@ -1,43 +1,15 @@ -# Groth16 Verifier in fe-lang +# Groth16 verifier (BN254) in Fe -Groth16 Verifier in fe-lang allows you to verify circom circuits using [snarkjs][https://github.com/iden3/snarkjs] +This ingot contains a Groth16 verifier for BN254. -Using the groth16.fe, you will be able to leverage zero-knowledge proofs with fe-lang +## Layout +- Verifier module (demo VK): `verifiers/src/groth16_bn254.fe` +- Public exports + contract wrapper: `verifiers/src/lib.fe` -## How to use the verifier in your fe-lang project? - -As we mention above, you will be able to verify circom circuits in your fe-lang project. [This page] [https://docs.circom.io/getting-started/proving-circuits/] provides an information on how to prove circuits with zero-knowledge. - -To be able to use groth16 verifier in your project, you need to write your zero-knowledge circuits in circom (other zero-knowledge frameworks will be provided later) - -1. Clone snarkjs repo -`git clone https://github.com/iden3/snarkjs.git` or clone the fork: https://github.com/onurinanc/snarkjs - -2. Go to the local repository - -3. Find snarkjs/templates/verifier_groth16.sol.ejs - -4. Delete all the lines inside verifier_groth16.sol.ejs - -5. Copy all the lines inside groth16.fe and paste it into verifier_groth16.sol.ejs - -6. Using the [circom documentation] [https://docs.circom.io/getting-started/proving-circuits/#verifying-a-proof], you should use all the instructions until this part, [Verifying from a Smart Contract] [https://docs.circom.io/getting-started/proving-circuits/#verifying-a-proof] - -**Note:** instead of `snarkjs` command use `~/snarkjs/cli.js` as the command - -7. In the last step change `verifier.sol` with `verifier.fe` using the following command: - -`~/snarkjs/cli.js zkey export solidityverifier multiplier2_0001.zkey verifier.fe` - -8. Copy all the content inside the Verifier.fe file into verifers/src/main.fe - -9. You are able to crate groth16 verifier in fe-lang! - -10. To generate the verifier inputs, use `~/snarkjs/cli.js generatecall` - -11. Insert the verifier inputs to `verifyProof()` function in `verifier.fe` file +## Adapting to your circuit +`verifiers/src/groth16_bn254.fe` is circuit-specific: replace the `verifying_key_*` constants (including `verifying_key_ic`) with the values generated for your circuit. ## Disclaimer: **This implementation has not been reviewed or audited. Use at your own risk.** -We do not give any warranties and will not be liable for any losses incurred through any use of this code base. \ No newline at end of file +We do not give any warranties and will not be liable for any losses incurred through any use of this code base. diff --git a/verifiers/fe.toml b/verifiers/fe.toml index 93b72d4..1f6294a 100644 --- a/verifiers/fe.toml +++ b/verifiers/fe.toml @@ -1,6 +1,7 @@ +# Ingot config +[ingot] name = "verifiers" -version = "1.0" +version = "0.1.0" [dependencies] -crypto = "../crypto" -# my_lib = { path = "../my_lib", version = "1.0" } \ No newline at end of file +crypto = true diff --git a/verifiers/src/groth16.fe.template b/verifiers/src/groth16.fe.template deleted file mode 100644 index f99d078..0000000 --- a/verifiers/src/groth16.fe.template +++ /dev/null @@ -1,147 +0,0 @@ -use std::buf::{MemoryBuffer, MemoryBufferWriter, MemoryBufferReader} -use std::precompiles -use crypto::curve::bn254::{G1Point, G2Point, G1, G2, negate, add, scalar_mul} - -struct VerifyingKey { - pub alfa1: G1Point - pub beta2: G2Point - pub gamma2: G2Point - pub delta2: G2Point - pub IC: Array> -} - -struct Proof { - pub A: G1Point - pub B: G2Point - pub C: G1Point -} - -fn verifyingKey() -> VerifyingKey { - let alfa1: G1Point = G1Point( - x: <%=vk_alpha_1[0]%>, - y: <%=vk_alpha_1[1]%> - ) - - let beta2: G2Point = G2Point( - x: [<%=vk_beta_2[0][1]%>, - <%=vk_beta_2[0][0]%>], - y: [<%=vk_beta_2[1][1]%>, - <%=vk_beta_2[1][0]%>] - ) - - let gamma2: G2Point = G2Point( - x: [<%=vk_gamma_2[0][1]%>, - <%=vk_gamma_2[0][0]%>], - y: [<%=vk_gamma_2[1][1]%>, - <%=vk_gamma_2[1][0]%>] - ) - - let delta2: G2Point = G2Point( - x: [<%=vk_delta_2[0][1]%>, - <%=vk_delta_2[0][0]%>], - y: [<%=vk_delta_2[1][1]%>, - <%=vk_delta_2[1][0]%>] - ) - - let IC: Array> = [ - <% for (let i=0; i - G1Point( - x: <%=IC[i][0]%>, - y: <%=IC[i][1]%> - ), - <% } %> - - G1Point( - x: <%=IC[IC.length - 1][0]%>, - y: <%=IC[IC.length - 1][1]%> - ) - ] - - return VerifyingKey( - alfa1: alfa1, - beta2: beta2, - gamma2: gamma2, - delta2: delta2, - IC: IC - ) -} - -pub fn pairing(a1: G1Point, a2: G2Point, - b1: G1Point, b2: G2Point, - c1: G1Point, c2: G2Point, - d1: G1Point, d2: G2Point) -> bool { - - let input_len: u256 = 32 * 24 - let mut buf: MemoryBuffer = MemoryBuffer::new(len: input_len) - let mut writer: MemoryBufferWriter = buf.writer() - - writer.write(value: a1.x) - writer.write(value: a1.y) - writer.write(value: a2.x[0]) - writer.write(value: a2.x[1]) - writer.write(value: a2.y[0]) - writer.write(value: a2.y[1]) - writer.write(value: b1.x) - writer.write(value: b1.y) - writer.write(value: b2.x[0]) - writer.write(value: b2.x[1]) - writer.write(value: b2.y[0]) - writer.write(value: b2.y[1]) - writer.write(value: c1.x) - writer.write(value: c1.y) - writer.write(value: c2.x[0]) - writer.write(value: c2.x[1]) - writer.write(value: c2.y[0]) - writer.write(value: c2.y[1]) - writer.write(value: d1.x) - writer.write(value: d1.y) - writer.write(value: d2.x[0]) - writer.write(value: d2.x[1]) - writer.write(value: d2.y[0]) - writer.write(value: d2.y[1]) - - let res: bool = precompiles::ec_pairing(buf: buf) - - return res -} - -pub fn verifyProof(a: Array, - b: Array, 2>, - c: Array, - input: Array>) -> bool { - - let proof: Proof = Proof( - A: G1Point(x: a[0], y: a[1]), - B: G2Point(x: [b[0][0], b[0][1]], y: [b[1][0], b[1][1]]), - C: G1Point(x: c[0], y: c[1])) - - let vk: VerifyingKey = verifyingKey() - - let snark_scalar_field: u256 = 21888242871839275222246405745257275088548364400416034343698204186575808495617 - - let mut vk_x: G1Point = G1Point(x: 0, y: 0) - vk_x = add(p: vk_x, q: vk.IC[0]) - - let mut i: u256 = 0 - - while i < 1 { - assert input[i] < snark_scalar_field - let mul_temp: G1Point = scalar_mul(p: vk.IC[i+1], r: input[i]) - vk_x = add(p: vk_x, q: mul_temp) - i += 1 - } - - let neg_proofA: G1Point = negate(p: proof.A) - let res: bool = pairing( - a1: neg_proofA, - a2: proof.B, - b1: vk.alfa1, - b2: vk.beta2, - c1: vk_x, - c2: vk.gamma2, - d1: proof.C, - d2: vk.delta2 - ) - - return res -} \ No newline at end of file diff --git a/verifiers/src/groth16_bn254.fe b/verifiers/src/groth16_bn254.fe new file mode 100644 index 0000000..03fd7ed --- /dev/null +++ b/verifiers/src/groth16_bn254.fe @@ -0,0 +1,109 @@ +use crypto::bn254 +use crypto::bn254::{G1Point, G2Point} + +const SNARK_SCALAR_FIELD: u256 = 21888242871839275222246405745257275088548364400416034343698204186575808495617 + +pub struct Proof { pub a: G1Point, pub b: G2Point, pub c: G1Point } + +pub fn verifying_key_alpha1() -> G1Point { + G1Point { + x: 18986994054831033570197018374507745079604334824038474376810821382341658341358, + y: 9857034825708937828129722307559364555144967863984840417006236609097843967147, + } +} + +pub fn verifying_key_beta2() -> G2Point { + // NOTE: Values are stored as (c0, c1) and are encoded for the precompile as (c1, c0). + G2Point { + x_c0: 18390029660978007887012397144274200115548124992262045309168725395291020532646, + x_c1: 9138224228407402916103338031673805059436445969293455575712717090470338056266, + y_c0: 3861004002262797311682051645188776069221901493726904234127961112799581415639, + y_c1: 13521623794085633987820957758529862860973247929283590310564032250223240671848, + } +} + +pub fn verifying_key_gamma2() -> G2Point { + G2Point { + x_c0: 10857046999023057135944570762232829481370756359578518086990519993285655852781, + x_c1: 11559732032986387107991004021392285783925812861821192530917403151452391805634, + y_c0: 8495653923123431417604973247489272438418190587263600148770280649306958101930, + y_c1: 4082367875863433681332203403145435568316851327593401208105741076214120093531, + } +} + +pub fn verifying_key_delta2() -> G2Point { + G2Point { + x_c0: 11517822973057293310299905404808144348048624735019551898183051528663733638390, + x_c1: 3810475614507152687978045945018363916901751186873297176897861851851025646174, + y_c0: 18901678484212115678597129685298885730350402277813795965474832351751410499421, + y_c1: 7205787144229011189765531580637096098035200993493430523391610777645930427053, + } +} + +pub fn verifying_key_ic(i: u256) -> G1Point { + if i == 0 { + return G1Point { + x: 67521893739652156791009141370073344821154292280368295170345470354897934196, + y: 17040597570089860980787757123222393819903832599488360597826421841263128988811, + } + } + if i == 1 { + return G1Point { + x: 6621938124027639260974226744038109301855429178070752528844179929793898435285, + y: 2291636765942087387769041560925446553420342985960064087254817725377189124295, + } + } + + // IC only has 2 points in this verifier. + std::evm::ops::revert(0, 0) +} + +pub fn verify(proof: Proof, public_input: u256) -> bool { + if public_input >= SNARK_SCALAR_FIELD { + std::evm::ops::revert(0, 0) + } + + let vk_x = bn254::ec_add( + verifying_key_ic(0), + bn254::ec_mul(verifying_key_ic(1), public_input), + ) + + bn254::pairing_prod4( + bn254::negate(proof.a), + proof.b, + verifying_key_alpha1(), + verifying_key_beta2(), + vk_x, + verifying_key_gamma2(), + proof.c, + verifying_key_delta2(), + ) +} + +pub fn verify_proof(proof: Proof, public_input: u256) -> bool { + verify(proof, public_input) +} + +/// Compatibility wrapper matching the v1 signature / snarkjs calldata shape. +/// +/// The `b` point is provided in precompile order: `[[x_c1, x_c0], [y_c1, y_c0]]`. +pub fn verifyProof(a: [u256; 2], b: [[u256; 2]; 2], c: [u256; 2], input: [u256; 1]) -> bool { + let proof = Proof { + a: G1Point { + x: a[0], + y: a[1], + }, + b: G2Point { + x_c0: b[0][1], + x_c1: b[0][0], + y_c0: b[1][1], + y_c1: b[1][0], + }, + c: G1Point { + x: c[0], + y: c[1], + }, + } + + verify(proof, input[0]) +} diff --git a/verifiers/src/lib.fe b/verifiers/src/lib.fe new file mode 100644 index 0000000..9d866e7 --- /dev/null +++ b/verifiers/src/lib.fe @@ -0,0 +1,41 @@ +pub use groth16_bn254::{self, *} + +msg Groth16Bn254VerifierMsg { + // verifyProof(uint256[2],uint256[2][2],uint256[2],uint256[1]) + #[selector = 0x43753b4d] + VerifyProof { + a_x: u256, + a_y: u256, + b_x_c1: u256, + b_x_c0: u256, + b_y_c1: u256, + b_y_c0: u256, + c_x: u256, + c_y: u256, + input: u256, + } -> bool, +} + +/// Stateless contract wrapper for the demo verifying key in `groth16_bn254`. +pub contract Groth16Bn254Verifier { + recv Groth16Bn254VerifierMsg { + VerifyProof { + a_x, + a_y, + b_x_c1, + b_x_c0, + b_y_c1, + b_y_c0, + c_x, + c_y, + input, + } -> bool { + groth16_bn254::verifyProof( + a: [a_x, a_y], + b: [[b_x_c1, b_x_c0], [b_y_c1, b_y_c0]], + c: [c_x, c_y], + input: [input], + ) + } + } +} diff --git a/verifiers/src/main.fe b/verifiers/src/main.fe deleted file mode 100644 index ea04036..0000000 --- a/verifiers/src/main.fe +++ /dev/null @@ -1,180 +0,0 @@ -use std::buf::{MemoryBuffer, MemoryBufferWriter, MemoryBufferReader} -use std::precompiles -use crypto::curve::bn254::{G1Point, G2Point, G1, G2, negate, add, scalar_mul} - -contract Main { - pub fn verify(self) -> u256 { - let verification: bool = verifyProof( - a: [0x28930e0aeb50e7e3b5f9a54a6abdce99978e00701914dfd4d87f8dc5ea9e1d02, 0x02c1e99774e679c144aceac4e9fdbc67dc858533d9f49c5933939c89010131b7], - b: [[0x0684d8357689fb95e886a8251db0e142ffdda8032e314750455b9f5ff13159ca, 0x26189ebc171412019704e808c432062721db66c4a22635f20ed422a2147ad5bf], [0x005d309291fd34bef6248c17779114907b6d912a5a02ddae46d902dbd05e2e1c, 0x01673b4a2e94569e28e23a9ae808b9f92b4d21beca07522f79d16ec419a6e85c]], - c: [0x25170145c09315e2df3c93d155b39df35434469607b0121a16125224190a596a, 0x05467081343913d54408694735a8d149578e7cbb3168f2b4283a7fe2861a7a42], - input: [0x0000000000000000000000000000000000000000000000000000000000000021]) - - if(verification == true) { - return 1 - } else { - return 0 - } - } -} - -struct VerifyingKey { - pub alfa1: G1Point - pub beta2: G2Point - pub gamma2: G2Point - pub delta2: G2Point - pub IC: Array -} - -struct Proof { - pub A: G1Point - pub B: G2Point - pub C: G1Point -} - -fn verifyingKey() -> VerifyingKey { - let alfa1: G1Point = G1Point( - x: 18986994054831033570197018374507745079604334824038474376810821382341658341358, - y: 9857034825708937828129722307559364555144967863984840417006236609097843967147 - ) - - let beta2: G2Point = G2Point( - x: [9138224228407402916103338031673805059436445969293455575712717090470338056266, - 18390029660978007887012397144274200115548124992262045309168725395291020532646], - y: [13521623794085633987820957758529862860973247929283590310564032250223240671848, - 3861004002262797311682051645188776069221901493726904234127961112799581415639] - ) - - let gamma2: G2Point = G2Point( - x: [11559732032986387107991004021392285783925812861821192530917403151452391805634, - 10857046999023057135944570762232829481370756359578518086990519993285655852781], - y: [4082367875863433681332203403145435568316851327593401208105741076214120093531, - 8495653923123431417604973247489272438418190587263600148770280649306958101930] - ) - - let delta2: G2Point = G2Point( - x: [3810475614507152687978045945018363916901751186873297176897861851851025646174, - 11517822973057293310299905404808144348048624735019551898183051528663733638390], - y: [7205787144229011189765531580637096098035200993493430523391610777645930427053, - 18901678484212115678597129685298885730350402277813795965474832351751410499421] - ) - - let IC: Array = [ - - G1Point( - x: 67521893739652156791009141370073344821154292280368295170345470354897934196, - y: 17040597570089860980787757123222393819903832599488360597826421841263128988811 - ), - - - G1Point( - x: 6621938124027639260974226744038109301855429178070752528844179929793898435285, - y: 2291636765942087387769041560925446553420342985960064087254817725377189124295 - ) - ] - - return VerifyingKey( - alfa1: alfa1, - beta2: beta2, - gamma2: gamma2, - delta2: delta2, - IC: IC - ) -} - -pub fn pairing(a1: G1Point, a2: G2Point, - b1: G1Point, b2: G2Point, - c1: G1Point, c2: G2Point, - d1: G1Point, d2: G2Point) -> bool { - - let input_len: u256 = 32 * 24 // all writers work now - let mut buf: MemoryBuffer = MemoryBuffer::new(len: input_len) - let mut writer: MemoryBufferWriter = buf.writer() - - writer.write(value: a1.x) - writer.write(value: a1.y) - writer.write(value: a2.x[0]) - writer.write(value: a2.x[1]) - writer.write(value: a2.y[0]) - writer.write(value: a2.y[1]) - writer.write(value: b1.x) - writer.write(value: b1.y) - writer.write(value: b2.x[0]) - writer.write(value: b2.x[1]) - writer.write(value: b2.y[0]) - writer.write(value: b2.y[1]) - writer.write(value: c1.x) - writer.write(value: c1.y) - writer.write(value: c2.x[0]) - writer.write(value: c2.x[1]) - writer.write(value: c2.y[0]) - writer.write(value: c2.y[1]) - writer.write(value: d1.x) - writer.write(value: d1.y) - writer.write(value: d2.x[0]) - writer.write(value: d2.x[1]) - writer.write(value: d2.y[0]) - writer.write(value: d2.y[1]) - - let res: bool = precompiles::ec_pairing(buf: buf) - - return res -} - -pub fn verifyProof(a: Array, - b: Array, 2>, - c: Array, - input: Array) -> bool { - - let proof: Proof = Proof( - A: G1Point(x: a[0], y: a[1]), - B: G2Point(x: [b[0][0], b[0][1]], y: [b[1][0], b[1][1]]), - C: G1Point(x: c[0], y: c[1])) - - let vk: VerifyingKey = verifyingKey() - - let snark_scalar_field: u256 = 21888242871839275222246405745257275088548364400416034343698204186575808495617 - - let mut vk_x: G1Point = G1Point(x: 0, y: 0) - vk_x = add(p: vk_x, q: vk.IC[0]) - - let mut i: u256 = 0 - - while i < 1 { - assert input[i] < snark_scalar_field - let mul_temp: G1Point = scalar_mul(p: vk.IC[i+1], r: input[i]) - vk_x = add(p: vk_x, q: mul_temp) - i += 1 - } - - let neg_proofA: G1Point = negate(p: proof.A) - - let res: bool = pairing( - a1: neg_proofA, - a2: proof.B, - b1: vk.alfa1, - b2: vk.beta2, - c1: vk_x, - c2: vk.gamma2, - d1: proof.C, - d2: vk.delta2 - ) - - return res -} - -// This tests verify the proof that we are able to factor -// the number 33 without revealing its inputs (3 * 11) - -#test -fn test_verify_proof() { - let verification: bool = verifyProof( - a: [0x28930e0aeb50e7e3b5f9a54a6abdce99978e00701914dfd4d87f8dc5ea9e1d02, 0x02c1e99774e679c144aceac4e9fdbc67dc858533d9f49c5933939c89010131b7], - b: [[0x0684d8357689fb95e886a8251db0e142ffdda8032e314750455b9f5ff13159ca, 0x26189ebc171412019704e808c432062721db66c4a22635f20ed422a2147ad5bf], [0x005d309291fd34bef6248c17779114907b6d912a5a02ddae46d902dbd05e2e1c, 0x01673b4a2e94569e28e23a9ae808b9f92b4d21beca07522f79d16ec419a6e85c]], - c: [0x25170145c09315e2df3c93d155b39df35434469607b0121a16125224190a596a, 0x05467081343913d54408694735a8d149578e7cbb3168f2b4283a7fe2861a7a42], - input: [0x0000000000000000000000000000000000000000000000000000000000000021] - ) - - assert verification == true -} -