mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-08 15:01:30 +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:
@@ -20,6 +20,8 @@ rayon = "1.7.0"
|
||||
num-bigint = "0.4"
|
||||
num-integer = "0.1"
|
||||
color-eyre = "=0.6.2"
|
||||
ark-bn254 = {version="0.4.0"}
|
||||
ark-groth16 = { version = "^0.4.0" }
|
||||
|
||||
# tmp imports for espresso's sumcheck
|
||||
espresso_subroutines = {git="https://github.com/EspressoSystems/hyperplonk", package="subroutines"}
|
||||
@@ -29,13 +31,13 @@ ark-pallas = {version="0.4.0", features=["r1cs"]}
|
||||
ark-vesta = {version="0.4.0", features=["r1cs"]}
|
||||
ark-bn254 = {version="0.4.0", features=["r1cs"]}
|
||||
ark-grumpkin = {version="0.4.0", features=["r1cs"]}
|
||||
ark-groth16 = { version = "^0.4.0" }
|
||||
rand = "0.8.5"
|
||||
tracing = { version = "0.1", default-features = false, features = [ "attributes" ] }
|
||||
tracing-subscriber = { version = "0.2" }
|
||||
|
||||
[features]
|
||||
default = ["parallel"]
|
||||
light-test = []
|
||||
|
||||
parallel = [
|
||||
"ark-std/parallel",
|
||||
|
||||
@@ -174,7 +174,7 @@ impl<F: PrimeField> ToBitsGadget<F> for LimbVar<F> {
|
||||
pub struct NonNativeUintVar<F: PrimeField>(pub Vec<LimbVar<F>>);
|
||||
|
||||
impl<F: PrimeField> NonNativeUintVar<F> {
|
||||
const fn bits_per_limb() -> usize {
|
||||
pub const fn bits_per_limb() -> usize {
|
||||
assert!(F::MODULUS_BIT_SIZE > 250);
|
||||
// For a `F` with order > 250 bits, 55 is chosen for optimizing the most
|
||||
// expensive part `Az∘Bz` when checking the R1CS relation for CycleFold.
|
||||
|
||||
@@ -245,7 +245,7 @@ pub mod tests {
|
||||
/// - M(01) = 4*eq_00(r) + 3*eq_10(r) + 9*eq_01(r) + 2*eq_11(r)
|
||||
/// - M(11) = 4*eq_00(r) + 2*eq_10(r) + 2*eq_01(r) + 0*eq_11(r)
|
||||
///
|
||||
/// This is used by Hypernova in LCCCS to perform a verifier-chosen random linear combination between the columns
|
||||
/// This is used by HyperNova in LCCCS to perform a verifier-chosen random linear combination between the columns
|
||||
/// of the matrix and the z vector. This technique is also used extensively in "An Algebraic Framework for
|
||||
/// Universal and Updatable SNARKs".
|
||||
#[test]
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
/// This file implements the onchain (Ethereum's EVM) decider.
|
||||
use ark_bn254::Bn254;
|
||||
use ark_crypto_primitives::sponge::Absorb;
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_ec::{AffineRepr, CurveGroup, Group};
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_groth16::Groth16;
|
||||
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
|
||||
use ark_snark::SNARK;
|
||||
use ark_std::rand::{CryptoRng, RngCore};
|
||||
use ark_std::Zero;
|
||||
use ark_std::{One, Zero};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use super::decider_eth_circuit::{DeciderEthCircuit, KZGChallengesGadget};
|
||||
use super::{circuits::CF2, nifs::NIFS, CommittedInstance, Nova};
|
||||
use crate::commitment::{
|
||||
kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme,
|
||||
kzg::{Proof as KZGProof, KZG},
|
||||
pedersen::Params as PedersenParams,
|
||||
CommitmentScheme,
|
||||
};
|
||||
use crate::folding::circuits::nonnative::affine::NonNativeAffineVar;
|
||||
use crate::frontend::FCircuit;
|
||||
@@ -143,8 +147,12 @@ where
|
||||
z_i: Vec<C1::ScalarField>,
|
||||
running_instance: &Self::CommittedInstance,
|
||||
incoming_instance: &Self::CommittedInstance,
|
||||
proof: Self::Proof,
|
||||
proof: &Self::Proof,
|
||||
) -> Result<bool, Error> {
|
||||
if i <= C1::ScalarField::one() {
|
||||
return Err(Error::NotEnoughSteps);
|
||||
}
|
||||
|
||||
let (snark_vk, cs_vk): (S::VerifyingKey, CS1::VerifierParams) = vp;
|
||||
|
||||
// compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT)
|
||||
@@ -199,6 +207,78 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepares solidity calldata for calling the NovaDecider contract
|
||||
pub fn prepare_calldata(
|
||||
function_signature_check: [u8; 4],
|
||||
i: ark_bn254::Fr,
|
||||
z_0: Vec<ark_bn254::Fr>,
|
||||
z_i: Vec<ark_bn254::Fr>,
|
||||
running_instance: &CommittedInstance<ark_bn254::G1Projective>,
|
||||
incoming_instance: &CommittedInstance<ark_bn254::G1Projective>,
|
||||
proof: Proof<ark_bn254::G1Projective, KZG<'static, Bn254>, Groth16<Bn254>>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
Ok(vec![
|
||||
function_signature_check.to_vec(),
|
||||
i.into_bigint().to_bytes_be(), // i
|
||||
z_0.iter()
|
||||
.flat_map(|v| v.into_bigint().to_bytes_be())
|
||||
.collect::<Vec<u8>>(), // z_0
|
||||
z_i.iter()
|
||||
.flat_map(|v| v.into_bigint().to_bytes_be())
|
||||
.collect::<Vec<u8>>(), // z_i
|
||||
point_to_eth_format(running_instance.cmW.into_affine())?, // U_i_cmW
|
||||
point_to_eth_format(running_instance.cmE.into_affine())?, // U_i_cmE
|
||||
running_instance.u.into_bigint().to_bytes_be(), // U_i_u
|
||||
incoming_instance.u.into_bigint().to_bytes_be(), // u_i_u
|
||||
proof.r.into_bigint().to_bytes_be(), // r
|
||||
running_instance
|
||||
.x
|
||||
.iter()
|
||||
.flat_map(|v| v.into_bigint().to_bytes_be())
|
||||
.collect::<Vec<u8>>(), // U_i_x
|
||||
point_to_eth_format(incoming_instance.cmW.into_affine())?, // u_i_cmW
|
||||
incoming_instance
|
||||
.x
|
||||
.iter()
|
||||
.flat_map(|v| v.into_bigint().to_bytes_be())
|
||||
.collect::<Vec<u8>>(), // u_i_x
|
||||
point_to_eth_format(proof.cmT.into_affine())?, // cmT
|
||||
point_to_eth_format(proof.snark_proof.a)?, // pA
|
||||
point2_to_eth_format(proof.snark_proof.b)?, // pB
|
||||
point_to_eth_format(proof.snark_proof.c)?, // pC
|
||||
proof.kzg_challenges[0].into_bigint().to_bytes_be(), // challenge_W
|
||||
proof.kzg_challenges[1].into_bigint().to_bytes_be(), // challenge_E
|
||||
proof.kzg_proofs[0].eval.into_bigint().to_bytes_be(), // eval W
|
||||
proof.kzg_proofs[1].eval.into_bigint().to_bytes_be(), // eval E
|
||||
point_to_eth_format(proof.kzg_proofs[0].proof.into_affine())?, // W kzg_proof
|
||||
point_to_eth_format(proof.kzg_proofs[1].proof.into_affine())?, // E kzg_proof
|
||||
]
|
||||
.concat())
|
||||
}
|
||||
|
||||
fn point_to_eth_format<C: AffineRepr>(p: C) -> Result<Vec<u8>, Error>
|
||||
where
|
||||
C::BaseField: PrimeField,
|
||||
{
|
||||
// the encoding of the additive identity is [0, 0] on the EVM
|
||||
let zero_point = (&C::BaseField::zero(), &C::BaseField::zero());
|
||||
let (x, y) = p.xy().unwrap_or(zero_point);
|
||||
|
||||
Ok([x.into_bigint().to_bytes_be(), y.into_bigint().to_bytes_be()].concat())
|
||||
}
|
||||
fn point2_to_eth_format(p: ark_bn254::G2Affine) -> Result<Vec<u8>, Error> {
|
||||
let zero_point = (&ark_bn254::Fq2::zero(), &ark_bn254::Fq2::zero());
|
||||
let (x, y) = p.xy().unwrap_or(zero_point);
|
||||
|
||||
Ok([
|
||||
x.c1.into_bigint().to_bytes_be(),
|
||||
x.c0.into_bigint().to_bytes_be(),
|
||||
y.c1.into_bigint().to_bytes_be(),
|
||||
y.c0.into_bigint().to_bytes_be(),
|
||||
]
|
||||
.concat())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
@@ -298,7 +378,7 @@ pub mod tests {
|
||||
let start = Instant::now();
|
||||
let decider_vp = (g16_vk, kzg_vk);
|
||||
let verified = DECIDER::verify(
|
||||
decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, proof,
|
||||
decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, &proof,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(verified);
|
||||
|
||||
@@ -426,10 +426,14 @@ where
|
||||
.hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
|
||||
(u_i.x[0]).enforce_equal(&u_i_x)?;
|
||||
|
||||
#[cfg(feature = "light-test")]
|
||||
println!("[WARNING]: Running with the 'light-test' feature, skipping the big part of the DeciderEthCircuit.\n Only for testing purposes.");
|
||||
|
||||
// The following two checks (and their respective allocations) are disabled for normal
|
||||
// tests since they take several millions of constraints and would take several minutes
|
||||
// (and RAM) to run the test.
|
||||
#[cfg(not(test))]
|
||||
// (and RAM) to run the test. It is active by default, and not active only when
|
||||
// 'light-test' feature is used.
|
||||
#[cfg(not(feature = "light-test"))]
|
||||
{
|
||||
// imports here instead of at the top of the file, so we avoid having multiple
|
||||
// `#[cfg(not(test))]`
|
||||
|
||||
@@ -307,7 +307,7 @@ fn pow_i<F: PrimeField>(i: usize, betas: &Vec<F>) -> F {
|
||||
|
||||
/// calculates F[x] using the optimized binary-tree technique
|
||||
/// described in Claim 4.4
|
||||
/// of [Protogalaxy](https://eprint.iacr.org/2023/1106.pdf)
|
||||
/// of [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf)
|
||||
fn calc_f_from_btree<F: PrimeField>(
|
||||
fw: &[F],
|
||||
betas: &[F],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ec::{pairing::Pairing, CurveGroup};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_std::rand::CryptoRng;
|
||||
use ark_std::{fmt::Debug, rand::RngCore};
|
||||
@@ -34,8 +34,6 @@ pub enum Error {
|
||||
ProtoGalaxy(folding::protogalaxy::ProtoGalaxyError),
|
||||
#[error("std::io::Error")]
|
||||
IOError(#[from] std::io::Error),
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
|
||||
// Relation errors
|
||||
#[error("Relation not satisfied")]
|
||||
@@ -68,6 +66,8 @@ pub enum Error {
|
||||
OutOfBounds,
|
||||
#[error("Could not construct the Evaluation Domain")]
|
||||
NewDomainFail,
|
||||
#[error("The number of folded steps must be greater than 1")]
|
||||
NotEnoughSteps,
|
||||
|
||||
// Commitment errors
|
||||
#[error("Pedersen parameters length is not sufficient (generators.len={0} < vector.len={1} unsatisfied)")]
|
||||
@@ -78,6 +78,8 @@ pub enum Error {
|
||||
CommitmentVerificationFail,
|
||||
|
||||
// Other
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
#[error("Randomness for blinding not found")]
|
||||
MissingRandomness,
|
||||
#[error("Missing value: {0}")]
|
||||
@@ -178,8 +180,27 @@ pub trait Decider<
|
||||
z_i: Vec<C1::ScalarField>,
|
||||
running_instance: &Self::CommittedInstance,
|
||||
incoming_instance: &Self::CommittedInstance,
|
||||
proof: Self::Proof,
|
||||
proof: &Self::Proof,
|
||||
// returns `Result<bool, Error>` to differentiate between an error occurred while performing
|
||||
// the verification steps, and the verification logic of the scheme not passing.
|
||||
) -> Result<bool, Error>;
|
||||
}
|
||||
|
||||
/// DeciderOnchain extends the Decider into preparing the calldata
|
||||
pub trait DeciderOnchain<E: Pairing, C1: CurveGroup, C2: CurveGroup>
|
||||
where
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
C2::BaseField: PrimeField,
|
||||
{
|
||||
type Proof;
|
||||
type CommittedInstance: Clone + Debug;
|
||||
|
||||
fn prepare_calldata(
|
||||
i: C1::ScalarField,
|
||||
z_0: Vec<C1::ScalarField>,
|
||||
z_i: Vec<C1::ScalarField>,
|
||||
running_instance: &Self::CommittedInstance,
|
||||
incoming_instance: &Self::CommittedInstance,
|
||||
proof: Self::Proof,
|
||||
) -> Result<Vec<u8>, Error>;
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ impl<C: CurveGroup> SumCheckVerifier<C> for IOPVerifierState<C> {
|
||||
.clone()
|
||||
.into_iter()
|
||||
.zip(self.challenges.clone().into_iter())
|
||||
.map(|(evaluations, challenge)| {
|
||||
.map(|(coeffs, challenge)| {
|
||||
// Removed check on number of evaluations here since verifier receives polynomial in coeffs form
|
||||
let prover_poly = DensePolynomial::from_coefficients_slice(&coeffs);
|
||||
Ok(prover_poly.evaluate(&challenge))
|
||||
|
||||
Reference in New Issue
Block a user