sonobe/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol
solidity-verifiers/templates/nova_cyclefold_decider.askama.solRaw
1/*
2 Sonobe's Nova + CycleFold decider verifier.
3 Joint effort by 0xPARC & PSE.
4
5 More details at https://github.com/privacy-scaling-explorations/sonobe
6 Usage and design documentation at https://privacy-scaling-explorations.github.io/sonobe-docs/
7
8 Uses the https://github.com/iden3/snarkjs/blob/master/templates/verifier_groth16.sol.ejs
9 Groth16 verifier implementation and a KZG10 Solidity template adapted from
10 https://github.com/weijiekoh/libkzg.
11 Additionally we implement the NovaDecider contract, which combines the
12 Groth16 and KZG10 verifiers to verify the zkSNARK proofs coming from
13 Nova+CycleFold folding.
14*/
15
16
17/* =============================== */
18/* KZG10 verifier methods */
19{{ kzg10_verifier }}
20
21/* =============================== */
22/* Groth16 verifier methods */
23{{ groth16_verifier }}
24
25
26/* =============================== */
27/* Nova+CycleFold Decider verifier */
28/**
29 * @notice Computes the decomposition of a `uint256` into num_limbs limbs of bits_per_limb bits each.
30 * @dev Compatible with sonobe::folding-schemes::folding::circuits::nonnative::nonnative_field_to_field_elements.
31 */
32library LimbsDecomposition {
33 function decompose(uint256 x) internal pure returns (uint256[{{num_limbs}}] memory) {
34 uint256[{{num_limbs}}] memory limbs;
35 for (uint8 i = 0; i < {{num_limbs}}; i++) {
36 limbs[i] = (x >> ({{bits_per_limb}} * i)) & ((1 << {{bits_per_limb}}) - 1);
37 }
38 return limbs;
39 }
40}
41
42/**
43 * @author PSE & 0xPARC
44 * @title NovaDecider contract, for verifying Nova IVC SNARK proofs.
45 * @dev This is an askama template which, when templated, features a Groth16 and KZG10 verifiers from which this contract inherits.
46 */
47contract NovaDecider is Groth16Verifier, KZG10Verifier {
48 /**
49 * @notice Computes the linear combination of a and b with r as the coefficient.
50 * @dev All ops are done mod the BN254 scalar field prime
51 */
52 function rlc(uint256 a, uint256 r, uint256 b) internal pure returns (uint256 result) {
53 assembly {
54 result := addmod(a, mulmod(r, b, BN254_SCALAR_FIELD), BN254_SCALAR_FIELD)
55 }
56 }
57
58 /**
59 * @notice Verifies a nova cyclefold proof consisting of two KZG proofs and of a groth16 proof.
60 * @dev The selector of this function is "dynamic", since it depends on `z_len`.
61 */
62 function verifyNovaProof(
63 // inputs are grouped to prevent errors due stack too deep
64 uint256[{{ 1 + z_len * 2 }}] calldata i_z0_zi, // [i, z0, zi] where |z0| == |zi|
65 uint256[4] calldata U_i_cmW_U_i_cmE, // [U_i_cmW[2], U_i_cmE[2]]
66 uint256[3] calldata U_i_u_u_i_u_r, // [U_i_u, u_i_u, r]
67 uint256[4] calldata U_i_x_u_i_cmW, // [U_i_x[2], u_i_cmW[2]]
68 uint256[4] calldata u_i_x_cmT, // [u_i_x[2], cmT[2]]
69 uint256[2] calldata pA, // groth16
70 uint256[2][2] calldata pB, // groth16
71 uint256[2] calldata pC, // groth16
72 uint256[4] calldata challenge_W_challenge_E_kzg_evals, // [challenge_W, challenge_E, eval_W, eval_E]
73 uint256[2][2] calldata kzg_proof // [proof_W, proof_E]
74 ) public view returns (bool) {
75
76 require(i_z0_zi[0] >= 2, "Folding: the number of folded steps should be at least 2");
77
78 // from gamma_abc_len, we subtract 1.
79 uint256[{{ public_inputs_len - 1 }}] memory public_inputs;
80
81 public_inputs[0] = i_z0_zi[0];
82
83 for (uint i = 0; i < {{ z_len * 2 }}; i++) {
84 public_inputs[1 + i] = i_z0_zi[1 + i];
85 }
86
87 {
88 // U_i.u + r * u_i.u
89 uint256 u = rlc(U_i_u_u_i_u_r[0], U_i_u_u_i_u_r[2], U_i_u_u_i_u_r[1]);
90 // U_i.x + r * u_i.x
91 uint256 x0 = rlc(U_i_x_u_i_cmW[0], U_i_u_u_i_u_r[2], u_i_x_cmT[0]);
92 uint256 x1 = rlc(U_i_x_u_i_cmW[1], U_i_u_u_i_u_r[2], u_i_x_cmT[1]);
93
94 public_inputs[{{ z_len * 2 + 1 }}] = u;
95 public_inputs[{{ z_len * 2 + 2 }}] = x0;
96 public_inputs[{{ z_len * 2 + 3 }}] = x1;
97 }
98
99 {
100 // U_i.cmE + r * u_i.cmT
101 uint256[2] memory mulScalarPoint = super.mulScalar([u_i_x_cmT[2], u_i_x_cmT[3]], U_i_u_u_i_u_r[2]);
102 uint256[2] memory cmE = super.add([U_i_cmW_U_i_cmE[2], U_i_cmW_U_i_cmE[3]], mulScalarPoint);
103
104 {
105 uint256[{{num_limbs}}] memory cmE_x_limbs = LimbsDecomposition.decompose(cmE[0]);
106 uint256[{{num_limbs}}] memory cmE_y_limbs = LimbsDecomposition.decompose(cmE[1]);
107
108 for (uint8 k = 0; k < {{num_limbs}}; k++) {
109 public_inputs[{{ z_len * 2 + 4 }} + k] = cmE_x_limbs[k];
110 public_inputs[{{ z_len * 2 + 4 + num_limbs }} + k] = cmE_y_limbs[k];
111 }
112 }
113
114 require(this.check(cmE, kzg_proof[1], challenge_W_challenge_E_kzg_evals[1], challenge_W_challenge_E_kzg_evals[3]), "KZG: verifying proof for challenge E failed");
115 }
116
117 {
118 // U_i.cmW + r * u_i.cmW
119 uint256[2] memory mulScalarPoint = super.mulScalar([U_i_x_u_i_cmW[2], U_i_x_u_i_cmW[3]], U_i_u_u_i_u_r[2]);
120 uint256[2] memory cmW = super.add([U_i_cmW_U_i_cmE[0], U_i_cmW_U_i_cmE[1]], mulScalarPoint);
121
122 {
123 uint256[{{num_limbs}}] memory cmW_x_limbs = LimbsDecomposition.decompose(cmW[0]);
124 uint256[{{num_limbs}}] memory cmW_y_limbs = LimbsDecomposition.decompose(cmW[1]);
125
126 for (uint8 k = 0; k < {{num_limbs}}; k++) {
127 public_inputs[{{ z_len * 2 + 4 + num_limbs * 2 }} + k] = cmW_x_limbs[k];
128 public_inputs[{{ z_len * 2 + 4 + num_limbs * 3 }} + k] = cmW_y_limbs[k];
129 }
130 }
131
132 require(this.check(cmW, kzg_proof[0], challenge_W_challenge_E_kzg_evals[0], challenge_W_challenge_E_kzg_evals[2]), "KZG: verifying proof for challenge W failed");
133 }
134
135 {
136 // add challenges
137 public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 }}] = challenge_W_challenge_E_kzg_evals[0];
138 public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 + 1 }}] = challenge_W_challenge_E_kzg_evals[1];
139 public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 + 2 }}] = challenge_W_challenge_E_kzg_evals[2];
140 public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 + 3 }}] = challenge_W_challenge_E_kzg_evals[3];
141
142 uint256[{{num_limbs}}] memory cmT_x_limbs;
143 uint256[{{num_limbs}}] memory cmT_y_limbs;
144
145 cmT_x_limbs = LimbsDecomposition.decompose(u_i_x_cmT[2]);
146 cmT_y_limbs = LimbsDecomposition.decompose(u_i_x_cmT[3]);
147
148 for (uint8 k = 0; k < {{num_limbs}}; k++) {
149 public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 }} + 4 + k] = cmT_x_limbs[k];
150 public_inputs[{{ z_len * 2 + 4 + num_limbs * 5}} + 4 + k] = cmT_y_limbs[k];
151 }
152
153 // last element of the groth16 proof's public inputs is `r`
154 public_inputs[{{ public_inputs_len - 2 }}] = U_i_u_u_i_u_r[2];
155
156 bool success_g16 = this.verifyProof(pA, pB, pC, public_inputs);
157 require(success_g16 == true, "Groth16: verifying proof failed");
158 }
159
160 return(true);
161 }
162}