From 52bca1b07f16a96e8a5d5bfccaf77e9423e27047 Mon Sep 17 00:00:00 2001 From: Lucas Clemente Vella Date: Mon, 14 Aug 2023 17:12:48 +0100 Subject: [PATCH 1/3] STARK prover --- backend/Cargo.toml | 3 +- backend/src/lib.rs | 15 +- backend/src/pilstark/estark.rs | 153 ++++++++++++ .../json_exporter/expression_counter.rs | 0 .../json_exporter/mod.rs | 230 ++++++++++-------- backend/src/{pilcom_cli => pilstark}/mod.rs | 12 +- compiler/src/lib.rs | 4 +- compiler/src/verify.rs | 2 +- compiler/tests/pil.rs | 2 +- powdr_cli/src/main.rs | 2 +- 10 files changed, 313 insertions(+), 110 deletions(-) create mode 100644 backend/src/pilstark/estark.rs rename backend/src/{pilcom_cli => pilstark}/json_exporter/expression_counter.rs (100%) rename backend/src/{pilcom_cli => pilstark}/json_exporter/mod.rs (72%) rename backend/src/{pilcom_cli => pilstark}/mod.rs (68%) diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 995475549c..413bf98180 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -13,8 +13,9 @@ number = { path = "../number" } strum = { version = "0.24.1", features = ["derive"] } ast = { version = "0.1.0", path = "../ast" } log = "0.4.17" -json = "^0.12" +serde_json = "1.0" thiserror = "1.0.43" +starky = { git = "https://github.com/0xEigenLabs/eigen-zkvm.git", branch = "main" } [dev-dependencies] mktemp = "0.5.0" diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 2982d1589f..65b861802b 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,6 +1,6 @@ #[cfg(feature = "halo2")] mod halo2_impl; -mod pilcom_cli; +mod pilstark; use ast::analyzed::Analyzed; use number::{DegreeType, FieldElement}; @@ -15,8 +15,10 @@ pub enum BackendType { #[cfg(feature = "halo2")] #[strum(serialize = "halo2-mock")] Halo2Mock, - #[strum(serialize = "pilcom-cli")] - PilcomCli, + #[strum(serialize = "estark")] + EStark, + #[strum(serialize = "pil-stark-cli")] + PilStarkCli, } impl BackendType { @@ -26,7 +28,9 @@ impl BackendType { #[cfg(feature = "halo2")] const HALO2_MOCK_FACTORY: WithoutSetupFactory = WithoutSetupFactory(PhantomData); - const PILCOM_CLI_FACTORY: WithoutSetupFactory = + const ESTARK_FACTORY: WithoutSetupFactory = + WithoutSetupFactory(PhantomData); + const PIL_STARK_CLI_FACTORY: WithoutSetupFactory = WithoutSetupFactory(PhantomData); match self { @@ -34,7 +38,8 @@ impl BackendType { BackendType::Halo2 => &HALO2_FACTORY, #[cfg(feature = "halo2")] BackendType::Halo2Mock => &HALO2_MOCK_FACTORY, - BackendType::PilcomCli => &PILCOM_CLI_FACTORY, + BackendType::PilStarkCli => &PIL_STARK_CLI_FACTORY, + BackendType::EStark => &ESTARK_FACTORY, } } } diff --git a/backend/src/pilstark/estark.rs b/backend/src/pilstark/estark.rs new file mode 100644 index 0000000000..6fa88785f5 --- /dev/null +++ b/backend/src/pilstark/estark.rs @@ -0,0 +1,153 @@ +use crate::{pilstark, BackendImpl}; +use ast::analyzed::Analyzed; +use number::{BigInt, DegreeType, FieldElement, GoldilocksField}; + +use starky::{ + merklehash::MerkleTreeGL, + polsarray::{PolKind, PolsArray}, + stark_gen::StarkProof, + stark_setup::StarkSetup, + stark_verify::stark_verify, + transcript::TranscriptGL, + types::{StarkStruct, Step, PIL}, +}; + +pub struct EStark { + params: StarkStruct, +} + +impl BackendImpl for EStark { + /// Creates our default configuration stark struct. + fn new(degree: DegreeType) -> Self { + if F::modulus().to_arbitrary_integer() != GoldilocksField::modulus().to_arbitrary_integer() + { + unimplemented!("eSTARK is only implemented for Goldilocks field"); + } + assert!(degree > 1); + + let n_bits = (DegreeType::BITS - (degree - 1).leading_zeros()) as usize; + let n_bits_ext = n_bits + 1; + + let steps = (2..=n_bits_ext) + .rev() + .step_by(4) + .map(|b| Step { nBits: b }) + .collect(); + + let params = StarkStruct { + nBits: n_bits, + nBitsExt: n_bits_ext, + nQueries: 2, + verificationHashType: "GL".to_owned(), + steps, + }; + + Self { params } + } + + fn prove( + &self, + pil: &Analyzed, + fixed: &[(&str, Vec)], + witness: &[(&str, Vec)], + prev_proof: Option, + ) -> (Option, Option) { + if prev_proof.is_some() { + unimplemented!("aggregration is not implemented"); + } + + log::info!("Creating eSTARK proof."); + + let mut pil: PIL = pilstark::json_exporter::export(pil); + + // TODO starky requires a fixed column with the equivalent + // semantics to Polygon zkEVM's `L1` column. + // It takes the name of that column via the API. + // Powdr generated PIL will always have `main.first_step`, + // but directly given PIL may not have it. + // This is a hack to inject such column if it doesn't exist. + // It should be eventually improved. + let mut fixed = fixed.to_vec(); + if !fixed.iter().any(|&(k, _)| k == "main.first_step") { + use starky::types::Reference; + pil.nConstants += 1; + pil.references.insert( + "main.first_step".to_string(), + Reference { + polType: None, + type_: "constP".to_string(), + id: fixed.len(), + polDeg: fixed[0].1.len(), + isArray: false, + elementType: None, + len: None, + }, + ); + fixed.push(( + "main.first_step", + [vec![F::one()], vec![F::zero(); fixed[0].1.len() - 1]].concat(), + )); + } + + let const_pols = to_starky_pols_array(&fixed, &pil, PolKind::Constant); + + if witness.is_empty() { + return (None, None); + } + + let cm_pols = to_starky_pols_array(witness, &pil, PolKind::Commit); + + let mut setup = StarkSetup::::new( + &const_pols, + &mut pil, + &self.params, + Some("main.first_step".to_string()), + ) + .unwrap(); + + let starkproof = StarkProof::::stark_gen::( + &cm_pols, + &const_pols, + &setup.const_tree, + &setup.starkinfo, + &setup.program, + &pil, + &self.params, + ) + .unwrap(); + + assert!(stark_verify::( + &starkproof, + &setup.const_root, + &setup.starkinfo, + &self.params, + &mut setup.program, + ) + .unwrap()); + + ( + Some(serde_json::to_vec(&starkproof).unwrap()), + Some(serde_json::to_string(&pil).unwrap()), + ) + } +} + +fn to_starky_pols_array( + array: &[(&str, Vec)], + pil: &PIL, + kind: PolKind, +) -> PolsArray { + let mut output = PolsArray::new(pil, kind); + assert_eq!(output.array.len(), array.len()); + for ((_, from), to) in array.iter().zip(output.array.iter_mut()) { + assert_eq!(from.len(), to.len()); + + for (f, t) in from.iter().zip(to.iter_mut()) { + *t = TryInto::::try_into(f.to_integer().to_arbitrary_integer()) + .unwrap() + .into(); + } + } + + output +} diff --git a/backend/src/pilcom_cli/json_exporter/expression_counter.rs b/backend/src/pilstark/json_exporter/expression_counter.rs similarity index 100% rename from backend/src/pilcom_cli/json_exporter/expression_counter.rs rename to backend/src/pilstark/json_exporter/expression_counter.rs diff --git a/backend/src/pilcom_cli/json_exporter/mod.rs b/backend/src/pilstark/json_exporter/mod.rs similarity index 72% rename from backend/src/pilcom_cli/json_exporter/mod.rs rename to backend/src/pilstark/json_exporter/mod.rs index 8647f67f53..eb6dc06e6b 100644 --- a/backend/src/pilcom_cli/json_exporter/mod.rs +++ b/backend/src/pilstark/json_exporter/mod.rs @@ -1,28 +1,42 @@ -use std::cmp; -use std::collections::{BTreeMap, HashMap}; - -use json::{object, JsonValue}; use number::FieldElement; +use std::cmp; +use std::collections::HashMap; use ast::analyzed::{ - Analyzed, BinaryOperator, Expression, FunctionValueDefinition, IdentityKind, PolyID, - PolynomialReference, PolynomialType, Reference, StatementIdentifier, UnaryOperator, + self, Analyzed, BinaryOperator, Expression, FunctionValueDefinition, IdentityKind, PolyID, + PolynomialReference, PolynomialType, StatementIdentifier, UnaryOperator, +}; +use starky::types::{ + ConnectionIdentity, Expression as StarkyExpr, PermutationIdentity, PlookupIdentity, + PolIdentity, Reference, PIL, }; use self::expression_counter::compute_intermediate_expression_ids; mod expression_counter; +const DEFAULT_EXPR: StarkyExpr = StarkyExpr { + op: String::new(), + deg: 0, + id: None, + next: None, + value: None, + values: None, + keep: None, + keep2ns: None, + idQ: None, + const_: None, +}; struct Exporter<'a, T> { analyzed: &'a Analyzed, - expressions: Vec, + expressions: Vec, /// Translates from polynomial IDs to expression IDs for intermediate /// polynomials. intermediate_poly_expression_ids: HashMap, number_q: u64, } -pub fn export(analyzed: &Analyzed) -> JsonValue { +pub fn export(analyzed: &Analyzed) -> PIL { let mut exporter = Exporter::new(analyzed); let mut publics = Vec::new(); let mut pol_identities = Vec::new(); @@ -48,14 +62,14 @@ pub fn export(analyzed: &Analyzed) -> JsonValue { } StatementIdentifier::PublicDeclaration(name) => { let pub_def = &analyzed.public_declarations[name]; - let (_, json, _) = exporter.polynomial_reference_to_json(&pub_def.polynomial); + let (_, expr) = exporter.polynomial_reference_to_json(&pub_def.polynomial); let id = publics.len(); - publics.push(object! { + publics.push(starky::types::Public { + polType: polynomial_reference_type_to_type(&expr.op).to_string(), + polId: expr.id.unwrap(), + idx: pub_def.index as usize, + id, name: name.clone(), - polId: json["id"].clone(), // This includes the array offset - polType: polynomial_reference_type_to_type(json["op"].as_str().unwrap()), - idx: pub_def.index, - id: id }); } StatementIdentifier::Identity(id) => { @@ -73,55 +87,57 @@ pub fn export(analyzed: &Analyzed) -> JsonValue { let right = exporter.extract_expression_vec(&identity.right.expressions, 1); let sel_right = exporter.extract_expression_opt(&identity.right.selector, 1); match identity.kind { - IdentityKind::Polynomial => pol_identities.push(object! { + IdentityKind::Polynomial => pol_identities.push(PolIdentity { e: sel_left.unwrap(), fileName: file_name, - line: line + line, }), IdentityKind::Plookup => { - plookup_identities.push(object! { + plookup_identities.push(PlookupIdentity { selF: sel_left, - f: left, + f: Some(left), selT: sel_right, - t: right, + t: Some(right), fileName: file_name, - line: line + line, }); } IdentityKind::Permutation => { - permutation_identities.push(object! { + permutation_identities.push(PermutationIdentity { selF: sel_left, - f: left, + f: Some(left), selT: sel_right, - t: right, + t: Some(right), fileName: file_name, - line: line + line, }); } IdentityKind::Connect => { - connection_identities.push(object! { - pols: left, - connections: right, + connection_identities.push(ConnectionIdentity { + pols: Some(left), + connections: Some(right), fileName: file_name, - line: line + line, }); } } } } } - object! { + PIL { nCommitments: analyzed.commitment_count(), - nQ: exporter.number_q, + nQ: exporter.number_q as usize, nIm: analyzed.intermediate_count(), nConstants: analyzed.constant_count(), - publics: publics, + publics, references: exporter.references(), expressions: exporter.expressions, polIdentities: pol_identities, plookupIdentities: plookup_identities, - permutationIdentities: permutation_identities, - connectionIdentities: connection_identities, + permutationIdentities: Some(permutation_identities), + connectionIdentities: Some(connection_identities), + cm_dims: Vec::new(), + q2exp: Vec::new(), } } @@ -156,7 +172,7 @@ impl<'a, T: FieldElement> Exporter<'a, T> { } } - fn references(&self) -> JsonValue { + fn references(&self) -> HashMap { self.analyzed .definitions .iter() @@ -166,35 +182,31 @@ impl<'a, T: FieldElement> Exporter<'a, T> { } else { poly.id }; - let mut out = object! { - type: polynomial_type_to_json_string(poly.poly_type), - id: id, - polDeg: poly.degree as i64, + let out = Reference { + polType: None, + type_: polynomial_type_to_json_string(poly.poly_type).to_string(), + id: id as usize, + polDeg: poly.degree as usize, isArray: poly.is_array(), + elementType: None, + len: poly.length.map(|l| l as usize), }; - if poly.is_array() { - out["len"] = JsonValue::from(poly.length.unwrap() as i64); - } (name.clone(), out) }) - .collect::>() - .into() + .collect::>() } /// Processes the given expression /// @returns the expression ID fn extract_expression(&mut self, expr: &Expression, max_degree: u32) -> usize { let id = self.expressions.len(); - let (degree, mut json, dependencies) = self.expression_to_json(expr); + let (degree, mut expr) = self.expression_to_json(expr); if degree > max_degree { - json["idQ"] = self.number_q.into(); - json["deg"] = 1.into(); + expr.idQ = Some(self.number_q as usize); + expr.deg = 1; self.number_q += 1; } - if !dependencies.is_empty() && json["op"] != "exp" { - json["deps"] = dependencies.into(); - } - self.expressions.push(json); + self.expressions.push(expr); id } @@ -213,45 +225,45 @@ impl<'a, T: FieldElement> Exporter<'a, T> { .collect() } - /// returns the degree, the JSON value and the dependencies (intermediate polynomial IDs) - fn expression_to_json(&self, expr: &Expression) -> (u32, JsonValue, Vec) { + /// returns the degree and the JSON value (intermediate polynomial IDs) + fn expression_to_json(&self, expr: &Expression) -> (u32, StarkyExpr) { match expr { Expression::Constant(name) => ( 0, - object! { - op: "number", + StarkyExpr { + op: "number".to_string(), deg: 0, - value: format!("{}", self.analyzed.constants[name]), + value: Some(format!("{}", self.analyzed.constants[name])), + ..DEFAULT_EXPR }, - Vec::new(), ), - Expression::Reference(Reference::Poly(reference)) => { + Expression::Reference(analyzed::Reference::Poly(reference)) => { self.polynomial_reference_to_json(reference) } - Expression::Reference(Reference::LocalVar(_)) => { + Expression::Reference(analyzed::Reference::LocalVar(_)) => { panic!("No local variable references allowed here.") } Expression::PublicReference(name) => ( 0, - object! { - op: "public", + StarkyExpr { + op: "public".to_string(), deg: 0, - id: self.analyzed.public_declarations[name].id, + id: Some(self.analyzed.public_declarations[name].id as usize), + ..DEFAULT_EXPR }, - Vec::new(), ), Expression::Number(value) => ( 0, - object! { - op: "number", + StarkyExpr { + op: "number".to_string(), deg: 0, - value: format!("{value}"), + value: Some(format!("{value}")), + ..DEFAULT_EXPR }, - Vec::new(), ), Expression::BinaryOperation(left, op, right) => { - let (deg_left, left, deps_left) = self.expression_to_json(left); - let (deg_right, right, deps_right) = self.expression_to_json(right); + let (deg_left, left) = self.expression_to_json(left); + let (deg_right, right) = self.expression_to_json(right); let (op, degree) = match op { BinaryOperator::Add => ("add", cmp::max(deg_left, deg_right)), BinaryOperator::Sub => ("sub", cmp::max(deg_left, deg_right)), @@ -284,26 +296,26 @@ impl<'a, T: FieldElement> Exporter<'a, T> { }; ( degree, - object! { - op: op, - deg: degree, - values: [left, right], + StarkyExpr { + op: op.to_string(), + deg: degree as usize, + values: Some(vec![left, right]), + ..DEFAULT_EXPR }, - [deps_left, deps_right].concat(), ) } Expression::UnaryOperation(op, value) => { - let (deg, value, deps) = self.expression_to_json(value); + let (deg, value) = self.expression_to_json(value); match op { - UnaryOperator::Plus => (deg, value, deps), + UnaryOperator::Plus => (deg, value), UnaryOperator::Minus => ( deg, - object! { - op: "neg", - deg: deg, - values: [value], + StarkyExpr { + op: "neg".to_string(), + deg: deg as usize, + values: Some(vec![value]), + ..DEFAULT_EXPR }, - deps, ), UnaryOperator::LogicalNot => panic!("Operator {op} not allowed here."), } @@ -332,7 +344,7 @@ impl<'a, T: FieldElement> Exporter<'a, T> { poly_id, next, }: &PolynomialReference, - ) -> (u32, JsonValue, Vec) { + ) -> (u32, StarkyExpr) { let PolyID { id, ptype } = poly_id.unwrap(); let id = if ptype == PolynomialType::Intermediate { assert!(index.is_none()); @@ -340,26 +352,22 @@ impl<'a, T: FieldElement> Exporter<'a, T> { } else { id + index.unwrap_or_default() }; - let poly_json = object! { - id: id, - op: polynomial_reference_type_to_json_string(ptype), + let poly = StarkyExpr { + id: Some(id as usize), + op: polynomial_reference_type_to_json_string(ptype).to_string(), deg: 1, - next: *next, - }; - let dependencies = if ptype == PolynomialType::Intermediate { - vec![id] - } else { - Vec::new() + next: Some(*next), + ..DEFAULT_EXPR }; - (1, poly_json, dependencies) + (1, poly) } } #[cfg(test)] mod test { use pil_analyzer::analyze; - use std::fs; - use std::process::Command; + use serde_json::Value as JsonValue; + use std::{fs, process::Command}; use test_log::test; use number::GoldilocksField; @@ -377,7 +385,7 @@ mod test { .join(file); let analyzed = analyze::(&file); - let json_out = export(&analyzed); + let pil_out = export(&analyzed); let pilcom = std::env::var("PILCOM").expect( "Please set the PILCOM environment variable to the path to the pilcom repository.", @@ -403,10 +411,42 @@ mod test { panic!("Pilcom did not generate {output_file:?} at the expected location.") }); drop(temp_dir); - let pilcom_parsed = json::parse(&pilcom_out).expect("Invalid json from pilcom."); + + let json_out = serde_json::to_value(pil_out).unwrap(); + + let mut pilcom_parsed = + serde_json::from_str(&pilcom_out).expect("Invalid json from pilcom."); + + // Filter out expression's "deps" before comparison, since we don't + // export them. + filter_out_deps(&mut pilcom_parsed); + (json_out, pilcom_parsed) } + fn filter_out_deps(value: &mut serde_json::Value) { + match value { + JsonValue::Array(arr) => { + for e in arr { + filter_out_deps(e); + } + } + JsonValue::Object(obj) => { + match obj.entry("deps") { + serde_json::map::Entry::Occupied(deps) => { + deps.remove(); + } + _ => (), + } + + for (_, e) in obj.iter_mut() { + filter_out_deps(e); + } + } + _ => (), + } + } + fn compare_export_file(file: &str) { let (json_out, pilcom_parsed) = generate_json_pair(file); assert_eq!(json_out, pilcom_parsed); diff --git a/backend/src/pilcom_cli/mod.rs b/backend/src/pilstark/mod.rs similarity index 68% rename from backend/src/pilcom_cli/mod.rs rename to backend/src/pilstark/mod.rs index 10f81cbd5a..75b1653c84 100644 --- a/backend/src/pilcom_cli/mod.rs +++ b/backend/src/pilstark/mod.rs @@ -1,12 +1,13 @@ +pub mod estark; mod json_exporter; use crate::{BackendImpl, Proof}; use ast::analyzed::Analyzed; use number::{DegreeType, FieldElement}; -pub struct PilcomCli; +pub struct PilStarkCli; -impl BackendImpl for PilcomCli { +impl BackendImpl for PilStarkCli { fn new(_degree: DegreeType) -> Self { Self } @@ -19,9 +20,12 @@ impl BackendImpl for PilcomCli { prev_proof: Option, ) -> (Option, Option) { if prev_proof.is_some() { - unimplemented!("Aggregration is not implemented for Pilcom CLI backend"); + unimplemented!("Aggregration is not implemented for pil-stark CLI backend"); } - (None, Some(json_exporter::export(pil).to_string())) + ( + None, + Some(serde_json::to_string(&json_exporter::export(pil)).unwrap()), + ) } } diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 95df3003f3..81131a5d6d 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -270,9 +270,9 @@ pub fn write_proving_results_to_fs( match proof { Some(proof) => { let fname = if is_aggregation { - "proof.bin" - } else { "proof_aggr.bin" + } else { + "proof.bin" }; // No need to bufferize the writing, because we write the whole diff --git a/compiler/src/verify.rs b/compiler/src/verify.rs index 67734618ef..b04a966594 100644 --- a/compiler/src/verify.rs +++ b/compiler/src/verify.rs @@ -12,7 +12,7 @@ pub fn verify_asm_string(file_name: &str, contents: &str, input inputs, &temp_dir, true, - Some(BackendType::PilcomCli), + Some(BackendType::PilStarkCli), ) .unwrap(); verify(&temp_dir); diff --git a/compiler/tests/pil.rs b/compiler/tests/pil.rs index 2263b9d021..145754ad64 100644 --- a/compiler/tests/pil.rs +++ b/compiler/tests/pil.rs @@ -16,7 +16,7 @@ pub fn verify_pil(file_name: &str, query_callback: Option Option Date: Tue, 19 Sep 2023 17:03:53 +0200 Subject: [PATCH 2/3] run estark proofs in tests --- compiler/tests/asm.rs | 45 ++++++++++++++++++++++++++++++++++++++ compiler/tests/pil.rs | 50 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/compiler/tests/asm.rs b/compiler/tests/asm.rs index 40101f892b..dfc38889be 100644 --- a/compiler/tests/asm.rs +++ b/compiler/tests/asm.rs @@ -14,6 +14,21 @@ fn verify_asm(file_name: &str, inputs: Vec) { verify_asm_string(&file_name, &contents, inputs) } +fn gen_estark_proof(file_name: &str, inputs: Vec) { + compiler::compile_pil_or_asm( + format!( + "{}/../test_data/asm/{file_name}", + env!("CARGO_MANIFEST_DIR") + ) + .as_str(), + inputs, + &mktemp::Temp::new_dir().unwrap(), + true, + Some(backend::BackendType::EStark), + ) + .unwrap(); +} + #[cfg(feature = "halo2")] fn gen_halo2_proof(file_name: &str, inputs: Vec) { compiler::compile_pil_or_asm( @@ -43,6 +58,7 @@ fn simple_sum_asm() { let i = [16, 4, 1, 2, 8, 5]; verify_asm::(f, slice_to_vec(&i)); gen_halo2_proof(f, slice_to_vec(&i)); + gen_estark_proof(f, slice_to_vec(&i)); } #[test] @@ -50,6 +66,7 @@ fn secondary_block_machine_add2() { let f = "secondary_block_machine_add2.asm"; verify_asm::(f, vec![]); gen_halo2_proof(f, vec![]); + gen_estark_proof(f, vec![]); } #[test] @@ -58,6 +75,10 @@ fn palindrome() { let i = [7, 1, 7, 3, 9, 3, 7, 1]; verify_asm::(f, slice_to_vec(&i)); gen_halo2_proof(f, slice_to_vec(&i)); + // currently starky leads to + // thread 'functional_instructions' has overflowed its stack + // leave it out until that's fixed + //gen_estark_proof(f, slice_to_vec(&i)); } #[test] @@ -66,6 +87,7 @@ fn single_function_vm() { let i = []; verify_asm::(f, slice_to_vec(&i)); gen_halo2_proof(f, slice_to_vec(&i)); + gen_estark_proof(f, slice_to_vec(&i)); } #[test] @@ -78,6 +100,7 @@ fn empty_vm() { let i = []; verify_asm::(f, slice_to_vec(&i)); gen_halo2_proof(f, slice_to_vec(&i)); + gen_estark_proof(f, slice_to_vec(&i)); } #[test] @@ -86,6 +109,10 @@ fn vm_to_block_unique_interface() { let i = []; verify_asm::(f, slice_to_vec(&i)); gen_halo2_proof(f, slice_to_vec(&i)); + // currently starky leads to + // thread 'functional_instructions' has overflowed its stack + // leave it out until that's fixed + //gen_estark_proof(f, slice_to_vec(&i)); } #[test] @@ -94,6 +121,7 @@ fn block_to_block() { let i = []; verify_asm::(f, slice_to_vec(&i)); gen_halo2_proof(f, slice_to_vec(&i)); + gen_estark_proof(f, slice_to_vec(&i)); } // Commented out until thibaut provides the "keccak.asm" @@ -121,6 +149,7 @@ fn vm_to_block_multiple_interfaces() { let i = []; verify_asm::(f, slice_to_vec(&i)); gen_halo2_proof(f, slice_to_vec(&i)); + gen_estark_proof(f, slice_to_vec(&i)); } #[test] @@ -130,6 +159,7 @@ fn vm_to_vm() { let i = []; verify_asm::(f, slice_to_vec(&i)); gen_halo2_proof(f, slice_to_vec(&i)); + gen_estark_proof(f, slice_to_vec(&i)); } #[test] @@ -137,6 +167,7 @@ fn test_mem_read_write() { let f = "mem_read_write.asm"; verify_asm::(f, Default::default()); gen_halo2_proof(f, Default::default()); + gen_estark_proof(f, Default::default()); } #[test] @@ -145,6 +176,7 @@ fn test_multi_assign() { let i = [7]; verify_asm::(f, slice_to_vec(&i)); gen_halo2_proof(f, slice_to_vec(&i)); + gen_estark_proof(f, slice_to_vec(&i)); } #[test] @@ -153,6 +185,10 @@ fn test_bit_access() { let i = [20]; verify_asm::(f, slice_to_vec(&i)); gen_halo2_proof(f, slice_to_vec(&i)); + // currently starky leads to + // thread 'functional_instructions' has overflowed its stack + // leave it out until that's fixed + //gen_estark_proof(f, slice_to_vec(&i)); } #[test] @@ -161,6 +197,10 @@ fn functional_instructions() { let i = [20]; verify_asm::(f, slice_to_vec(&i)); gen_halo2_proof(f, slice_to_vec(&i)); + // currently starky leads to + // thread 'functional_instructions' has overflowed its stack + // leave it out until that's fixed + //gen_estark_proof(f, slice_to_vec(&i)); } #[test] @@ -168,6 +208,7 @@ fn full_pil_constant() { let f = "full_pil_constant.asm"; verify_asm::(f, Default::default()); gen_halo2_proof(f, Default::default()); + gen_estark_proof(f, Default::default()); } #[test] @@ -175,6 +216,7 @@ fn intermediate() { let f = "intermediate.asm"; verify_asm::(f, Default::default()); gen_halo2_proof(f, Default::default()); + gen_estark_proof(f, Default::default()); } #[test] @@ -182,6 +224,7 @@ fn intermediate_nested() { let f = "intermediate_nested.asm"; verify_asm::(f, Default::default()); gen_halo2_proof(f, Default::default()); + gen_estark_proof(f, Default::default()); } #[test] @@ -198,6 +241,7 @@ fn book() { verify_asm::(f.to_str().unwrap(), slice_to_vec(&i)); gen_halo2_proof(f.to_str().unwrap(), slice_to_vec(&i)); + gen_estark_proof(f.to_str().unwrap(), slice_to_vec(&i)); } } } @@ -215,4 +259,5 @@ fn test_macros_in_instructions() { let f = "macros_in_instructions.asm"; verify_asm::(f, Default::default()); gen_halo2_proof(f, Default::default()); + gen_estark_proof(f, Default::default()); } diff --git a/compiler/tests/pil.rs b/compiler/tests/pil.rs index 145754ad64..934c422d4d 100644 --- a/compiler/tests/pil.rs +++ b/compiler/tests/pil.rs @@ -23,6 +23,21 @@ pub fn verify_pil(file_name: &str, query_callback: Option Option) { + compiler::compile_pil_or_asm( + format!( + "{}/../test_data/pil/{file_name}", + env!("CARGO_MANIFEST_DIR") + ) + .as_str(), + inputs, + &mktemp::Temp::new_dir().unwrap(), + true, + Some(BackendType::EStark), + ) + .unwrap(); +} + #[cfg(feature = "halo2")] fn gen_halo2_proof(file_name: &str, inputs: Vec) { compiler::compile_pil_or_asm( @@ -47,6 +62,7 @@ fn test_fibonacci() { let f = "fibonacci.pil"; verify_pil(f, None); gen_halo2_proof(f, Default::default()); + gen_estark_proof(f, Default::default()); } #[test] @@ -54,6 +70,7 @@ fn test_constant_in_identity() { let f = "constant_in_identity.pil"; verify_pil(f, None); gen_halo2_proof(f, Default::default()); + gen_estark_proof(f, Default::default()); } #[test] @@ -61,12 +78,14 @@ fn test_fibonacci_macro() { let f = "fib_macro.pil"; verify_pil(f, None); gen_halo2_proof(f, Default::default()); + gen_estark_proof(f, Default::default()); } #[test] fn test_global() { verify_pil("global.pil", None); // Halo2 would take too long for this. + // Starky requires at least one witness column, this test has none. } #[test] @@ -84,13 +103,14 @@ fn test_sum_via_witness_query() { }), ); // prover query string uses a different convention, - // so we cannot directly use the halo2_proof function here. + // so we cannot directly use the halo2_proof and estark functions here. } #[test] fn test_witness_lookup() { + let f = "witness_lookup.pil"; verify_pil( - "witness_lookup.pil", + f, Some(|q| match q { "\"input\", 0" => Some(3.into()), "\"input\", 1" => Some(5.into()), @@ -99,6 +119,7 @@ fn test_witness_lookup() { }), ); // halo2 fails with "gates must contain at least one constraint" + gen_estark_proof(f, vec![3.into(), 5.into(), 2.into()]); } #[test] @@ -109,14 +130,18 @@ fn test_underdetermined_zero_no_solution() { #[test] fn test_pair_lookup() { - verify_pil("pair_lookup.pil", None); + let f = "pair_lookup.pil"; + verify_pil(f, None); // halo2 would take too long for this + // starky would take too long for this in debug mode } #[test] fn test_block_lookup_or() { - verify_pil("block_lookup_or.pil", None); + let f = "block_lookup_or.pil"; + verify_pil(f, None); // halo2 would take too long for this + // starky would take too long for this in debug mode } #[test] @@ -124,26 +149,35 @@ fn test_halo_without_lookup() { let f = "halo_without_lookup.pil"; verify_pil(f, None); gen_halo2_proof(f, Default::default()); + gen_estark_proof(f, Default::default()); } #[test] fn test_simple_div() { - verify_pil("simple_div.pil", None); + let f = "simple_div.pil"; + verify_pil(f, None); + // starky would take too long for this in debug mode } #[test] fn test_single_line_blocks() { - verify_pil("single_line_blocks.pil", None); + let f = "single_line_blocks.pil"; + verify_pil(f, None); + gen_estark_proof(f, Default::default()); } #[test] fn test_two_block_machine_functions() { - verify_pil("two_block_machine_functions.pil", None); + let f = "two_block_machine_functions.pil"; + verify_pil(f, None); + gen_estark_proof(f, Default::default()); } #[test] fn test_fixed_columns() { - verify_pil("fixed_columns.pil", None); + let f = "fixed_columns.pil"; + verify_pil(f, None); + // Starky requires at least one witness column, this test has none. } #[test] From eed69492383f19987bf58d1a0ca5e43716a4129b Mon Sep 17 00:00:00 2001 From: Leo Alt Date: Tue, 26 Sep 2023 15:03:12 +0200 Subject: [PATCH 3/3] book --- book/src/backends/estark.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/book/src/backends/estark.md b/book/src/backends/estark.md index 3b19c8d4bc..54419bbda3 100644 --- a/book/src/backends/estark.md +++ b/book/src/backends/estark.md @@ -1,3 +1,4 @@ # eSTARK -Integrated support for [eSTARK](https://eprint.iacr.org/2023/474) with the Goldilocks field is under development. \ No newline at end of file +powdr supports the [eSTARK](https://eprint.iacr.org/2023/474) proof system with the Goldilocks field, +implemented by the [starky library from eigen-zkvm](https://github.com/0xEigenLabs/eigen-zkvm/). \ No newline at end of file