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[2] calldata u_i_cmW, // [u_i_cmW[2]]
67 uint256[3] calldata cmT_r, // [cmT[2], r]
68 uint256[2] calldata pA, // groth16
69 uint256[2][2] calldata pB, // groth16
70 uint256[2] calldata pC, // groth16
71 uint256[4] calldata challenge_W_challenge_E_kzg_evals, // [challenge_W, challenge_E, eval_W, eval_E]
72 uint256[2][2] calldata kzg_proof // [proof_W, proof_E]
73 ) public view returns (bool) {
74
75 require(i_z0_zi[0] >= 2, "Folding: the number of folded steps should be at least 2");
76
77 // from gamma_abc_len, we subtract 1.
78 uint256[{{ public_inputs_len - 1 }}] memory public_inputs;
79
80 public_inputs[0] = {{pp_hash}};
81 public_inputs[1] = i_z0_zi[0];
82
83 for (uint i = 0; i < {{ z_len * 2 }}; i++) {
84 public_inputs[2 + i] = i_z0_zi[1 + i];
85 }
86
87 {
88 // U_i.cmW + r * u_i.cmW
89 uint256[2] memory mulScalarPoint = super.mulScalar([u_i_cmW[0], u_i_cmW[1]], cmT_r[2]);
90 uint256[2] memory cmW = super.add([U_i_cmW_U_i_cmE[0], U_i_cmW_U_i_cmE[1]], mulScalarPoint);
91
92 {
93 uint256[{{num_limbs}}] memory cmW_x_limbs = LimbsDecomposition.decompose(cmW[0]);
94 uint256[{{num_limbs}}] memory cmW_y_limbs = LimbsDecomposition.decompose(cmW[1]);
95
96 for (uint8 k = 0; k < {{num_limbs}}; k++) {
97 public_inputs[{{ z_len * 2 + 2 }} + k] = cmW_x_limbs[k];
98 public_inputs[{{ z_len * 2 + 2 + num_limbs }} + k] = cmW_y_limbs[k];
99 }
100 }
101
102 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");
103 }
104
105 {
106 // U_i.cmE + r * cmT
107 uint256[2] memory mulScalarPoint = super.mulScalar([cmT_r[0], cmT_r[1]], cmT_r[2]);
108 uint256[2] memory cmE = super.add([U_i_cmW_U_i_cmE[2], U_i_cmW_U_i_cmE[3]], mulScalarPoint);
109
110 {
111 uint256[{{num_limbs}}] memory cmE_x_limbs = LimbsDecomposition.decompose(cmE[0]);
112 uint256[{{num_limbs}}] memory cmE_y_limbs = LimbsDecomposition.decompose(cmE[1]);
113
114 for (uint8 k = 0; k < {{num_limbs}}; k++) {
115 public_inputs[{{ z_len * 2 + 2 + num_limbs * 2 }} + k] = cmE_x_limbs[k];
116 public_inputs[{{ z_len * 2 + 2 + num_limbs * 3 }} + k] = cmE_y_limbs[k];
117 }
118 }
119
120 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");
121 }
122
123 {
124 // add challenges
125 public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 }}] = challenge_W_challenge_E_kzg_evals[0];
126 public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 + 1 }}] = challenge_W_challenge_E_kzg_evals[1];
127 public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 + 2 }}] = challenge_W_challenge_E_kzg_evals[2];
128 public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 + 3 }}] = challenge_W_challenge_E_kzg_evals[3];
129
130 uint256[{{num_limbs}}] memory cmT_x_limbs;
131 uint256[{{num_limbs}}] memory cmT_y_limbs;
132
133 cmT_x_limbs = LimbsDecomposition.decompose(cmT_r[0]);
134 cmT_y_limbs = LimbsDecomposition.decompose(cmT_r[1]);
135
136 for (uint8 k = 0; k < {{num_limbs}}; k++) {
137 public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 }} + 4 + k] = cmT_x_limbs[k];
138 public_inputs[{{ z_len * 2 + 2 + num_limbs * 5 }} + 4 + k] = cmT_y_limbs[k];
139 }
140
141 bool success_g16 = this.verifyProof(pA, pB, pC, public_inputs);
142 require(success_g16 == true, "Groth16: verifying proof failed");
143 }
144
145 return(true);
146 }
147}