mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-08 15:01:30 +01:00
Add CLI interface for verifier contract generation (#74)
* add: solidity-verifier workspace member * chore: Update toolchain to 1.74 * feat: Add basic clap cli interface for solidity verifier This includes a cli parser that serves as a way to the user to generate the desired Solidity contracts. * chore: Expose SoldityVerifier template struct * feat: Finish first working version * change: Modify some settings * fix: Fix rebase conflicts * chore: Leave resolver 2 for workspace * chore: Rename KZG+G16 template Now the template refers to Nova + Cyclefold and has a Warning attached to it * fixup * chore: Rename to NovaCyclefoldDecider the template * chore: Change constructors to `new` instead of `from` * add: ProtocolData trait helper This trait helps to treat the serialized data required by the Template as a single element while still allowing a flexible usage. This is specially interesting as allows the cli to operate considering a single path of input data where all the data for the selected protocol co-exists. Reducing the amount of parsing and arguments the user needs to pass to the cli. * chore: Create `From` impls formally Previously we had functions called `from` which had nothing to do with the trait `From`. This addresses this issue and fixes it. Now both `new` and `from` are avaliable. But `from` follows the `From` trait. * add: Support G16, KZG and Nova+Cyclefold in cli This adds a `render` fn for `Protocol` which makes it easier to add new protocols to the CLI as is mainly based in the `ProtocolData` impl behind the scenes of the selected protocol. Aside from that, this commit reworks some minor parts of the CLI config as shorteners for commands or adding `pragma` as an optional parameter. * chore: Adapt `main.rs` to new cli changes As seen, this allows to have a much easier `main.rs` which doesn't have to do any `match` over the selected protocol. * chore: Make solidity helper fns `cfg(test)` * chore: Rework folding-schemes-solidity structure * chore: Remove g1_crs_batch_points_len from KZGData * add: Serde tests for all template targets * tmp: Add NovaCyclefold testing * add: HeaderInclusion template When we use templates that are composed by others (as happens with `NovaCyclefold` one) we sadly see that the License and the `pragma` attributes are rendered once per sub-template. This generic structure solves this issue by being actually the only item rendered which has a sub-template the template we indeed want to render at the end. * chore: Add tests for NovaCyclefold contract This also includes small changes to the lib architecture such as adding constants for GPL3_SDPX_IDENTIFIER or move the default pragma versions used to `mod.rs` * chore: Update g16 to use HeaderInclusion template rendering Now the `ProtocolData` impl falls back to the usage of `HeaderInclusion` it is easier to handle complex templates like `NovaCyclefold`. * add: Small builder-pattern to construct HeaderInclusion Templates As mentioned in previous commits, the idea is that the header is set on an automatic wrapper template applied to the one that we actually want to render. This builder pattern makes it less complex to do such a thing. Specially avoiding unidiomatic `From` implementations. * remove: sdpx & pragma from KZG template Those are externalized and handled by HeaderInclusion template utility * chore: Update templates to use HeaderInclusion builder * chore: Update tests to use HeaderInclusion builderPattern * remove: fixed pragma version in novacyclefold template * chore: Accept Into<Template> in builder * tmp: Only KZG return passes. Fix Groth * fix: Prevent `revert` from paniking for negative tests * feat: Merge G16 and KZG contract results in NovaCyclefold * chore: Add assets for quicker/easier testing Now instead of generating the protocoldata & proofs on each test, we just deserialize * fix: Address clippy & warnings * fix: Spelling to prevent PR farmers LOL * chore: Add about and long_about to CLI tool * add: README.md * chore: Revert asset-based testing approach * remove: Assets folder * fix: Rebase issues * fix: use &mut for Reader * fix: rebase error with Contract name * chore: Reduce tests LOC with setup fn * chore: Set MIT license indentifier for CLI & KZG * chore: Add extra usage example * chore: Update novacyclefold contract comments on soundess * chore: Typo * chore: Allow type complexity clippy for setup fn * chore: Address Pierre's comments * chore: Rename workspace members - folding-schemes-solidity -> soliity-verifiers
This commit is contained in:
39
solidity-verifiers/Cargo.toml
Normal file
39
solidity-verifiers/Cargo.toml
Normal file
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "solidity-verifiers"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ark-ec = "0.4"
|
||||
ark-ff = "0.4"
|
||||
ark-poly = "0.4"
|
||||
ark-std = "0.4"
|
||||
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"
|
||||
|
||||
[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" }
|
||||
|
||||
[features]
|
||||
default = ["parallel"]
|
||||
|
||||
parallel = [
|
||||
"ark-std/parallel",
|
||||
"ark-ff/parallel",
|
||||
"ark-poly/parallel",
|
||||
]
|
||||
|
||||
|
||||
|
||||
3
solidity-verifiers/README.md
Normal file
3
solidity-verifiers/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# `solidity-verifiers`
|
||||
|
||||
This crate implements templating logic to output verifier contracts for `folding-schemes`-generated decider proofs.
|
||||
3
solidity-verifiers/askama.toml
Normal file
3
solidity-verifiers/askama.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[[escaper]]
|
||||
path = "askama::Text"
|
||||
extensions = ["sol"]
|
||||
166
solidity-verifiers/src/evm.rs
Normal file
166
solidity-verifiers/src/evm.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
pub use revm;
|
||||
use revm::{
|
||||
primitives::{hex, Address, CreateScheme, ExecutionResult, Output, TransactTo, TxEnv},
|
||||
InMemoryDB, EVM,
|
||||
};
|
||||
use std::{
|
||||
fmt::{self, Debug, Formatter},
|
||||
fs::{create_dir_all, File},
|
||||
io::{self, Write},
|
||||
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()))
|
||||
.unwrap()
|
||||
.write_all(solidity.as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Compile solidity with `--via-ir` flag, then return creation bytecode.
|
||||
///
|
||||
/// # 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> {
|
||||
let mut process = match Command::new("solc")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.arg("--bin")
|
||||
.arg("--optimize")
|
||||
.arg("-")
|
||||
.spawn()
|
||||
{
|
||||
Ok(process) => process,
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
||||
panic!("Command 'solc' not found");
|
||||
}
|
||||
Err(err) => {
|
||||
panic!("Failed to spawn process with command 'solc':\n{err}");
|
||||
}
|
||||
};
|
||||
process
|
||||
.stdin
|
||||
.take()
|
||||
.unwrap()
|
||||
.write_all(solidity.as_ref())
|
||||
.unwrap();
|
||||
let output = process.wait_with_output().unwrap();
|
||||
let stdout = str::from_utf8(&output.stdout).unwrap();
|
||||
if let Some(binary) = find_binary(stdout, contract_name) {
|
||||
binary
|
||||
} else {
|
||||
panic!(
|
||||
"Compilation fails:\n{}",
|
||||
str::from_utf8(&output.stderr).unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Find binary from `stdout` with given `contract_name`.
|
||||
/// `contract_name` is provided since `solc` may compile multiple contracts or libraries.
|
||||
/// hence, we need to find the correct binary.
|
||||
fn find_binary(stdout: &str, contract_name: &str) -> Option<Vec<u8>> {
|
||||
let start_contract = stdout.find(contract_name)?;
|
||||
let stdout_contract = &stdout[start_contract..];
|
||||
let start = stdout_contract.find("Binary:")? + 8;
|
||||
Some(hex::decode(&stdout_contract[start..stdout_contract.len() - 1]).unwrap())
|
||||
}
|
||||
|
||||
/// Evm runner.
|
||||
pub(crate) struct Evm {
|
||||
evm: EVM<InMemoryDB>,
|
||||
}
|
||||
|
||||
impl Debug for Evm {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let mut debug_struct = f.debug_struct("Evm");
|
||||
debug_struct
|
||||
.field("env", &self.evm.env)
|
||||
.field("db", &self.evm.db.as_ref().unwrap())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Evm {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
evm: EVM {
|
||||
env: Default::default(),
|
||||
db: Some(Default::default()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Evm {
|
||||
/// Apply create transaction with given `bytecode` as creation bytecode.
|
||||
/// Return created `address`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if execution reverts or halts unexpectedly.
|
||||
pub(crate) 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),
|
||||
data: bytecode.into(),
|
||||
..Default::default()
|
||||
});
|
||||
match output {
|
||||
Output::Create(_, Some(address)) => address,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply call transaction to given `address` with `calldata`.
|
||||
/// Returns `gas_used` and `return_data`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if execution reverts or halts unexpectedly.
|
||||
pub(crate) 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),
|
||||
data: calldata.into(),
|
||||
..Default::default()
|
||||
});
|
||||
match output {
|
||||
Output::Call(output) => (gas_used, output.into()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn transact_success_or_panic(&mut self, tx: TxEnv) -> (u64, Output) {
|
||||
self.evm.env.tx = tx;
|
||||
let result = self.evm.transact_commit().unwrap();
|
||||
self.evm.env.tx = Default::default();
|
||||
match result {
|
||||
ExecutionResult::Success {
|
||||
gas_used,
|
||||
output,
|
||||
logs,
|
||||
..
|
||||
} => {
|
||||
if !logs.is_empty() {
|
||||
println!("--- logs from {} ---", logs[0].address);
|
||||
for (log_idx, log) in logs.iter().enumerate() {
|
||||
println!("log#{log_idx}");
|
||||
for (topic_idx, topic) in log.topics.iter().enumerate() {
|
||||
println!(" topic{topic_idx}: {topic:?}");
|
||||
}
|
||||
}
|
||||
println!("--- end ---");
|
||||
}
|
||||
(gas_used, output)
|
||||
}
|
||||
ExecutionResult::Revert { gas_used, output } => (gas_used, Output::Call(output)),
|
||||
ExecutionResult::Halt { reason, gas_used } => panic!(
|
||||
"Transaction halts unexpectedly with gas_used {gas_used} and reason {reason:?}"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
7
solidity-verifiers/src/lib.rs
Normal file
7
solidity-verifiers/src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod evm;
|
||||
mod utils;
|
||||
mod verifiers;
|
||||
|
||||
pub use verifiers::*;
|
||||
pub use verifiers::{Groth16Data, KzgData, NovaCyclefoldData, ProtocolData};
|
||||
43
solidity-verifiers/src/utils/encoding.rs
Normal file
43
solidity-verifiers/src/utils/encoding.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
/// Defines encodings of G1 and G2 elements for use in Solidity templates.
|
||||
use ark_bn254::{Fq, G1Affine, G2Affine};
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FqWrapper(pub Fq);
|
||||
|
||||
impl Display for FqWrapper {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct G1Repr(pub [FqWrapper; 2]);
|
||||
|
||||
impl Display for G1Repr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:#?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a G1 element to a representation that can be used in Solidity templates.
|
||||
pub fn g1_to_fq_repr(g1: G1Affine) -> G1Repr {
|
||||
G1Repr([FqWrapper(g1.x), FqWrapper(g1.y)])
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct G2Repr(pub [[FqWrapper; 2]; 2]);
|
||||
|
||||
impl Display for G2Repr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:#?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a G2 element to a representation that can be used in Solidity templates.
|
||||
pub fn g2_to_fq_repr(g2: G2Affine) -> G2Repr {
|
||||
G2Repr([
|
||||
[FqWrapper(g2.x.c0), FqWrapper(g2.x.c1)],
|
||||
[FqWrapper(g2.y.c0), FqWrapper(g2.y.c1)],
|
||||
])
|
||||
}
|
||||
66
solidity-verifiers/src/utils/mod.rs
Normal file
66
solidity-verifiers/src/utils/mod.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use crate::{GPL3_SDPX_IDENTIFIER, PRAGMA_GROTH16_VERIFIER};
|
||||
use askama::Template;
|
||||
|
||||
pub mod encoding;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "header_template.askama.sol", ext = "sol")]
|
||||
pub(crate) struct HeaderInclusion<T: Template> {
|
||||
/// SPDX-License-Identifier
|
||||
pub sdpx: String,
|
||||
/// The `pragma` statement.
|
||||
pub pragma_version: String,
|
||||
/// The template to render alongside the header.
|
||||
pub template: T,
|
||||
}
|
||||
|
||||
impl<T: Template + Default> HeaderInclusion<T> {
|
||||
pub fn builder() -> HeaderInclusionBuilder<T> {
|
||||
HeaderInclusionBuilder::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HeaderInclusionBuilder<T: Template + Default> {
|
||||
/// SPDX-License-Identifier
|
||||
sdpx: String,
|
||||
/// The `pragma` statement.
|
||||
pragma_version: String,
|
||||
/// The template to render alongside the header.
|
||||
template: T,
|
||||
}
|
||||
|
||||
impl<T: Template + Default> Default for HeaderInclusionBuilder<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
sdpx: GPL3_SDPX_IDENTIFIER.to_string(),
|
||||
pragma_version: PRAGMA_GROTH16_VERIFIER.to_string(),
|
||||
template: T::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Template + Default> HeaderInclusionBuilder<T> {
|
||||
pub fn sdpx<S: Into<String>>(mut self, sdpx: S) -> Self {
|
||||
self.sdpx = sdpx.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn pragma_version<S: Into<String>>(mut self, pragma_version: S) -> Self {
|
||||
self.pragma_version = pragma_version.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn template(mut self, template: impl Into<T>) -> Self {
|
||||
self.template = template.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> HeaderInclusion<T> {
|
||||
HeaderInclusion {
|
||||
sdpx: self.sdpx,
|
||||
pragma_version: self.pragma_version,
|
||||
template: self.template,
|
||||
}
|
||||
}
|
||||
}
|
||||
72
solidity-verifiers/src/verifiers/g16.rs
Normal file
72
solidity-verifiers/src/verifiers/g16.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
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 ark_bn254::Bn254;
|
||||
use ark_groth16::VerifyingKey;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use askama::Template;
|
||||
|
||||
use super::PRAGMA_GROTH16_VERIFIER;
|
||||
|
||||
#[derive(Template, Default)]
|
||||
#[template(path = "groth16_verifier.askama.sol", ext = "sol")]
|
||||
pub(crate) struct Groth16Verifier {
|
||||
/// The `alpha * G`, where `G` is the generator of `G1`.
|
||||
pub(crate) vkey_alpha_g1: G1Repr,
|
||||
/// The `alpha * H`, where `H` is the generator of `G2`.
|
||||
pub(crate) vkey_beta_g2: G2Repr,
|
||||
/// The `gamma * H`, where `H` is the generator of `G2`.
|
||||
pub(crate) vkey_gamma_g2: G2Repr,
|
||||
/// The `delta * H`, where `H` is the generator of `G2`.
|
||||
pub(crate) vkey_delta_g2: G2Repr,
|
||||
/// Length of the `gamma_abc_g1` vector.
|
||||
pub(crate) gamma_abc_len: usize,
|
||||
/// The `gamma^{-1} * (beta * a_i + alpha * b_i + c_i) * H`, where `H` is the generator of `E::G1`.
|
||||
pub(crate) gamma_abc_g1: Vec<G1Repr>,
|
||||
}
|
||||
|
||||
impl From<Groth16Data> for Groth16Verifier {
|
||||
fn from(g16_data: Groth16Data) -> 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
|
||||
.0
|
||||
.gamma_abc_g1
|
||||
.iter()
|
||||
.copied()
|
||||
.map(g1_to_fq_repr)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
#[derive(CanonicalDeserialize, CanonicalSerialize, Clone, PartialEq, Debug)]
|
||||
pub struct Groth16Data(pub(crate) VerifyingKey<Bn254>);
|
||||
|
||||
impl From<VerifyingKey<Bn254>> for Groth16Data {
|
||||
fn from(value: VerifyingKey<Bn254>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolData for Groth16Data {
|
||||
const PROTOCOL_NAME: &'static str = "Groth16";
|
||||
|
||||
fn render_as_template(self, pragma: Option<String>) -> Vec<u8> {
|
||||
HeaderInclusion::<Groth16Verifier>::builder()
|
||||
.sdpx(GPL3_SDPX_IDENTIFIER.to_string())
|
||||
.pragma_version(pragma.unwrap_or(PRAGMA_GROTH16_VERIFIER.to_string()))
|
||||
.template(self)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap()
|
||||
.into_bytes()
|
||||
}
|
||||
}
|
||||
72
solidity-verifiers/src/verifiers/kzg.rs
Normal file
72
solidity-verifiers/src/verifiers/kzg.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
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 ark_bn254::{Bn254, G1Affine};
|
||||
use ark_poly_commit::kzg10::VerifierKey;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use askama::Template;
|
||||
|
||||
use super::PRAGMA_KZG10_VERIFIER;
|
||||
|
||||
#[derive(Template, Default)]
|
||||
#[template(path = "kzg10_verifier.askama.sol", ext = "sol")]
|
||||
pub(crate) struct KZG10Verifier {
|
||||
/// The generator of `G1`.
|
||||
pub(crate) g1: G1Repr,
|
||||
/// The generator of `G2`.
|
||||
pub(crate) g2: G2Repr,
|
||||
/// The verification key
|
||||
pub(crate) vk: G2Repr,
|
||||
/// Length of the trusted setup vector.
|
||||
pub(crate) g1_crs_len: usize,
|
||||
/// The trusted setup vector.
|
||||
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();
|
||||
|
||||
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
|
||||
.iter()
|
||||
.map(|g1| g1_to_fq_repr(*g1))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(CanonicalDeserialize, CanonicalSerialize, Clone, PartialEq, Debug)]
|
||||
pub struct KzgData {
|
||||
pub(crate) vk: VerifierKey<Bn254>,
|
||||
pub(crate) g1_crs_batch_points: Option<Vec<G1Affine>>,
|
||||
}
|
||||
|
||||
impl From<(VerifierKey<Bn254>, Option<Vec<G1Affine>>)> for KzgData {
|
||||
fn from(value: (VerifierKey<Bn254>, Option<Vec<G1Affine>>)) -> Self {
|
||||
Self {
|
||||
vk: value.0,
|
||||
g1_crs_batch_points: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolData for KzgData {
|
||||
const PROTOCOL_NAME: &'static str = "KZG";
|
||||
|
||||
fn render_as_template(self, pragma: Option<String>) -> Vec<u8> {
|
||||
HeaderInclusion::<KZG10Verifier>::builder()
|
||||
.sdpx(MIT_SDPX_IDENTIFIER.to_string())
|
||||
.pragma_version(pragma.unwrap_or(PRAGMA_KZG10_VERIFIER.to_string()))
|
||||
.template(self)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap()
|
||||
.into_bytes()
|
||||
}
|
||||
}
|
||||
428
solidity-verifiers/src/verifiers/mod.rs
Normal file
428
solidity-verifiers/src/verifiers/mod.rs
Normal file
@@ -0,0 +1,428 @@
|
||||
//! Solidity templates for the verifier contracts.
|
||||
//! We use askama for templating and define which variables are required for each template.
|
||||
|
||||
// Pragma statements for verifiers
|
||||
pub(crate) const PRAGMA_GROTH16_VERIFIER: &str = "pragma solidity >=0.7.0 <0.9.0;"; // from snarkjs, avoid changing
|
||||
pub(crate) const PRAGMA_KZG10_VERIFIER: &str = "pragma solidity >=0.8.1 <=0.8.4;";
|
||||
|
||||
/// Default SDPX License identifier
|
||||
pub(crate) const GPL3_SDPX_IDENTIFIER: &str = "// SPDX-License-Identifier: GPL-3.0";
|
||||
pub(crate) const MIT_SDPX_IDENTIFIER: &str = "// SPDX-License-Identifier: MIT";
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
|
||||
|
||||
mod g16;
|
||||
mod kzg;
|
||||
mod nova_cyclefold;
|
||||
|
||||
pub use g16::Groth16Data;
|
||||
pub use kzg::KzgData;
|
||||
pub use nova_cyclefold::NovaCyclefoldData;
|
||||
|
||||
pub trait ProtocolData: CanonicalDeserialize + CanonicalSerialize {
|
||||
const PROTOCOL_NAME: &'static str;
|
||||
|
||||
fn serialize_name<W: Write>(&self, writer: &mut W) -> Result<(), SerializationError> {
|
||||
Self::PROTOCOL_NAME
|
||||
.to_string()
|
||||
.serialize_uncompressed(writer)
|
||||
}
|
||||
|
||||
fn serialize_protocol_data<W: Write>(&self, writer: &mut W) -> Result<(), SerializationError> {
|
||||
self.serialize_name(writer)?;
|
||||
self.serialize_compressed(writer)
|
||||
}
|
||||
fn deserialize_protocol_data<R: Read + Copy>(
|
||||
mut reader: R,
|
||||
) -> Result<Self, SerializationError> {
|
||||
let name: String = String::deserialize_uncompressed(&mut reader)?;
|
||||
let data = Self::deserialize_compressed(&mut reader)?;
|
||||
|
||||
if name != Self::PROTOCOL_NAME {
|
||||
return Err(SerializationError::InvalidData);
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn render_as_template(self, pragma: Option<String>) -> Vec<u8>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::evm::{compile_solidity, save_solidity, Evm};
|
||||
use crate::utils::HeaderInclusion;
|
||||
use crate::{Groth16Data, KzgData, NovaCyclefoldData, ProtocolData};
|
||||
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_groth16::Groth16;
|
||||
use ark_poly_commit::kzg10::VerifierKey;
|
||||
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::{KZGProver, KZGSetup, ProverKey},
|
||||
CommitmentProver,
|
||||
},
|
||||
transcript::{
|
||||
poseidon::{poseidon_test_config, PoseidonTranscript},
|
||||
Transcript,
|
||||
},
|
||||
};
|
||||
use itertools::chain;
|
||||
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];
|
||||
|
||||
/// Default setup length for testing.
|
||||
const DEFAULT_SETUP_LEN: usize = 5;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct TestAddCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
pub x: u8,
|
||||
pub y: u8,
|
||||
pub z: u8,
|
||||
}
|
||||
|
||||
impl<F: PrimeField> ConstraintSynthesizer<F> for TestAddCircuit<F> {
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
|
||||
let x = FpVar::<F>::new_witness(cs.clone(), || Ok(F::from(self.x)))?;
|
||||
let y = FpVar::<F>::new_witness(cs.clone(), || Ok(F::from(self.y)))?;
|
||||
let z = FpVar::<F>::new_input(cs.clone(), || Ok(F::from(self.z)))?;
|
||||
let comp_z = x.clone() + y.clone();
|
||||
comp_z.enforce_equal(&z)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn setup<'a>(
|
||||
n: usize,
|
||||
) -> (
|
||||
ProverKey<'a, G1>,
|
||||
VerifierKey<Bn254>,
|
||||
ark_groth16::ProvingKey<Bn254>,
|
||||
ark_groth16::VerifyingKey<Bn254>,
|
||||
TestAddCircuit<Fr>,
|
||||
) {
|
||||
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
|
||||
let (x, y, z) = (21, 21, 42);
|
||||
let circuit = TestAddCircuit::<Fr> {
|
||||
_f: PhantomData,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
};
|
||||
let (g16_pk, g16_vk) = Groth16::<Bn254>::setup(circuit, &mut rng).unwrap();
|
||||
|
||||
let (kzg_pk, kzg_vk): (ProverKey<G1>, VerifierKey<Bn254>) =
|
||||
KZGSetup::<Bn254>::setup(&mut rng, n);
|
||||
(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 = KZGProver::<G1>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
|
||||
let (eval, proof) =
|
||||
KZGProver::<G1>::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.into_affine());
|
||||
let (x_comm, y_comm) = cm_affine.xy().unwrap();
|
||||
let (x_proof, y_proof) = proof_affine.xy().unwrap();
|
||||
let y = 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 = KZGProver::<G1>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
|
||||
let (eval, proof) =
|
||||
KZGProver::<G1>::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.into_affine());
|
||||
let (x_comm, y_comm) = cm_affine.xy().unwrap();
|
||||
let (x_proof, y_proof) = proof_affine.xy().unwrap();
|
||||
let y = 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);
|
||||
}
|
||||
}
|
||||
69
solidity-verifiers/src/verifiers/nova_cyclefold.rs
Normal file
69
solidity-verifiers/src/verifiers/nova_cyclefold.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use crate::utils::HeaderInclusion;
|
||||
use crate::{Groth16Data, KzgData, ProtocolData, PRAGMA_GROTH16_VERIFIER};
|
||||
use ark_bn254::{Bn254, G1Affine};
|
||||
use ark_groth16::VerifyingKey;
|
||||
use ark_poly_commit::kzg10::VerifierKey;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use askama::Template;
|
||||
|
||||
use super::g16::Groth16Verifier;
|
||||
use super::kzg::KZG10Verifier;
|
||||
|
||||
#[derive(Template, Default)]
|
||||
#[template(path = "nova_cyclefold_decider.askama.sol", ext = "sol")]
|
||||
pub(crate) struct NovaCyclefoldDecider {
|
||||
groth16_verifier: Groth16Verifier,
|
||||
kzg10_verifier: KZG10Verifier,
|
||||
}
|
||||
|
||||
impl From<NovaCyclefoldData> for NovaCyclefoldDecider {
|
||||
fn from(value: NovaCyclefoldData) -> Self {
|
||||
Self {
|
||||
groth16_verifier: Groth16Verifier::from(value.g16_data),
|
||||
kzg10_verifier: KZG10Verifier::from(value.kzg_data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(CanonicalDeserialize, CanonicalSerialize, PartialEq, Debug)]
|
||||
pub struct NovaCyclefoldData {
|
||||
g16_data: Groth16Data,
|
||||
kzg_data: KzgData,
|
||||
}
|
||||
|
||||
impl ProtocolData for NovaCyclefoldData {
|
||||
const PROTOCOL_NAME: &'static str = "NovaCyclefold";
|
||||
|
||||
fn render_as_template(self, pragma: Option<String>) -> Vec<u8> {
|
||||
HeaderInclusion::<NovaCyclefoldDecider>::builder()
|
||||
.pragma_version(pragma.unwrap_or(PRAGMA_GROTH16_VERIFIER.to_string()))
|
||||
.template(self)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap()
|
||||
.into_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Groth16Data, KzgData)> for NovaCyclefoldData {
|
||||
fn from(value: (Groth16Data, KzgData)) -> Self {
|
||||
Self {
|
||||
g16_data: value.0,
|
||||
kzg_data: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NovaCyclefoldData {
|
||||
pub fn new(
|
||||
vkey_g16: VerifyingKey<Bn254>,
|
||||
vkey_kzg: VerifierKey<Bn254>,
|
||||
crs_points: Vec<G1Affine>,
|
||||
) -> Self {
|
||||
Self {
|
||||
g16_data: Groth16Data::from(vkey_g16),
|
||||
// TODO: Remove option from crs points
|
||||
kzg_data: KzgData::from((vkey_kzg, Some(crs_points))),
|
||||
}
|
||||
}
|
||||
}
|
||||
169
solidity-verifiers/templates/groth16_verifier.askama.sol
Normal file
169
solidity-verifiers/templates/groth16_verifier.askama.sol
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
Copyright 2021 0KIMS association.
|
||||
|
||||
* `solidity-verifiers` added comment
|
||||
This file is a template built out of [snarkJS](https://github.com/iden3/snarkjs) groth16 verifier.
|
||||
See the original ejs template [here](https://github.com/iden3/snarkjs/blob/master/templates/verifier_groth16.sol.ejs)
|
||||
*
|
||||
|
||||
snarkJS is a free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
snarkJS is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with snarkJS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
contract Groth16Verifier {
|
||||
// Scalar field size
|
||||
uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
// Base field size
|
||||
uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||
|
||||
// Verification Key data
|
||||
uint256 constant alphax = {{ vkey_alpha_g1.0[0] }};
|
||||
uint256 constant alphay = {{ vkey_alpha_g1.0[1] }};
|
||||
uint256 constant betax1 = {{ vkey_beta_g2.0[0][1] }};
|
||||
uint256 constant betax2 = {{ vkey_beta_g2.0[0][0] }};
|
||||
uint256 constant betay1 = {{ vkey_beta_g2.0[1][1] }};
|
||||
uint256 constant betay2 = {{ vkey_beta_g2.0[1][0] }};
|
||||
uint256 constant gammax1 = {{ vkey_gamma_g2.0[0][1] }};
|
||||
uint256 constant gammax2 = {{ vkey_gamma_g2.0[0][0] }};
|
||||
uint256 constant gammay1 = {{ vkey_gamma_g2.0[1][1] }};
|
||||
uint256 constant gammay2 = {{ vkey_gamma_g2.0[1][0] }};
|
||||
uint256 constant deltax1 = {{ vkey_delta_g2.0[0][1] }};
|
||||
uint256 constant deltax2 = {{ vkey_delta_g2.0[0][0] }};
|
||||
uint256 constant deltay1 = {{ vkey_delta_g2.0[1][1] }};
|
||||
uint256 constant deltay2 = {{ vkey_delta_g2.0[1][0] }};
|
||||
|
||||
{% for (i, point) in gamma_abc_g1.iter().enumerate() %}
|
||||
uint256 constant IC{{i}}x = {{ point.0[0] }};
|
||||
uint256 constant IC{{i}}y = {{ point.0[1] }};
|
||||
{% endfor %}
|
||||
|
||||
// Memory data
|
||||
uint16 constant pVk = 0;
|
||||
uint16 constant pPairing = 128;
|
||||
|
||||
uint16 constant pLastMem = 896;
|
||||
|
||||
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)) {
|
||||
mstore(0, 0)
|
||||
return(0, 0x20)
|
||||
}
|
||||
}
|
||||
|
||||
// G1 function to multiply a G1 value(x,y) to value in an address
|
||||
function g1_mulAccC(pR, x, y, s) {
|
||||
let success
|
||||
let mIn := mload(0x40)
|
||||
mstore(mIn, x)
|
||||
mstore(add(mIn, 32), y)
|
||||
mstore(add(mIn, 64), s)
|
||||
|
||||
success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
|
||||
|
||||
if iszero(success) {
|
||||
mstore(0, 0)
|
||||
return(0, 0x20)
|
||||
}
|
||||
|
||||
mstore(add(mIn, 64), mload(pR))
|
||||
mstore(add(mIn, 96), mload(add(pR, 32)))
|
||||
|
||||
success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
|
||||
|
||||
if iszero(success) {
|
||||
mstore(0, 0)
|
||||
return(0, 0x20)
|
||||
}
|
||||
}
|
||||
|
||||
function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
|
||||
let _pPairing := add(pMem, pPairing)
|
||||
let _pVk := add(pMem, pVk)
|
||||
|
||||
mstore(_pVk, IC0x)
|
||||
mstore(add(_pVk, 32), IC0y)
|
||||
|
||||
// Compute the linear combination vk_x
|
||||
{% for (i, _) in gamma_abc_g1.iter().enumerate() %}
|
||||
{% if loop.first -%}
|
||||
{%- else -%}
|
||||
g1_mulAccC(_pVk, IC{{i}}x, IC{{i}}y, calldataload(add(pubSignals, {{(i-1)*32}})))
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
|
||||
// -A
|
||||
mstore(_pPairing, calldataload(pA))
|
||||
mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))
|
||||
|
||||
// B
|
||||
mstore(add(_pPairing, 64), calldataload(pB))
|
||||
mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
|
||||
mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
|
||||
mstore(add(_pPairing, 160), calldataload(add(pB, 96)))
|
||||
|
||||
// alpha1
|
||||
mstore(add(_pPairing, 192), alphax)
|
||||
mstore(add(_pPairing, 224), alphay)
|
||||
|
||||
// beta2
|
||||
mstore(add(_pPairing, 256), betax1)
|
||||
mstore(add(_pPairing, 288), betax2)
|
||||
mstore(add(_pPairing, 320), betay1)
|
||||
mstore(add(_pPairing, 352), betay2)
|
||||
|
||||
// vk_x
|
||||
mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
|
||||
mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))
|
||||
|
||||
|
||||
// gamma2
|
||||
mstore(add(_pPairing, 448), gammax1)
|
||||
mstore(add(_pPairing, 480), gammax2)
|
||||
mstore(add(_pPairing, 512), gammay1)
|
||||
mstore(add(_pPairing, 544), gammay2)
|
||||
|
||||
// C
|
||||
mstore(add(_pPairing, 576), calldataload(pC))
|
||||
mstore(add(_pPairing, 608), calldataload(add(pC, 32)))
|
||||
|
||||
// delta2
|
||||
mstore(add(_pPairing, 640), deltax1)
|
||||
mstore(add(_pPairing, 672), deltax2)
|
||||
mstore(add(_pPairing, 704), deltay1)
|
||||
mstore(add(_pPairing, 736), deltay2)
|
||||
|
||||
|
||||
let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)
|
||||
|
||||
isOk := and(success, mload(_pPairing))
|
||||
}
|
||||
|
||||
let pMem := mload(0x40)
|
||||
mstore(0x40, add(pMem, pLastMem))
|
||||
|
||||
// Validate that all evaluations ∈ F
|
||||
{% for (i, _) in gamma_abc_g1.iter().enumerate() %}
|
||||
checkField(calldataload(add(_pubSignals, {{i*32}})))
|
||||
{% endfor %}
|
||||
|
||||
// Validate all evaluations
|
||||
let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem)
|
||||
|
||||
mstore(0, isValid)
|
||||
|
||||
return(0, 0x20)
|
||||
}
|
||||
}
|
||||
}
|
||||
4
solidity-verifiers/templates/header_template.askama.sol
Normal file
4
solidity-verifiers/templates/header_template.askama.sol
Normal file
@@ -0,0 +1,4 @@
|
||||
{{ sdpx }}
|
||||
{{ pragma_version }}
|
||||
|
||||
{{template}}
|
||||
271
solidity-verifiers/templates/kzg10_verifier.askama.sol
Normal file
271
solidity-verifiers/templates/kzg10_verifier.askama.sol
Normal file
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* @author Privacy and Scaling Explorations team - pse.dev
|
||||
* @dev Contains utility functions for ops in BN254; in G_1 mostly.
|
||||
* @notice Forked from https://github.com/weijiekoh/libkzg/tree/master.
|
||||
* Among others, a few of the changes we did on this fork were:
|
||||
* - Templating the pragma version
|
||||
* - Removing type wrappers and use uints instead
|
||||
* - Performing changes on arg types
|
||||
* - Update some of the `require` statements
|
||||
* - Use the bn254 scalar field instead of checking for overflow on the babyjub prime
|
||||
* - In batch checking, we compute auxiliary polynomials and their commitments at the same time.
|
||||
*/
|
||||
contract KZG10Verifier {
|
||||
|
||||
// prime of field F_p over which y^2 = x^3 + 3 is defined
|
||||
uint256 public constant BN254_PRIME_FIELD =
|
||||
21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||
uint256 public constant BN254_SCALAR_FIELD =
|
||||
21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
|
||||
/**
|
||||
* @notice Performs scalar multiplication in G_1.
|
||||
* @param p G_1 point to multiply
|
||||
* @param s Scalar to multiply by
|
||||
* @return r G_1 point p multiplied by scalar s
|
||||
*/
|
||||
function mulScalar(uint256[2] memory p, uint256 s) internal view returns (uint256[2] memory r) {
|
||||
uint256[3] memory input;
|
||||
input[0] = p[0];
|
||||
input[1] = p[1];
|
||||
input[2] = s;
|
||||
bool success;
|
||||
assembly {
|
||||
success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40)
|
||||
switch success
|
||||
case 0 { invalid() }
|
||||
}
|
||||
require(success, "bn254: scalar mul failed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Negates a point in G_1.
|
||||
* @param p G_1 point to negate
|
||||
* @return uint256[2] G_1 point -p
|
||||
*/
|
||||
function negate(uint256[2] memory p) internal pure returns (uint256[2] memory) {
|
||||
if (p[0] == 0 && p[1] == 0) {
|
||||
return p;
|
||||
}
|
||||
return [p[0], BN254_PRIME_FIELD - (p[1] % BN254_PRIME_FIELD)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Adds two points in G_1.
|
||||
* @param p1 G_1 point 1
|
||||
* @param p2 G_1 point 2
|
||||
* @return r G_1 point p1 + p2
|
||||
*/
|
||||
function add(uint256[2] memory p1, uint256[2] memory p2) internal view returns (uint256[2] memory r) {
|
||||
bool success;
|
||||
uint256[4] memory input = [p1[0], p1[1], p2[0], p2[1]];
|
||||
assembly {
|
||||
success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40)
|
||||
switch success
|
||||
case 0 { invalid() }
|
||||
}
|
||||
|
||||
require(success, "bn254: point add failed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Computes the pairing check e(p1, p2) * e(p3, p4) == 1
|
||||
* @dev Note that G_2 points a*i + b are encoded as two elements of F_p, (a, b)
|
||||
* @param a_1 G_1 point 1
|
||||
* @param a_2 G_2 point 1
|
||||
* @param b_1 G_1 point 2
|
||||
* @param b_2 G_2 point 2
|
||||
* @return result true if pairing check is successful
|
||||
*/
|
||||
function pairing(uint256[2] memory a_1, uint256[2][2] memory a_2, uint256[2] memory b_1, uint256[2][2] memory b_2)
|
||||
internal
|
||||
view
|
||||
returns (bool result)
|
||||
{
|
||||
uint256[12] memory input = [
|
||||
a_1[0],
|
||||
a_1[1],
|
||||
a_2[0][1], // imaginary part first
|
||||
a_2[0][0],
|
||||
a_2[1][1], // imaginary part first
|
||||
a_2[1][0],
|
||||
b_1[0],
|
||||
b_1[1],
|
||||
b_2[0][1], // imaginary part first
|
||||
b_2[0][0],
|
||||
b_2[1][1], // imaginary part first
|
||||
b_2[1][0]
|
||||
];
|
||||
|
||||
uint256[1] memory out;
|
||||
bool success;
|
||||
|
||||
assembly {
|
||||
success := staticcall(sub(gas(), 2000), 8, input, 0x180, out, 0x20)
|
||||
switch success
|
||||
case 0 { invalid() }
|
||||
}
|
||||
|
||||
require(success, "bn254: pairing failed");
|
||||
|
||||
return out[0] == 1;
|
||||
}
|
||||
|
||||
uint256[2] G_1 = [
|
||||
{{ g1.0[0] }},
|
||||
{{ g1.0[1] }}
|
||||
];
|
||||
uint256[2][2] G_2 = [
|
||||
[
|
||||
{{ g2.0[0][0] }},
|
||||
{{ g2.0[0][1] }}
|
||||
],
|
||||
[
|
||||
{{ g2.0[1][0] }},
|
||||
{{ g2.0[1][1] }}
|
||||
]
|
||||
];
|
||||
uint256[2][2] VK = [
|
||||
[
|
||||
{{ vk.0[0][0] }},
|
||||
{{ vk.0[0][1] }}
|
||||
],
|
||||
[
|
||||
{{ vk.0[1][0] }},
|
||||
{{ vk.0[1][1] }}
|
||||
]
|
||||
];
|
||||
|
||||
uint256[2][{{ g1_crs_len }}] G1_CRS = [
|
||||
{%- for (i, point) in g1_crs.iter().enumerate() %}
|
||||
[
|
||||
{{ point.0[0] }},
|
||||
{{ point.0[1] }}
|
||||
{% if loop.last -%}
|
||||
]
|
||||
{%- else -%}
|
||||
],
|
||||
{%- endif -%}
|
||||
{% endfor -%}
|
||||
];
|
||||
|
||||
/**
|
||||
* @notice Verifies a single point evaluation proof. Function name follows `ark-poly`.
|
||||
* @dev To avoid ops in G_2, we slightly tweak how the verification is done.
|
||||
* @param c G_1 point commitment to polynomial.
|
||||
* @param pi G_1 point proof.
|
||||
* @param x Value to prove evaluation of polynomial at.
|
||||
* @param y Evaluation poly(x).
|
||||
* @return result Indicates if KZG proof is correct.
|
||||
*/
|
||||
function check(uint256[2] calldata c, uint256[2] calldata pi, uint256 x, uint256 y)
|
||||
public
|
||||
view
|
||||
returns (bool result)
|
||||
{
|
||||
//
|
||||
// we want to:
|
||||
// 1. avoid gas intensive ops in G2
|
||||
// 2. format the pairing check in line with what the evm opcode expects.
|
||||
//
|
||||
// we can do this by tweaking the KZG check to be:
|
||||
//
|
||||
// e(pi, vk - x * g2) = e(c - y * g1, g2) [initial check]
|
||||
// e(pi, vk - x * g2) * e(c - y * g1, g2)^{-1} = 1
|
||||
// e(pi, vk - x * g2) * e(-c + y * g1, g2) = 1 [bilinearity of pairing for all subsequent steps]
|
||||
// e(pi, vk) * e(pi, -x * g2) * e(-c + y * g1, g2) = 1
|
||||
// e(pi, vk) * e(-x * pi, g2) * e(-c + y * g1, g2) = 1
|
||||
// e(pi, vk) * e(x * -pi - c + y * g1, g2) = 1 [done]
|
||||
// |_ rhs_pairing _|
|
||||
//
|
||||
uint256[2] memory rhs_pairing =
|
||||
add(mulScalar(negate(pi), x), add(negate(c), mulScalar(G_1, y)));
|
||||
return pairing(pi, VK, rhs_pairing, G_2);
|
||||
}
|
||||
|
||||
function evalPolyAt(uint256[] memory _coefficients, uint256 _index) public pure returns (uint256) {
|
||||
uint256 m = BN254_SCALAR_FIELD;
|
||||
uint256 result = 0;
|
||||
uint256 powerOfX = 1;
|
||||
|
||||
for (uint256 i = 0; i < _coefficients.length; i++) {
|
||||
uint256 coeff = _coefficients[i];
|
||||
assembly {
|
||||
result := addmod(result, mulmod(powerOfX, coeff, m), m)
|
||||
powerOfX := mulmod(powerOfX, _index, m)
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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).
|
||||
* @param l_coeffs coefficients of the lagrange polynomial l(x).
|
||||
* @param x_vals x values to evaluate the polynomials at.
|
||||
* @param y_vals y values to which l(x) should evaluate to.
|
||||
* @return uint256[2] commitment to z(x).
|
||||
* @return uint256[2] commitment to l(x).
|
||||
*/
|
||||
function checkAndCommitAuxPolys(
|
||||
uint256[] memory z_coeffs,
|
||||
uint256[] memory l_coeffs,
|
||||
uint256[] memory x_vals,
|
||||
uint256[] memory y_vals
|
||||
) public view returns (uint256[2] memory, uint256[2] memory) {
|
||||
// z(x) is of degree len(x_vals), it is a product of linear polynomials (x - x_i)
|
||||
// l(x) is of degree len(x_vals) - 1
|
||||
uint256[2] memory z_commit;
|
||||
uint256[2] memory l_commit;
|
||||
for (uint256 i = 0; i < x_vals.length; i++) {
|
||||
z_commit = add(z_commit, mulScalar(G1_CRS[i], z_coeffs[i])); // update commitment to z(x)
|
||||
l_commit = add(l_commit, mulScalar(G1_CRS[i], l_coeffs[i])); // update commitment to l(x)
|
||||
|
||||
uint256 eval_z = evalPolyAt(z_coeffs, x_vals[i]);
|
||||
uint256 eval_l = evalPolyAt(l_coeffs, x_vals[i]);
|
||||
|
||||
require(eval_z == 0, "checkAndCommitAuxPolys: wrong zero poly");
|
||||
require(eval_l == y_vals[i], "checkAndCommitAuxPolys: wrong lagrange poly");
|
||||
}
|
||||
// z(x) has len(x_vals) + 1 coeffs, we add to the commitment the last coeff of z(x)
|
||||
z_commit = add(z_commit, mulScalar(G1_CRS[z_coeffs.length - 1], z_coeffs[z_coeffs.length - 1]));
|
||||
|
||||
return (z_commit, l_commit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Verifies a batch of point evaluation proofs. Function name follows `ark-poly`.
|
||||
* @dev To avoid ops in G_2, we slightly tweak how the verification is done.
|
||||
* @param c G1 point commitment to polynomial.
|
||||
* @param pi G2 point proof.
|
||||
* @param x_vals Values to prove evaluation of polynomial at.
|
||||
* @param y_vals Evaluation poly(x).
|
||||
* @param l_coeffs Coefficients of the lagrange polynomial.
|
||||
* @param z_coeffs Coefficients of the zero polynomial z(x) = (x - x_1)(x - x_2)...(x - x_n).
|
||||
* @return result Indicates if KZG proof is correct.
|
||||
*/
|
||||
function batchCheck(
|
||||
uint256[2] calldata c,
|
||||
uint256[2][2] calldata pi,
|
||||
uint256[] calldata x_vals,
|
||||
uint256[] calldata y_vals,
|
||||
uint256[] calldata l_coeffs,
|
||||
uint256[] calldata z_coeffs
|
||||
) public view returns (bool result) {
|
||||
//
|
||||
// we want to:
|
||||
// 1. avoid gas intensive ops in G2
|
||||
// 2. format the pairing check in line with what the evm opcode expects.
|
||||
//
|
||||
// we can do this by tweaking the KZG check to be:
|
||||
//
|
||||
// e(z(r) * g1, pi) * e(g1, l(r) * g2) = e(c, g2) [initial check]
|
||||
// e(z(r) * g1, pi) * e(l(r) * g1, g2) * e(c, g2)^{-1} = 1 [bilinearity of pairing]
|
||||
// e(z(r) * g1, pi) * e(l(r) * g1 - c, g2) = 1 [done]
|
||||
//
|
||||
(uint256[2] memory z_commit, uint256[2] memory l_commit) =
|
||||
checkAndCommitAuxPolys(z_coeffs, l_coeffs, x_vals, y_vals);
|
||||
uint256[2] memory neg_commit = negate(c);
|
||||
return pairing(z_commit, pi, add(l_commit, neg_commit), G_2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{{ groth16_verifier }}
|
||||
|
||||
{{ kzg10_verifier }}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
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");
|
||||
|
||||
// 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");
|
||||
|
||||
return(true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user