arnaucube
arnaucube/sonobePublic Self-hosted repository
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}