├── example_0001.zkey ├── Flux-Diagram.drawio.png ├── Dockerfile ├── Makefile ├── playground.cairo ├── docs └── GettingStarted.md ├── README.md ├── verifier_groth16.cairo └── verifier_groth16.sol /example_0001.zkey: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/circom_export_to_cairo/HEAD/example_0001.zkey -------------------------------------------------------------------------------- /Flux-Diagram.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/circom_export_to_cairo/HEAD/Flux-Diagram.drawio.png -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian 2 | 3 | RUN apt update && apt upgrade -y && \ 4 | apt install -y python libgmp3-dev npm pip && \ 5 | pip3 install cairo-lang && \ 6 | npm install -g snarkjs 7 | 8 | COPY verifier_groth16.cairo /home 9 | COPY playground.cairo /home 10 | COPY example_0001.zkey /home 11 | COPY Makefile /home 12 | 13 | WORKDIR /home 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PHONY: docker-build, docker-run, compile, run, clean ,generate_verifier, make_verifier, replace_template 2 | 3 | docker-build: Dockerfile 4 | 5 | docker build -t circomtest . 6 | 7 | docker-run: Dockerfile 8 | 9 | docker run -i circomtest 10 | 11 | compile: playground.cairo 12 | 13 | cairo-compile playground.cairo --output playground-compiled.json 14 | 15 | run: playground-compiled.json 16 | 17 | cairo-run \ 18 | --program=playground-compiled.json --print_output \ 19 | --print_info --relocate_prints --layout=small 20 | 21 | clean : playground-compiled.json 22 | 23 | rm playground-compiled.json 24 | 25 | #Generates a cairo verifier by replacing the original template 26 | generate_verifier : replace_template make_verifier 27 | 28 | replace_template: verifier_groth16.cairo 29 | 30 | cp verifier_groth16.cairo verifier_groth16.sol.ejs 31 | mv verifier_groth16.sol.ejs /usr/local/lib/node_modules/snarkjs/templates/verifier_groth16.sol.ejs 32 | 33 | make_verifier: example_0001.zkey 34 | 35 | snarkjs zkey export solidityverifier example_0001.zkey verifier.cairo 36 | -------------------------------------------------------------------------------- /playground.cairo: -------------------------------------------------------------------------------- 1 | #This is a simple cairo program, it executes a binary search and returns 1(TRUE) when the number is found 2 | %builtins output range_check 3 | 4 | from starkware.cairo.common.serialize import serialize_word 5 | from starkware.cairo.common.alloc import alloc 6 | from starkware.cairo.common.math_cmp import is_le 7 | from starkware.cairo.common.bool import TRUE, FALSE 8 | from starkware.cairo.common.math import unsigned_div_rem 9 | 10 | #array must be sorted 11 | #Returns TRUE if the number is found in the array, or FALSE otherwise 12 | func binary_search{range_check_ptr : felt}(array : felt*, number : felt, length : felt, position : felt) -> (r: felt): 13 | alloc_locals 14 | let comp1 : felt = is_le(length + 1, position) 15 | if comp1 == TRUE: 16 | return(FALSE) 17 | else: 18 | let (mid, _) = unsigned_div_rem(position + length, 2) 19 | if array[mid] == number: 20 | return(TRUE) 21 | end 22 | let comp2 : felt = is_le(array[mid], number) 23 | if comp2 == TRUE: 24 | return binary_search(array, number, length, mid +1) 25 | else: 26 | return binary_search(array, number, mid -1, position) 27 | end 28 | end 29 | end 30 | 31 | func main{output_ptr : felt*, range_check_ptr}(): 32 | 33 | let (array : felt*) = alloc() 34 | assert array[0] = 1 35 | assert array[1] = 2 36 | assert array[2] = 3 37 | assert array[3] = 4 38 | assert array[4] = 5 39 | assert array[5] = 6 40 | 41 | let number = 5 42 | 43 | let result : felt = binary_search(array, number, 5, 0) 44 | serialize_word(result) 45 | 46 | return () 47 | end 48 | -------------------------------------------------------------------------------- /docs/GettingStarted.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Our goal is to find out how this verifier contract (a solidity contract) is produced, and instead output a cairo verifier contract (a starknet contract). In order to do this, the snarkjs library uses a [solidity template](https://github.com/iden3/snarkjs/blob/master/templates/verifier_groth16.sol.ejs). 4 | 5 | To learn about Circom: 6 | * [Circom 2 Documentation](https://docs.circom.io/): Explains what is circom, how to use it from the creation of a circuit to the generation of a verifier contract (this is what we are interested in). 7 | * [Background in Zero Knowledge Proof](https://docs.circom.io/background/background/): Helps understand a bit more about how circom works. 8 | 9 | Our work consists of creating a cairo template, based off of this solidity template. 10 | * [How is a solidity verifier generated? (from a validation key)](https://hackmd.io/HCGJsQgCRRSc0Y5DJqZYEw?view)): Shows the specific functions in the [snarkjs library](https://github.com/iden3/snarkjs) that contribute to the output of the solidity verifier 11 | * [Hello, Cairo](https://www.cairo-lang.org/docs/hello_cairo/index.html): To learn about Cairo (The language in which this template is written) 12 | * [Cairo Common Library](https://github.com/starkware-libs/cairo-lang/tree/master/src/starkware/cairo/common): Contains every function you can import from the common library 13 | * [Cairo Playground](https://www.cairo-lang.org/playground/): Lets you compile and run cairo code online 14 | * [Hello, StarkNet](https://www.cairo-lang.org/docs/hello_starknet/index.html): To learn about starknet contracts 15 | * [cairo-alt_bn128 Library](https://github.com/tekkac/cairo-alt_bn128): Contains the functions needed to handle everything elliptic-curve related. 16 | 17 | Other useful texts: 18 | 19 | * [Cairo for Blockchain Developers](https://www.cairo-lang.org/cairo-for-blockchain-developers/): it’s a good text to learn more about cairo. 20 | * [Create your first zero-knowledge snark circuit using circom and snarkjs](https://blog.iden3.io/first-zk-proof.html): it’s complementary reading of Circom 2 Documentation. 21 | * [Elliptic Curve Cryptography Explainded](https://fangpenlin.com/posts/2019/10/07/elliptic-curve-cryptography-explained/): Explains how elliptic curves work (Recommended) 22 | * [The Basics of Pairings](https://www.youtube.com/watch?v=F4x2kQTKYFY): Explains how elliptic curves work, and their use in cryptography (Not a must watch, only recommend watching the first hour if interested). 23 | * [Swagtimus.ethL2'22’s Newsletter](https://swagtimus.substack.com): Weekly summary of all things StarkNet 24 | * [starknet-devnet](https://github.com/Shard-Labs/starknet-devnet): A devnet who's aim is to mimic the official Starknet Alpha testnet 25 | * [Cairo smart test framework](https://github.com/bellissimogiorno/cairo-integer-types/blob/main/templates/cairo_smart_test_framework.py): Provides tools for unit- and property-based testing Cairo code from within Python 26 | * [Cairo Goldmine](https://github.com/beautyisourbusiness/cairo-goldmine): Comprehensive list of repositories on the starknet ecosystem 27 | * [pytest-cairo](https://github.com/TimNooren/pytest-cairo): pytest support for cairo-lang and starknet 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Circom export to Cairo 2 | 3 | ## Diagram 4 | 5 | ![Flux-Diagram](Flux-Diagram.drawio.png "Flux-Diagram") 6 | 7 | This Repository contains the following: 8 | 9 | - verifier_groth16.cairo a work-in-progress template for cairo 10 | - verifier_groth16.sol the original template used by snarkjs to generate a SolidityVerifier 11 | - playground.cairo a simple example program written in cairo 12 | - Makefile used to compile and run the playground.cairo through the commands "make compile" and "make run", and "make generate_verifier", used to create a verifier.cairo with our template (explained below) 13 | - Dockerfile used to create a docker image with everything ready to compile and run cairo code, and generate a verifier through snarkjs 14 | - example_001.zkey, a validation key generated though circom from a basic circuit 15 | 16 | ## How to use it 17 | 18 | First write and compile a circuit and compute the witness through circom, then generate a validation key through snarkjs (this process is properly explained at https://docs.circom.io/getting-started/installation/), this will yield a .zkey, which we can use to generate a solidity verifier through the command: 19 | 20 | ``` bash 21 | snarkjs zkey export solidityverifier [name of your key].zkey [nme of the verifier produced] 22 | ``` 23 | 24 | What we can do with this is change the name of our cairo template to verifier_groth16.sol.ejs and use it ro replace the original file in snarkjs/templates, so that a "CairoVerifier" is produced instead of a Solidity one. 25 | 26 | The Makefile has a target called "generate_verifier" that will copy our template into the templates folder and generate a verifier from the example zkey provided (example_001.zkey) 27 | 28 | ### How to test this process in a Docker container 29 | 30 | * Create a docker container with the compiler and all the requirements: 31 | ``` bash 32 | make docker-build 33 | ``` 34 | * Log into the container created: 35 | ``` bash 36 | make docker-run 37 | ``` 38 | the prompt will be located at the `/home` directory. The file `verifier_groth16.cairo` is the template file that generates Cairo code. The provided commands are written in the `Makefile`. 39 | * To run the generation of the verifier: 40 | ``` bash 41 | make generate_verifier 42 | ``` 43 | it generates the file `verifier.cairo`. 44 | 45 | If you want to test this process without docker, you should put the template file (`verifier_groth16.cairo`) in the same directory that npm puts the program files (`npm root -g` shows that directory). 46 | 47 | ## Doubts 48 | 49 | The numbers provided by the zkey are too big for the cairo program (Cairo language cant support such numbers, which leads to output being truncated, and incompatibility with math library functions). These numbers come from the R1CS, which defines a set of equations modulo p, where F_p is the field where the values of the arithmetic circuit reside. This makes me question wether the approach of making a cairo template is correct, as the original template is based off of the groth16 protocol, for ZK-SNARK proofs, while Cairo was designed to be used with the STARK protocol. This makes me question where does my "cairo verifier" fit into the diagram, and "who", would verify it. 50 | 51 | ### Problems concerning solidity template 52 | 53 | On the functions addition, scalar_mu and pairing, there is a block of Yul code, which is not very descriptive. This makes "translating" these functions to cairo seemingly impossible, as we cant understand what they do. It is possible to understand some of them from context, such as addition and scalar_mu, but for the pairing function there is no practical way to replicate it without knowing how the function works: 54 | 55 | ```solidity 56 | /// @return the result of computing the pairing check 57 | /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 58 | /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should 59 | /// return true. 60 | function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) { 61 | require(p1.length == p2.length,"pairing-lengths-failed"); 62 | uint elements = p1.length; 63 | uint inputSize = elements * 6; 64 | uint[] memory input = new uint[](inputSize); 65 | for (uint i = 0; i < elements; i++) 66 | { 67 | input[i * 6 + 0] = p1[i].X; 68 | input[i * 6 + 1] = p1[i].Y; 69 | input[i * 6 + 2] = p2[i].X[0]; 70 | input[i * 6 + 3] = p2[i].X[1]; 71 | input[i * 6 + 4] = p2[i].Y[0]; 72 | input[i * 6 + 5] = p2[i].Y[1]; 73 | } 74 | uint[1] memory out; 75 | bool success; 76 | // solium-disable-next-line security/no-inline-assembly 77 | assembly { 78 | success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) 79 | // Use "invalid" to make gas estimation work 80 | switch success case 0 { invalid() } 81 | } 82 | require(success,"pairing-opcode-failed"); 83 | return out[0] != 0; 84 | } 85 | ``` 86 | 87 | ## On how the problem with the pairing function was solved 88 | 89 | After reading the contract that is called by the pairing function on the solidity template, and going through the libraries it uses I found how the pairing check is made: 90 | (On go-ethereum/core/vm/contracts.go) 91 | 92 | ``` go 93 | func (c *bn256Pairing) Run(input []byte) ([]byte, error) { 94 | // Handle some corner cases cheaply 95 | if len(input)%192 > 0 { 96 | return nil, errBadPairingInput 97 | } 98 | // Convert the input into a set of coordinates 99 | var ( 100 | cs []*bn256.G1 101 | ts []*bn256.G2 102 | ) 103 | for i := 0; i < len(input); i += 192 { 104 | c, err := newCurvePoint(input[i : i+64]) 105 | if err != nil { 106 | return nil, err 107 | } 108 | t, err := newTwistPoint(input[i+64 : i+192]) 109 | if err != nil { 110 | return nil, err 111 | } 112 | cs = append(cs, c) 113 | ts = append(ts, t) 114 | } 115 | // Execute the pairing checks and return the results 116 | if bn256.PairingCheck(cs, ts) { 117 | return true32Byte, nil 118 | } 119 | return false32Byte, nil 120 | } 121 | ``` 122 | 123 | (On go-ethereum/crypto/bn256/cloudflare/bn256.go) 124 | 125 | ``` go 126 | // PairingCheck calculates the Optimal Ate pairing for a set of points. 127 | func PairingCheck(a []*G1, b []*G2) bool { 128 | acc := new(gfP12) 129 | acc.SetOne() 130 | 131 | for i := 0; i < len(a); i++ { 132 | if a[i].p.IsInfinity() || b[i].p.IsInfinity() { 133 | continue 134 | } 135 | acc.Mul(acc, miller(b[i].p, a[i].p)) 136 | } 137 | return finalExponentiation(acc).IsOne() 138 | } 139 | ``` 140 | 141 | This behaves similarly to the pairing function on the cairo-alt_bn128 library: 142 | 143 | ``` cairo 144 | func pairing{range_check_ptr}(Q : G2Point, P : G1Point) -> (res : FQ12): 145 | alloc_locals 146 | let (local twisted_Q : GTPoint) = twist(Q) 147 | let (local f : FQ12) = fq12_one() 148 | let (cast_P : GTPoint) = g1_to_gt(P) 149 | return miller_loop(Q=twisted_Q, P=cast_P, R=twisted_Q, n=log_ate_loop_count + 1, f=f) 150 | end 151 | ``` 152 | 153 | But the difference lies in the last two lines: 154 | 155 | ``` go 156 | acc.Mul(acc, miller(b[i].p, a[i].p)) 157 | } 158 | return finalExponentiation(acc).IsOne() 159 | ``` 160 | Where we can see that the results of the miller loop are multiplied against the previous one and stored in acc. 161 | And then the final result is compared to one. This also coincides with the comment of top of the pairing function on the solidity template: 162 | 163 | ``` solidity 164 | /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 165 | ``` 166 | 167 | This is also shown on the pairing_test function on cairo-alt_bn128/alt_bn128_example.cairo, where p1 and p2, and -p1 and p2 are paired, and their pairing results are multiplied and shown to result in the FQ12 one (That is to say 1, followed by 11 zeroes) 168 | 169 | -------------------------------------------------------------------------------- /verifier_groth16.cairo: -------------------------------------------------------------------------------- 1 | #This is a template for cairo based on verifier_groth16.sol.ejs on snarkjs/templates 2 | %lang starknet 3 | 4 | from starkware.cairo.common.cairo_builtins import HashBuiltin 5 | from starkware.cairo.common.bool import FALSE, TRUE 6 | from starkware.cairo.common.math import assert_nn, unsigned_div_rem 7 | from starkware.cairo.common.alloc import alloc 8 | from alt_bn128_g1 import G1Point, ec_add, ec_mul 9 | from alt_bn128_g2 import G2Point 10 | from alt_bn128_pair import pairing 11 | from alt_bn128_field import FQ12, is_zero, FQ2, fq12_diff, fq12_eq_zero, fq12_mul, fq12_one 12 | from bigint import BigInt3 13 | 14 | struct VerifyingKey: 15 | member alfa1 : G1Point 16 | member beta2 : G2Point 17 | member gamma2 : G2Point 18 | member delta2 : G2Point 19 | member IC : G1Point* 20 | member IC_length : felt 21 | 22 | end 23 | 24 | struct Proof: 25 | member A : G1Point 26 | member B : G2Point 27 | member C : G1Point 28 | 29 | end 30 | 31 | #Auxiliary functions (Builders) 32 | #Creates a G1Point from the received felts: G1Point(x,y) 33 | func BuildG1Point{range_check_ptr : felt}(x1 : felt, x2 : felt, x3 : felt, y1 : felt, y2 : felt, y3 : felt) -> (r: G1Point): 34 | alloc_locals 35 | let X : BigInt3 = BigInt3(x1,x2,x3) 36 | let Y : BigInt3 = BigInt3(y1,y2,y3) 37 | 38 | return (G1Point(X,Y)) 39 | 40 | end 41 | 42 | #Creates a G2Point from the received felts: G2Point([a,b],[c,d]) 43 | func BuildG2Point{range_check_ptr : felt}(a1 : felt, a2 : felt, a3 : felt, b1 : felt, b2 : felt, b3 : felt, c1 : felt, c2 : felt, c3 : felt, d1 : felt, d2 : felt, d3 : felt) -> (r : G2Point): 44 | alloc_locals 45 | let A : BigInt3 = BigInt3(a1,a2,a3) 46 | let B : BigInt3 = BigInt3(b1,b2,b3) 47 | let C : BigInt3 = BigInt3(c1,c2,c3) 48 | let D : BigInt3 = BigInt3(d1,d2,d3) 49 | 50 | let x : FQ2 = FQ2(B,A) 51 | let y : FQ2 = FQ2(D,C) 52 | 53 | return (G2Point(x, y)) 54 | 55 | end 56 | 57 | #Returns negated BigInt3 58 | func negateBigInt3{range_check_ptr : felt}(n : BigInt3) -> (r : BigInt3): 59 | let (_, nd0) = unsigned_div_rem(n.d0, 60193888514187762220203335) 60 | let d0 = 60193888514187762220203335 -nd0 61 | let (_, nd1) = unsigned_div_rem(n.d1, 104997207448309323063248289) 62 | let d1 = 104997207448309323063248289 -nd1 63 | let (_, nd2) = unsigned_div_rem(n.d2, 3656382694611191768777987) 64 | let d2 = 3656382694611191768777987 -nd2 65 | 66 | return(BigInt3(d0,d1,d2)) 67 | 68 | end 69 | 70 | #Returns negated G1Point(addition of a G1Point and a negated G1Point should be zero) 71 | func negate{range_check_ptr : felt}(p : G1Point) -> (r: G1Point): 72 | alloc_locals 73 | let x_is_zero : felt = is_zero(p.x) 74 | if x_is_zero == TRUE: 75 | let y_is_zero : felt = is_zero(p.y) 76 | if y_is_zero == TRUE: 77 | return (G1Point(BigInt3(0,0,0),BigInt3(0,0,0))) 78 | end 79 | end 80 | 81 | let neg_y : BigInt3 = negateBigInt3(p.y) 82 | return (G1Point(p.x, neg_y)) 83 | end 84 | 85 | #Computes the pairing for each pair of points in p1 and p2, multiplies each new result and returns the final result 86 | #pairing_result should iniially be an fq12_one 87 | func compute_pairings{range_check_ptr : felt}(p1 : G1Point*, p2 : G2Point*, pairing_result : FQ12, position : felt, length : felt) -> (result : FQ12): 88 | if position != length: 89 | let current_pairing_result : FQ12 = pairing(p2[position], p1[position]) 90 | let mul_result : FQ12 = fq12_mul(pairing_result, current_pairing_result) 91 | 92 | return compute_pairings(p1, p2,mul_result, position+1, length) 93 | end 94 | return(pairing_result) 95 | end 96 | 97 | #Returns the result of computing the pairing check 98 | func pairings{range_check_ptr : felt}(p1 : G1Point*, p2: G2Point*, length : felt) -> (r : felt): 99 | alloc_locals 100 | assert_nn(length) 101 | let initial_result : FQ12 = fq12_one() 102 | let pairing_result : FQ12 = compute_pairings(p1,p2,initial_result,0,length) 103 | 104 | let one : FQ12 = fq12_one() 105 | let diff : FQ12 = fq12_diff(pairing_result, one) 106 | let result : felt = fq12_eq_zero(diff) 107 | return(result) 108 | end 109 | 110 | #Pairing check for four pairs 111 | func pairingProd4{range_check_ptr : felt}(a1 : G1Point, a2 : G2Point, b1 : G1Point, b2 : G2Point, c1 : G1Point, c2 : G2Point, d1 : G1Point, d2 : G2Point) -> (r : felt): 112 | let (p1 : G1Point*) = alloc() 113 | let (p2 : G2Point*) = alloc() 114 | 115 | assert p1[0] = a1 116 | assert p1[1] = b1 117 | assert p1[2] = c1 118 | assert p1[3] = d1 119 | 120 | assert p2[0] = a2 121 | assert p2[1] = b2 122 | assert p2[2] = c2 123 | assert p2[3] = d2 124 | 125 | return pairings(p1,p2,4) 126 | 127 | end 128 | 129 | func verifyingKey{range_check_ptr : felt}() -> (vk : VerifyingKey): 130 | alloc_locals 131 | let alfa1 : G1Point = BuildG1Point( 132 | <%=vk_alpha_1[0]%>, <%=vk_alpha_1[1]%>, <%=vk_alpha_1[2]%>, 133 | <%=vk_alpha_1[3]%>, <%=vk_alpha_1[4]%>, <%=vk_alpha_1[5]%>, 134 | ) 135 | 136 | let beta2 : G2Point = BuildG2Point( 137 | <%=vk_beta_2[0][3]%>, <%=vk_beta_2[0][4]%>, <%=vk_beta_2[0][5]%>, 138 | <%=vk_beta_2[0][0]%>, <%=vk_beta_2[0][1]%>, <%=vk_beta_2[0][2]%>, 139 | <%=vk_beta_2[1][3]%>, <%=vk_beta_2[1][4]%>, <%=vk_beta_2[1][5]%>, 140 | <%=vk_beta_2[1][0]%>, <%=vk_beta_2[1][1]%>, <%=vk_beta_2[1][2]%> 141 | ) 142 | 143 | let gamma2 : G2Point = BuildG2Point( 144 | <%=vk_gamma_2[0][3]%>, <%=vk_gamma_2[0][4]%>, <%=vk_gamma_2[0][5]%>, 145 | <%=vk_gamma_2[0][0]%>, <%=vk_gamma_2[0][1]%>, <%=vk_gamma_2[0][2]%>, 146 | <%=vk_gamma_2[1][3]%>, <%=vk_gamma_2[1][4]%>, <%=vk_gamma_2[1][5]%>, 147 | <%=vk_gamma_2[1][0]%>, <%=vk_gamma_2[1][1]%>, <%=vk_gamma_2[1][2]%> 148 | ) 149 | let delta2 : G2Point = BuildG2Point( 150 | <%=vk_delta_2[0][3]%>, <%=vk_delta_2[0][4]%>, <%=vk_delta_2[0][5]%>, 151 | <%=vk_delta_2[0][0]%>, <%=vk_delta_2[0][1]%>, <%=vk_delta_2[0][2]%>, 152 | <%=vk_delta_2[1][3]%>, <%=vk_delta_2[1][4]%>, <%=vk_delta_2[1][5]%>, 153 | <%=vk_delta_2[1][0]%>, <%=vk_delta_2[1][1]%>, <%=vk_delta_2[1][2]%> 154 | ) 155 | 156 | let (IC : G1Point*) = alloc() 157 | <% for (let i=0; i 158 | let point_<%=i%> : G1Point = BuildG1Point( 159 | <%=IC[i][0]%>, <%=IC[i][1]%>, <%=IC[i][2]%>, 160 | <%=IC[i][3]%>, <%=IC[i][4]%>, <%=IC[i][5]%>) 161 | assert IC[<%=i%>] = point_<%=i%> 162 | <% } %> 163 | let IC_length : felt = <%=IC.length%> 164 | 165 | return(VerifyingKey(alfa1, beta2, gamma2, delta2, IC, IC_length)) 166 | 167 | end 168 | 169 | #Computes the linear combination for vk_x 170 | func vk_x_linear_combination{range_check_ptr : felt}( vk_x : G1Point, input : BigInt3*, position : felt, length : felt, IC : G1Point*) -> (result_vk_x : G1Point): 171 | if position != length: 172 | let mul_result : G1Point = ec_mul(IC[position + 1], input[position]) 173 | let add_result : G1Point = ec_add(vk_x, mul_result) 174 | 175 | return vk_x_linear_combination(add_result, input, position + 1, length, IC) 176 | end 177 | return(vk_x) 178 | end 179 | 180 | func verify{range_check_ptr : felt}(input : BigInt3*, proof: Proof, input_len : felt) -> (r : felt): 181 | alloc_locals 182 | let vk : VerifyingKey = verifyingKey() 183 | assert input_len = vk.IC_length + 1 184 | let initial_vk_x : G1Point = BuildG1Point(0, 0, 0, 0, 0, 0) 185 | let computed_vk_x : G1Point = vk_x_linear_combination(initial_vk_x, input, 0, vk.IC_length - 1, vk.IC) 186 | let vk_x : G1Point = ec_add(computed_vk_x, vk.IC[0]) 187 | 188 | let neg_proof_A : G1Point = negate(proof.A) 189 | return pairingProd4(neg_proof_A, proof.B , vk.alfa1, vk.beta2, vk_x, vk.gamma2, proof.C, vk.delta2) 190 | 191 | end 192 | 193 | #Fills the empty array output with the BigInt3 version of each number in input 194 | func getBigInt3array{range_check_ptr : felt}(input : felt*, output : BigInt3*, input_position, output_position, length): 195 | if output_position != length: 196 | let big_int : BigInt3 = BigInt3(input[input_position], input[input_position + 1], input[input_position +2]) 197 | assert output[output_position] = big_int 198 | 199 | getBigInt3array(input,output,input_position+3, output_position+1,length) 200 | return() 201 | end 202 | return() 203 | end 204 | 205 | #a_len, b1_len, b2_len and c_len are all 6, input_len would be 3 * amount of inputs 206 | @external 207 | func verifyProof{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr : felt}(a_len : felt, a : felt*, b1_len : felt, b1 : felt*, b2_len : felt, b2 : felt*, 208 | c_len : felt, c : felt*, input_len : felt, input : felt*) -> (r : felt): 209 | alloc_locals 210 | let A : G1Point = BuildG1Point(a[0], a[1], a[2], a[3], a[4], a[5]) 211 | let B : G2Point = BuildG2Point(b1[0], b1[1], b1[2], b1[3], b1[4], b1[5], b2[0], b2[1], b2[2], b2[3], b2[4], b2[5]) 212 | let C : G1Point = BuildG1Point(c[0], c[1], c[2], c[3], c[4], c[5]) 213 | 214 | let (big_input : BigInt3*) = alloc() 215 | getBigInt3array(input, big_input, 0, 0, input_len/3) 216 | 217 | let proof : Proof = Proof(A, B, C) 218 | let result : felt = verify(big_input, proof, input_len) 219 | return(result) 220 | 221 | end 222 | -------------------------------------------------------------------------------- /verifier_groth16.sol: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2017 Christian Reitwiessner 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 5 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | // 7 | // 2019 OKIMS 8 | // ported to solidity 0.6 9 | // fixed linter warnings 10 | // added requiere error messages 11 | // 12 | // 13 | // SPDX-License-Identifier: GPL-3.0 14 | pragma solidity ^0.6.11; 15 | library Pairing { 16 | struct G1Point { 17 | uint X; 18 | uint Y; 19 | } 20 | // Encoding of field elements is: X[0] * z + X[1] 21 | struct G2Point { 22 | uint[2] X; 23 | uint[2] Y; 24 | } 25 | /// @return the generator of G1 26 | function P1() internal pure returns (G1Point memory) { 27 | return G1Point(1, 2); 28 | } 29 | /// @return the generator of G2 30 | function P2() internal pure returns (G2Point memory) { 31 | // Original code point 32 | return G2Point( 33 | [11559732032986387107991004021392285783925812861821192530917403151452391805634, 34 | 10857046999023057135944570762232829481370756359578518086990519993285655852781], 35 | [4082367875863433681332203403145435568316851327593401208105741076214120093531, 36 | 8495653923123431417604973247489272438418190587263600148770280649306958101930] 37 | ); 38 | 39 | /* 40 | // Changed by Jordi point 41 | return G2Point( 42 | [10857046999023057135944570762232829481370756359578518086990519993285655852781, 43 | 11559732032986387107991004021392285783925812861821192530917403151452391805634], 44 | [8495653923123431417604973247489272438418190587263600148770280649306958101930, 45 | 4082367875863433681332203403145435568316851327593401208105741076214120093531] 46 | ); 47 | */ 48 | } 49 | /// @return r the negation of p, i.e. p.addition(p.negate()) should be zero. 50 | function negate(G1Point memory p) internal pure returns (G1Point memory r) { 51 | // The prime q in the base field F_q for G1 52 | uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; 53 | if (p.X == 0 && p.Y == 0) 54 | return G1Point(0, 0); 55 | return G1Point(p.X, q - (p.Y % q)); 56 | } 57 | /// @return r the sum of two points of G1 58 | function addition(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { 59 | uint[4] memory input; 60 | input[0] = p1.X; 61 | input[1] = p1.Y; 62 | input[2] = p2.X; 63 | input[3] = p2.Y; 64 | bool success; 65 | // solium-disable-next-line security/no-inline-assembly 66 | assembly { 67 | success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) 68 | // Use "invalid" to make gas estimation work 69 | switch success case 0 { invalid() } 70 | } 71 | require(success,"pairing-add-failed"); 72 | } 73 | /// @return r the product of a point on G1 and a scalar, i.e. 74 | /// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p. 75 | function scalar_mul(G1Point memory p, uint s) internal view returns (G1Point memory r) { 76 | uint[3] memory input; 77 | input[0] = p.X; 78 | input[1] = p.Y; 79 | input[2] = s; 80 | bool success; 81 | // solium-disable-next-line security/no-inline-assembly 82 | assembly { 83 | success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) 84 | // Use "invalid" to make gas estimation work 85 | switch success case 0 { invalid() } 86 | } 87 | require (success,"pairing-mul-failed"); 88 | } 89 | /// @return the result of computing the pairing check 90 | /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 91 | /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should 92 | /// return true. 93 | function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) { 94 | require(p1.length == p2.length,"pairing-lengths-failed"); 95 | uint elements = p1.length; 96 | uint inputSize = elements * 6; 97 | uint[] memory input = new uint[](inputSize); 98 | for (uint i = 0; i < elements; i++) 99 | { 100 | input[i * 6 + 0] = p1[i].X; 101 | input[i * 6 + 1] = p1[i].Y; 102 | input[i * 6 + 2] = p2[i].X[0]; 103 | input[i * 6 + 3] = p2[i].X[1]; 104 | input[i * 6 + 4] = p2[i].Y[0]; 105 | input[i * 6 + 5] = p2[i].Y[1]; 106 | } 107 | uint[1] memory out; 108 | bool success; 109 | // solium-disable-next-line security/no-inline-assembly 110 | assembly { 111 | success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) 112 | // Use "invalid" to make gas estimation work 113 | switch success case 0 { invalid() } 114 | } 115 | require(success,"pairing-opcode-failed"); 116 | return out[0] != 0; 117 | } 118 | /// Convenience method for a pairing check for two pairs. 119 | function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) internal view returns (bool) { 120 | G1Point[] memory p1 = new G1Point[](2); 121 | G2Point[] memory p2 = new G2Point[](2); 122 | p1[0] = a1; 123 | p1[1] = b1; 124 | p2[0] = a2; 125 | p2[1] = b2; 126 | return pairing(p1, p2); 127 | } 128 | /// Convenience method for a pairing check for three pairs. 129 | function pairingProd3( 130 | G1Point memory a1, G2Point memory a2, 131 | G1Point memory b1, G2Point memory b2, 132 | G1Point memory c1, G2Point memory c2 133 | ) internal view returns (bool) { 134 | G1Point[] memory p1 = new G1Point[](3); 135 | G2Point[] memory p2 = new G2Point[](3); 136 | p1[0] = a1; 137 | p1[1] = b1; 138 | p1[2] = c1; 139 | p2[0] = a2; 140 | p2[1] = b2; 141 | p2[2] = c2; 142 | return pairing(p1, p2); 143 | } 144 | /// Convenience method for a pairing check for four pairs. 145 | function pairingProd4( 146 | G1Point memory a1, G2Point memory a2, 147 | G1Point memory b1, G2Point memory b2, 148 | G1Point memory c1, G2Point memory c2, 149 | G1Point memory d1, G2Point memory d2 150 | ) internal view returns (bool) { 151 | G1Point[] memory p1 = new G1Point[](4); 152 | G2Point[] memory p2 = new G2Point[](4); 153 | p1[0] = a1; 154 | p1[1] = b1; 155 | p1[2] = c1; 156 | p1[3] = d1; 157 | p2[0] = a2; 158 | p2[1] = b2; 159 | p2[2] = c2; 160 | p2[3] = d2; 161 | return pairing(p1, p2); 162 | } 163 | } 164 | contract Verifier { 165 | using Pairing for *; 166 | struct VerifyingKey { 167 | Pairing.G1Point alfa1; 168 | Pairing.G2Point beta2; 169 | Pairing.G2Point gamma2; 170 | Pairing.G2Point delta2; 171 | Pairing.G1Point[] IC; 172 | } 173 | struct Proof { 174 | Pairing.G1Point A; 175 | Pairing.G2Point B; 176 | Pairing.G1Point C; 177 | } 178 | function verifyingKey() internal pure returns (VerifyingKey memory vk) { 179 | vk.alfa1 = Pairing.G1Point( 180 | <%=vk_alpha_1[0]%>, 181 | <%=vk_alpha_1[1]%> 182 | ); 183 | 184 | vk.beta2 = Pairing.G2Point( 185 | [<%=vk_beta_2[0][1]%>, 186 | <%=vk_beta_2[0][0]%>], 187 | [<%=vk_beta_2[1][1]%>, 188 | <%=vk_beta_2[1][0]%>] 189 | ); 190 | vk.gamma2 = Pairing.G2Point( 191 | [<%=vk_gamma_2[0][1]%>, 192 | <%=vk_gamma_2[0][0]%>], 193 | [<%=vk_gamma_2[1][1]%>, 194 | <%=vk_gamma_2[1][0]%>] 195 | ); 196 | vk.delta2 = Pairing.G2Point( 197 | [<%=vk_delta_2[0][1]%>, 198 | <%=vk_delta_2[0][0]%>], 199 | [<%=vk_delta_2[1][1]%>, 200 | <%=vk_delta_2[1][0]%>] 201 | ); 202 | vk.IC = new Pairing.G1Point[](<%=IC.length%>); 203 | <% for (let i=0; i 204 | vk.IC[<%=i%>] = Pairing.G1Point( 205 | <%=IC[i][0]%>, 206 | <%=IC[i][1]%> 207 | ); 208 | <% } %> 209 | } 210 | function verify(uint[] memory input, Proof memory proof) internal view returns (uint) { 211 | uint256 snark_scalar_field = 21888242871839275222246405745257275088548364400416034343698204186575808495617; 212 | VerifyingKey memory vk = verifyingKey(); 213 | require(input.length + 1 == vk.IC.length,"verifier-bad-input"); 214 | // Compute the linear combination vk_x 215 | Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); 216 | for (uint i = 0; i < input.length; i++) { 217 | require(input[i] < snark_scalar_field,"verifier-gte-snark-scalar-field"); 218 | vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i])); 219 | } 220 | vk_x = Pairing.addition(vk_x, vk.IC[0]); 221 | if (!Pairing.pairingProd4( 222 | Pairing.negate(proof.A), proof.B, 223 | vk.alfa1, vk.beta2, 224 | vk_x, vk.gamma2, 225 | proof.C, vk.delta2 226 | )) return 1; 227 | return 0; 228 | } 229 | /// @return r bool true if proof is valid 230 | function verifyProof( 231 | uint[2] memory a, 232 | uint[2][2] memory b, 233 | uint[2] memory c, 234 | uint[<%=IC.length-1%>] memory input 235 | ) public view returns (bool r) { 236 | Proof memory proof; 237 | proof.A = Pairing.G1Point(a[0], a[1]); 238 | proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]); 239 | proof.C = Pairing.G1Point(c[0], c[1]); 240 | uint[] memory inputValues = new uint[](input.length); 241 | for(uint i = 0; i < input.length; i++){ 242 | inputValues[i] = input[i]; 243 | } 244 | if (verify(inputValues, proof) == 0) { 245 | return true; 246 | } else { 247 | return false; 248 | } 249 | } 250 | } 251 | --------------------------------------------------------------------------------