fix: kzg verification with EVM (#83)

This commit is contained in:
dante
2022-12-23 07:04:53 -05:00
committed by GitHub
parent 4e0b7f08c2
commit e2f23d7852
12 changed files with 6136 additions and 70 deletions

View File

@@ -54,11 +54,11 @@ jobs:
components: rustfmt, clippy
- name: Mock proving tests (public outputs)
run: cargo test --release --verbose tests::mock_public_outputs_
run: cargo test --release --verbose tests::mock_public_outputs_ -- --test-threads 16
- name: Mock proving tests (public inputs)
run: cargo test --release --verbose tests::mock_public_inputs_
run: cargo test --release --verbose tests::mock_public_inputs_ -- --test-threads 16
- name: Mock proving tests (public params)
run: cargo test --release --verbose tests::mock_public_params_
run: cargo test --release --verbose tests::mock_public_params_ -- --test-threads 16
full-proving-tests:
@@ -77,7 +77,26 @@ jobs:
run: cargo test --release --verbose tests::ipa_fullprove_ -- --test-threads 1
- name: KZG full-prove tests
run: cargo test --release --verbose tests::kzg_fullprove_ -- --test-threads 1
full-proving-evm-tests:
runs-on: self-hosted
timeout-minutes: 480
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Install solc
run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.17 && solc --version
- name: KZG full-prove tests (EVM)
run: cargo test --release --verbose tests_evm::kzg_evm_fullprove_ -- --test-threads 1
prove-and-verify-tests:
runs-on: self-hosted

1
.gitignore vendored
View File

@@ -1,5 +1,4 @@
target
Cargo.lock
data
*.pf
*.vk

6022
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -10,8 +10,6 @@ halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2",
halo2curves = { git = 'https://github.com/privacy-scaling-explorations/halo2curves', tag = "0.3.0" }
# nalgebra = "0.31"
rand = "0.8"
mnist = "0.5"
ndarray = "0.15"
itertools = "0.10.3"
tensorflow = {version = "0.18.0", features = ["eager"], optional = true }
plotters = { version = "0.3.0", optional = true }
@@ -21,18 +19,19 @@ clap = { version = "4.0.7", features = ["derive"] }
serde = { version = "1.0.126", features = ["derive"], optional = true }
serde_json = { version = "1.0.64", optional = true }
log = { version = "0.4.17", optional = true }
colog = { version = "1.1.0", optional = true }
tabled = { version = "0.9.0", optional = true}
# evm related deps
hashbrown = "0.13"
ethereum_types = { package = "ethereum-types", version = "0.14", default-features = false, features = ["std"], optional=true}
foundry_evm = { git = "https://github.com/foundry-rs/foundry", package = "foundry-evm", rev = "b28119b56d7dd18c268a471167a0c547c301c13e", optional=true }
halo2_wrong_ecc = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2022_10_22", package = "ecc"}
plonk_verifier = { git = "https://github.com/privacy-scaling-explorations/plonk-verifier.git", tag = "v2022_10_22"}
ethereum_types = { package = "ethereum-types", version = "0.14.1", default-features = false, features = ["std"], optional=true}
foundry_evm = { git = "https://github.com/foundry-rs/foundry", package = "foundry-evm", rev = "4f21719", optional=true }
halo2_wrong_ecc = { git = "https://github.com/zkonduit/halo2wrong", package = "ecc", branch = "master", optional=true}
plonk_verifier = { git = "https://github.com/zkonduit/plonk-verifier", branch = "main"}
colog = { version = "1.1.0", optional = true }
[dev-dependencies]
criterion = {version = "0.3", features = ["html_reports"]}
lazy_static = "1.4.0"
mnist = "0.5"
seq-macro = "0.3.1"
test-case = "2.2.2"
ctor = "0.1.26"
@@ -65,4 +64,4 @@ dev-graph = ["halo2_proofs/dev-graph", "plotters"]
tensorflow = ["dep:tensorflow"]
onnx = ["dep:tract-onnx"]
ezkl = ["onnx", "serde", "serde_json", "log", "colog", "tabled"]
evm = ["ethereum_types", "foundry_evm"]
evm = ["ethereum_types", "foundry_evm", "halo2_wrong_ecc"]

View File

@@ -150,7 +150,9 @@ cargo run --release --bin ezkl -- table -M ./examples/onnx_models/ff.onnx
```
### verifying with the EVM ◊
Note that `fullprove` can also be run with an EVM verifier. In this case we use KZG commitments, rather than the default IPA commitments, and we need to pass the `evm` feature flag to conditionally compile the requisite [foundry_evm](https://github.com/foundry-rs/foundry) dependencies. Using `foundry_evm` we spin up a local EVM executor and verify the generated proof. In future releases we'll create a simple pipeline for deploying to EVM based networks.
Note that `fullprove` can also be run with an EVM verifier. In this case we use KZG commitments, rather than the default IPA commitments, and we need to pass the `evm` feature flag to conditionally compile the requisite [foundry_evm](https://github.com/foundry-rs/foundry) dependencies. Using `foundry_evm` we spin up a local EVM executor and verify the generated proof. In future releases we'll create a simple pipeline for deploying to EVM based networks. Also note that this requires a local [solc](https://docs.soliditylang.org/en/v0.8.17/installing-solidity.html) installation.
Example:
```bash

View File

@@ -163,8 +163,14 @@ where
512,
);
let bias =
VarTensor::new_advice(cs, K, max(OUT_CHANNELS, CLASSES), vec![OUT_CHANNELS], true, 512);
let bias = VarTensor::new_advice(
cs,
K,
max(OUT_CHANNELS, CLASSES),
vec![OUT_CHANNELS],
true,
512,
);
let output = VarTensor::new_advice(
cs,
K,

View File

@@ -4,12 +4,11 @@ use ezkl::commands::{Cli, Commands, ProofSystem};
use ezkl::fieldutils::i32_to_felt;
use ezkl::graph::Model;
#[cfg(feature = "evm")]
use ezkl::pfsys::kzg::aggregation::{
aggregation::AggregationCircuit, evm_verify, gen_aggregation_evm_verifier,
gen_application_snark, gen_kzg_proof, gen_pk, gen_srs,
use ezkl::pfsys::aggregation::{
evm_verify, gen_aggregation_evm_verifier, gen_application_snark, gen_kzg_proof, gen_pk,
gen_srs, AggregationCircuit,
};
use ezkl::pfsys::{create_keys, load_params, load_vk, Proof};
#[cfg(not(feature = "evm"))]
use ezkl::pfsys::{
create_proof_model, parse_prover_errors, prepare_circuit_and_public_input, prepare_data,
save_params, save_vk, verify_proof_model,
@@ -20,7 +19,6 @@ use halo2_proofs::poly::ipa::commitment::IPACommitmentScheme;
use halo2_proofs::poly::ipa::multiopen::ProverIPA;
use halo2_proofs::poly::kzg::commitment::KZGCommitmentScheme;
use halo2_proofs::poly::kzg::multiopen::ProverGWC;
#[cfg(not(feature = "evm"))]
use halo2_proofs::poly::kzg::{
commitment::ParamsKZG, multiopen::VerifierGWC, strategy::SingleStrategy as KZGSingleStrategy,
};
@@ -32,6 +30,8 @@ use halo2_proofs::{
VerificationStrategy,
},
};
#[cfg(feature = "evm")]
use halo2curves::bn256::G1Affine;
use halo2curves::bn256::{Bn256, Fr};
use halo2curves::pasta::vesta;
use halo2curves::pasta::Fp;
@@ -39,6 +39,8 @@ use log::{error, info, trace};
#[cfg(feature = "evm")]
use plonk_verifier::system::halo2::transcript::evm::EvmTranscript;
use rand::seq::SliceRandom;
#[cfg(feature = "evm")]
use std::time::Instant;
use tabled::Table;
pub fn main() {
@@ -137,7 +139,6 @@ pub fn main() {
ProofSystem::KZG => {
// We will need aggregator k > application k > bits
// let application_logrows = args.logrows; //bits + 1;
let (circuit, public_inputs) = prepare_circuit_and_public_input(&data);
let aggregation_logrows = args.logrows + 6;
let params = gen_srs(aggregation_logrows);

View File

@@ -272,8 +272,7 @@ impl<F: FieldExt> Nonlinearity<F> for ReLu<F> {
} else {
let d_inv_x = (x as f32) / (scale[0] as f32);
let rounded = d_inv_x.round();
let integral: i32 = unsafe { rounded.to_int_unchecked() };
i32_to_felt(integral)
i32_to_felt(rounded as i32)
}
}
}
@@ -289,8 +288,7 @@ impl<F: FieldExt> Nonlinearity<F> for Sigmoid<F> {
let kix = (x as f32) / (scale[0] as f32);
let fout = (scale[1] as f32) / (1.0 + (-kix).exp());
let rounded = fout.round();
let xi: i32 = unsafe { rounded.to_int_unchecked() };
fieldutils::i32_to_felt(xi)
fieldutils::i32_to_felt(rounded as i32)
}
}
@@ -302,8 +300,7 @@ impl<F: FieldExt> Nonlinearity<F> for DivideBy<F> {
fn nonlinearity(x: i32, scale: &[usize]) -> F {
let d_inv_x = (x as f32) / (scale[0] as f32);
let rounded = d_inv_x.round();
let integral: i32 = unsafe { rounded.to_int_unchecked() };
fieldutils::i32_to_felt(integral)
fieldutils::i32_to_felt(rounded as i32)
}
}

View File

@@ -13,7 +13,7 @@ pub fn vector_to_quantized(
let mult = scale_to_multiplier(scale);
let scaled: Vec<i32> = vec
.iter()
.map(|e| unsafe { (mult * e + shift).round().to_int_unchecked::<i32>() })
.map(|e| (mult * e + shift).round() as i32)
.collect();
Tensor::new(Some(&scaled), dims)
}

View File

@@ -1,3 +1,4 @@
#![deny(warnings, unsafe_code)]
#![feature(slice_flatten)]
/// Methods for configuring tensor operations and assigning values to them in a Halo2 circuit.

View File

@@ -1,11 +1,8 @@
use super::super::prepare_circuit_and_public_input;
use super::super::ModelInput;
use super::prepare_circuit_and_public_input;
use super::ModelInput;
use crate::fieldutils::i32_to_felt;
#[cfg(feature = "evm")]
use ethereum_types::Address;
#[cfg(feature = "evm")]
use foundry_evm::executor::{fork::MultiFork, Backend, ExecutorBuilder};
#[cfg(feature = "evm")]
use halo2_proofs::plonk::VerifyingKey;
use halo2_proofs::{
circuit::{Layouter, SimpleFloorPlanner, Value},
@@ -33,16 +30,12 @@ use halo2_wrong_ecc::{
},
EccConfig,
};
#[cfg(feature = "evm")]
use halo2curves::bn256::Fq;
use halo2curves::bn256::{Bn256, Fq, Fr, G1Affine};
use itertools::Itertools;
use log::trace;
#[cfg(feature = "evm")]
use plonk_verifier::{
loader::evm::{encode_calldata, EvmLoader},
loader::evm::{self, encode_calldata, EvmLoader},
system::halo2::transcript::evm::EvmTranscript,
verifier::PlonkVerifier,
};
use plonk_verifier::{
loader::native::NativeLoader,
@@ -61,8 +54,6 @@ use plonk_verifier::{
};
use rand::rngs::OsRng;
use std::io::Cursor;
#[cfg(feature = "evm")]
use std::rc::Rc;
use std::{iter, rc::Rc};
const LIMBS: usize = 4;
@@ -159,22 +150,22 @@ pub fn aggregate<'a>(
let accumulators = snarks
.iter()
.flat_map(|snark| {
let protocol = snark.protocol.loaded(loader);
let instances = assign_instances(&snark.instances);
let mut transcript =
PoseidonTranscript::<Rc<Halo2Loader>, _>::new(loader, snark.proof());
let proof =
Plonk::read_proof(svk, &snark.protocol, &instances, &mut transcript).unwrap();
Plonk::succinct_verify(svk, &snark.protocol, &instances, &proof).unwrap()
let proof = Plonk::read_proof(svk, &protocol, &instances, &mut transcript).unwrap();
Plonk::succinct_verify(svk, &protocol, &instances, &proof).unwrap()
})
.collect_vec();
let acccumulator = {
let accumulator = {
let mut transcript = PoseidonTranscript::<Rc<Halo2Loader>, _>::new(loader, as_proof);
let proof = As::read_proof(&Default::default(), &accumulators, &mut transcript).unwrap();
As::verify(&Default::default(), &accumulators, &proof).unwrap()
};
acccumulator
accumulator
}
#[derive(Clone)]
@@ -324,7 +315,10 @@ impl Circuit<Fr> for AggregationCircuit {
let KzgAccumulator { lhs, rhs } =
aggregate(&self.svk, &loader, &self.snarks, self.as_proof());
Ok((lhs.assigned(), rhs.assigned()))
let lhs = lhs.assigned().clone();
let rhs = rhs.assigned().clone();
Ok((lhs, rhs))
},
)?;
@@ -368,7 +362,6 @@ pub fn gen_application_snark(params: &ParamsKZG<Bn256>, data: &ModelInput) -> Sn
Snark::new(protocol, pi_inner, proof)
}
#[cfg(feature = "evm")]
pub fn gen_aggregation_evm_verifier(
params: &ParamsKZG<Bn256>,
vk: &VerifyingKey<G1Affine>,
@@ -382,20 +375,20 @@ pub fn gen_aggregation_evm_verifier(
vk,
Config::kzg()
.with_num_instance(num_instance.clone())
.with_accumulator_indices(accumulator_indices),
.with_accumulator_indices(Some(accumulator_indices)),
);
let loader = EvmLoader::new::<Fq, Fr>();
let mut transcript = EvmTranscript::<_, Rc<EvmLoader>, _, _>::new(loader.clone());
let protocol = protocol.loaded(&loader);
let mut transcript = EvmTranscript::<_, Rc<EvmLoader>, _, _>::new(&loader.clone());
let instances = transcript.load_instances(num_instance);
let proof = Plonk::read_proof(&svk, &protocol, &instances, &mut transcript).unwrap();
Plonk::verify(&svk, &dk, &protocol, &instances, &proof).unwrap();
loader.deployment_code()
evm::compile_yul(&loader.yul_code())
}
#[cfg(feature = "evm")]
pub fn evm_verify(deployment_code: Vec<u8>, instances: Vec<Vec<Fr>>, proof: Vec<u8>) {
let calldata = encode_calldata(&instances, &proof);
let success = {

View File

@@ -1,16 +1,22 @@
use lazy_static::lazy_static;
use std::env::var;
use std::process::Command;
lazy_static! {
static ref CARGO_TARGET_DIR: String = var("CARGO_TARGET_DIR").unwrap_or("./target".to_string());
}
#[cfg(test)]
#[ctor::ctor]
fn init() {
println!("using cargo target dir: {}", *CARGO_TARGET_DIR);
build_ezkl();
}
const TESTS: [&str; 12] = [
const TESTS: [&str; 11] = [
"1l_mlp",
"1l_flatten",
"1l_average",
"2l_relu_sigmoid",
"1l_reshape",
"1l_sigmoid",
"1l_relu",
@@ -21,6 +27,17 @@ const TESTS: [&str; 12] = [
"2l_relu_sigmoid_conv",
];
const TESTS_EVM: [&str; 8] = [
"1l_mlp",
"1l_flatten",
"1l_average",
"1l_reshape",
"1l_sigmoid",
"1l_relu",
"2l_relu_sigmoid_small",
"2l_relu_small",
];
macro_rules! test_func {
() => {
#[cfg(test)]
@@ -35,9 +52,7 @@ macro_rules! test_func {
use crate::ipa_prove_and_verify;
use crate::kzg_fullprove;
use crate::kzg_prove_and_verify;
use crate::kzg_evm_fullprove;
seq!(N in 0..=11 {
seq!(N in 0..=10 {
#(#[test_case(TESTS[N])])*
fn mock_public_outputs_(test: &str) {
mock(test.to_string());
@@ -69,14 +84,25 @@ macro_rules! test_func {
}
#(#[test_case(TESTS[N])])*
// #[ignore]
fn kzg_prove_and_verify_(test: &str) {
kzg_prove_and_verify(test.to_string());
}
});
}
};
}
macro_rules! test_func_evm {
() => {
#[cfg(test)]
mod tests_evm {
use seq_macro::seq;
use crate::TESTS_EVM;
use test_case::test_case;
use crate::kzg_evm_fullprove;
seq!(N in 0..=7 {
// these take a particularly long time to run
#(#[test_case(TESTS[N])])*
#[ignore]
#(#[test_case(TESTS_EVM[N])])*
fn kzg_evm_fullprove_(test: &str) {
kzg_evm_fullprove(test.to_string());
}
@@ -86,10 +112,11 @@ macro_rules! test_func {
}
test_func!();
test_func_evm!();
// Mock prove (fast, but does not cover some potential issues)
fn mock(example_name: String) {
let status = Command::new("target/release/ezkl")
let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR))
.args([
"--bits=16",
"-K=17",
@@ -108,7 +135,7 @@ fn mock(example_name: String) {
// Mock prove (fast, but does not cover some potential issues)
fn mock_public_inputs(example_name: String) {
let status = Command::new("target/release/ezkl")
let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR))
.args([
"--public-inputs",
"--bits=16",
@@ -128,7 +155,7 @@ fn mock_public_inputs(example_name: String) {
// Mock prove (fast, but does not cover some potential issues)
fn mock_public_params(example_name: String) {
let status = Command::new("target/release/ezkl")
let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR))
.args([
"--public-params",
"--bits=16",
@@ -148,7 +175,7 @@ fn mock_public_params(example_name: String) {
// full prove (slower, covers more, but still reuses the pk)
fn ipa_fullprove(example_name: String) {
let status = Command::new("target/release/ezkl")
let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR))
.args([
"--bits=16",
"-K=17",
@@ -167,7 +194,7 @@ fn ipa_fullprove(example_name: String) {
// prove-serialize-verify, the usual full path
fn ipa_prove_and_verify(example_name: String) {
let status = Command::new("target/release/ezkl")
let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR))
.args([
"--bits=16",
"-K=17",
@@ -186,7 +213,7 @@ fn ipa_prove_and_verify(example_name: String) {
.status()
.expect("failed to execute process");
assert!(status.success());
let status = Command::new("target/release/ezkl")
let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR))
.args([
"--bits=16",
"-K=17",
@@ -207,7 +234,7 @@ fn ipa_prove_and_verify(example_name: String) {
// prove-serialize-verify, the usual full path
fn kzg_prove_and_verify(example_name: String) {
let status = Command::new("target/release/ezkl")
let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR))
.args([
"--bits=16",
"-K=17",
@@ -227,7 +254,7 @@ fn kzg_prove_and_verify(example_name: String) {
.status()
.expect("failed to execute process");
assert!(status.success());
let status = Command::new("target/release/ezkl")
let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR))
.args([
"--bits=16",
"-K=17",
@@ -250,7 +277,7 @@ fn kzg_prove_and_verify(example_name: String) {
// KZG tests
// full prove (slower, covers more, but still reuses the pk)
fn kzg_fullprove(example_name: String) {
let status = Command::new("target/release/ezkl")
let status = Command::new(format!("{}/release/ezkl", *CARGO_TARGET_DIR))
.args([
"--bits=16",
"-K=17",