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:
@@ -12,19 +12,23 @@ ark-groth16 = "0.4"
|
||||
askama = { version = "0.12.0", features = ["config"], default-features = false }
|
||||
ark-bn254 = "0.4.0"
|
||||
ark-poly-commit = "0.4.0"
|
||||
folding-schemes = { path = "../folding-schemes/" }
|
||||
itertools = "0.12.1"
|
||||
ark-serialize = "0.4.1"
|
||||
revm = "3.5.0"
|
||||
revm = {version="3.5.0", default-features=false, features=["std"]}
|
||||
rust-crypto = "0.2"
|
||||
num-bigint = "0.4.3"
|
||||
folding-schemes = { path = "../folding-schemes/"} # without 'light-test' enabled
|
||||
|
||||
[dev-dependencies]
|
||||
ark-crypto-primitives = "0.4.0"
|
||||
ark-groth16 = "0.4"
|
||||
ark-r1cs-std = "0.4.0"
|
||||
ark-relations = "0.4.0"
|
||||
revm = "3.5.0"
|
||||
tracing = { version = "0.1", default-features = false, features = [ "attributes" ] }
|
||||
tracing-subscriber = { version = "0.2" }
|
||||
ark-bn254 = {version="0.4.0", features=["r1cs"]}
|
||||
ark-grumpkin = {version="0.4.0", features=["r1cs"]}
|
||||
rand = "0.8.5"
|
||||
folding-schemes = { path = "../folding-schemes/", features=["light-test"]}
|
||||
|
||||
[features]
|
||||
default = ["parallel"]
|
||||
|
||||
@@ -5,17 +5,23 @@ use revm::{
|
||||
};
|
||||
use std::{
|
||||
fmt::{self, Debug, Formatter},
|
||||
fs::{create_dir_all, File},
|
||||
fs::{self, create_dir_all, File},
|
||||
io::{self, Write},
|
||||
path::PathBuf,
|
||||
process::{Command, Stdio},
|
||||
str,
|
||||
};
|
||||
|
||||
// from: https://github.com/privacy-scaling-explorations/halo2-solidity-verifier/blob/85cb77b171ce3ee493628007c7a1cfae2ea878e6/examples/separately.rs#L56
|
||||
pub(crate) fn save_solidity(name: impl AsRef<str>, solidity: &str) {
|
||||
const DIR_GENERATED: &str = "./generated";
|
||||
create_dir_all(DIR_GENERATED).unwrap();
|
||||
File::create(format!("{DIR_GENERATED}/{}", name.as_ref()))
|
||||
pub fn save_solidity(name: impl AsRef<str>, solidity: &str) {
|
||||
let curdir = PathBuf::from(".");
|
||||
let curdir_abs_path = fs::canonicalize(curdir).expect("Failed to get current directory");
|
||||
let curdir_abs_path = curdir_abs_path
|
||||
.to_str()
|
||||
.expect("Failed to convert path to string");
|
||||
let dir_generated = format!("{curdir_abs_path}/generated");
|
||||
create_dir_all(dir_generated.clone()).unwrap();
|
||||
File::create(format!("{}/{}", dir_generated, name.as_ref()))
|
||||
.unwrap()
|
||||
.write_all(solidity.as_bytes())
|
||||
.unwrap();
|
||||
@@ -25,7 +31,7 @@ pub(crate) fn save_solidity(name: impl AsRef<str>, solidity: &str) {
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if executable `solc` can not be found, or compilation fails.
|
||||
pub(crate) fn compile_solidity(solidity: impl AsRef<[u8]>, contract_name: &str) -> Vec<u8> {
|
||||
pub fn compile_solidity(solidity: impl AsRef<[u8]>, contract_name: &str) -> Vec<u8> {
|
||||
let mut process = match Command::new("solc")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
@@ -72,7 +78,7 @@ fn find_binary(stdout: &str, contract_name: &str) -> Option<Vec<u8>> {
|
||||
}
|
||||
|
||||
/// Evm runner.
|
||||
pub(crate) struct Evm {
|
||||
pub struct Evm {
|
||||
evm: EVM<InMemoryDB>,
|
||||
}
|
||||
|
||||
@@ -103,7 +109,7 @@ impl Evm {
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if execution reverts or halts unexpectedly.
|
||||
pub(crate) fn create(&mut self, bytecode: Vec<u8>) -> Address {
|
||||
pub fn create(&mut self, bytecode: Vec<u8>) -> Address {
|
||||
let (_, output) = self.transact_success_or_panic(TxEnv {
|
||||
gas_limit: u64::MAX,
|
||||
transact_to: TransactTo::Create(CreateScheme::Create),
|
||||
@@ -121,7 +127,7 @@ impl Evm {
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if execution reverts or halts unexpectedly.
|
||||
pub(crate) fn call(&mut self, address: Address, calldata: Vec<u8>) -> (u64, Vec<u8>) {
|
||||
pub fn call(&mut self, address: Address, calldata: Vec<u8>) -> (u64, Vec<u8>) {
|
||||
let (gas_used, output) = self.transact_success_or_panic(TxEnv {
|
||||
gas_limit: u64::MAX,
|
||||
transact_to: TransactTo::Call(address),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#[cfg(test)]
|
||||
mod evm;
|
||||
mod utils;
|
||||
mod verifiers;
|
||||
pub mod evm;
|
||||
pub mod utils;
|
||||
pub mod verifiers;
|
||||
|
||||
pub use verifiers::*;
|
||||
pub use verifiers::{Groth16Data, KzgData, NovaCyclefoldData, ProtocolData};
|
||||
pub use verifiers::{
|
||||
get_decider_template_for_cyclefold_decider, Groth16VerifierKey, KZG10VerifierKey,
|
||||
NovaCycleFoldVerifierKey, ProtocolVerifierKey,
|
||||
};
|
||||
|
||||
@@ -1,11 +1,37 @@
|
||||
use crate::{GPL3_SDPX_IDENTIFIER, PRAGMA_GROTH16_VERIFIER};
|
||||
use askama::Template;
|
||||
|
||||
use crypto::{digest::Digest, sha3::Sha3};
|
||||
use num_bigint::BigUint;
|
||||
pub mod encoding;
|
||||
|
||||
/// Formats call data from a vec of bytes to a hashmap
|
||||
/// Useful for debugging directly on the EVM
|
||||
/// !! Should follow the contract's function signature, we assuming the order of arguments is correct
|
||||
pub fn get_formatted_calldata(calldata: Vec<u8>) -> Vec<String> {
|
||||
let mut formatted_calldata = vec![];
|
||||
for i in (4..calldata.len()).step_by(32) {
|
||||
let val = BigUint::from_bytes_be(&calldata[i..i + 32]);
|
||||
formatted_calldata.push(format!("{}", val));
|
||||
}
|
||||
formatted_calldata
|
||||
}
|
||||
|
||||
/// Computes the function selector for the nova cyclefold verifier
|
||||
/// It is computed on the fly since it depends on the length of the first parameter array
|
||||
pub fn get_function_selector_for_nova_cyclefold_verifier(
|
||||
first_param_array_length: usize,
|
||||
) -> [u8; 4] {
|
||||
let mut hasher = Sha3::keccak256();
|
||||
let fn_sig = format!("verifyNovaProof(uint256[{}],uint256[4],uint256[3],uint256[4],uint256[4],uint256[2],uint256[2][2],uint256[2],uint256[4],uint256[2][2])", first_param_array_length);
|
||||
hasher.input_str(&fn_sig);
|
||||
let hash = &mut [0u8; 32];
|
||||
hasher.result(hash);
|
||||
[hash[0], hash[1], hash[2], hash[3]]
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "header_template.askama.sol", ext = "sol")]
|
||||
pub(crate) struct HeaderInclusion<T: Template> {
|
||||
pub struct HeaderInclusion<T: Template> {
|
||||
/// SPDX-License-Identifier
|
||||
pub sdpx: String,
|
||||
/// The `pragma` statement.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::utils::encoding::{g1_to_fq_repr, g2_to_fq_repr};
|
||||
use crate::utils::encoding::{G1Repr, G2Repr};
|
||||
use crate::utils::HeaderInclusion;
|
||||
use crate::{ProtocolData, GPL3_SDPX_IDENTIFIER};
|
||||
use crate::{ProtocolVerifierKey, GPL3_SDPX_IDENTIFIER};
|
||||
use ark_bn254::Bn254;
|
||||
use ark_groth16::VerifyingKey;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
@@ -26,15 +26,15 @@ pub(crate) struct Groth16Verifier {
|
||||
pub(crate) gamma_abc_g1: Vec<G1Repr>,
|
||||
}
|
||||
|
||||
impl From<Groth16Data> for Groth16Verifier {
|
||||
fn from(g16_data: Groth16Data) -> Self {
|
||||
impl From<Groth16VerifierKey> for Groth16Verifier {
|
||||
fn from(g16_vk: Groth16VerifierKey) -> Self {
|
||||
Self {
|
||||
vkey_alpha_g1: g1_to_fq_repr(g16_data.0.alpha_g1),
|
||||
vkey_beta_g2: g2_to_fq_repr(g16_data.0.beta_g2),
|
||||
vkey_gamma_g2: g2_to_fq_repr(g16_data.0.gamma_g2),
|
||||
vkey_delta_g2: g2_to_fq_repr(g16_data.0.delta_g2),
|
||||
gamma_abc_len: g16_data.0.gamma_abc_g1.len(),
|
||||
gamma_abc_g1: g16_data
|
||||
vkey_alpha_g1: g1_to_fq_repr(g16_vk.0.alpha_g1),
|
||||
vkey_beta_g2: g2_to_fq_repr(g16_vk.0.beta_g2),
|
||||
vkey_gamma_g2: g2_to_fq_repr(g16_vk.0.gamma_g2),
|
||||
vkey_delta_g2: g2_to_fq_repr(g16_vk.0.delta_g2),
|
||||
gamma_abc_len: g16_vk.0.gamma_abc_g1.len(),
|
||||
gamma_abc_g1: g16_vk
|
||||
.0
|
||||
.gamma_abc_g1
|
||||
.iter()
|
||||
@@ -45,18 +45,18 @@ impl From<Groth16Data> for Groth16Verifier {
|
||||
}
|
||||
}
|
||||
|
||||
// Ideally I would like to link this to the `Decider` trait in FoldingSchemes.
|
||||
// For now, this is the easiest as NovaCyclefold isn't clear target from where we can get all it's needed arguments.
|
||||
// Ideally this would be linked to the `Decider` trait in FoldingSchemes.
|
||||
// For now, this is the easiest as NovaCycleFold isn't clear target from where we can get all it's needed arguments.
|
||||
#[derive(CanonicalDeserialize, CanonicalSerialize, Clone, PartialEq, Debug)]
|
||||
pub struct Groth16Data(pub(crate) VerifyingKey<Bn254>);
|
||||
pub struct Groth16VerifierKey(pub(crate) VerifyingKey<Bn254>);
|
||||
|
||||
impl From<VerifyingKey<Bn254>> for Groth16Data {
|
||||
impl From<VerifyingKey<Bn254>> for Groth16VerifierKey {
|
||||
fn from(value: VerifyingKey<Bn254>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolData for Groth16Data {
|
||||
impl ProtocolVerifierKey for Groth16VerifierKey {
|
||||
const PROTOCOL_NAME: &'static str = "Groth16";
|
||||
|
||||
fn render_as_template(self, pragma: Option<String>) -> Vec<u8> {
|
||||
@@ -70,3 +70,77 @@ impl ProtocolData for Groth16Data {
|
||||
.into_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Groth16VerifierKey;
|
||||
use crate::{
|
||||
evm::{compile_solidity, save_solidity, Evm},
|
||||
ProtocolVerifierKey,
|
||||
};
|
||||
use ark_bn254::{Bn254, Fr};
|
||||
use ark_crypto_primitives::snark::SNARK;
|
||||
use ark_ec::AffineRepr;
|
||||
use ark_ff::{BigInt, BigInteger, PrimeField};
|
||||
use ark_groth16::Groth16;
|
||||
use ark_std::rand::{RngCore, SeedableRng};
|
||||
use ark_std::test_rng;
|
||||
use askama::Template;
|
||||
use itertools::chain;
|
||||
|
||||
use super::Groth16Verifier;
|
||||
use crate::verifiers::tests::{setup, DEFAULT_SETUP_LEN};
|
||||
|
||||
pub const FUNCTION_SELECTOR_GROTH16_VERIFY_PROOF: [u8; 4] = [0x43, 0x75, 0x3b, 0x4d];
|
||||
|
||||
#[test]
|
||||
fn groth16_vk_serde_roundtrip() {
|
||||
let (_, _, _, vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
|
||||
let g16_vk = Groth16VerifierKey::from(vk);
|
||||
let mut bytes = vec![];
|
||||
g16_vk.serialize_protocol_verifier_key(&mut bytes).unwrap();
|
||||
let obtained_g16_vk =
|
||||
Groth16VerifierKey::deserialize_protocol_verifier_key(bytes.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(g16_vk, obtained_g16_vk)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_groth16_verifier_accepts_and_rejects_proofs() {
|
||||
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
|
||||
let (_, _, g16_pk, g16_vk, circuit) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_vk = Groth16VerifierKey::from(g16_vk);
|
||||
|
||||
let proof = Groth16::<Bn254>::prove(&g16_pk, circuit, &mut rng).unwrap();
|
||||
let res = Groth16Verifier::from(g16_vk).render().unwrap();
|
||||
save_solidity("groth16_verifier.sol", &res);
|
||||
let groth16_verifier_bytecode = compile_solidity(&res, "Verifier");
|
||||
let mut evm = Evm::default();
|
||||
let verifier_address = evm.create(groth16_verifier_bytecode);
|
||||
let (a_x, a_y) = proof.a.xy().unwrap();
|
||||
let (b_x, b_y) = proof.b.xy().unwrap();
|
||||
let (c_x, c_y) = proof.c.xy().unwrap();
|
||||
let mut calldata: Vec<u8> = chain![
|
||||
FUNCTION_SELECTOR_GROTH16_VERIFY_PROOF,
|
||||
a_x.into_bigint().to_bytes_be(),
|
||||
a_y.into_bigint().to_bytes_be(),
|
||||
b_x.c1.into_bigint().to_bytes_be(),
|
||||
b_x.c0.into_bigint().to_bytes_be(),
|
||||
b_y.c1.into_bigint().to_bytes_be(),
|
||||
b_y.c0.into_bigint().to_bytes_be(),
|
||||
c_x.into_bigint().to_bytes_be(),
|
||||
c_y.into_bigint().to_bytes_be(),
|
||||
BigInt::from(Fr::from(circuit.z)).to_bytes_be(),
|
||||
]
|
||||
.collect();
|
||||
let (_, output) = evm.call(verifier_address, calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 1);
|
||||
|
||||
// change calldata to make it invalid
|
||||
let last_calldata_element = calldata.last_mut().unwrap();
|
||||
*last_calldata_element = 0;
|
||||
let (_, output) = evm.call(verifier_address, calldata);
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::utils::encoding::{g1_to_fq_repr, g2_to_fq_repr};
|
||||
use crate::utils::encoding::{G1Repr, G2Repr};
|
||||
use crate::utils::HeaderInclusion;
|
||||
use crate::{ProtocolData, MIT_SDPX_IDENTIFIER};
|
||||
use crate::{ProtocolVerifierKey, MIT_SDPX_IDENTIFIER};
|
||||
use ark_bn254::{Bn254, G1Affine};
|
||||
use ark_poly_commit::kzg10::VerifierKey;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
@@ -24,16 +24,15 @@ pub(crate) struct KZG10Verifier {
|
||||
pub(crate) g1_crs: Vec<G1Repr>,
|
||||
}
|
||||
|
||||
impl From<KzgData> for KZG10Verifier {
|
||||
fn from(data: KzgData) -> Self {
|
||||
let g1_crs_batch_points = data.g1_crs_batch_points.unwrap_or_default();
|
||||
|
||||
impl From<KZG10VerifierKey> for KZG10Verifier {
|
||||
fn from(data: KZG10VerifierKey) -> Self {
|
||||
Self {
|
||||
g1: g1_to_fq_repr(data.vk.g),
|
||||
g2: g2_to_fq_repr(data.vk.h),
|
||||
vk: g2_to_fq_repr(data.vk.beta_h),
|
||||
g1_crs_len: g1_crs_batch_points.len(),
|
||||
g1_crs: g1_crs_batch_points
|
||||
g1_crs_len: data.g1_crs_batch_points.len(),
|
||||
g1_crs: data
|
||||
.g1_crs_batch_points
|
||||
.iter()
|
||||
.map(|g1| g1_to_fq_repr(*g1))
|
||||
.collect(),
|
||||
@@ -42,13 +41,13 @@ impl From<KzgData> for KZG10Verifier {
|
||||
}
|
||||
|
||||
#[derive(CanonicalDeserialize, CanonicalSerialize, Clone, PartialEq, Debug)]
|
||||
pub struct KzgData {
|
||||
pub struct KZG10VerifierKey {
|
||||
pub(crate) vk: VerifierKey<Bn254>,
|
||||
pub(crate) g1_crs_batch_points: Option<Vec<G1Affine>>,
|
||||
pub(crate) g1_crs_batch_points: Vec<G1Affine>,
|
||||
}
|
||||
|
||||
impl From<(VerifierKey<Bn254>, Option<Vec<G1Affine>>)> for KzgData {
|
||||
fn from(value: (VerifierKey<Bn254>, Option<Vec<G1Affine>>)) -> Self {
|
||||
impl From<(VerifierKey<Bn254>, Vec<G1Affine>)> for KZG10VerifierKey {
|
||||
fn from(value: (VerifierKey<Bn254>, Vec<G1Affine>)) -> Self {
|
||||
Self {
|
||||
vk: value.0,
|
||||
g1_crs_batch_points: value.1,
|
||||
@@ -56,7 +55,7 @@ impl From<(VerifierKey<Bn254>, Option<Vec<G1Affine>>)> for KzgData {
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolData for KzgData {
|
||||
impl ProtocolVerifierKey for KZG10VerifierKey {
|
||||
const PROTOCOL_NAME: &'static str = "KZG";
|
||||
|
||||
fn render_as_template(self, pragma: Option<String>) -> Vec<u8> {
|
||||
@@ -70,3 +69,118 @@ impl ProtocolData for KzgData {
|
||||
.into_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::KZG10VerifierKey;
|
||||
use crate::{
|
||||
evm::{compile_solidity, Evm},
|
||||
utils::HeaderInclusion,
|
||||
ProtocolVerifierKey,
|
||||
};
|
||||
use ark_bn254::{Bn254, Fr, G1Projective as G1};
|
||||
use ark_ec::{AffineRepr, CurveGroup};
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_std::rand::{RngCore, SeedableRng};
|
||||
use ark_std::Zero;
|
||||
use ark_std::{test_rng, UniformRand};
|
||||
use askama::Template;
|
||||
use itertools::chain;
|
||||
|
||||
use folding_schemes::{
|
||||
commitment::{kzg::KZG, CommitmentScheme},
|
||||
transcript::{
|
||||
poseidon::{poseidon_test_config, PoseidonTranscript},
|
||||
Transcript,
|
||||
},
|
||||
};
|
||||
|
||||
use super::KZG10Verifier;
|
||||
use crate::verifiers::tests::{setup, DEFAULT_SETUP_LEN};
|
||||
|
||||
const FUNCTION_SELECTOR_KZG10_CHECK: [u8; 4] = [0x9e, 0x78, 0xcc, 0xf7];
|
||||
|
||||
#[test]
|
||||
fn kzg_vk_serde_roundtrip() {
|
||||
let (pk, vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
|
||||
let kzg_vk = KZG10VerifierKey::from((vk, pk.powers_of_g[0..3].to_vec()));
|
||||
let mut bytes = vec![];
|
||||
kzg_vk.serialize_protocol_verifier_key(&mut bytes).unwrap();
|
||||
let obtained_kzg_vk =
|
||||
KZG10VerifierKey::deserialize_protocol_verifier_key(bytes.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(kzg_vk, obtained_kzg_vk)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kzg_verifier_compiles() {
|
||||
let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let kzg_vk = KZG10VerifierKey::from((kzg_vk.clone(), kzg_pk.powers_of_g[0..3].to_vec()));
|
||||
|
||||
let res = HeaderInclusion::<KZG10Verifier>::builder()
|
||||
.template(kzg_vk)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
let kzg_verifier_bytecode = compile_solidity(res, "KZG10");
|
||||
let mut evm = Evm::default();
|
||||
_ = evm.create(kzg_verifier_bytecode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kzg_verifier_accepts_and_rejects_proofs() {
|
||||
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
|
||||
let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
|
||||
|
||||
let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let kzg_vk = KZG10VerifierKey::from((kzg_vk.clone(), kzg_pk.powers_of_g[0..3].to_vec()));
|
||||
|
||||
let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))
|
||||
.take(DEFAULT_SETUP_LEN)
|
||||
.collect();
|
||||
let cm = KZG::<Bn254>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
|
||||
let proof = KZG::<Bn254>::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap();
|
||||
let template = HeaderInclusion::<KZG10Verifier>::builder()
|
||||
.template(kzg_vk)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
let kzg_verifier_bytecode = compile_solidity(template, "KZG10");
|
||||
let mut evm = Evm::default();
|
||||
let verifier_address = evm.create(kzg_verifier_bytecode);
|
||||
|
||||
let (cm_affine, proof_affine) = (cm.into_affine(), proof.proof.into_affine());
|
||||
let (x_comm, y_comm) = cm_affine.xy().unwrap();
|
||||
let (x_proof, y_proof) = proof_affine.xy().unwrap();
|
||||
let y = proof.eval.into_bigint().to_bytes_be();
|
||||
|
||||
transcript_v.absorb_point(&cm).unwrap();
|
||||
let x = transcript_v.get_challenge();
|
||||
|
||||
let x = x.into_bigint().to_bytes_be();
|
||||
let mut calldata: Vec<u8> = chain![
|
||||
FUNCTION_SELECTOR_KZG10_CHECK,
|
||||
x_comm.into_bigint().to_bytes_be(),
|
||||
y_comm.into_bigint().to_bytes_be(),
|
||||
x_proof.into_bigint().to_bytes_be(),
|
||||
y_proof.into_bigint().to_bytes_be(),
|
||||
x.clone(),
|
||||
y,
|
||||
]
|
||||
.collect();
|
||||
|
||||
let (_, output) = evm.call(verifier_address, calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 1);
|
||||
|
||||
// change calldata to make it invalid
|
||||
let last_calldata_element = calldata.last_mut().unwrap();
|
||||
*last_calldata_element = 0;
|
||||
let (_, output) = evm.call(verifier_address, calldata);
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ mod g16;
|
||||
mod kzg;
|
||||
mod nova_cyclefold;
|
||||
|
||||
pub use g16::Groth16Data;
|
||||
pub use kzg::KzgData;
|
||||
pub use nova_cyclefold::NovaCyclefoldData;
|
||||
pub use g16::Groth16VerifierKey;
|
||||
pub use kzg::KZG10VerifierKey;
|
||||
pub use nova_cyclefold::{get_decider_template_for_cyclefold_decider, NovaCycleFoldVerifierKey};
|
||||
|
||||
pub trait ProtocolData: CanonicalDeserialize + CanonicalSerialize {
|
||||
pub trait ProtocolVerifierKey: CanonicalDeserialize + CanonicalSerialize {
|
||||
const PROTOCOL_NAME: &'static str;
|
||||
|
||||
fn serialize_name<W: Write>(&self, writer: &mut W) -> Result<(), SerializationError> {
|
||||
@@ -27,11 +27,14 @@ pub trait ProtocolData: CanonicalDeserialize + CanonicalSerialize {
|
||||
.serialize_uncompressed(writer)
|
||||
}
|
||||
|
||||
fn serialize_protocol_data<W: Write>(&self, writer: &mut W) -> Result<(), SerializationError> {
|
||||
fn serialize_protocol_verifier_key<W: Write>(
|
||||
&self,
|
||||
writer: &mut W,
|
||||
) -> Result<(), SerializationError> {
|
||||
self.serialize_name(writer)?;
|
||||
self.serialize_compressed(writer)
|
||||
}
|
||||
fn deserialize_protocol_data<R: Read + Copy>(
|
||||
fn deserialize_protocol_verifier_key<R: Read + Copy>(
|
||||
mut reader: R,
|
||||
) -> Result<Self, SerializationError> {
|
||||
let name: String = String::deserialize_uncompressed(&mut reader)?;
|
||||
@@ -48,51 +51,31 @@ pub trait ProtocolData: CanonicalDeserialize + CanonicalSerialize {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::evm::{compile_solidity, save_solidity, Evm};
|
||||
use crate::utils::HeaderInclusion;
|
||||
use crate::{Groth16Data, KzgData, NovaCyclefoldData, ProtocolData};
|
||||
pub mod tests {
|
||||
use ark_bn254::{Bn254, Fr, G1Projective as G1};
|
||||
use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK};
|
||||
use ark_ec::{AffineRepr, CurveGroup};
|
||||
use ark_ff::{BigInt, BigInteger, PrimeField};
|
||||
use ark_crypto_primitives::snark::CircuitSpecificSetupSNARK;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_groth16::Groth16;
|
||||
use ark_poly_commit::kzg10::VerifierKey;
|
||||
use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey;
|
||||
use ark_r1cs_std::alloc::AllocVar;
|
||||
use ark_r1cs_std::eq::EqGadget;
|
||||
use ark_r1cs_std::fields::fp::FpVar;
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
|
||||
use ark_std::rand::{RngCore, SeedableRng};
|
||||
use ark_std::Zero;
|
||||
use ark_std::{test_rng, UniformRand};
|
||||
use askama::Template;
|
||||
use folding_schemes::{
|
||||
commitment::{
|
||||
kzg::{ProverKey, KZG},
|
||||
CommitmentScheme,
|
||||
},
|
||||
transcript::{
|
||||
poseidon::{poseidon_test_config, PoseidonTranscript},
|
||||
Transcript,
|
||||
},
|
||||
};
|
||||
use itertools::chain;
|
||||
use ark_std::test_rng;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::g16::Groth16Verifier;
|
||||
use super::kzg::KZG10Verifier;
|
||||
use super::nova_cyclefold::NovaCyclefoldDecider;
|
||||
|
||||
// Function signatures for proof verification on kzg10 and groth16 contracts
|
||||
pub const FUNCTION_SIGNATURE_KZG10_CHECK: [u8; 4] = [0x9e, 0x78, 0xcc, 0xf7];
|
||||
pub const FUNCTION_SIGNATURE_GROTH16_VERIFY_PROOF: [u8; 4] = [0x43, 0x75, 0x3b, 0x4d];
|
||||
pub const FUNCTION_SIGNATURE_NOVA_CYCLEFOLD_CHECK: [u8; 4] = [0x37, 0x98, 0x0b, 0xb6];
|
||||
use folding_schemes::commitment::{
|
||||
kzg::{ProverKey as KZGProverKey, KZG},
|
||||
CommitmentScheme,
|
||||
};
|
||||
|
||||
/// Default setup length for testing.
|
||||
const DEFAULT_SETUP_LEN: usize = 5;
|
||||
pub const DEFAULT_SETUP_LEN: usize = 5;
|
||||
|
||||
/// Test circuit used to test the Groth16 proof generation
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct TestAddCircuit<F: PrimeField> {
|
||||
pub struct TestAddCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
pub x: u8,
|
||||
pub y: u8,
|
||||
@@ -111,11 +94,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn setup<'a>(
|
||||
pub fn setup<'a>(
|
||||
n: usize,
|
||||
) -> (
|
||||
ProverKey<'a, G1>,
|
||||
VerifierKey<Bn254>,
|
||||
KZGProverKey<'a, G1>,
|
||||
KZGVerifierKey<Bn254>,
|
||||
ark_groth16::ProvingKey<Bn254>,
|
||||
ark_groth16::VerifyingKey<Bn254>,
|
||||
TestAddCircuit<Fr>,
|
||||
@@ -130,297 +113,8 @@ mod tests {
|
||||
};
|
||||
let (g16_pk, g16_vk) = Groth16::<Bn254>::setup(circuit, &mut rng).unwrap();
|
||||
|
||||
let (kzg_pk, kzg_vk): (ProverKey<G1>, VerifierKey<Bn254>) =
|
||||
let (kzg_pk, kzg_vk): (KZGProverKey<G1>, KZGVerifierKey<Bn254>) =
|
||||
KZG::<Bn254>::setup(&mut rng, n).unwrap();
|
||||
(kzg_pk, kzg_vk, g16_pk, g16_vk, circuit)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn groth16_data_serde_roundtrip() {
|
||||
let (_, _, _, vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
|
||||
let g16_data = Groth16Data::from(vk);
|
||||
let mut bytes = vec![];
|
||||
g16_data.serialize_protocol_data(&mut bytes).unwrap();
|
||||
let obtained_g16_data = Groth16Data::deserialize_protocol_data(bytes.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(g16_data, obtained_g16_data)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kzg_data_serde_roundtrip() {
|
||||
let (pk, vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
|
||||
let kzg_data = KzgData::from((vk, Some(pk.powers_of_g[0..3].to_vec())));
|
||||
let mut bytes = vec![];
|
||||
kzg_data.serialize_protocol_data(&mut bytes).unwrap();
|
||||
let obtained_kzg_data = KzgData::deserialize_protocol_data(bytes.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(kzg_data, obtained_kzg_data)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_data_serde_roundtrip() {
|
||||
let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_data = Groth16Data::from(g16_vk);
|
||||
let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
|
||||
let mut bytes = vec![];
|
||||
let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
|
||||
|
||||
nova_cyclefold_data
|
||||
.serialize_protocol_data(&mut bytes)
|
||||
.unwrap();
|
||||
let obtained_nova_cyclefold_data =
|
||||
NovaCyclefoldData::deserialize_protocol_data(bytes.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(nova_cyclefold_data, obtained_nova_cyclefold_data)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_decider_template_renders() {
|
||||
let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_data = Groth16Data::from(g16_vk);
|
||||
let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
|
||||
|
||||
let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
|
||||
.template(nova_cyclefold_data)
|
||||
.build();
|
||||
|
||||
save_solidity("NovaDecider.sol", &decider_template.render().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_decider_template_compiles() {
|
||||
let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_data = Groth16Data::from(g16_vk);
|
||||
let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
|
||||
|
||||
let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
|
||||
.template(nova_cyclefold_data)
|
||||
.build();
|
||||
let decider_verifier_bytecode =
|
||||
compile_solidity(decider_template.render().unwrap(), "NovaDecider");
|
||||
let mut evm = Evm::default();
|
||||
_ = evm.create(decider_verifier_bytecode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_groth16_verifier_accepts_and_rejects_proofs() {
|
||||
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
|
||||
let (_, _, g16_pk, g16_vk, circuit) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_data = Groth16Data::from(g16_vk);
|
||||
|
||||
let proof = Groth16::<Bn254>::prove(&g16_pk, circuit, &mut rng).unwrap();
|
||||
let res = Groth16Verifier::from(g16_data).render().unwrap();
|
||||
save_solidity("groth16_verifier.sol", &res);
|
||||
let groth16_verifier_bytecode = compile_solidity(&res, "Verifier");
|
||||
let mut evm = Evm::default();
|
||||
let verifier_address = evm.create(groth16_verifier_bytecode);
|
||||
let (a_x, a_y) = proof.a.xy().unwrap();
|
||||
let (b_x, b_y) = proof.b.xy().unwrap();
|
||||
let (c_x, c_y) = proof.c.xy().unwrap();
|
||||
let mut calldata: Vec<u8> = chain![
|
||||
FUNCTION_SIGNATURE_GROTH16_VERIFY_PROOF,
|
||||
a_x.into_bigint().to_bytes_be(),
|
||||
a_y.into_bigint().to_bytes_be(),
|
||||
b_x.c1.into_bigint().to_bytes_be(),
|
||||
b_x.c0.into_bigint().to_bytes_be(),
|
||||
b_y.c1.into_bigint().to_bytes_be(),
|
||||
b_y.c0.into_bigint().to_bytes_be(),
|
||||
c_x.into_bigint().to_bytes_be(),
|
||||
c_y.into_bigint().to_bytes_be(),
|
||||
BigInt::from(Fr::from(circuit.z)).to_bytes_be(),
|
||||
]
|
||||
.collect();
|
||||
let (_, output) = evm.call(verifier_address, calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 1);
|
||||
|
||||
// change calldata to make it invalid
|
||||
let last_calldata_element = calldata.last_mut().unwrap();
|
||||
*last_calldata_element = 0;
|
||||
let (_, output) = evm.call(verifier_address, calldata);
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kzg_verifier_template_renders() {
|
||||
let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let kzg_data = KzgData::from((kzg_vk.clone(), Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
|
||||
let res = HeaderInclusion::<KZG10Verifier>::builder()
|
||||
.template(kzg_data)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
// TODO: Unsure what this is testing. If we want to test correct rendering,
|
||||
// we should first check that it COMPLETELY renders to what we expect.
|
||||
assert!(res.contains(&kzg_vk.g.x.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kzg_verifier_compiles() {
|
||||
let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let kzg_data = KzgData::from((kzg_vk.clone(), Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
|
||||
let res = HeaderInclusion::<KZG10Verifier>::builder()
|
||||
.template(kzg_data)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
let kzg_verifier_bytecode = compile_solidity(res, "KZG10");
|
||||
let mut evm = Evm::default();
|
||||
_ = evm.create(kzg_verifier_bytecode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kzg_verifier_accepts_and_rejects_proofs() {
|
||||
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
|
||||
let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
|
||||
|
||||
let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let kzg_data = KzgData::from((kzg_vk.clone(), Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
|
||||
let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))
|
||||
.take(DEFAULT_SETUP_LEN)
|
||||
.collect();
|
||||
let cm = KZG::<Bn254>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
|
||||
let proof = KZG::<Bn254>::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap();
|
||||
let template = HeaderInclusion::<KZG10Verifier>::builder()
|
||||
.template(kzg_data)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
let kzg_verifier_bytecode = compile_solidity(template, "KZG10");
|
||||
let mut evm = Evm::default();
|
||||
let verifier_address = evm.create(kzg_verifier_bytecode);
|
||||
|
||||
let (cm_affine, proof_affine) = (cm.into_affine(), proof.proof.into_affine());
|
||||
let (x_comm, y_comm) = cm_affine.xy().unwrap();
|
||||
let (x_proof, y_proof) = proof_affine.xy().unwrap();
|
||||
let y = proof.eval.into_bigint().to_bytes_be();
|
||||
|
||||
transcript_v.absorb_point(&cm).unwrap();
|
||||
let x = transcript_v.get_challenge();
|
||||
|
||||
let x = x.into_bigint().to_bytes_be();
|
||||
let mut calldata: Vec<u8> = chain![
|
||||
FUNCTION_SIGNATURE_KZG10_CHECK,
|
||||
x_comm.into_bigint().to_bytes_be(),
|
||||
y_comm.into_bigint().to_bytes_be(),
|
||||
x_proof.into_bigint().to_bytes_be(),
|
||||
y_proof.into_bigint().to_bytes_be(),
|
||||
x.clone(),
|
||||
y,
|
||||
]
|
||||
.collect();
|
||||
|
||||
let (_, output) = evm.call(verifier_address, calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 1);
|
||||
|
||||
// change calldata to make it invalid
|
||||
let last_calldata_element = calldata.last_mut().unwrap();
|
||||
*last_calldata_element = 0;
|
||||
let (_, output) = evm.call(verifier_address, calldata);
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_verifier_compiles() {
|
||||
let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_data = Groth16Data::from(g16_vk);
|
||||
let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
|
||||
|
||||
let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
|
||||
.template(nova_cyclefold_data)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
let nova_cyclefold_verifier_bytecode = compile_solidity(decider_template, "NovaDecider");
|
||||
let mut evm = Evm::default();
|
||||
_ = evm.create(nova_cyclefold_verifier_bytecode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_verifier_accepts_and_rejects_proofs() {
|
||||
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
|
||||
let (kzg_pk, kzg_vk, g16_pk, g16_vk, circuit) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_data = Groth16Data::from(g16_vk);
|
||||
let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
|
||||
|
||||
let g16_proof = Groth16::<Bn254>::prove(&g16_pk, circuit, &mut rng).unwrap();
|
||||
|
||||
let (a_x, a_y) = g16_proof.a.xy().unwrap();
|
||||
let (b_x, b_y) = g16_proof.b.xy().unwrap();
|
||||
let (c_x, c_y) = g16_proof.c.xy().unwrap();
|
||||
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
|
||||
let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
|
||||
|
||||
let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))
|
||||
.take(DEFAULT_SETUP_LEN)
|
||||
.collect();
|
||||
let cm = KZG::<Bn254>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
|
||||
let proof = KZG::<Bn254>::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap();
|
||||
|
||||
let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
|
||||
.template(nova_cyclefold_data)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
let nova_cyclefold_verifier_bytecode = compile_solidity(decider_template, "NovaDecider");
|
||||
|
||||
let mut evm = Evm::default();
|
||||
let verifier_address = evm.create(nova_cyclefold_verifier_bytecode);
|
||||
|
||||
let (cm_affine, proof_affine) = (cm.into_affine(), proof.proof.into_affine());
|
||||
let (x_comm, y_comm) = cm_affine.xy().unwrap();
|
||||
let (x_proof, y_proof) = proof_affine.xy().unwrap();
|
||||
let y = proof.eval.into_bigint().to_bytes_be();
|
||||
|
||||
transcript_v.absorb_point(&cm).unwrap();
|
||||
let x = transcript_v.get_challenge();
|
||||
|
||||
let x = x.into_bigint().to_bytes_be();
|
||||
let mut calldata: Vec<u8> = chain![
|
||||
FUNCTION_SIGNATURE_NOVA_CYCLEFOLD_CHECK,
|
||||
a_x.into_bigint().to_bytes_be(),
|
||||
a_y.into_bigint().to_bytes_be(),
|
||||
b_x.c1.into_bigint().to_bytes_be(),
|
||||
b_x.c0.into_bigint().to_bytes_be(),
|
||||
b_y.c1.into_bigint().to_bytes_be(),
|
||||
b_y.c0.into_bigint().to_bytes_be(),
|
||||
c_x.into_bigint().to_bytes_be(),
|
||||
c_y.into_bigint().to_bytes_be(),
|
||||
BigInt::from(Fr::from(circuit.z)).to_bytes_be(),
|
||||
x_comm.into_bigint().to_bytes_be(),
|
||||
y_comm.into_bigint().to_bytes_be(),
|
||||
x_proof.into_bigint().to_bytes_be(),
|
||||
y_proof.into_bigint().to_bytes_be(),
|
||||
x.clone(),
|
||||
y,
|
||||
]
|
||||
.collect();
|
||||
|
||||
let (_, output) = evm.call(verifier_address, calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 1);
|
||||
|
||||
// change calldata to make it invalid
|
||||
let last_calldata_element = calldata.last_mut().unwrap();
|
||||
*last_calldata_element = 0;
|
||||
let (_, output) = evm.call(verifier_address, calldata);
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,69 @@
|
||||
use crate::utils::HeaderInclusion;
|
||||
use crate::{Groth16Data, KzgData, ProtocolData, PRAGMA_GROTH16_VERIFIER};
|
||||
use ark_bn254::{Bn254, G1Affine};
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use ark_bn254::{Bn254, Fq, G1Affine};
|
||||
use ark_groth16::VerifyingKey;
|
||||
use ark_poly_commit::kzg10::VerifierKey;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use askama::Template;
|
||||
|
||||
use folding_schemes::folding::circuits::nonnative::uint::NonNativeUintVar;
|
||||
|
||||
use super::g16::Groth16Verifier;
|
||||
use super::kzg::KZG10Verifier;
|
||||
use crate::utils::HeaderInclusion;
|
||||
use crate::{Groth16VerifierKey, KZG10VerifierKey, ProtocolVerifierKey, PRAGMA_GROTH16_VERIFIER};
|
||||
|
||||
pub fn get_decider_template_for_cyclefold_decider(
|
||||
nova_cyclefold_vk: NovaCycleFoldVerifierKey,
|
||||
) -> String {
|
||||
HeaderInclusion::<NovaCycleFoldDecider>::builder()
|
||||
.template(nova_cyclefold_vk)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[derive(Template, Default)]
|
||||
#[template(path = "nova_cyclefold_decider.askama.sol", ext = "sol")]
|
||||
pub(crate) struct NovaCyclefoldDecider {
|
||||
pub(crate) struct NovaCycleFoldDecider {
|
||||
groth16_verifier: Groth16Verifier,
|
||||
kzg10_verifier: KZG10Verifier,
|
||||
// z_len denotes the FCircuit state (z_i) length
|
||||
z_len: usize,
|
||||
public_inputs_len: usize,
|
||||
num_limbs: usize,
|
||||
bits_per_limb: usize,
|
||||
}
|
||||
|
||||
impl From<NovaCyclefoldData> for NovaCyclefoldDecider {
|
||||
fn from(value: NovaCyclefoldData) -> Self {
|
||||
impl From<NovaCycleFoldVerifierKey> for NovaCycleFoldDecider {
|
||||
fn from(value: NovaCycleFoldVerifierKey) -> Self {
|
||||
let groth16_verifier = Groth16Verifier::from(value.g16_vk);
|
||||
let public_inputs_len = groth16_verifier.gamma_abc_len;
|
||||
let bits_per_limb = NonNativeUintVar::<Fq>::bits_per_limb();
|
||||
Self {
|
||||
groth16_verifier: Groth16Verifier::from(value.g16_data),
|
||||
kzg10_verifier: KZG10Verifier::from(value.kzg_data),
|
||||
groth16_verifier,
|
||||
kzg10_verifier: KZG10Verifier::from(value.kzg_vk),
|
||||
z_len: value.z_len,
|
||||
public_inputs_len,
|
||||
num_limbs: (250_f32 / (bits_per_limb as f32)).ceil() as usize,
|
||||
bits_per_limb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(CanonicalDeserialize, CanonicalSerialize, PartialEq, Debug)]
|
||||
pub struct NovaCyclefoldData {
|
||||
g16_data: Groth16Data,
|
||||
kzg_data: KzgData,
|
||||
#[derive(CanonicalDeserialize, CanonicalSerialize, PartialEq, Debug, Clone)]
|
||||
pub struct NovaCycleFoldVerifierKey {
|
||||
g16_vk: Groth16VerifierKey,
|
||||
kzg_vk: KZG10VerifierKey,
|
||||
z_len: usize,
|
||||
}
|
||||
|
||||
impl ProtocolData for NovaCyclefoldData {
|
||||
const PROTOCOL_NAME: &'static str = "NovaCyclefold";
|
||||
impl ProtocolVerifierKey for NovaCycleFoldVerifierKey {
|
||||
const PROTOCOL_NAME: &'static str = "NovaCycleFold";
|
||||
|
||||
fn render_as_template(self, pragma: Option<String>) -> Vec<u8> {
|
||||
HeaderInclusion::<NovaCyclefoldDecider>::builder()
|
||||
HeaderInclusion::<NovaCycleFoldDecider>::builder()
|
||||
.pragma_version(pragma.unwrap_or(PRAGMA_GROTH16_VERIFIER.to_string()))
|
||||
.template(self)
|
||||
.build()
|
||||
@@ -45,25 +73,384 @@ impl ProtocolData for NovaCyclefoldData {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Groth16Data, KzgData)> for NovaCyclefoldData {
|
||||
fn from(value: (Groth16Data, KzgData)) -> Self {
|
||||
impl From<(Groth16VerifierKey, KZG10VerifierKey, usize)> for NovaCycleFoldVerifierKey {
|
||||
fn from(value: (Groth16VerifierKey, KZG10VerifierKey, usize)) -> Self {
|
||||
Self {
|
||||
g16_data: value.0,
|
||||
kzg_data: value.1,
|
||||
g16_vk: value.0,
|
||||
kzg_vk: value.1,
|
||||
z_len: value.2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NovaCyclefoldData {
|
||||
// implements From assuming that the 'batchCheck' method from the KZG10 template will not be used
|
||||
// in the NovaCycleFoldDecider verifier contract
|
||||
impl From<(VerifyingKey<Bn254>, VerifierKey<Bn254>, usize)> for NovaCycleFoldVerifierKey {
|
||||
fn from(value: (VerifyingKey<Bn254>, VerifierKey<Bn254>, usize)) -> Self {
|
||||
let g16_vk = Groth16VerifierKey::from(value.0);
|
||||
// pass `Vec::new()` since batchCheck will not be used
|
||||
let kzg_vk = KZG10VerifierKey::from((value.1, Vec::new()));
|
||||
Self {
|
||||
g16_vk,
|
||||
kzg_vk,
|
||||
z_len: value.2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NovaCycleFoldVerifierKey {
|
||||
pub fn new(
|
||||
vkey_g16: VerifyingKey<Bn254>,
|
||||
vkey_kzg: VerifierKey<Bn254>,
|
||||
crs_points: Vec<G1Affine>,
|
||||
z_len: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
g16_data: Groth16Data::from(vkey_g16),
|
||||
// TODO: Remove option from crs points
|
||||
kzg_data: KzgData::from((vkey_kzg, Some(crs_points))),
|
||||
g16_vk: Groth16VerifierKey::from(vkey_g16),
|
||||
kzg_vk: KZG10VerifierKey::from((vkey_kzg, crs_points)),
|
||||
z_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as G1};
|
||||
use ark_crypto_primitives::snark::SNARK;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_groth16::VerifyingKey as G16VerifierKey;
|
||||
use ark_groth16::{Groth16, ProvingKey};
|
||||
use ark_grumpkin::{constraints::GVar as GVar2, Projective as G2};
|
||||
use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey;
|
||||
use ark_r1cs_std::alloc::AllocVar;
|
||||
use ark_r1cs_std::fields::fp::FpVar;
|
||||
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
|
||||
use ark_std::Zero;
|
||||
use askama::Template;
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Instant;
|
||||
|
||||
use folding_schemes::{
|
||||
commitment::{
|
||||
kzg::{ProverKey as KZGProverKey, KZG},
|
||||
pedersen::Pedersen,
|
||||
CommitmentScheme,
|
||||
},
|
||||
folding::nova::{
|
||||
decider_eth::{prepare_calldata, Decider as DeciderEth},
|
||||
decider_eth_circuit::DeciderEthCircuit,
|
||||
get_cs_params_len, Nova, ProverParams,
|
||||
},
|
||||
frontend::FCircuit,
|
||||
transcript::poseidon::poseidon_test_config,
|
||||
Decider, Error, FoldingScheme,
|
||||
};
|
||||
|
||||
use super::NovaCycleFoldDecider;
|
||||
use crate::verifiers::tests::{setup, DEFAULT_SETUP_LEN};
|
||||
use crate::{
|
||||
evm::{compile_solidity, save_solidity, Evm},
|
||||
utils::{get_function_selector_for_nova_cyclefold_verifier, HeaderInclusion},
|
||||
verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider,
|
||||
NovaCycleFoldVerifierKey, ProtocolVerifierKey,
|
||||
};
|
||||
|
||||
/// Test circuit to be folded
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CubicFCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
}
|
||||
impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
|
||||
type Params = ();
|
||||
fn new(_params: Self::Params) -> Self {
|
||||
Self { _f: PhantomData }
|
||||
}
|
||||
fn state_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> {
|
||||
Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)])
|
||||
}
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
_i: usize,
|
||||
z_i: Vec<FpVar<F>>,
|
||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?;
|
||||
let z_i = z_i[0].clone();
|
||||
|
||||
Ok(vec![&z_i * &z_i * &z_i + &z_i + &five])
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the circuit that we want to fold, it implements the FCircuit trait. The parameter z_i
|
||||
/// denotes the current state which contains 5 elements, and z_{i+1} denotes the next state which
|
||||
/// we get by applying the step.
|
||||
/// In this example we set z_i and z_{i+1} to have five elements, and at each step we do different
|
||||
/// operations on each of them.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MultiInputsFCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
}
|
||||
impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
|
||||
type Params = ();
|
||||
|
||||
fn new(_params: Self::Params) -> Self {
|
||||
Self { _f: PhantomData }
|
||||
}
|
||||
fn state_len(&self) -> usize {
|
||||
5
|
||||
}
|
||||
|
||||
/// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new
|
||||
/// z_{i+1}
|
||||
fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> {
|
||||
let a = z_i[0] + F::from(4_u32);
|
||||
let b = z_i[1] + F::from(40_u32);
|
||||
let c = z_i[2] * F::from(4_u32);
|
||||
let d = z_i[3] * F::from(40_u32);
|
||||
let e = z_i[4] + F::from(100_u32);
|
||||
|
||||
Ok(vec![a, b, c, d, e])
|
||||
}
|
||||
|
||||
/// generates the constraints for the step of F for the given z_i
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
_i: usize,
|
||||
z_i: Vec<FpVar<F>>,
|
||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
let four = FpVar::<F>::new_constant(cs.clone(), F::from(4u32))?;
|
||||
let forty = FpVar::<F>::new_constant(cs.clone(), F::from(40u32))?;
|
||||
let onehundred = FpVar::<F>::new_constant(cs.clone(), F::from(100u32))?;
|
||||
let a = z_i[0].clone() + four.clone();
|
||||
let b = z_i[1].clone() + forty.clone();
|
||||
let c = z_i[2].clone() * four;
|
||||
let d = z_i[3].clone() * forty;
|
||||
let e = z_i[4].clone() + onehundred;
|
||||
|
||||
Ok(vec![a, b, c, d, e])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_vk_serde_roundtrip() {
|
||||
let (_, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
|
||||
let mut bytes = vec![];
|
||||
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((g16_vk, kzg_vk, 1));
|
||||
|
||||
nova_cyclefold_vk
|
||||
.serialize_protocol_verifier_key(&mut bytes)
|
||||
.unwrap();
|
||||
let obtained_nova_cyclefold_vk =
|
||||
NovaCycleFoldVerifierKey::deserialize_protocol_verifier_key(bytes.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(nova_cyclefold_vk, obtained_nova_cyclefold_vk)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_decider_template_renders() {
|
||||
let (_, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((g16_vk, kzg_vk, 1));
|
||||
|
||||
let decider_solidity_code = HeaderInclusion::<NovaCycleFoldDecider>::builder()
|
||||
.template(nova_cyclefold_vk)
|
||||
.build();
|
||||
|
||||
save_solidity("NovaDecider.sol", &decider_solidity_code.render().unwrap());
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn init_test_prover_params<FC: FCircuit<Fr, Params = ()>>() -> (
|
||||
ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>,
|
||||
KZGVerifierKey<Bn254>,
|
||||
) {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
let f_circuit = FC::new(());
|
||||
let (cs_len, cf_cs_len) =
|
||||
get_cs_params_len::<G1, GVar, G2, GVar2, FC>(&poseidon_config, f_circuit).unwrap();
|
||||
let (kzg_pk, kzg_vk): (KZGProverKey<G1>, KZGVerifierKey<Bn254>) =
|
||||
KZG::<Bn254>::setup(&mut rng, cs_len).unwrap();
|
||||
let (cf_pedersen_params, _) = Pedersen::<G2>::setup(&mut rng, cf_cs_len).unwrap();
|
||||
let fs_prover_params = ProverParams::<G1, G2, KZG<Bn254>, Pedersen<G2>> {
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
cs_params: kzg_pk.clone(),
|
||||
cf_cs_params: cf_pedersen_params,
|
||||
};
|
||||
(fs_prover_params, kzg_vk)
|
||||
}
|
||||
|
||||
/// Initializes Nova parameters and DeciderEth parameters. Only for test purposes.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn init_params<FC: FCircuit<Fr, Params = ()>>() -> (
|
||||
ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>,
|
||||
KZGVerifierKey<Bn254>,
|
||||
ProvingKey<Bn254>,
|
||||
G16VerifierKey<Bn254>,
|
||||
) {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
let start = Instant::now();
|
||||
let (fs_prover_params, kzg_vk) = init_test_prover_params::<FC>();
|
||||
println!("generated Nova folding params: {:?}", start.elapsed());
|
||||
let f_circuit = FC::new(());
|
||||
|
||||
pub type NOVA_FCircuit<FC> =
|
||||
Nova<G1, GVar, G2, GVar2, FC, KZG<'static, Bn254>, Pedersen<G2>>;
|
||||
let z_0 = vec![Fr::zero(); f_circuit.state_len()];
|
||||
let nova = NOVA_FCircuit::init(&fs_prover_params, f_circuit, z_0.clone()).unwrap();
|
||||
|
||||
let decider_circuit =
|
||||
DeciderEthCircuit::<G1, GVar, G2, GVar2, KZG<Bn254>, Pedersen<G2>>::from_nova::<FC>(
|
||||
nova.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let start = Instant::now();
|
||||
let (g16_pk, g16_vk) =
|
||||
Groth16::<Bn254>::circuit_specific_setup(decider_circuit.clone(), &mut rng).unwrap();
|
||||
println!(
|
||||
"generated G16 (Decider circuit) params: {:?}",
|
||||
start.elapsed()
|
||||
);
|
||||
(fs_prover_params, kzg_vk, g16_pk, g16_vk)
|
||||
}
|
||||
|
||||
/// This function allows to define which FCircuit to use for the test, and how many prove_step
|
||||
/// rounds to perform.
|
||||
/// Actions performed by this test:
|
||||
/// - runs the NovaCycleFold folding scheme for the given FCircuit and n_steps times
|
||||
/// - generates a DeciderEth proof, and executes it through the EVM
|
||||
/// - modifies the calldata and checks that it does not pass the EVM check
|
||||
/// - modifies the z_0 and checks that it does not pass the EVM check
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn nova_cyclefold_solidity_verifier_opt<FC: FCircuit<Fr, Params = ()>>(
|
||||
params: (
|
||||
ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>,
|
||||
KZGVerifierKey<Bn254>,
|
||||
ProvingKey<Bn254>,
|
||||
G16VerifierKey<Bn254>,
|
||||
),
|
||||
z_0: Vec<Fr>,
|
||||
n_steps: usize,
|
||||
) {
|
||||
let (fs_prover_params, kzg_vk, g16_pk, g16_vk) = params.clone();
|
||||
|
||||
pub type NOVA_FCircuit<FC> =
|
||||
Nova<G1, GVar, G2, GVar2, FC, KZG<'static, Bn254>, Pedersen<G2>>;
|
||||
pub type DECIDERETH_FCircuit<FC> = DeciderEth<
|
||||
G1,
|
||||
GVar,
|
||||
G2,
|
||||
GVar2,
|
||||
FC,
|
||||
KZG<'static, Bn254>,
|
||||
Pedersen<G2>,
|
||||
Groth16<Bn254>,
|
||||
NOVA_FCircuit<FC>,
|
||||
>;
|
||||
let f_circuit = FC::new(());
|
||||
|
||||
let nova_cyclefold_vk =
|
||||
NovaCycleFoldVerifierKey::from((g16_vk.clone(), kzg_vk.clone(), f_circuit.state_len()));
|
||||
|
||||
let mut nova = NOVA_FCircuit::init(&fs_prover_params, f_circuit, z_0).unwrap();
|
||||
for _ in 0..n_steps {
|
||||
nova.prove_step().unwrap();
|
||||
}
|
||||
|
||||
let rng = rand::rngs::OsRng;
|
||||
let start = Instant::now();
|
||||
let proof = DECIDERETH_FCircuit::prove(
|
||||
(g16_pk, fs_prover_params.cs_params.clone()),
|
||||
rng,
|
||||
nova.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
println!("generated Decider proof: {:?}", start.elapsed());
|
||||
|
||||
let verified = DECIDERETH_FCircuit::<FC>::verify(
|
||||
(g16_vk, kzg_vk),
|
||||
nova.i,
|
||||
nova.z_0.clone(),
|
||||
nova.z_i.clone(),
|
||||
&nova.U_i,
|
||||
&nova.u_i,
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(verified);
|
||||
|
||||
let function_selector =
|
||||
get_function_selector_for_nova_cyclefold_verifier(nova.z_0.len() * 2 + 1);
|
||||
|
||||
let calldata: Vec<u8> = prepare_calldata(
|
||||
function_selector,
|
||||
nova.i,
|
||||
nova.z_0,
|
||||
nova.z_i,
|
||||
&nova.U_i,
|
||||
&nova.u_i,
|
||||
proof,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk);
|
||||
|
||||
let nova_cyclefold_verifier_bytecode =
|
||||
compile_solidity(decider_solidity_code, "NovaDecider");
|
||||
|
||||
let mut evm = Evm::default();
|
||||
let verifier_address = evm.create(nova_cyclefold_verifier_bytecode);
|
||||
|
||||
let (_, output) = evm.call(verifier_address, calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 1);
|
||||
|
||||
// change i to make calldata invalid, placed between bytes 4 - 35
|
||||
let mut invalid_calldata = calldata.clone();
|
||||
invalid_calldata[35] += 1;
|
||||
let (_, output) = evm.call(verifier_address, invalid_calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
|
||||
// change z_0 to make the EVM check fail, placed between bytes 35 - 67
|
||||
let mut invalid_calldata = calldata.clone();
|
||||
invalid_calldata[67] += 1;
|
||||
let (_, output) = evm.call(verifier_address, invalid_calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
|
||||
// change z_i to make the EVM check fail, placed between bytes 68 - 100
|
||||
let mut invalid_calldata = calldata.clone();
|
||||
invalid_calldata[99] += 1;
|
||||
let (_, output) = evm.call(verifier_address, invalid_calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_solidity_verifier() {
|
||||
let params = init_params::<CubicFCircuit<Fr>>();
|
||||
let z_0 = vec![Fr::from(3_u32)];
|
||||
nova_cyclefold_solidity_verifier_opt::<CubicFCircuit<Fr>>(params.clone(), z_0.clone(), 2);
|
||||
nova_cyclefold_solidity_verifier_opt::<CubicFCircuit<Fr>>(params.clone(), z_0.clone(), 3);
|
||||
|
||||
let params = init_params::<MultiInputsFCircuit<Fr>>();
|
||||
let z_0 = vec![
|
||||
Fr::from(1_u32),
|
||||
Fr::from(1_u32),
|
||||
Fr::from(1_u32),
|
||||
Fr::from(1_u32),
|
||||
Fr::from(1_u32),
|
||||
];
|
||||
nova_cyclefold_solidity_verifier_opt::<MultiInputsFCircuit<Fr>>(
|
||||
params.clone(),
|
||||
z_0.clone(),
|
||||
2,
|
||||
);
|
||||
nova_cyclefold_solidity_verifier_opt::<MultiInputsFCircuit<Fr>>(
|
||||
params.clone(),
|
||||
z_0.clone(),
|
||||
3,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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