mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-09 15:31:29 +01:00
Add solidity verifier of the nova+cyclefold (#87)
* Add solidity verifier of the nova+cyclefold, and add method to prepare the calldata from Decider's proof. Missing conversion of the point coordinates into limbs (ark compatible) * chore: adding comments linking to the contract's signature * chore: update .gitignore * chore: add num-bigint as dev dependency * fix: work with abs path for storing generated sol code * chore: update comment * feat: solidity verifier working on single and multi-input circuits * feat: multi-input folding verification working + fixing encoding of additive identity in calldata * chore: make bigint a dependency * refactor: import utils functions from utils.rs and make them available from anywhere * chore: make utils and evm available publicly * fix: pub mod instead * chore: make relevant method public and add `get_decider_template_for_cyclefold_decider` to exported objects * solidity-verifiers: move tests to their corresponding files * small update: Cyclefold -> CycleFold at the missing places * abstract nova-cyclefold solidity verifiers tests to avoid code duplication, and abstract also the computed setup params (FS & Decider) to compute them only once for all related tests to save test time * small polish after rebase to last main branch changes * rm unneeded Option for KZGData::g1_crs_batch_points * add checks modifying z_0 & z_i to nova_cyclefold_solidity_verifier test * add light-test feature to decider_eth_circuit to use it in solidity-verifier tests without the big circuit * solidity-verifiers: groth16 template: port the fix from https://github.com/iden3/snarkjs/pull/480 & https://github.com/iden3/snarkjs/issues/479 * add print warning msg for light-test in DeciderEthCircuit * solidity-verifiers: update limbs logic to nonnative last version, parametrize limbs params solidity-verifiers: * update solidity limbs logic to last nonnative impl version, and to last u_i.x impl * parametrize limbs params * add light-test feature: replace the '#[cfg(not(test))]' by the 'light-test' feature that by default is not enabled, so when running the github actions we enable the feature 'light-tests', and then we can have a full-test that runs the test without the 'light-tests' flag, but we don't run this big test every time. The choice of a feature is to allow us to control this from other-crates tests (for example for the solidity-verifier separated crate tests, to avoid running the full heavy circuit in the solidity tests) * move solidity constants into template constants for auto compute of params * polishing * revm use only needed feature This is to avoid c depencency for c-kzg which is behind the c-kzg flag and not needed. * nova_cyclefold_decider.sol header * rearrange test helpers position, add error for min number of steps * in solidity-verifiers: 'data'->'vk/verifier key' * add From for NovaCycleFoldVerifierKey from original vks to simplify dev flow, also conditionally template the batchCheck related structs and methods from the KZG10 solidity template --------- Co-authored-by: dmpierre <pdaixmoreux@gmail.com>
This commit is contained in:
@@ -56,7 +56,7 @@ contract Groth16Verifier {
|
||||
function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[{{ gamma_abc_len - 1 }}] calldata _pubSignals) public view returns (bool) {
|
||||
assembly {
|
||||
function checkField(v) {
|
||||
if iszero(lt(v, q)) {
|
||||
if iszero(lt(v, r)) {
|
||||
mstore(0, 0)
|
||||
return(0, 0x20)
|
||||
}
|
||||
@@ -166,4 +166,4 @@ contract Groth16Verifier {
|
||||
return(0, 0x20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ contract KZG10Verifier {
|
||||
]
|
||||
];
|
||||
|
||||
{% if g1_crs_len>0 %} // only enabled if g1_crs_len>0, for batch_check
|
||||
uint256[2][{{ g1_crs_len }}] G1_CRS = [
|
||||
{%- for (i, point) in g1_crs.iter().enumerate() %}
|
||||
[
|
||||
@@ -148,6 +149,7 @@ contract KZG10Verifier {
|
||||
{%- endif -%}
|
||||
{% endfor -%}
|
||||
];
|
||||
{%~ endif %}
|
||||
|
||||
/**
|
||||
* @notice Verifies a single point evaluation proof. Function name follows `ark-poly`.
|
||||
@@ -198,6 +200,7 @@ contract KZG10Verifier {
|
||||
return result;
|
||||
}
|
||||
|
||||
{% if g1_crs_len>0 %} // only enabled if g1_crs_len>0, for batch_check
|
||||
/**
|
||||
* @notice Ensures that z(x) == 0 and l(x) == y for all x in x_vals and y in y_vals. It returns the commitment to z(x) and l(x).
|
||||
* @param z_coeffs coefficients of the zero polynomial z(x) = (x - x_1)(x - x_2)...(x - x_n).
|
||||
@@ -268,4 +271,5 @@ contract KZG10Verifier {
|
||||
uint256[2] memory neg_commit = negate(c);
|
||||
return pairing(z_commit, pi, add(l_commit, neg_commit), G_2);
|
||||
}
|
||||
{%~ endif %}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,162 @@
|
||||
/*
|
||||
Sonobe's Nova + CycleFold decider verifier.
|
||||
Joint effort by 0xPARC & PSE.
|
||||
|
||||
More details at https://github.com/privacy-scaling-explorations/sonobe
|
||||
Usage and design documentation at https://privacy-scaling-explorations.github.io/sonobe-docs/
|
||||
|
||||
Uses the https://github.com/iden3/snarkjs/blob/master/templates/verifier_groth16.sol.ejs
|
||||
Groth16 verifier implementation and a KZG10 Solidity template adapted from
|
||||
https://github.com/weijiekoh/libkzg.
|
||||
Additionally we implement the NovaDecider contract, which combines the
|
||||
Groth16 and KZG10 verifiers to verify the zkSNARK proofs coming from
|
||||
Nova+CycleFold folding.
|
||||
*/
|
||||
|
||||
|
||||
/* =============================== */
|
||||
/* KZG10 verifier methods */
|
||||
{{ kzg10_verifier }}
|
||||
|
||||
/* =============================== */
|
||||
/* Groth16 verifier methods */
|
||||
{{ groth16_verifier }}
|
||||
|
||||
{{ kzg10_verifier }}
|
||||
|
||||
/* =============================== */
|
||||
/* Nova+CycleFold Decider verifier */
|
||||
/**
|
||||
* @notice Computes the decomposition of a `uint256` into num_limbs limbs of bits_per_limb bits each.
|
||||
* @dev Compatible with sonobe::folding-schemes::folding::circuits::nonnative::nonnative_field_to_field_elements.
|
||||
*/
|
||||
library LimbsDecomposition {
|
||||
function decompose(uint256 x) internal pure returns (uint256[{{num_limbs}}] memory) {
|
||||
uint256[{{num_limbs}}] memory limbs;
|
||||
for (uint8 i = 0; i < {{num_limbs}}; i++) {
|
||||
limbs[i] = (x >> ({{bits_per_limb}} * i)) & ((1 << {{bits_per_limb}}) - 1);
|
||||
}
|
||||
return limbs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author PSE & 0xPARC
|
||||
* @title NovaDecider contract, for verifying zk-snarks Nova IVC proofs.
|
||||
* @dev This is an askama template. It will feature a snarkjs groth16 and a kzg10 verifier, from which this contract inherits.
|
||||
* WARNING: This contract is not complete nor finished. It lacks checks to ensure that no soundness issues can happen.
|
||||
* Indeed, we know some of the checks that are missing. And we're working on the solution
|
||||
* but for now, it's good enough for testing and benchmarking.
|
||||
* @title NovaDecider contract, for verifying Nova IVC SNARK proofs.
|
||||
* @dev This is an askama template which, when templated, features a Groth16 and KZG10 verifiers from which this contract inherits.
|
||||
*/
|
||||
contract NovaDecider is Groth16Verifier, KZG10Verifier {
|
||||
function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[1] calldata _pubSignals, uint256[2] calldata c, uint256[2] calldata pi, uint256 x, uint256 y) public view returns (bool) {
|
||||
bool success_kzg = super.check(c, pi, x, y);
|
||||
require(success_kzg == true, "KZG Failed");
|
||||
/**
|
||||
* @notice Computes the linear combination of a and b with r as the coefficient.
|
||||
* @dev All ops are done mod the BN254 scalar field prime
|
||||
*/
|
||||
function rlc(uint256 a, uint256 r, uint256 b) internal pure returns (uint256 result) {
|
||||
assembly {
|
||||
result := addmod(a, mulmod(r, b, BN254_SCALAR_FIELD), BN254_SCALAR_FIELD)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Verifies a nova cyclefold proof consisting of two KZG proofs and of a groth16 proof.
|
||||
* @dev The selector of this function is "dynamic", since it depends on `z_len`.
|
||||
*/
|
||||
function verifyNovaProof(
|
||||
// inputs are grouped to prevent errors due stack too deep
|
||||
uint256[{{ 1 + z_len * 2 }}] calldata i_z0_zi, // [i, z0, zi] where |z0| == |zi|
|
||||
uint256[4] calldata U_i_cmW_U_i_cmE, // [U_i_cmW[2], U_i_cmE[2]]
|
||||
uint256[3] calldata U_i_u_u_i_u_r, // [U_i_u, u_i_u, r]
|
||||
uint256[4] calldata U_i_x_u_i_cmW, // [U_i_x[2], u_i_cmW[2]]
|
||||
uint256[4] calldata u_i_x_cmT, // [u_i_x[2], cmT[2]]
|
||||
uint256[2] calldata pA, // groth16
|
||||
uint256[2][2] calldata pB, // groth16
|
||||
uint256[2] calldata pC, // groth16
|
||||
uint256[4] calldata challenge_W_challenge_E_kzg_evals, // [challenge_W, challenge_E, eval_W, eval_E]
|
||||
uint256[2][2] calldata kzg_proof // [proof_W, proof_E]
|
||||
) public view returns (bool) {
|
||||
|
||||
require(i_z0_zi[0] >= 2, "Folding: the number of folded steps should be at least 2");
|
||||
|
||||
// from gamma_abc_len, we subtract 1.
|
||||
uint256[{{ public_inputs_len - 1 }}] memory public_inputs;
|
||||
|
||||
public_inputs[0] = i_z0_zi[0];
|
||||
|
||||
for (uint i = 0; i < {{ z_len * 2 }}; i++) {
|
||||
public_inputs[1 + i] = i_z0_zi[1 + i];
|
||||
}
|
||||
|
||||
{
|
||||
// U_i.u + r * u_i.u
|
||||
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]);
|
||||
// U_i.x + r * u_i.x
|
||||
uint256 x0 = rlc(U_i_x_u_i_cmW[0], U_i_u_u_i_u_r[2], u_i_x_cmT[0]);
|
||||
uint256 x1 = rlc(U_i_x_u_i_cmW[1], U_i_u_u_i_u_r[2], u_i_x_cmT[1]);
|
||||
|
||||
public_inputs[{{ z_len * 2 + 1 }}] = u;
|
||||
public_inputs[{{ z_len * 2 + 2 }}] = x0;
|
||||
public_inputs[{{ z_len * 2 + 3 }}] = x1;
|
||||
}
|
||||
|
||||
{
|
||||
// U_i.cmE + r * u_i.cmT
|
||||
uint256[2] memory mulScalarPoint = super.mulScalar([u_i_x_cmT[2], u_i_x_cmT[3]], U_i_u_u_i_u_r[2]);
|
||||
uint256[2] memory cmE = super.add([U_i_cmW_U_i_cmE[2], U_i_cmW_U_i_cmE[3]], mulScalarPoint);
|
||||
|
||||
{
|
||||
uint256[{{num_limbs}}] memory cmE_x_limbs = LimbsDecomposition.decompose(cmE[0]);
|
||||
uint256[{{num_limbs}}] memory cmE_y_limbs = LimbsDecomposition.decompose(cmE[1]);
|
||||
|
||||
for (uint8 k = 0; k < {{num_limbs}}; k++) {
|
||||
public_inputs[{{ z_len * 2 + 4 }} + k] = cmE_x_limbs[k];
|
||||
public_inputs[{{ z_len * 2 + 4 + num_limbs }} + k] = cmE_y_limbs[k];
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
{
|
||||
// U_i.cmW + r * u_i.cmW
|
||||
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]);
|
||||
uint256[2] memory cmW = super.add([U_i_cmW_U_i_cmE[0], U_i_cmW_U_i_cmE[1]], mulScalarPoint);
|
||||
|
||||
// for now, we do not relate the Groth16 and KZG10 proofs
|
||||
bool success_g16 = super.verifyProof(_pA, _pB, _pC, _pubSignals);
|
||||
require(success_g16 == true, "G16 Failed");
|
||||
{
|
||||
uint256[{{num_limbs}}] memory cmW_x_limbs = LimbsDecomposition.decompose(cmW[0]);
|
||||
uint256[{{num_limbs}}] memory cmW_y_limbs = LimbsDecomposition.decompose(cmW[1]);
|
||||
|
||||
for (uint8 k = 0; k < {{num_limbs}}; k++) {
|
||||
public_inputs[{{ z_len * 2 + 4 + num_limbs * 2 }} + k] = cmW_x_limbs[k];
|
||||
public_inputs[{{ z_len * 2 + 4 + num_limbs * 3 }} + k] = cmW_y_limbs[k];
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
{
|
||||
// add challenges
|
||||
public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 }}] = challenge_W_challenge_E_kzg_evals[0];
|
||||
public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 + 1 }}] = challenge_W_challenge_E_kzg_evals[1];
|
||||
public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 + 2 }}] = challenge_W_challenge_E_kzg_evals[2];
|
||||
public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 + 3 }}] = challenge_W_challenge_E_kzg_evals[3];
|
||||
|
||||
uint256[{{num_limbs}}] memory cmT_x_limbs;
|
||||
uint256[{{num_limbs}}] memory cmT_y_limbs;
|
||||
|
||||
cmT_x_limbs = LimbsDecomposition.decompose(u_i_x_cmT[2]);
|
||||
cmT_y_limbs = LimbsDecomposition.decompose(u_i_x_cmT[3]);
|
||||
|
||||
for (uint8 k = 0; k < {{num_limbs}}; k++) {
|
||||
public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 }} + 4 + k] = cmT_x_limbs[k];
|
||||
public_inputs[{{ z_len * 2 + 4 + num_limbs * 5}} + 4 + k] = cmT_y_limbs[k];
|
||||
}
|
||||
|
||||
// last element of the groth16 proof's public inputs is `r`
|
||||
public_inputs[{{ public_inputs_len - 2 }}] = U_i_u_u_i_u_r[2];
|
||||
|
||||
bool success_g16 = this.verifyProof(pA, pB, pC, public_inputs);
|
||||
require(success_g16 == true, "Groth16: verifying proof failed");
|
||||
}
|
||||
|
||||
return(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user