├── .gitmodules ├── README.md ├── asset ├── Prover_success.png └── Verifier_success.png ├── groth16.py └── verifier_contract ├── .github └── workflows │ └── test.yml ├── .gitignore ├── README.md ├── foundry.toml ├── script └── Counter.s.sol ├── src └── VerifierPairingCheck.sol └── test └── VerifierPairingCheck.sol /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "verifier_contract/lib/forge-std"] 2 | path = verifier_contract/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Groth16 2 | 3 | Groth16 implementation in Python. Final project of RareSkills ZK bootcamp cohort 8. 4 | 5 | ## Instructions 6 | 7 | Run trusted setup and prover steps: 8 | 9 | ```shell 10 | python3 groth16.py 11 | ``` 12 | 13 | Run verifier steps: 14 | 15 | ```shell 16 | cd verifier_contract 17 | 18 | forge test 19 | ``` 20 | 21 | Proof of concept: 22 | 23 | ![prover success](./asset/Prover_success.png) 24 | 25 | ![verifier success](./asset/Verifier_success.png) 26 | 27 | ## Design 28 | 29 | To make the engineering part easier, this groth16 implementation only works for a simple graph bipartite problem. Imagine there are 4 nodes x1, x2, x3, x4. x1 and x3 on top and x2 and x4 on bottom. Our goal is to prove that x1/x3 are in a "group" and x2/x4 are in the other "group". 30 | 31 | Another way to view this problem is, we color all vertices with 2 colors and make sure vertices from different "groups" have different colors. For example, we can color x1/x3 as red and x2/x4 as green. 32 | 33 | We turn this problem into arithmetic circuits by hand, also build R1CS matrices by hand. The program starts here, and in the end prover produces a succicct proof and verifier can verify it efficiently, in a zero knowledge way. 34 | -------------------------------------------------------------------------------- /asset/Prover_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ret2basic/Groth16/96a3424b12f9cf76ac733bc0390f68f0692ceaae/asset/Prover_success.png -------------------------------------------------------------------------------- /asset/Verifier_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ret2basic/Groth16/96a3424b12f9cf76ac733bc0390f68f0692ceaae/asset/Verifier_success.png -------------------------------------------------------------------------------- /groth16.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import galois 3 | from functools import reduce 4 | from py_ecc.bn128 import G1, G2, multiply, add, curve_order, Z1, pairing, neg, final_exponentiate, FQ12 5 | 6 | # curve_order = 1151 7 | GF = galois.GF(curve_order) # we work with bn128/bn254 curve 8 | 9 | # tau is a random point -> we evaluate the polynomial at p(tau) 10 | # alpha and beta are two random numbers generated by trusted setup 11 | # gamma and delta are two random numbers to hide public input and private input 12 | # In pratice all the following numbers should be random and only known by trusted setup 13 | tau = GF(123) 14 | alpha = GF(456) 15 | beta = GF(789) 16 | gamma = GF(135) 17 | delta = GF(246) 18 | r = GF(11) 19 | s = GF(22) 20 | 21 | """ 22 | Suppose we have a simple graph below that we wish to prove is bipartite (can be two colored). 23 | Say there are 4 vertices x1, x2, x3, x4. 24 | Imagine x1 and x3 are on the top and x2 and x4 are on the bottom. 25 | 26 | 27 | First we need to convert this problem to arithmetic circuit: 28 | https://www.rareskills.io/post/arithmetic-circuit 29 | 30 | Constraint 1: each node can be colored 1 or 2 31 | (x1 - 1) * (x1 - 2) = 0 -> x1 has color 1 or 2 32 | (x2 - 1) * (x2 - 2) = 0 -> x2 has color 1 or 2 33 | (x3 - 1) * (x3 - 2) = 0 -> x3 has color 1 or 2 34 | (x4 - 1) * (x4 - 2) = 0 -> x4 has color 1 or 2 35 | 36 | Constraint 2: 37 | x1 * x2 - 2 = 0 -> x1 and x2 should have different colors 38 | x1 * x4 - 2 = 0 -> x1 and x4 should have different colors 39 | x2 * x3 - 2 = 0 -> x2 and x3 should have different colors 40 | Note that "x3 and x4 should have different colors" is implicitly implied. 41 | 42 | 43 | Next we turn arithmetic circuits into R1CS, making sure each relation only has one multiplication: 44 | 45 | x1 * x1 = 3x1 - 2 46 | x2 * x2 = 3x2 - 2 47 | x3 * x3 = 3x3 - 2 48 | x4 * x4 = 3x4 - 2 49 | x1 * x2 = 2 50 | x1 * x4 = 2 51 | x2 * x3 = 2 52 | 53 | Let our witness be [1, x1, x2, x3, x4], then we turn above 7 constraints into a matrix 54 | with 7 rows and 5 columns. 7 rows because there are 7 constraints and 5 columns because 55 | witness vector has 5 elements. 56 | """ 57 | 58 | # R1CS matrices 59 | 60 | L = np.array([ 61 | [0, 1, 0, 0, 0], 62 | [0, 0, 1, 0, 0], 63 | [0, 0, 0, 1, 0], 64 | [0, 0, 0, 0, 1], 65 | [0, 1, 0, 0, 0], 66 | [0, 1, 0, 0, 0], 67 | [0, 0, 1, 0, 0], 68 | ]) 69 | 70 | R = np.array([ 71 | [0, 1, 0, 0, 0], 72 | [0, 0, 1, 0, 0], 73 | [0, 0, 0, 1, 0], 74 | [0, 0, 0, 0, 1], 75 | [0, 0, 1, 0, 0], 76 | [0, 0, 0, 0, 1], 77 | [0, 0, 0, 1, 0], 78 | ]) 79 | 80 | O = np.array([ 81 | [curve_order-2, 3, 0, 0, 0], 82 | [curve_order-2, 0, 3, 0, 0], 83 | [curve_order-2, 0, 0, 3, 0], 84 | [curve_order-2, 0, 0, 0, 3], 85 | [2, 0, 0, 0, 0], 86 | [2, 0, 0, 0, 0], 87 | [2, 0, 0, 0, 0], 88 | ]) 89 | 90 | L_galois = GF(L) 91 | R_galois = GF(R) 92 | O_galois = GF(O) 93 | 94 | # In reality this witness is prover's secret, only prover knows it 95 | x1 = GF(1) 96 | x2 = GF(2) 97 | x3 = GF(1) 98 | x4 = GF(2) 99 | # a is the witness 100 | a = GF(np.array([1, x1, x2, x3, x4])) 101 | 102 | assert all(np.equal(np.matmul(L_galois, a) * np.matmul(R_galois, a), np.matmul(O_galois, a))), "not equal" 103 | 104 | # witness = [1, x1, x2, x3, x4] 105 | # Only the first entry [1] is public input 106 | # [x1, x2, x3, x4] are private inputs that only the prover knows 107 | l = 0 108 | public_inputs = a[:l+1] 109 | private_inputs = a[l+1:] 110 | 111 | """ 112 | You can see that R1CS matrices are pretty sparse. If we do pairing on each row, the proving system will 113 | take forever to run. To make computation less expensive, we turn R1CS into QAP. 114 | 115 | First step is converting each column of the matrices into polynomial using Lagrange Interpolation. 116 | We call those polynomials U_polys, V_polys and W_polys. 117 | """ 118 | 119 | def interpolate_column_galois(col): 120 | xs = GF(np.array(range(1, len(col) + 1))) 121 | return galois.lagrange_poly(xs, col) 122 | 123 | U_polys = np.apply_along_axis(interpolate_column_galois, 0, L_galois) 124 | V_polys = np.apply_along_axis(interpolate_column_galois, 0, R_galois) 125 | W_polys = np.apply_along_axis(interpolate_column_galois, 0, O_galois) 126 | 127 | """ 128 | Next, we compute QAP formula (U * a)(V * a) = (W * a) + h * t 129 | where * can be viewed as inner product 130 | """ 131 | 132 | def inner_product_polynomials_with_witness(polys, witness): 133 | mul_ = lambda x, y: x * y 134 | sum_ = lambda x, y: x + y 135 | return reduce(sum_, map(mul_, polys, witness)) 136 | 137 | # U * a 138 | sum_au = inner_product_polynomials_with_witness(U_polys, a) 139 | # V * a 140 | sum_av = inner_product_polynomials_with_witness(V_polys, a) 141 | # W * a 142 | sum_aw = inner_product_polynomials_with_witness(W_polys, a) 143 | 144 | # t(x) = (x-1)(x-2)(x-3)(x-4)(x-5)(x-6)(x-7) 145 | t = galois.Poly([1, curve_order - 1], field = GF)\ 146 | * galois.Poly([1, curve_order - 2], field = GF)\ 147 | * galois.Poly([1, curve_order - 3], field = GF)\ 148 | * galois.Poly([1, curve_order - 4], field = GF)\ 149 | * galois.Poly([1, curve_order - 5], field = GF)\ 150 | * galois.Poly([1, curve_order - 6], field = GF)\ 151 | * galois.Poly([1, curve_order - 7], field = GF) 152 | 153 | # t(tau) 154 | t_evaluated_at_tau = t(tau) 155 | print(f"t_evaluated_at_tau: {t_evaluated_at_tau}") 156 | print(f"type of t_evaluated_at_tau: {type(t_evaluated_at_tau)}") 157 | 158 | # (U * a)(V * a) = (W * a) + h * t 159 | # h = ((U * a)(V * a) - (W * a)) / t 160 | h = (sum_au * sum_av - sum_aw) // t 161 | HT = h * t 162 | 163 | print(f"U_polys: {U_polys}") 164 | print(f"V_polys: {V_polys}") 165 | print(f"W_polys: {W_polys}") 166 | print(f"HT: {HT}") 167 | 168 | assert sum_au * sum_av == sum_aw + HT, "division has a remainder" 169 | 170 | """ 171 | We can further reduce computation by only evaluating polynomial at a single point instead of 172 | comparing the entire polynomial. This works because of Schwartz-Zippel Lemma. In short, this 173 | lemma says we pick a random x from a large pool and evaluate p(x) and q(x), and if p(x) = q(x), 174 | we can deduce that p = q with negligible probability. 175 | 176 | Of course that random point should be a secret, so we introduce a trusted 3rd party to generate it 177 | and publish "powers of tau" for prover and verifer to compute. This process is called "encrypted 178 | polynomial evaluation" in RareSkills ZK book: 179 | https://www.rareskills.io/post/encrypted-polynomial-evaluation 180 | 181 | In Groth16, alpha and beta are introduced to prevent prover from forging fake proofs. We introduce 182 | them as randomness to the system and attacker won't able to guess. It also affects power of tau. 183 | See https://www.rareskills.io/post/groth16 for formula derivation. 184 | """ 185 | 186 | # polynomial degree is 6 187 | 188 | # Powers of tau for A 189 | def generate_powers_of_tau_G1(tau): 190 | return [multiply(G1, int(tau ** i)) for i in range(t.degree)] # up to tau**6 191 | 192 | # Shift for [A] 193 | alpha_G1 = multiply(G1, int(alpha)) 194 | 195 | # Powers of tau for B 196 | def generate_powers_of_tau_G2(tau): 197 | return [multiply(G2, int(tau ** i)) for i in range(t.degree)] # up to tau**6 198 | 199 | # Shift for [B] 200 | beta_G2 = multiply(G2, int(beta)) 201 | 202 | # Powers of tau for h(tau)t(tau) 203 | def generate_powers_of_tau_HT(tau): 204 | before_delta_inverse = [multiply(G1, int(tau ** i * t_evaluated_at_tau)) for i in range(t.degree - 1)] # up to tau**5 205 | return [multiply(entry, int(delta_inverse)) for entry in before_delta_inverse] 206 | 207 | # [beta] as G1 point and [delta] and G1 point 208 | def generate_beta1_and_delta1(): 209 | return multiply(G1, int(beta)), multiply(G1, int(delta)) 210 | 211 | # Debug notes for generate_powers_of_tau_HT() 212 | print(f"tau ** 2: {tau ** 1}") # 123 213 | print(f"type of tau ** 2: {type(tau ** 2)}") # 214 | print(f"type of int(tau ** 2): {type(int(tau ** 2))}") # 215 | # Conclusion: we need multiply(G1, some_int) -> int(tau ** i * t_evaluated_at_tau) 216 | 217 | # Compute components needed for powers of tau for C 218 | beta_times_U_polys = [beta * U_polys[i] for i in range(len(U_polys))] 219 | alpha_times_V_polys = [alpha * V_polys[i] for i in range(len(V_polys))] 220 | 221 | # Powers of tau for C 222 | C_polys = [beta_times_U_polys[i] + alpha_times_V_polys[i] + W_polys[i] for i in range(len(W_polys))] 223 | C_polys_tau = [C_polys[i](tau) for i in range(len(C_polys))] 224 | powers_of_tau_for_C = [multiply(G1, int(C_polys_tau[i])) for i in range(len(C_polys_tau))] 225 | 226 | # Powers of tau for public inputs hidden by gamma 227 | gamma_inverse = GF(1) / gamma 228 | powers_of_tau_for_public_inputs = powers_of_tau_for_C[:l+1] 229 | powers_of_tau_for_public_inputs = [multiply(entry, int(gamma_inverse)) for entry in powers_of_tau_for_public_inputs] 230 | 231 | # Powers of tau for private inputs hidden by delta 232 | delta_inverse = GF(1) / delta 233 | powers_of_tau_for_private_inputs = powers_of_tau_for_C[l+1:] 234 | powers_of_tau_for_private_inputs = [multiply(entry, int(delta_inverse)) for entry in powers_of_tau_for_private_inputs] 235 | 236 | # Gamma as G2 point 237 | gamma_G2 = multiply(G2, int(gamma)) 238 | 239 | # Delta as G2 point 240 | delta_G2 = multiply(G2, int(delta)) 241 | 242 | """ 243 | At this stage we have completed the trusted setup, and we reach to the prover steps. 244 | 245 | Prover computes the following things: 246 | 1. [A] as G1 point after random shift by alpha. 247 | 2. [B] as G2 point after random shift by beta. 248 | 3. Polynomial h(x) derived from QAP equation. 249 | 4. [h(tau)t(tau)] as G1 point. This is computed based on powers of tau for h(tau)t(tau). 250 | 5. [C] as G1 point. This is computed based on powers of tau for C. 251 | 252 | In the end, prover generates proof = ([A], [B], [C]) and sends to the verifier. 253 | Verifier logic is implemented in a seperate Solidity contract. The only thing that verifier does is 254 | checking if pairing([A], [B]) == pairing([alpha], [beta]) + pairing([C], [G2]) returns true. 255 | 256 | Prover step also needs r and s to stop external attacker from guessing witness (bruteforce small 257 | sample space). r and s are again random shifts. 258 | """ 259 | 260 | """ 261 | Modification regarding gamma and delta: 262 | 263 | We use l to separate public inputs and private inputs. For example, our witness vector is 264 | [1, x1, x2, x3, x4], the only public input here is 1, which is the 0th element. We can say l = 0. 265 | When computing [C], we start from l+1 = 1, skipping the first term in the inner product. This also 266 | affects verifier's computation, since verifier now needs to handle public input. 267 | 268 | Trusted setup now has to provide another two random values gamma and delta. Gamma is used to hide 269 | public input and delta is used to hide private input. 270 | """ 271 | 272 | def inner_product(ec_points, coeffs): 273 | return reduce(add, (multiply(point, int(coeff)) for point, coeff in zip(ec_points, coeffs)), Z1) 274 | 275 | def encrypted_evaluation_G1(poly): 276 | powers_of_tau = generate_powers_of_tau_G1(tau) 277 | evaluate_on_ec = inner_product(powers_of_tau, poly.coeffs[::-1]) 278 | 279 | return evaluate_on_ec 280 | 281 | def encrypted_evaluation_G2(poly): 282 | powers_of_tau = generate_powers_of_tau_G2(tau) 283 | evaluate_on_ec = inner_product(powers_of_tau, poly.coeffs[::-1]) 284 | 285 | return evaluate_on_ec 286 | 287 | def encrypted_evaluation_HT(poly): 288 | powers_of_tau = generate_powers_of_tau_HT(tau) 289 | evaluate_on_ec = inner_product(powers_of_tau, poly.coeffs[::-1]) 290 | 291 | return evaluate_on_ec 292 | 293 | old_A = encrypted_evaluation_G1(sum_au) 294 | old_B2 = encrypted_evaluation_G2(sum_av) 295 | old_B1 = encrypted_evaluation_G1(sum_av) 296 | HT_at_tau = encrypted_evaluation_HT(h) # Info about t is embedded in powers_of_tau_HT 297 | # old_C needs extra attention 298 | old_C = inner_product(powers_of_tau_for_private_inputs, private_inputs) 299 | 300 | print(f"old_A : {old_A}") 301 | print(f"old_B: {old_B2}") 302 | print(f"old_B: {old_B1}") 303 | print(f"HT_evaluated_on_ec: {HT_at_tau}") 304 | print(f"old_C: {old_C}") 305 | 306 | # Get [beta]1 and [delta]1 from trusted setup 307 | beta_G1, delta_G1 = generate_beta1_and_delta1() 308 | # [A] as G1 point -> [old_A] shift by [alpha], then shift by r 309 | A1 = add(add(old_A, alpha_G1), multiply(delta_G1, int(r))) 310 | # [B] as G2 point -> [old_B2] shift by [beta]2, then shift by s 311 | B2 = add(add(old_B2, beta_G2), multiply(delta_G2, int(s))) 312 | # [B] as G1 point -> [old_B1] shift by [beta]1, then shift by s 313 | B1 = add(add(old_B1, beta_G1), multiply(delta_G1, int(s))) 314 | # [C] = [old_C] + h(tau)t(tau) 315 | last_term_of_C1 = neg(multiply(delta_G1, int(r * s))) # -rs[delta]1 316 | C1 = add(add(add(add(old_C, HT_at_tau), multiply(A1, int(s))), multiply(B1, int(r))), last_term_of_C1) 317 | 318 | print(f"A1: {A1}") 319 | print(f"B2: {B2}") 320 | print(f"C1: {C1}") 321 | print(f"alpha1: {alpha_G1}") 322 | print(f"beta2: {beta_G2}") 323 | print(f"inner_product_1: {inner_product(powers_of_tau_for_public_inputs, public_inputs)}") 324 | print(f"gamma2: {gamma_G2}") 325 | print(f"delta2: {delta_G2}") 326 | 327 | print(f"pairing(B2, A1): {pairing(B2, A1)}") 328 | print(f"pairing(beta_G2, alpha_G1): {pairing(beta_G2, alpha_G1)}") 329 | print(f"pairing(G2, C1): {pairing(G2, C1)}") 330 | 331 | proof = [A1, B2, C1] 332 | print(f"Proof: {proof}") 333 | 334 | # Final check 335 | # Code comes from Zigtur: https://github.com/zigtur/Groth16/blob/main/Groth16.ipynb 336 | first = pairing(B2, neg(A1)) 337 | second = pairing(beta_G2, alpha_G1) 338 | third = pairing(gamma_G2, inner_product(powers_of_tau_for_public_inputs, public_inputs)) 339 | fourth = pairing(delta_G2, C1) 340 | print("Pairing check:", final_exponentiate(first * second * third * fourth) == FQ12.one()) -------------------------------------------------------------------------------- /verifier_contract/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /verifier_contract/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /verifier_contract/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /verifier_contract/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /verifier_contract/script/Counter.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console} from "forge-std/Script.sol"; 5 | 6 | contract CounterScript is Script { 7 | function setUp() public {} 8 | 9 | function run() public { 10 | vm.broadcast(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /verifier_contract/src/VerifierPairingCheck.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | contract Pairing { 5 | 6 | struct ECPoint { 7 | uint256 x; 8 | uint256 y; 9 | } 10 | 11 | struct ECPoint2 { 12 | uint256 x1; 13 | uint256 x2; 14 | uint256 y1; 15 | uint256 y2; 16 | } 17 | 18 | uint256 public constant field_modulus = 21888242871839275222246405745257275088696311157297823662689037894645226208583; 19 | 20 | function pairing(uint256[24] memory points) public view returns (uint256 success) { 21 | (bool ok, bytes memory result) = address(8).staticcall(abi.encode(points)); 22 | require(ok, "pairing failed"); 23 | success = abi.decode(result, (uint256)); 24 | } 25 | 26 | function verifier() public view returns (bool result) { 27 | ECPoint memory A1 = ECPoint( 28 | 8500541046950386545555084973797234505746134092760246356398756868558730993376, 29 | 2728018757505247577599145744834566349470298286886538670623480300640165475336 30 | ); 31 | 32 | ECPoint2 memory B2 = ECPoint2( 33 | 614811356399083613340076117861640930237533867059048572035591919945188572231, 34 | 407513596731636209116223701744982894724875670065054367654203415562053383548, 35 | 20225459237741896907766333525611796225694823438894717812917114930592349384649, 36 | 6599646197841011670943800527194023871199577780963695091771137022800627814386 37 | ); 38 | 39 | ECPoint memory C1 = ECPoint( 40 | 7011711771538146751310900819804679590820264377681734431060390682855291171961, 41 | 10869683322270460627523057894322968893310116613290927008550199847577697543596 42 | ); 43 | 44 | ECPoint memory alpha1 = ECPoint( 45 | 21075150262531933410243350581015787595168186273792137383090173989227707707945, 46 | 16803223452753302947971852296593396982314855353154622521214134510030153364020 47 | ); 48 | 49 | ECPoint2 memory beta2 = ECPoint2( 50 | 9082831086857428347666240538839414598038862953015883554220983146969721132295, 51 | 5946153445888908926765895545450119051763958795422085692866573086426414981726, 52 | 5690917527450736811890845844855066153994393778369678029586091525080118377665, 53 | 5982238848039039019799448545650619183523751194592694323674161867008355731522 54 | ); 55 | 56 | ECPoint memory inner_product1 = ECPoint( 57 | 18203846039728683528457583855779458464804567271139690167314298788231390053471, 58 | 13631105025914729630461642470538628325532158281989889737606409126931242073916 59 | ); 60 | 61 | ECPoint2 memory gamma2 = ECPoint2( 62 | 3422176839735843643577512702252960766443933154095106059593868758352128430632, 63 | 12537673901413488976022732694559403660565095083955194429984527002181624936409, 64 | 5873520565998122640774253709468845083826129197980545879367249660359920981815, 65 | 7842045849139628952740994290150012870613503236269653328239290990207371146041 66 | ); 67 | 68 | ECPoint2 memory delta2 = ECPoint2( 69 | 15406631789566073765258184924288664281353256630222092854167934549758149051822, 70 | 15927868458446079653126710794319459662618357872010717621750847588778804939173, 71 | 12835518685042502042279653518711211224896062119915294166512231593944238642168, 72 | 9346447135837843657377157296380400932320333888697131601581184685634856556180 73 | ); 74 | 75 | uint256 negative_A1_x = A1.x; 76 | uint256 negative_A1_y = field_modulus - A1.y; 77 | 78 | // pairing(A1, B2) = pairing(alpha1 + beta2) + pairing(inner_product1, gamma2) + pairing(C1, delta2) 79 | // 0 = -pairing(A1, B2) + pairing(alpha1 + beta2) + pairing(inner_product1, gamma2) + pairing(C1, delta2) 80 | // 0 = pairing(-A1, B2) + pairing(alpha1 + beta2) + pairing(inner_product1, gamma2) + pairing(C1, delta2) 81 | uint256[24] memory points = [ 82 | // first pairing 83 | negative_A1_x, 84 | negative_A1_y, 85 | B2.x2, 86 | B2.x1, 87 | B2.y2, 88 | B2.y1, 89 | // second pairing 90 | alpha1.x, 91 | alpha1.y, 92 | beta2.x2, 93 | beta2.x1, 94 | beta2.y2, 95 | beta2.y1, 96 | // third pairing 97 | inner_product1.x, 98 | inner_product1.y, 99 | gamma2.x2, 100 | gamma2.x1, 101 | gamma2.y2, 102 | gamma2.y1, 103 | // fourth pairing 104 | C1.x, 105 | C1.y, 106 | delta2.x2, 107 | delta2.x1, 108 | delta2.y2, 109 | delta2.y1 110 | ]; 111 | 112 | uint256 success = pairing(points); 113 | result = success == 1; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /verifier_contract/test/VerifierPairingCheck.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {Test, console} from "forge-std/Test.sol"; 5 | import {Pairing} from "../src/VerifierPairingCheck.sol"; 6 | 7 | contract PairingTest is Test { 8 | 9 | Pairing public pairing; 10 | 11 | function setUp() public { 12 | pairing = new Pairing(); 13 | } 14 | 15 | function test_pairing() public { 16 | assertTrue(pairing.verifier()); 17 | } 18 | } 19 | --------------------------------------------------------------------------------