From 2a0a8f0f50c2f6ebc95009c30f9e44312d5ee1b7 Mon Sep 17 00:00:00 2001 From: nullbitx8 <92404251+nullbitx8@users.noreply.github.com> Date: Sat, 14 Oct 2023 02:58:51 -0400 Subject: [PATCH 01/13] Table formatting in the Fibonacci Tutorial (#151) --- tutorials/part3_chapter1.ipynb | 1 + tutorials/part3_chapter3.ipynb | 1 + 2 files changed, 2 insertions(+) diff --git a/tutorials/part3_chapter1.ipynb b/tutorials/part3_chapter1.ipynb index 80cd17e8..a9d6e74b 100644 --- a/tutorials/part3_chapter1.ipynb +++ b/tutorials/part3_chapter1.ipynb @@ -52,6 +52,7 @@ "| ... || ... ||| ... ||\n", "\n", "Note that these 4 step instances share the same signals and constraints, although not the same value assignments for signals. They are essentially different instantiations of the same signals and constraints, or different \"step instances\" of the same \"step type\". In Chiquito, we define signals and constraints in \"step types\" and we generate \"witness assignments\" for signal values. \"Step types\" with \"witness assignments\" become \"step instances\". In the example above, we have 4 step instances but only 1 step type, defined as the same 3 signals and 3 constraints. For simplicity, we call this single step type \"fibo step\", also added to the table below:\n", + "\n", "| Step Type | Step Instance Index || Signals ||| Setups ||\n", "| :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: |\n", "||| a | b | c | constraint 1 | constraint 2 | constraint 3 |\n", diff --git a/tutorials/part3_chapter3.ipynb b/tutorials/part3_chapter3.ipynb index c1a4e8e9..e202cb01 100644 --- a/tutorials/part3_chapter3.ipynb +++ b/tutorials/part3_chapter3.ipynb @@ -8,6 +8,7 @@ "# Part 3: Fibonacci Example\n", "# Chapter 3: Witness\n", "Now, we will generate multiple witnesses to test the soundness of our circuit constraints. Note that we only intend to accept the following set of values for signals \"a\", \"b\", and \"c\". \"Soundness\" in this context refers to faulty witness successfully verified against the constraints (false positives), so any set of witness assignments that is different from the table below but still passes the constraints incurs a \"soundness\" error.\n", + "\n", "| Step Type | Step Instance Index || Signals ||| Setups ||\n", "| :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: |\n", "||| a | b | c | constraint 1 | constraint 2 | constraint 3 |\n", From 9e1afe87e6bfcefdc31cb9ec9284a65141992d72 Mon Sep 17 00:00:00 2001 From: John Smith Date: Sat, 14 Oct 2023 15:00:31 +0800 Subject: [PATCH 02/13] feat: add pretty debug information for common items (#111) Resolve #85 - [x] TraceWitness - [x] Assignments --------- Co-authored-by: Leo Lara --- src/plonkish/backend/halo2.rs | 4 +- src/plonkish/backend/plaf.rs | 4 +- src/plonkish/ir/assignments.rs | 69 +++++++++++++++++++++++++++++++++- src/wit_gen.rs | 56 ++++++++++++++++++++++++++- 4 files changed, 126 insertions(+), 7 deletions(-) diff --git a/src/plonkish/backend/halo2.rs b/src/plonkish/backend/halo2.rs index b51efb68..55f0a289 100644 --- a/src/plonkish/backend/halo2.rs +++ b/src/plonkish/backend/halo2.rs @@ -188,7 +188,7 @@ impl + Hash> ChiquitoHalo2 { } fn assign_advice(&self, region: &mut Region, witness: &Assignments) -> Result<(), Error> { - for (column, assignments) in witness { + for (column, assignments) in witness.iter() { let column = self.convert_advice_column(column); for (offset, value) in assignments.iter().enumerate() { @@ -200,7 +200,7 @@ impl + Hash> ChiquitoHalo2 { } fn assign_fixed(&self, region: &mut Region, fixed: &Assignments) -> Result<(), Error> { - for (column, values) in fixed { + for (column, values) in fixed.iter() { let column = self.convert_fixed_column(column); for (offset, value) in values.iter().enumerate() { diff --git a/src/plonkish/backend/plaf.rs b/src/plonkish/backend/plaf.rs index c4948c2c..aaca3823 100644 --- a/src/plonkish/backend/plaf.rs +++ b/src/plonkish/backend/plaf.rs @@ -116,7 +116,7 @@ impl> ChiquitoPlaf { fixed.push(vec![None; plaf.info.num_rows]); } - for (column, values) in self.circuit.fixed_assignments.clone().into_iter() { + for (column, values) in self.circuit.fixed_assignments.clone().0.into_iter() { let column = self .c_column_id_to_p_column_index .get(&column.uuid()) @@ -306,7 +306,7 @@ impl ChiquitoPlafWitGen { }; if let Some(witness) = &witness { - for (column, assignments) in witness { + for (column, assignments) in witness.iter() { let p_column_index = self .c_column_id_to_p_column_index .get(&column.uuid()) diff --git a/src/plonkish/ir/assignments.rs b/src/plonkish/ir/assignments.rs index 38dbc669..1c7a387b 100644 --- a/src/plonkish/ir/assignments.rs +++ b/src/plonkish/ir/assignments.rs @@ -1,4 +1,8 @@ -use std::collections::HashMap; +use std::{ + collections::HashMap, + fmt, + ops::{Deref, DerefMut}, +}; use crate::field::Field; @@ -13,7 +17,51 @@ use crate::{ use super::{Column, PolyExpr}; -pub type Assignments = HashMap>; +#[derive(Debug, Clone)] +pub struct Assignments(pub HashMap>); + +impl fmt::Display for Assignments { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // get the decimal width based on the step_instances size, add extra one leading zero + let decimal_width = self.0.len().checked_ilog10().unwrap_or(0) + 2; + // offset(col_uuid): value0, value1, value2,... + for (i, (col, vals)) in self.0.iter().enumerate() { + let vals = vals.iter().fold(String::new(), |mut acc, val| { + acc.push_str(&format!("{:?}, ", val)); + acc + }); + writeln!( + f, + "{:0>width$}({}): {}", + i, + col.id, + vals, + width = decimal_width as usize, + )?; + } + Ok(()) + } +} + +impl Default for Assignments { + fn default() -> Self { + Self(HashMap::default()) + } +} + +impl Deref for Assignments { + type Target = HashMap>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Assignments { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} pub struct AssignmentGenerator { columns: Vec, @@ -227,3 +275,20 @@ impl AssignmentGenerator { None } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pretty_print_assignments() { + let display = format!( + "{}", + Assignments::(HashMap::from([ + (Column::advice("a", 1), vec![1, 2, 3]), + (Column::fixed("a"), vec![4, 5, 6]), + ])), + ); + println!("{}", display); + } +} diff --git a/src/wit_gen.rs b/src/wit_gen.rs index 9589d850..0666817f 100644 --- a/src/wit_gen.rs +++ b/src/wit_gen.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, hash::Hash, rc::Rc}; +use std::{collections::HashMap, fmt, hash::Hash, rc::Rc}; use crate::{ ast::{query::Queriable, StepTypeUUID}, @@ -14,6 +14,16 @@ pub struct StepInstance { pub assignments: HashMap, F>, } +impl fmt::Display for StepInstance { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "({}): ", self.step_type_uuid)?; + for (queriable, value) in self.assignments.iter() { + write!(f, "{:?} = {:?}, ", queriable, value)?; + } + Ok(()) + } +} + impl StepInstance { pub fn new(step_type_uuid: StepTypeUUID) -> StepInstance { StepInstance { @@ -38,6 +48,24 @@ pub struct TraceWitness { pub step_instances: Witness, } +impl fmt::Display for TraceWitness { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // get the decimal width based on the step_instances size, add extra one leading zero + let decimal_width = self.step_instances.len().checked_ilog10().unwrap_or(0) + 2; + // offset(step_uuid): assignations + for (i, step_instance) in self.step_instances.iter().enumerate() { + writeln!( + f, + "{:0>width$}{}", + i, + step_instance, + width = decimal_width as usize, + )?; + } + Ok(()) + } +} + #[derive(Debug)] pub struct TraceContext { witness: TraceWitness, @@ -200,6 +228,32 @@ mod tests { assert_eq!(ctx.witness.step_instances.len(), 5); } + #[test] + fn test_trace_witness_display() { + let display = format!( + "{}", + TraceWitness:: { + step_instances: vec![ + StepInstance { + step_type_uuid: 9, + assignments: HashMap::from([ + (Queriable::Fixed(FixedSignal::new("a".into()), 0), 1), + (Queriable::Fixed(FixedSignal::new("b".into()), 0), 2) + ]), + }, + StepInstance { + step_type_uuid: 10, + assignments: HashMap::from([ + (Queriable::Fixed(FixedSignal::new("a".into()), 0), 1), + (Queriable::Fixed(FixedSignal::new("b".into()), 0), 2) + ]), + } + ] + } + ); + println!("{}", display); + } + #[test] fn test_fixed_gen_context() { let mut ctx = FixedGenContext::new(3); From 8e0b667f68d5a30432c1074dbc2378963b082d96 Mon Sep 17 00:00:00 2001 From: Jaewon In Date: Sat, 14 Oct 2023 17:01:34 +0900 Subject: [PATCH 03/13] Use plaf automatic instance (#144) Resolves #129. @leolara @qwang98 Ready for review. Automatic instance is now enabled in plaf codebase,(https://github.com/Dhole/polyexen/pull/3) and this PR would apply that change. --- Cargo.toml | 2 +- examples/fibo_with_padding.rs | 3 +-- examples/fibonacci.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index de46569e..33ff5091 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.gi halo2curves = { git = 'https://github.com/privacy-scaling-explorations/halo2curves', tag = "0.3.2", features = [ "derive_serde", ] } -polyexen = { git = "https://github.com/Dhole/polyexen.git", rev = "67148a8aabb000a61a169b2533b2526c5b76c42f" } +polyexen = { git = "https://github.com/Dhole/polyexen.git", rev = "4d128ad2ebd0094160ea77e30fb9ce56abb854e0" } num-bigint = { version = "0.4", features = ["rand"] } uuid = { version = "1.4.0", features = ["v1", "rng"] } serde = { version = "1.0", features = ["derive"] } diff --git a/examples/fibo_with_padding.rs b/examples/fibo_with_padding.rs index 7a6240b2..f3e93ea8 100644 --- a/examples/fibo_with_padding.rs +++ b/examples/fibo_with_padding.rs @@ -234,9 +234,8 @@ fn main() { // this is unnecessary because Chiquito has a halo2 backend already let plaf_circuit = PlafH2Circuit { plaf, wit }; - let plaf_instance = vec![vec![34.field(), 7.field()]]; // same as halo2 boilerplate above - let prover_plaf = MockProver::::run(8, &plaf_circuit, plaf_instance).unwrap(); + let prover_plaf = MockProver::::run(8, &plaf_circuit, plaf_circuit.instance()).unwrap(); let result_plaf = prover_plaf.verify_par(); diff --git a/examples/fibonacci.rs b/examples/fibonacci.rs index d36e3fc1..37bd38d2 100644 --- a/examples/fibonacci.rs +++ b/examples/fibonacci.rs @@ -148,7 +148,7 @@ fn main() { let plaf_circuit = PlafH2Circuit { plaf, wit }; // same as halo2 boilerplate above - let prover_plaf = MockProver::::run(8, &plaf_circuit, Vec::new()).unwrap(); + let prover_plaf = MockProver::::run(8, &plaf_circuit, plaf_circuit.instance()).unwrap(); let result_plaf = prover_plaf.verify_par(); From c527f5edd2f1c3964186e3b48f95167cb11f9ae2 Mon Sep 17 00:00:00 2001 From: Steve Wang Date: Mon, 16 Oct 2023 14:54:45 +0700 Subject: [PATCH 04/13] Fixed LHS auto typing conversion for Expr arithmetic (#146) Now Expr arithmetic doesn't require LHS to be Expr but can be ToExpr as well. Thanks to Sandro for pointing out this error. --- Cargo.toml | 2 +- src/frontend/python/chiquito/expr.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 33ff5091..c15cf949 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chiquito" -version = "0.1.2023092400" +version = "0.1.2023101100" edition = "2021" license = "MIT OR Apache-2.0" authors = ["Leo Lara "] diff --git a/src/frontend/python/chiquito/expr.py b/src/frontend/python/chiquito/expr.py index 52f99364..22e11396 100644 --- a/src/frontend/python/chiquito/expr.py +++ b/src/frontend/python/chiquito/expr.py @@ -26,6 +26,7 @@ def __add__(self: Expr, rhs: ToExpr) -> Sum: return Sum([self, rhs]) def __radd__(self: Expr, lhs: ToExpr) -> Sum: + lhs = to_expr(lhs) return Expr.__add__(lhs, self) def __sub__(self: Expr, rhs: ToExpr) -> Sum: @@ -33,6 +34,7 @@ def __sub__(self: Expr, rhs: ToExpr) -> Sum: return Sum([self, Neg(rhs)]) def __rsub__(self: Expr, lhs: ToExpr) -> Sum: + lhs = to_expr(lhs) return Expr.__sub__(lhs, self) def __mul__(self: Expr, rhs: ToExpr) -> Mul: @@ -40,6 +42,7 @@ def __mul__(self: Expr, rhs: ToExpr) -> Mul: return Mul([self, rhs]) def __rmul__(self: Expr, lhs: ToExpr) -> Mul: + lhs = to_expr(lhs) return Expr.__mul__(lhs, self) def __pow__(self: Expr, rhs: int) -> Pow: From 7d7c7e61187a3af3623f4c4b5a6f65b1746702fa Mon Sep 17 00:00:00 2001 From: sraver Date: Tue, 17 Oct 2023 19:48:00 +0200 Subject: [PATCH 05/13] Add factorial circuit (#149) Adds an example of a circuit implementation that computes the factorial any given number, to a certain maximum. Using Python frontend to verify the approach, will continue to port the example to Rust. --- examples/factorial.py | 121 ++++++++++++++++++++++++++++ examples/factorial.rs | 180 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 301 insertions(+) create mode 100644 examples/factorial.py create mode 100644 examples/factorial.rs diff --git a/examples/factorial.py b/examples/factorial.py new file mode 100644 index 00000000..7a5bc510 --- /dev/null +++ b/examples/factorial.py @@ -0,0 +1,121 @@ +from chiquito.dsl import Circuit, StepType +from chiquito.cb import eq +from chiquito.util import F +from chiquito.chiquito_ast import Last + +MAX_FACTORIAL = 10 + +""" +| step_type | i | x | +---------------------------------- +| first_step | 0 | 1 | +| operation_step | 1 | 1 | +| operation_step | 2 | 2 | +| operation_step | 3 | 6 | +| result_step | 4 | 24 | +| result_step | 4 | 24 | +| result_step | 4 | 24 | + ... +""" + + +class FirstStep(StepType): + def setup(self): + # constrain `i` to zero + self.constr(eq(self.circuit.i, 0)) + # constrain `x` to one + self.constr(eq(self.circuit.x, 1)) + # constrain the next `x` to be equal to the current `x` + self.transition(eq(self.circuit.x, self.circuit.x.next())) + + def wg(self): + self.assign(self.circuit.i, F(0)) + self.assign(self.circuit.x, F(1)) + + +class OperationStep(StepType): + def setup(self): + # constrain i.prev() + 1 == i + self.transition(eq(self.circuit.i.rot(-1) + 1, self.circuit.i)) + # constrain i + 1 == i.next() + self.transition(eq(self.circuit.i + 1, self.circuit.i.next())) + # constrain the next `x` to be the product of the current `x` and the next `i` + self.transition(eq(self.circuit.x * (self.circuit.i + 1), self.circuit.x.next())) + + def wg(self, i_value, x_value): + self.assign(self.circuit.i, F(i_value)) + self.assign(self.circuit.x, F(x_value)) + + +class ResultStep(StepType): + def setup(self): + # constrain `x` to not change + self.transition(eq(self.circuit.x, self.circuit.x.next())) + # constrain `i` to not change + self.transition(eq(self.circuit.i, self.circuit.i.next())) + + def wg(self, i_value, x_value): + self.assign(self.circuit.i, F(i_value)) + self.assign(self.circuit.x, F(x_value)) + + +class Factorial(Circuit): + def setup(self): + # `i` holds the current iteration number + self.i = self.shared("i") + # `x` holds the current total result + self.x = self.forward("x") + + self.first_step = self.step_type(FirstStep(self, "first_step")) + self.operation_step = self.step_type(OperationStep(self, "operation_step")) + self.result_step = self.step_type(ResultStep(self, "result_step")) + + self.pragma_num_steps(MAX_FACTORIAL + 1) + self.pragma_first_step(self.first_step) + self.pragma_last_step(self.result_step) + + self.expose(self.x, Last()) + self.expose(self.i, Last()) + + def trace(self, n): + self.add(self.first_step) + current_result = 1 + + for i in range(1, n + 1): + current_result *= i + if i == n: + # we found the result + self.add(self.result_step, i, current_result) + else: + # more operations need to be done + self.add(self.operation_step, i, current_result) + + while self.needs_padding(): + # if padding is needed, we propagate final values + self.add(self.result_step, n, current_result) + + +class Examples: + def test_zero(self): + factorial = Factorial() + factorial_witness = factorial.gen_witness(0) + last_assignments = list(factorial_witness.step_instances[10].assignments.values()) + assert last_assignments[0] == 0 # i + assert last_assignments[1] == 1 # x + factorial.halo2_mock_prover(factorial_witness) + + def test_basic(self): + factorial = Factorial() + factorial_witness = factorial.gen_witness(7) + last_assignments = list(factorial_witness.step_instances[10].assignments.values()) + assert last_assignments[0] == 7 # i + assert last_assignments[1] == 5040 # x + factorial.halo2_mock_prover(factorial_witness) + + +if __name__ == "__main__": + x = Examples() + for method in [ + method for method in dir(x) if callable(getattr(x, method)) if not method.startswith('_') + ]: + getattr(x, method)() diff --git a/examples/factorial.rs b/examples/factorial.rs new file mode 100644 index 00000000..5da8391b --- /dev/null +++ b/examples/factorial.rs @@ -0,0 +1,180 @@ +use std::hash::Hash; + +use chiquito::{ + field::Field, + frontend::dsl::circuit, // main function for constructing an AST circuit + plonkish::backend::halo2::{chiquito2Halo2, ChiquitoHalo2Circuit}, /* compiles to + * Chiquito Halo2 + * backend, + * which can be + * integrated into + * Halo2 + * circuit */ + plonkish::compiler::{ + cell_manager::SingleRowCellManager, // input for constructing the compiler + compile, // input for constructing the compiler + config, + step_selector::SimpleStepSelectorBuilder, + }, + plonkish::ir::{assignments::AssignmentGenerator, Circuit}, // compiled circuit type + poly::ToField, +}; +use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; + +const MAX_FACTORIAL: usize = 10; + +fn generate + Hash>() -> (Circuit, Option>) { + // + // table for the circuit: + // | step_type | i | x | + // ---------------------------------- + // | first_step | 0 | 1 | + // | operation_step | 1 | 1 | + // | operation_step | 2 | 2 | + // | operation_step | 3 | 6 | + // | result_step | 4 | 24 | + // | result_step | 4 | 24 | + // | result_step | 4 | 24 | + // ... + + use chiquito::frontend::dsl::cb::*; // functions for constraint building + + let factorial_circuit = circuit::("factorial", |ctx| { + let i = ctx.shared("i"); + let x = ctx.forward("x"); + + // first step will make sure the circuit is initialized correctly + let first_step = ctx.step_type_def("first_step", |ctx| { + // define the setup + ctx.setup(move |ctx| { + // constrain `i` to zero + ctx.constr(eq(i, 0)); + // constrain `x` to one + ctx.constr(eq(x, 1)); + // constrain the next `x` to be equal to the current `x` + ctx.transition(eq(x, x.next())); + }); + // witness assignment + ctx.wg(move |ctx, ()| { + ctx.assign(i, 0.into()); + ctx.assign(x, 1.into()); + }) + }); + + // operation step will make sure every state transition is correct + let operation_step = ctx.step_type_def("operation_step", |ctx| { + // define the setup + ctx.setup(move |ctx| { + // constrain i.prev() + 1 == i + ctx.transition(eq(i.rot(-1) + 1, i)); + // constrain i + 1 == i.next() + ctx.transition(eq(i + 1, i.next())); + // constrain the next `x` to be the product of the current `x` and the next `i` + ctx.transition(eq(x * i.next(), x.next())); + }); + // witness assignment + ctx.wg(move |ctx, (i_value, x_value): (u32, u32)| { + ctx.assign(i, i_value.field()); + ctx.assign(x, x_value.field()); + }) + }); + + // result step will hold and propagate the value + let result_step = ctx.step_type_def("result_step", |ctx| { + // define the setup + ctx.setup(move |ctx| { + // constrain `i` to not change + ctx.transition(eq(i, i.next())); + // constrain `x` to not change + ctx.transition(eq(x, x.next())); + }); + // witness assignment + ctx.wg(move |ctx, (i_value, x_value): (u32, u32)| { + ctx.assign(i, i_value.field()); + ctx.assign(x, x_value.field()); + }) + }); + + ctx.pragma_first_step(&first_step); + ctx.pragma_last_step(&result_step); + ctx.pragma_num_steps(MAX_FACTORIAL + 1); + + ctx.trace(move |ctx, n| { + ctx.add(&first_step, ()); + + let mut current_result = 1; + + for i in 1..n + 1 { + current_result *= i; + if i == n { + // we found the result + ctx.add(&result_step, (i, current_result)); + } else { + // more operations need to be done + ctx.add(&operation_step, (i, current_result)); + } + } + + // if padding is needed, propagate final values + ctx.padding(&result_step, || (n, current_result)); + }) + }); + + compile( + config(SingleRowCellManager {}, SimpleStepSelectorBuilder {}), + &factorial_circuit, + ) +} + +// After compiling Chiquito AST to an IR, it is further parsed by a Chiquito Halo2 backend and +// integrated into a Halo2 circuit, which is done by the boilerplate code below. + +// standard main function for a Halo2 circuit +fn main() { + let (chiquito, wit_gen) = generate::(); + let compiled = chiquito2Halo2(chiquito); + let circuit = ChiquitoHalo2Circuit::new(compiled, wit_gen.map(|g| g.generate(0))); + + let prover = MockProver::::run(10, &circuit, circuit.instance()).unwrap(); + + let result = prover.verify_par(); + + println!("result = {:#?}", result); + + if let Err(failures) = &result { + for failure in failures.iter() { + println!("{}", failure); + } + } + + // plaf boilerplate + use chiquito::plonkish::backend::plaf::chiquito2Plaf; + use polyexen::plaf::{backends::halo2::PlafH2Circuit}; + + // get Chiquito ir + let (circuit, wit_gen) = generate::(); + // get Plaf + let (plaf, plaf_wit_gen) = chiquito2Plaf(circuit, 8, false); + let wit = plaf_wit_gen.generate(wit_gen.map(|v| v.generate(7))); + + // debug only: print witness + // println!("{}", polyexen::plaf::WitnessDisplayCSV(&wit)); + + // get Plaf halo2 circuit from Plaf's halo2 backend + // this is just a proof of concept, because Plaf only has backend for halo2 + // this is unnecessary because Chiquito has a halo2 backend already + let plaf_circuit = PlafH2Circuit { plaf, wit }; + + // same as halo2 boilerplate above + let prover_plaf = MockProver::::run(8, &plaf_circuit, Vec::new()).unwrap(); + + let result_plaf = prover_plaf.verify_par(); + + println!("result = {:#?}", result_plaf); + + if let Err(failures) = &result_plaf { + for failure in failures.iter() { + println!("{}", failure); + } + } +} From a0dfad673efe45e8a178b772781a30605b01f9d8 Mon Sep 17 00:00:00 2001 From: Steve Wang Date: Wed, 18 Oct 2023 12:08:34 +0700 Subject: [PATCH 06/13] User defined parameter k for halo2 mock prover (#155) Ready for review. @leolara --- Cargo.toml | 2 +- examples/fibonacci.py | 6 ++++-- examples/mimc7.py | 2 +- src/frontend/python/chiquito/dsl.py | 10 ++++++---- src/frontend/python/mod.rs | 18 ++++++++++++------ 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c15cf949..44a9e6ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chiquito" -version = "0.1.2023101100" +version = "0.1.2023101700" edition = "2021" license = "MIT OR Apache-2.0" authors = ["Leo Lara "] diff --git a/examples/fibonacci.py b/examples/fibonacci.py index 8771191e..4ffa7b31 100644 --- a/examples/fibonacci.py +++ b/examples/fibonacci.py @@ -79,6 +79,8 @@ def trace(self, n): fibo = Fibonacci() fibo_witness = fibo.gen_witness(7) -fibo.halo2_mock_prover(fibo_witness) +fibo.halo2_mock_prover( + fibo_witness, k=7 +) # 2^k specifies the number of PLONKish table rows in Halo2 another_fibo_witness = fibo.gen_witness(4) -fibo.halo2_mock_prover(another_fibo_witness) +fibo.halo2_mock_prover(another_fibo_witness, k=7) diff --git a/examples/mimc7.py b/examples/mimc7.py index a23e78c1..ce3a1ca5 100644 --- a/examples/mimc7.py +++ b/examples/mimc7.py @@ -167,4 +167,4 @@ def mapping(self, x_in_value, k_value): mimc7_super_witness = mimc7.gen_witness(F(1), F(2)) # for key, value in mimc7_super_witness.items(): # print(f"{key}: {str(value)}") -mimc7.halo2_mock_prover(mimc7_super_witness) +mimc7.halo2_mock_prover(mimc7_super_witness, k = 10) diff --git a/src/frontend/python/chiquito/dsl.py b/src/frontend/python/chiquito/dsl.py index 52abfc62..60b45686 100644 --- a/src/frontend/python/chiquito/dsl.py +++ b/src/frontend/python/chiquito/dsl.py @@ -70,7 +70,9 @@ def gen_witness(self: SuperCircuit, *args: Any) -> Dict[int, TraceWitness]: ) # so that we can generate different witness mapping in the next gen_witness() call return super_witness - def halo2_mock_prover(self: SuperCircuit, super_witness: Dict[int, TraceWitness]): + def halo2_mock_prover( + self: SuperCircuit, super_witness: Dict[int, TraceWitness], k: int = 16 + ): for rust_id, witness in super_witness.items(): if rust_id not in self.ast.sub_circuits: raise ValueError( @@ -79,7 +81,7 @@ def halo2_mock_prover(self: SuperCircuit, super_witness: Dict[int, TraceWitness] witness_json: str = witness.get_witness_json() super_witness[rust_id] = witness_json rust_chiquito.super_circuit_halo2_mock_prover( - list(self.ast.sub_circuits.keys()), super_witness + list(self.ast.sub_circuits.keys()), super_witness, k ) @@ -211,12 +213,12 @@ def gen_witness(self: Circuit, *args) -> TraceWitness: def get_ast_json(self: Circuit) -> str: return json.dumps(self.ast, cls=CustomEncoder, indent=4) - def halo2_mock_prover(self: Circuit, witness: TraceWitness): + def halo2_mock_prover(self: Circuit, witness: TraceWitness, k: int = 16): if self.rust_id == 0: ast_json: str = self.get_ast_json() self.rust_id: int = rust_chiquito.ast_to_halo2(ast_json) witness_json: str = witness.get_witness_json() - rust_chiquito.halo2_mock_prover(witness_json, self.rust_id) + rust_chiquito.halo2_mock_prover(witness_json, self.rust_id, k) def __str__(self: Circuit) -> str: return self.ast.__str__() diff --git a/src/frontend/python/mod.rs b/src/frontend/python/mod.rs index 489d7cc9..dcc80985 100644 --- a/src/frontend/python/mod.rs +++ b/src/frontend/python/mod.rs @@ -83,6 +83,7 @@ fn add_assignment_generator_to_rust_id( pub fn chiquito_super_circuit_halo2_mock_prover( rust_ids: Vec, super_witness: HashMap, + k: usize, ) { let mut super_circuit_ctx = SuperCircuitContext::::default(); @@ -114,7 +115,7 @@ pub fn chiquito_super_circuit_halo2_mock_prover( let circuit = ChiquitoHalo2SuperCircuit::new(compiled, super_assignments); - let prover = MockProver::::run(10, &circuit, circuit.instance()).unwrap(); + let prover = MockProver::::run(k as u32, &circuit, circuit.instance()).unwrap(); let result = prover.verify_par(); @@ -138,7 +139,7 @@ fn rust_id_to_halo2(uuid: UUID) -> CircuitMapStore { /// Runs `MockProver` for a single circuit given JSON of `TraceWitness` and `rust_id` of the /// circuit. -pub fn chiquito_halo2_mock_prover(witness_json: &str, rust_id: UUID) { +pub fn chiquito_halo2_mock_prover(witness_json: &str, rust_id: UUID, k: usize) { let trace_witness: TraceWitness = serde_json::from_str(witness_json).expect("Json deserialization to TraceWitness failed."); let (_, compiled, assignment_generator) = rust_id_to_halo2(rust_id); @@ -147,7 +148,7 @@ pub fn chiquito_halo2_mock_prover(witness_json: &str, rust_id: UUID) { assignment_generator.map(|g| g.generate_with_witness(trace_witness)), ); - let prover = MockProver::::run(7, &circuit, circuit.instance()).unwrap(); + let prover = MockProver::::run(k as u32, &circuit, circuit.instance()).unwrap(); let result = prover.verify_par(); @@ -1827,15 +1828,16 @@ fn ast_to_halo2(json: &PyString) -> u128 { } #[pyfunction] -fn halo2_mock_prover(witness_json: &PyString, rust_id: &PyLong) { +fn halo2_mock_prover(witness_json: &PyString, rust_id: &PyLong, k: &PyLong) { chiquito_halo2_mock_prover( witness_json.to_str().expect("PyString convertion failed."), rust_id.extract().expect("PyLong convertion failed."), + k.extract().expect("PyLong convertion failed."), ); } #[pyfunction] -fn super_circuit_halo2_mock_prover(rust_ids: &PyList, super_witness: &PyDict) { +fn super_circuit_halo2_mock_prover(rust_ids: &PyList, super_witness: &PyDict, k: &PyLong) { let uuids = rust_ids .iter() .map(|rust_id| { @@ -1864,7 +1866,11 @@ fn super_circuit_halo2_mock_prover(rust_ids: &PyList, super_witness: &PyDict) { }) .collect::>(); - chiquito_super_circuit_halo2_mock_prover(uuids, super_witness) + chiquito_super_circuit_halo2_mock_prover( + uuids, + super_witness, + k.extract().expect("PyLong convertion failed."), + ) } #[pymodule] From 44a54a157de22d94dfeabbd825e602e5262167e5 Mon Sep 17 00:00:00 2001 From: nullbitx8 <92404251+nullbitx8@users.noreply.github.com> Date: Sat, 21 Oct 2023 06:39:09 -0400 Subject: [PATCH 07/13] Fix cargo fmt suggestions (#158) --- examples/factorial.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/factorial.rs b/examples/factorial.rs index 5da8391b..c927ab49 100644 --- a/examples/factorial.rs +++ b/examples/factorial.rs @@ -24,7 +24,6 @@ use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; const MAX_FACTORIAL: usize = 10; fn generate + Hash>() -> (Circuit, Option>) { - // // table for the circuit: // | step_type | i | x | // ---------------------------------- @@ -149,7 +148,7 @@ fn main() { // plaf boilerplate use chiquito::plonkish::backend::plaf::chiquito2Plaf; - use polyexen::plaf::{backends::halo2::PlafH2Circuit}; + use polyexen::plaf::backends::halo2::PlafH2Circuit; // get Chiquito ir let (circuit, wit_gen) = generate::(); From d0933d7e4f1ebad59ef76d69b04dafc201d6d976 Mon Sep 17 00:00:00 2001 From: Steve Wang Date: Thu, 26 Oct 2023 07:09:51 -0400 Subject: [PATCH 08/13] Jupyter Book (#161) Ready for review. @leolara To run this: ``` pip install jupyter-book jupyter-book build book # book is the sub-directory where all the book stuffs live in ``` A `_build` folder with `html` artifacts should be created. Terminal should print out directions on how to open the build files in the browser. For example, pasting a local directory like `file:///Users/steve/Documents/repo/chiquito-6_2_23_v10/chiquito/book/_build/html/index.html` into the browser bar. Once we are good with the content and merge. I shall create a follow up PR that add CI to auto builder and host the book via gh-pages, though it could take quite some tweaking as our gh-pages branch currently builds and hosts the Rust doc. CI will follow something like the following: https://jupyterbook.org/en/stable/start/publish.html --- .gitignore | 1 + Appendix.md | 82 --------- book/_config.yml | 28 ++++ book/_toc.yml | 30 ++++ book/appendix_chapter1.md | 9 + book/appendix_chapter2.md | 22 +++ book/landing_page.md | 17 ++ book/part1_chapter1.md | 58 +++++++ book/part1_chapter2.md | 111 +++++++++++++ book/part1_chapter3.md | 113 +++++++++++++ book/part1_chapter4.md | 157 ++++++++++++++++++ book/part1_chapter5.ipynb | 129 ++++++++++++++ .../part2_chapter1.ipynb | 3 +- .../part2_chapter2.ipynb | 1 - .../part2_chapter3.ipynb | 1 - .../part2_chapter4.ipynb | 1 - .../part2_chapter5.ipynb | 1 - .../part3_chapter1.ipynb | 3 +- .../part3_chapter2.ipynb | 1 - .../part3_chapter3.ipynb | 3 +- .../part3_chapter4.ipynb | 1 - book/pselogo.png | Bin 0 -> 75960 bytes book/requirements.txt | 3 + examples/factorial.py | 17 +- examples/mimc7.py | 2 +- tutorials/part1.ipynb | 48 ------ tutorials/part2.ipynb | 56 ------- 27 files changed, 695 insertions(+), 203 deletions(-) delete mode 100644 Appendix.md create mode 100644 book/_config.yml create mode 100644 book/_toc.yml create mode 100644 book/appendix_chapter1.md create mode 100644 book/appendix_chapter2.md create mode 100644 book/landing_page.md create mode 100644 book/part1_chapter1.md create mode 100644 book/part1_chapter2.md create mode 100644 book/part1_chapter3.md create mode 100644 book/part1_chapter4.md create mode 100644 book/part1_chapter5.ipynb rename tutorials/part3_chapter1.ipynb => book/part2_chapter1.ipynb (95%) rename tutorials/part3_chapter2.ipynb => book/part2_chapter2.ipynb (99%) rename tutorials/part3_chapter3.ipynb => book/part2_chapter3.ipynb (99%) rename tutorials/part3_chapter4.ipynb => book/part2_chapter4.ipynb (99%) rename tutorials/part3_chapter5.ipynb => book/part2_chapter5.ipynb (99%) rename tutorials/part4_chapter1.ipynb => book/part3_chapter1.ipynb (96%) rename tutorials/part4_chapter2.ipynb => book/part3_chapter2.ipynb (99%) rename tutorials/part4_chapter3.ipynb => book/part3_chapter3.ipynb (99%) rename tutorials/part4_chapter4.ipynb => book/part3_chapter4.ipynb (99%) create mode 100644 book/pselogo.png create mode 100644 book/requirements.txt delete mode 100644 tutorials/part1.ipynb delete mode 100644 tutorials/part2.ipynb diff --git a/.gitignore b/.gitignore index 9dda4b0d..843d989a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ __pycache__ *.so .ipynb_checkpoints +_build diff --git a/Appendix.md b/Appendix.md deleted file mode 100644 index 0e5eb6f8..00000000 --- a/Appendix.md +++ /dev/null @@ -1,82 +0,0 @@ -# Appendix - -## Design Principles - -**Abstraction**. As circuit complexity increases, abstraction is inevitable. By abstracting constraint building and column placement, Chiquito improves the readability and learnability of Halo2 circuits, which can not only standardize and simplify the code base for complex projects such as the zkEVM, but also onboard more circuit developers. - -**Composability**. Chiquito circuits are fully composable with Halo2 circuits, which allows developers to write any part or the entirety of a circuit in Chiquito and integrate with other Halo2 circuits. - -**Modularity**. The AST and IR representations of a circuit allow Chiquito to integrate any frontend that can compile into the AST data structure and any backend that can compile from the IR data structure. For example, we can have a Python frontend that compiles to Chiquito AST/IR, which compiles to a Sangria backend. Modularity allows for future extensions. - -**User Experience**. Chiquito simplifies and optimizes user experience. For example, annotations for constraints are automatically generated for debugging messages. - -## Architecture - -There are two major architectural differences between Chiquito and Halo2: - -- Chiquito circuit is composed of “step” instances. Each step type defines the constraints among witnesses, fixed columns, and lookup tables. Step instances are also called “super rows”, each composed of one or multiple rows in a PLONKish table. We made this design choice to allow for more complex constraints, which sometimes require allocating multiple Halo2 rows. -- Chiquito DSL is based on “signals” rather than columns in order to abstract away column placements. One signal can be placed in different columns across different steps, all handled by Chiquito’s compiler. - -Chiquito circuit has the following architecture - -- Circuit (with optional input from Halo2) - - Forward signals (signals with constraints across different steps) - - Fixed columns - - Step types - - Internal signals (signals with constraints only within one step) - - Internal constraints (constraints for internal signals only) - - Transition constraints (constraints involving forward signals) - - Witness generation function for one step type instance - - Trace (global circuit witness generation) - - Adds instances of step types - -## Overall Workflow - -Chiquito is a DSL that compiles Chiquito AST to an IR which can be parsed by a Chiquito Halo2 backend and integrated into a Halo2 circuit. Therefore, to create a Halo2 circuit using Chiquito, we need to: - -- Call `circuit` function in DSL, which returns an `ast::Circuit` object. The `circuit` function defines the `ast::Circuit` object by: - - Importing Halo2 columns - - Generating fixed columns - - Adding forward signals - - Defining and instantiating “steps”, which defines internal signals, constraints, lookups, and witness generation -- Create a `Compiler` object that compiles the `ast::Circuit` object to an `ir::Circuit` object -- Call `chiquito2Halo2` function in Halo2 backend to convert the `ir::Circuit` object to a `ChiquitoHalo2` object -- Integrate `ChiquitoHalo2` object into a Halo2 circuit by including it in the Halo2 circuit config struct -- Call `configure` and `synthesize` functions defined in the Halo2 backend on the `ChiquitoHalo2` object - -## Exposed User Functions - -The above section describes the high level process of building and integrating a Chiquito Halo2 backend object into a Halo2 circuit. However, when building a circuit using Chiquito, the developer mostly call DSL functions to manipulate the `ast::Circuit` object. - -DSL functions are defined on five different levels, with nested relationships: - -- Circuit level: define and manipulate circuit-level objects, such as forward signals, step types, fixed columns, and imported Halo2 columns - - Step type level: define and manipulate step-specific objects, such as internal signals, constraints, witness generations - - Witness generation level: allow user-defined Turing-complete function to manipulate witness generation inputs and assign witness values - - Fixed column generation level: allow user-defined Turing-complete function to manipulate fixed column generation inputs and assign fixed column values - - Trace level: create the main circuit by instantiating step types; allow user-defined Turing-complete function to manipulate external trace inputs and assign input values to step type instances - -## Project Status (as of April 2023) - -The current implementation includes: - -- A DSL in Rust -- A backend in Halo2 -- Composability with Halo2 circuits -- A working prototype that passes zkEVM bytecode circuit tests -- Hashing function circuit examples - -## Vision and Next Steps - -Modularity - -- Expand frontend to other programming languages, e.g. Python -- Integrate additional backends and proof systems - -Library - -- Add additional circuit examples - -Infrastructure - -- Cmd tool and npm package for developers diff --git a/book/_config.yml b/book/_config.yml new file mode 100644 index 00000000..06a9cbfc --- /dev/null +++ b/book/_config.yml @@ -0,0 +1,28 @@ +# Book settings +# Learn more at https://jupyterbook.org/customize/config.html + +title: Introduction to Chiquito +author: Leo Lara, Steve Wang +logo: pselogo.png + +# Force re-execution of notebooks on each build. +# See https://jupyterbook.org/content/execute.html +execute: + execute_notebooks: force + +# Define the name of the latex output file for PDF builds +latex: + latex_documents: + targetname: book.tex + +# Information about where the book exists on the web +repository: + url: https://github.com/privacy-scaling-explorations/chiquito # Online location of your book + path_to_book: book # Optional path to your book, relative to the repository root + branch: main # Which branch of the repository should be used when creating links (optional) + +# Add GitHub buttons to your book +# See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository +html: + use_issues_button: true + use_repository_button: true diff --git a/book/_toc.yml b/book/_toc.yml new file mode 100644 index 00000000..7e5aa82b --- /dev/null +++ b/book/_toc.yml @@ -0,0 +1,30 @@ +# Table of contents +# Learn more at https://jupyterbook.org/customize/toc.html + +format: jb-book +root: landing_page +parts: + - caption: Part 1 - Intro to Chiquito + chapters: + - file: part1_chapter1 + - file: part1_chapter2 + - file: part1_chapter3 + - file: part1_chapter4 + - file: part1_chapter5 + - caption: Part 2 - Fibonacci Example + chapters: + - file: part2_chapter1 + - file: part2_chapter2 + - file: part2_chapter3 + - file: part2_chapter4 + - file: part2_chapter5 + - caption: Part 3 - MiMC7 Example + chapters: + - file: part3_chapter1 + - file: part3_chapter2 + - file: part3_chapter3 + - file: part3_chapter4 + - caption: Appendix + chapters: + - file: appendix_chapter1 + - file: appendix_chapter2 \ No newline at end of file diff --git a/book/appendix_chapter1.md b/book/appendix_chapter1.md new file mode 100644 index 00000000..7594b393 --- /dev/null +++ b/book/appendix_chapter1.md @@ -0,0 +1,9 @@ +# Design Principles + +**Abstraction**. As circuit complexity increases, abstraction is inevitable. By abstracting constraint building and column placement, Chiquito improves the readability and learnability of Halo2 circuits, which can not only standardize and simplify the code base for complex projects such as the zkEVM, but also onboard more circuit developers. + +**Composability**. Chiquito circuits are fully composable with Halo2 circuits, which allows developers to write any part or the entirety of a circuit in Chiquito and integrate with other Halo2 circuits. + +**Modularity**. The AST and IR representations of a circuit allow Chiquito to integrate any frontend that can compile into the AST data structure and any backend that can compile from the IR data structure. For example, we can have a Python frontend that compiles to Chiquito AST/IR, which compiles to a Sangria backend. Modularity allows for future extensions. + +**User Experience**. Chiquito simplifies and optimizes user experience. For example, annotations for constraints are automatically generated for debugging messages. diff --git a/book/appendix_chapter2.md b/book/appendix_chapter2.md new file mode 100644 index 00000000..ae3c0cff --- /dev/null +++ b/book/appendix_chapter2.md @@ -0,0 +1,22 @@ +# Chiquito vs Halo2 + +There are two major architectural differences between Chiquito and Halo2: + +- Chiquito circuit is composed of “step” instances. Each step type defines the constraints among witnesses, fixed columns, and lookup tables. Step instances are also called “super rows”, each composed of one or multiple rows in a PLONKish table. We made this design choice to allow for more complex constraints, which sometimes require allocating multiple Halo2 rows. +- Chiquito DSL is based on “signals” rather than columns in order to abstract away column placements. One signal can be placed in different columns across different steps, all handled by Chiquito’s compiler. + +Chiquito circuit has the following architecture + +- Circuit + - Setup + - Forward signals + - Shared signals + - Fixed signals + - Step types + - Setup + - Internal signals + - Constraints + - Transition constraints + - Lookup + - Witness generation + - Trace \ No newline at end of file diff --git a/book/landing_page.md b/book/landing_page.md new file mode 100644 index 00000000..ddfb7e10 --- /dev/null +++ b/book/landing_page.md @@ -0,0 +1,17 @@ +# Meet Chiquito + +Chiquito is a high-level structured language for implementing zero knowledge proof applications, currently being implemented in the DSL Working Group of PSE, Ethereum Foundation. It is a step-based zkDSL (zero knowledge domain specific language) that provides better syntax and abstraction for features like constraint building and column placement. Chiquito has a Halo2 backend, which is a low level zkDSL that writes circuits using the PLONKish arithemtization and is working on supporting additional backends. + +Chiquito can be written in both Python and Rust. This tutorial focuses on Python. + +The key advantages of Chiquito include: +- Abstraction and simplification on the readability and learnability of Halo2 circuits. +- Composabiity with other Halo2 circuits. +- Modularity of using multiple frontends (Python and Rust) and backends (Halo2 and beyond). +- Smooth user experience with a dynamically typed language (Python). + +

+ Image 2 +       + Image 3 +

diff --git a/book/part1_chapter1.md b/book/part1_chapter1.md new file mode 100644 index 00000000..fc818a50 --- /dev/null +++ b/book/part1_chapter1.md @@ -0,0 +1,58 @@ +# What is Zero Knowledge Proof (Developer POV)? + +Zero Knowledge proofs (or ZKP) allow developers to create mathematical proofs that a computation have been executed correctly, while not having to reveal all the data of this computation. + +## What are the applications of ZKP? + +There are three main areas of application: + + Privacy: because not all data has to be reveal we can proof computations and facts while preserving privacy or secrecy of some part of the data. + + Scalability and verified computation: usually to check that a computation has been executed correctly you would need to execute it and check the results are the same. With ZKP you can validate the mathematical proof of the computation, and in some systems this could require less time and space resources than executing the computation itself. + + Interoperability and trustlessness: because ZKPs are based on mathematical proofs, an organisation or individual can trust a ZKP proof without trusting other participants. + +## What are ZKP proving systems? + +Proving systems allow to create ZKP for arbitrary computations. They are like a computer system that can execute an arbitrary computation. A ZKP circuit is a definition for a proving system that proofs a specific computation. For example, for bubble sort algorithm you could write a circuit for a specific proving system that proofs bubble sort executions. + +## What are the elements of a ZKP circuit? + +The main elements are: + + The witness: which is the data of the computation. The witness is made of many elements, sometimes called signals or cells. Some signals are public and other private, the public part is sometimes called the **instance** and the private part is sometimes called the **advice**. The witness usually contains the input and output of the computation, but also most or all its intermediate values (the trace). + + Setup: it is a series of constraints or assertions over the witness data, that define which computations are valid. + + Witness generation: an algorithm that helps calculating the right values for all the signals. + + Proving and validation key: given a setup the proving system can generate proving and validation pair of keys. Both can be made public. + + Proof and prover: given a setup and a witness, the prover using the proving key can generate a proof. This proof will be valid if and only if the witness follows the setup constraints. + + Validator: given a proof and the public part of the witness and a validation key, the validator using the proving system can check that the proof is valid. That is, to generate that proof the prover had to have a witness (if which this public part is part of) that followed the setup. + +It is important to note that if the prover and validator have agreed on the setup will have the same proving and validation keys, and will not have to trust each other. + +![](../images/zkp-process-diagram.png) + +## What is a ZKP arithmetization? + +An arithmetization is the way to represent the setup to the proving system. They are very low level and based on polynomials. As a simile, if the proving system is a CPU, the arithmetization is its machine code. + +## What is a ZKP low-level DSL? + +It is a tool to write arithmetization in a more developer friendly way, but still very close to the arithmetization. If arithmetization is machine code, a ZKP low-level DSL is like assembly. + +## What is a ZKP high-level structured language? + +They allow to write the setup in a way that is closer to the way a developer thinks about the computation, and they get compiled to the arithmetization. Following the previous simile a ZKP high-level structured language is like Python. + +Chiquito is an example of ZKP high-level structured language that is based on steps. + +## What is a Finite Prime Field? + +In ZKP the witness signals are not made of bytes, they are elements (or values) of a Finite Prime Field. We need to understand how FPFs work in order to write circuits. + +As a developer you can see it as a unsigned integer type where the maximum value is a prime minus one (`p - 1` instead of `2^n-1`) and addition and multiplication overflows as expected, so `a + b = a + b mod p` and `a * b = a * b mod p`. + +Negative values are obtained by the formula `a + (-a) mod p = 0`. + +The prime `p` is usually a very big number when used in ZKP, but we can see an easy example with `p = 7`. + +The posible values would be `0, 1, 2, 3, 4, 5` and `6`. If we go over `p` it overflows around to `0`. So for example `6 + 1 = 0` or `5 + 3 = 1`. + +Then for example `-3 + 3 mod 7 = 0 ` which is satisfied by `4 + 3 mod 7 = 0 => -3 = 4`. + +This is a very short summary of what you need to know about FPFs. \ No newline at end of file diff --git a/book/part1_chapter2.md b/book/part1_chapter2.md new file mode 100644 index 00000000..f1002de3 --- /dev/null +++ b/book/part1_chapter2.md @@ -0,0 +1,111 @@ +# What is Chiquito? + +Chiquito is a high-level structured language for implementing zero knowledge proof applications. + +Chiquito is being implemented in the DSL Working Group of PSE, Ethereum Foundation. + +

+ Image 2 +       + Image 3 +

+ + +## Why is chiquito different from other ZKP DSLs? + +Most ZKP DSLs are based on writing constraints, witness generation and some abstraction for DRY like templates or gadgets. + +Chiquito allows the developer to think in more high-level and structured abstractions that most ZKP DSLs, while not sacrificing performance. + +## What is the chiquito programming model? + +Chiquito starts from the idea that every zero knowledge proof represents a program (the setup), which can have many computations (the trace) that is proven for a certain input, output and intermediate values (the witness). + +The main structured abstraction in chiquito is the **step**. Any computation can be divided in individual steps. A program is represented by a circuit that has one or many **step types**, a particular computation is represented as a series of **step instances** or **trace steps** that can have arbitrary order. + +A step type contains: + + Setup: a series of constraints or assertions that must hold for a step instance of this type to be valid. + + Witness generation: code that for a particular input sets the values of the witness for a particular step instance. + +A chiquito circuit contains a trace function that for a given input will generate the step instances in a particular order and use the step type witness generation. + +Another important piece of Chiquito are the signals. They represent elements of the witness. + +Chiquito has many more features, but these are enough to start writing basic circuits. + +## What proving system chiquito uses? + +Currently Halo2 backend is implemented, but we are looking into implementing other backends. + +Chiquito frontend comes in two flavours: rust and python, so you can write Chiquito applications in either Rust or Python. PyChiquito, and any other language interface in the future, uses ChiquitoCore for most of its work, so adding new languages is easy. + +## What are the features of chiquito? + + + Step-based, that abstracts out the computation that you want to prove. + + Signals, that abstract out the data (witness) and how it is placed and handled. + + Constraint builder, allows you to write the constraints of your application in a more readable and natural way. + + Trace based witness generation, a way to generate the data that you are trying to prove that matches how computation is done. + + Super circuits, that allow combining many circuits into one. + + Lookup tables, that allow sharing data between multiple circuits. + + Expose signals as public very easily. + + Automatic padding. + + Completely modular platform, that allows writing circuits in multiple languages and use different proving systems. + +PLONKish-specific features: + + Halo2 backend ready. + + PLONKish Cell Managers. These are modular strategies to place signals in the PLONKish columns and rotations. These allows for steps to use different numbers of rows and columns, in a configurable way, so you can find the most efficient structure for your circuit. + + PLONKish step selector builders. These are modular strategies to activate the steps in the witness. + +Planned: + + Nested steps, will allow more complex circuits and allow circuits coordination in proving systems without advice based lookup tables. + + Gadget abstraction. + + Arbitrary boolean assertions. + +In research: + + Signal typing system, which allows statically checking for soundness issues. + + Folding backend with ProtoStar, HyperNova, and/or others. + + Tracers + +## Fibonacci circtuit in PyChiquito. + +But better see for yourself: + +``` +class FiboStep(StepType): + def setup(self: FiboStep): + self.c = self.internal("c") + self.constr(eq(self.circuit.a + self.circuit.b, self.c)) + self.transition(eq(self.circuit.b, self.circuit.a.next())) + self.transition(eq(self.c, self.circuit.b.next())) + + def wg(self: FiboStep, args: Tuple[int, int]): + a_value, b_value = args + self.assign(self.circuit.a, F(a_value)) + self.assign(self.circuit.b, F(b_value)) + self.assign(self.c, F(a_value + b_value)) + +class Fibonacci(Circuit): + def setup(self: Fibonacci): + self.a: Queriable = self.forward("a") + self.b: Queriable = self.forward("b") + + self.fibo_step = self.step_type(FiboStep(self, "fibo_step")) + + self.pragma_num_steps(11) + + def trace(self: Fibonacci, args: Any): + self.add(self.fibo_step, (1, 1)) + a = 1 + b = 2 + for i in range(1, 11): + self.add(self.fibo_step, (a, b)) + prev_a = a + a = b + b += prev_a + +fibo = Fibonacci() +fibo_witness = fibo.gen_witness(None) +fibo.halo2_mock_prover(fibo_witness) +``` + +This is explained in more detail in the tutorial, but you can see already how concise and clear it is. \ No newline at end of file diff --git a/book/part1_chapter3.md b/book/part1_chapter3.md new file mode 100644 index 00000000..c5147876 --- /dev/null +++ b/book/part1_chapter3.md @@ -0,0 +1,113 @@ +# Chiquito Programming Model + +## ZKP as trace + +ZKP generates proofs that an algorithm has been execute correctly. + +The best way we can analyse a particular execution of an algorithm is by its trace. + +For example: + +```python= +def bubbleSort(arr): + n = len(arr) + + for i in range(n-1): + for j in range(0, n-i-1): + + if arr[j] > arr[j + 1]: + arr[j], arr[j + 1] = arr[j + 1], arr[j] +``` + +A trace for a particular input is the actual steps that are executed in order. + +For example if `arr = [64, 34, 25, 12, 22, 11, 90]`, the trace is: + +``` +set n to length of arr which is 7 +set i to 1 +set j to 0 +compare arr[j] > arr[j+1] => arr[0] > arr[1] => 64 > 34 => true +set arr[0] to arr[1] and arr[1] to arr[0] => arr[0] = 34, arr[1] = 64 +// and so on... +``` + +It is important to understand that each step has a pre-state, with the values of the variables before the execution of the step, and a post-state with the values of the variables after the execution of the step. The post-state of a step is the pre-state of the next one. + +If we include the states indented: +``` + arr = [64, 34, 25, 12, 22, 11, 90] + n = ?, i = ?, j = ? +set n to lenght of arr which is 7 + arr = [64, 34, 25, 12, 22, 11, 90] + n = 7, i = ?, j = ? +set i to 1 + arr = [64, 34, 25, 12, 22, 11, 90] + n = 7, i = 1, j = ? +set j to 0 + arr = [64, 34, 25, 12, 22, 11, 90] + n = 7, i = 1, j = 0 +compare arr[j] > arr[j+1] => arr[0] > arr[1] => 64 > 34 => true + arr = [64, 34, 25, 12, 22, 11, 90] + n = 7, i = 1, j = 0 +set arr[0] to arr[1] and arr[1] to arr[0] => arr[0] = 34, arr[1] = 64 + arr = [34, 64, 25, 12, 22, 11, 90] + n = 7, i = 1, j = 0 +// and so on... +``` + +## ZKP circuit as a trace checker + +We can see a ZKP circuit as a series of step types, and their constraints relative to the post-state and pre-state, and also potentially some internal values. + +For example, we can have the step type `set $variable to $value` and the constraint would be that the post state of `$variable` should be `$value`. + +## ZKP witness as a trace + +In the same way, we can see the witness as a the trace of a computation plus some information about the states between the steps. + +The programming model in chiquito follows the idea that every zero knowledge proof represents a program (the setup), which can have many computations (the trace) that is proven for a certain input, output and intermediate values (the witness). + +## Step types and step instances + +A trace is made of a sequence of step instances or trace steps, that contains the data about that step instance. + +Each step instance belong to a step type. A step types contains rules (or constraints) about whether a step instance is valid in relation to its data and the data of other step instances relative to it. + +Step types are part of the setup, which means that they cannot change between proofs, but this circuit can proof all possible traces, as they are part of the witness. + +## Signals and rotations + +Signals represent are elements of the witness that have a independent value for each trace step, but in chiquito paradigm you can see them as variables on the trace steps. + +Rotation refer to the capability of creating rules that involved the value of signals in other trace steps with an offset relative to the trace step, not just the value of a signal. A rotation of 0, represent the value of the signal in the same trace step, a rotation of 1 in the next step, a rotation of -1 in the previous step, in general any rotation number is posible. + +## Types of signals + +There are two types of signals that are shared between the step types, **forward** signals and **shared** signals. The difference is that forward signals can only rotate to the next step, while shared signals can rotate to any number of steps. However, circuits that only use forward signals are more efficient in some cases, so you should try to use only forward signals. + +**Internal** signals are not shared between step types, and belong to a specific step type and cannot be rotated. + +There is a special type of signal called **fixed**, that is not part of the witness and it is used to contain constants. + +| | Scope | Rotation | Role | +| -------- | ---------------- | --------- | ------- | +| Forward | All step types | Only next | Witness | +| Shared | All step types | Any | Witness | +| Internal | Single step type | None | Witness | +| Fixed | All step types | Any | Setup | + +## Putting everything together +- Circuit + - Setup + - Forward signals + - Shared signals + - Fixed signals + - Step types + - Setup + - Internal signals + - Constraints + - Transition constraints + - Lookup + - Witness generation + - Trace \ No newline at end of file diff --git a/book/part1_chapter4.md b/book/part1_chapter4.md new file mode 100644 index 00000000..52ccbeab --- /dev/null +++ b/book/part1_chapter4.md @@ -0,0 +1,157 @@ +# Python Syntax + +## Constraint builder + +One of the most important parts of defining a circuit is the constraints. The basis for writing constraints are arithmetic operations with signals. You use the operators `*`, `+` and `-` with numbers and signals. For example: + +`a + 1 * b` + +When you need to do a rotation on a signal you can use the operators `.next()`, `prev()` and or `.rot(n)`. For example: + +`a + 1 * b.next()` + + There are several helper functions to define constraints: + + `cb_and([a1 ... an])`: And operator + + `cb_or([a1 ... an])`: Or operator + + `xor(a, b)`: Xor operator + + `eq(a, b)`: Arithmetic equality. + + `select(selector, when_true, when_false)`: trinary operator + + `when(selector, when_true)`: Logical imply. + + `unless(selector, when_false)`: Logical imply when !selector. + + `cb_not(a)`: Not operator. + + isz(a): Equal to zero. + + `if_next_step(step_type, a)`: If next step is step_type, implies a. + + `next_step_must_be(step_type)`: Enforces next step must be step_type. + + `next_step_must_not_be(step_type)`: Enforces next step must not be step_type. + +In the current version of chiquito these operator cannot be combined arbitrarily, there is a solution in the works. For example `cb_and` cannot have operands that are `eq`. + +## Defining a step + +A chiquito circuit is made of steps, this is how we define steps: + +```python= +class AStep(StepType): + def setup(self): + # setup: rules about a valid step + def wg(self, a, b, c): + # witness generation: set data for a step instance +``` + +For the setup we need to define constraints that define which step witness is valid and also define the internal signals. For example: + +```python= +class AStep(StepType): + def setup(self): + self.c = self.internal("c") +``` + +To access shared signals we can do it with `self.circuit.signal_identifier`. + +For the constraints that we can use the method `constr`. For example: + +```python= +class AStep(StepType): + def setup(self): + self.constr(eq(self.circuit.a, self.circuit.b * 3)) + self.constr(when(self.circuit.a, eq(self.circuit.b, self.c)) + +# ... +``` + +The first constraint means that `a` must be equal to `b times 3`. The second that when `a` value is `1` then `b` must be equal to `c`. + +When you have a rotation you should use the method `transition`. For example: + +```python= +class AStep(StepType): + def setup(self): + self.transition(eq(self.circuit.a, self.circuit.b.next() * 3)) + self.transition(when(self.circuit.a, eq(self.circuit.b.next(), self.c)) + +# ... +``` + + +For the data of one step instance in the `wg` method we must use the `assign` method to assign all signals. + +```python= +class AStep(StepType): + def wg(self, a_val, b_val): + self.assign(self.circuit.a, a_val) + self.assign(self.circuit.b, b_val) + self.assign(self.c, a_val + b_val) + +# ... +``` + +The method `wg` can have any parameters, and do any computation to calculate the assignations. But signals cannot be rotated for assignation, you can only assign the current step. + +## Defining the circuit + +To put everything together we define the circuit, also with two methods. + +```python= +class ACircuit(Circuit): + def setup(self): + # Define shared signals and step types used, number of total steps plus more setup configuration. + def trace(self, arg1, arg2): + # Define algorithm to create the trace from the input. +``` + +For the setup we defined shared signals, step types and number of steps in the trace. For example: + +```python= +class ACircuit(Circuit): + def setup(self): + # shared signals + self.a = self.forward("a") + self.b = self.forward("b") + self.n = self.forward("n") + + # step types + self.a_step = self.step_type(AStep(self, "a_step")) + self.b_step = self.step_type(BStep(self, "b_step")) + self.c_step = self.step_type(CStep(self, "c_step")) + + # number of trace steps + self.pragma_num_steps(10) + +# ... +``` + +There are many optional things that you can also configure in the circuit setup: + +```python= +class ACircuit(Circuit): + def setup(self): + self.pragma_first_step(self.a_step) + self.pragma_last_step(self.b_step) + + self.expose(self.b, Last()) + self.expose(self.n, Last()) + +# ... +``` + +Then, we should define `trace`, this method add step instances or trace steps to create a particular trace. The most important method is `add()`, the first parameter is a step type, and the rest are the parameters to the step type `wg` method. This method adds a trace step + +For example: + +```python= +class ACircuit(Circuit): + def trace(self, n): + self.add(self.a_step, 1, 1, n) + a = 1 + b = 2 + for i in range(1, n): + self.add(self.b_step, a, b, n) + prev_a = a + a = b + b += prev_a + + while self.needs_padding(): + self.add(self.b_step, a, b, n) + +# ... +``` \ No newline at end of file diff --git a/book/part1_chapter5.ipynb b/book/part1_chapter5.ipynb new file mode 100644 index 00000000..9eb33613 --- /dev/null +++ b/book/part1_chapter5.ipynb @@ -0,0 +1,129 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "11105438-f775-4ff5-a91c-b236aedd9eb1", + "metadata": {}, + "source": [ + "# Setup\n", + "\n", + "## User Setup\n", + "### Python\n", + "Simply run the following line in command:\n", + "```\n", + "pip install chiquito\n", + "```\n", + "Then import chiquito in `.py` file. See more in the `\\examples` folder.\n", + "\n", + "### Rust\n", + "Add the following line to `[dependencies]` in `Cargo.toml`:\n", + "```\n", + "chiquito = { git = \"https://github.com/privacy-scaling-explorations/chiquito.git\" }\n", + "```\n", + "Then use chiquito in `.rs` file. See more in the `\\examples` folder.\n", + "\n", + "## Contributor Setup\n", + "### Python\n", + "Clone this repo and navigate to the root directory of this repository:\n", + "```\n", + "git clone https://github.com/privacy-scaling-explorations/chiquito/\n", + "cd chiquito\n", + "```\n", + "\n", + "Chiquito's Python frontend uses PyO3 and Maturin to expose Rust APIs to Python. Maturin requires the user to locally build a Python virtual environment. Run the following script to create a Python virtual environment, install required packages, and build the project.\n", + "```\n", + "python3 -m venv .env # create virtual environment\n", + "source .env/bin/activate # activate virtual environment\n", + "pip install -r requirements.txt # install required python dependencies from requirements.txt\n", + "maturin develop # build the project with rust bindings\n", + "```\n", + "\n", + "If the above doesn't work, follow the guide here: https://pyo3.rs/main/getting_started#python\n", + "\n", + "To test if your setup is successful, run the `fibonacci.py` example file using the following script in the virtual environment:\n", + "```\n", + "python3 example/fibonacci.py\n", + "```\n", + "\n", + "You can also run the `mimc7.py` example:\n", + "```\n", + "python3 example/mimc7.py\n", + "```\n", + "\n", + "If setup is correct, you should see an `OK(())` message printed in the terminal.\n", + "\n", + "### Rust\n", + "Simply clone and modify this repo:\n", + "```\n", + "git clone https://github.com/privacy-scaling-explorations/chiquito/\n", + "```\n", + "\n", + "### Jupyter\n", + "To modify and contribute to the Jupyter book `.ipynb` files in this book, you can use an IDE like VSCode. Alternatively, you can modify `.ipynb` files in the browser according to the following steps:\n", + "\n", + "First, install jupyter:\n", + "```\n", + "pip install jupyter\n", + "```\n", + "\n", + "Then, make sure you've set up your local Python virtual environment as directed above and install it as a Jupyter Lab Kernel called `chiquito_kernel`:\n", + "```\n", + "python -m ipykernel install --user --name=chiquito_kernel\n", + "```\n", + "\n", + "After that, run the following:\n", + "```\n", + "cd book # navigate to the book subfolder\n", + "jupyter lab # launch jupyter notebook in browser\n", + "```\n", + "\n", + "In browser, make sure you are using the Kernel we just created called `chiquito_kernel`, which you can change by going to the `Kernel` tab of Jupyter Lab's top menu bar and click `Change Kernel...`\n", + "\n", + "Chiquito's Python frontend requires using a Python virtual environment for its dependencies, which you should have setup following the [Python README](https://github.com/privacy-scaling-explorations/chiquito/tree/main/src/frontend/python/README.md).\n", + "\n", + "Specifically, after cloning Chiquito, you need to run the following commands in your local machine (NOT in Jupyter):\n", + "```bash\n", + "python3 -m venv .env # create virtual environment\n", + "source .env/bin/activate # activate virtual environment\n", + "pip install -r requirements.txt # install required python dependencies from requirements.txt\n", + "maturin develop # build the project with rust bindings\n", + "```\n", + "\n", + "Then install your local Python virtual environment as a Jupyter Lab Kernel called `chiquito_kernel`. \n", + "```bash\n", + "python -m ipykernel install --user --name=chiquito_kernel\n", + "```\n", + "\n", + "After that, run the following: \n", + "```bash\n", + "cd tutorials # navigate to the tutorials subfolder\n", + "jupyter lab # launch jupyter notebook in browser\n", + "```\n", + "\n", + "In browser, make sure you are using the Kernel we just created called `chiquito_kernel`, which you can change by going to the `Kernel` tab of Jupyter Lab's top menu bar and click `Change Kernel...`\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "chiquito_kernel", + "language": "python", + "name": "chiquito_kernel" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/part3_chapter1.ipynb b/book/part2_chapter1.ipynb similarity index 95% rename from tutorials/part3_chapter1.ipynb rename to book/part2_chapter1.ipynb index a9d6e74b..6d7a81a3 100644 --- a/tutorials/part3_chapter1.ipynb +++ b/book/part2_chapter1.ipynb @@ -6,8 +6,7 @@ "id": "14a9a617-1c05-40c8-b4ef-3cc2fbcf99fe", "metadata": {}, "source": [ - "# Part 3: Fibonacci Example\n", - "The best learning is by doing, In this Chapter, we will walk through the [fibonacci.py](https://github.com/privacy-scaling-explorations/chiquito/blob/main/examples/fibonacci.py) example.\n", + "The best learning is by doing, In this Part, we will walk through the [fibonacci.py](https://github.com/privacy-scaling-explorations/chiquito/blob/main/examples/fibonacci.py) example.\n", "# Chapter 1: Fibonacci and Chiquito Concepts\n", "The Fibonacci series is an infinite series, starting from \"1\" and \"1\", in which every number in the series is the sum of two numbers preceding it in the series. The first few rounds for the Fibonacci series are:\n", "- 1 + 1 = 2\n", diff --git a/tutorials/part3_chapter2.ipynb b/book/part2_chapter2.ipynb similarity index 99% rename from tutorials/part3_chapter2.ipynb rename to book/part2_chapter2.ipynb index cefb3443..33e22707 100644 --- a/tutorials/part3_chapter2.ipynb +++ b/book/part2_chapter2.ipynb @@ -5,7 +5,6 @@ "id": "8ab5d5e4-6d9e-4856-ad0b-0e2cf7bb13fb", "metadata": {}, "source": [ - "# Part 3: Fibonacci Example\n", "# Chapter 2: StepType and Circuit\n", "In this Chapter, we code out the concepts learned in Chapter 1 in PyChiquito, but before that, let's import the dependencies first.\n", "## Imports" diff --git a/tutorials/part3_chapter3.ipynb b/book/part2_chapter3.ipynb similarity index 99% rename from tutorials/part3_chapter3.ipynb rename to book/part2_chapter3.ipynb index e202cb01..e9f53e0c 100644 --- a/tutorials/part3_chapter3.ipynb +++ b/book/part2_chapter3.ipynb @@ -5,7 +5,6 @@ "id": "aab29f93-4311-4bff-a123-38f412556922", "metadata": {}, "source": [ - "# Part 3: Fibonacci Example\n", "# Chapter 3: Witness\n", "Now, we will generate multiple witnesses to test the soundness of our circuit constraints. Note that we only intend to accept the following set of values for signals \"a\", \"b\", and \"c\". \"Soundness\" in this context refers to faulty witness successfully verified against the constraints (false positives), so any set of witness assignments that is different from the table below but still passes the constraints incurs a \"soundness\" error.\n", "\n", diff --git a/tutorials/part3_chapter4.ipynb b/book/part2_chapter4.ipynb similarity index 99% rename from tutorials/part3_chapter4.ipynb rename to book/part2_chapter4.ipynb index 14996ecb..d301f6b4 100644 --- a/tutorials/part3_chapter4.ipynb +++ b/book/part2_chapter4.ipynb @@ -5,7 +5,6 @@ "id": "291ef2c6-bce5-4166-973d-d34d04f31bfb", "metadata": {}, "source": [ - "# Part 3: Fibonacci Example\n", "# Chapter 4: Multiple StepTypes" ] }, diff --git a/tutorials/part3_chapter5.ipynb b/book/part2_chapter5.ipynb similarity index 99% rename from tutorials/part3_chapter5.ipynb rename to book/part2_chapter5.ipynb index e01f227e..b01795bc 100644 --- a/tutorials/part3_chapter5.ipynb +++ b/book/part2_chapter5.ipynb @@ -5,7 +5,6 @@ "id": "5ec470ae-aca1-4c7e-a048-d115633531d8", "metadata": {}, "source": [ - "# Part 3: Fibonacci Example\n", "# Chapter 5: Padding and Exposing Signals\n", "In prior examples, all Fibonacci circuit witnesses have the same number of step instances. According to our analogy where a circuit is considered a piece of hardware and its witnesses compatible softwares, it's natural to think that we'd allow for more flexibility for our witnesses. Most immediately, and we shouldn't limit all Fibonacci circuit witnesses to have the same number of step instances.\n", "\n", diff --git a/tutorials/part4_chapter1.ipynb b/book/part3_chapter1.ipynb similarity index 96% rename from tutorials/part4_chapter1.ipynb rename to book/part3_chapter1.ipynb index c1a04c4f..e6ec6f28 100644 --- a/tutorials/part4_chapter1.ipynb +++ b/book/part3_chapter1.ipynb @@ -5,8 +5,7 @@ "id": "2b08fd8e-65b8-433b-8a99-71aa13ad30ad", "metadata": {}, "source": [ - "# Part 4: MiMC7 Example\n", - "In this Chapter, we will walk through the [mimc7.py] (https://github.com/privacy-scaling-explorations/chiquito/blob/main/examples/mimc7.py) example.\n", + "In this Part, we will walk through the [mimc7.py](https://github.com/privacy-scaling-explorations/chiquito/blob/main/examples/mimc7.py) example.\n", "# Chapter 1: MiMC7 Concepts\n", "MiMC7 is a zk-friendly hash function. By \"zk-friendly\" we mean that the function is designed to minimize circuit size for zero knowledge proofs by using only additions and multiplications. Other common non-zk-friendly hash functions such as SHA256 requires many bit operations and therefore can be very costly in terms of circuit size.\n", "\n", diff --git a/tutorials/part4_chapter2.ipynb b/book/part3_chapter2.ipynb similarity index 99% rename from tutorials/part4_chapter2.ipynb rename to book/part3_chapter2.ipynb index e142379b..fd131294 100644 --- a/tutorials/part4_chapter2.ipynb +++ b/book/part3_chapter2.ipynb @@ -5,7 +5,6 @@ "id": "1c24b711-a68f-4215-a8e6-556c078dddec", "metadata": {}, "source": [ - "# Part 4: MiMC7 Example\n", "# Chapter 2: First Attempt\n", "In this Chapter, we will code out the MiMC7 circuit based on our understanding of the algorithm from the prior chapter. There are no new Chiquito concepts other than the ones introduced in **Part 3: Fibonacci Example**, which you are highly recommended to go through first before starting this Chapter.\n", "\n", diff --git a/tutorials/part4_chapter3.ipynb b/book/part3_chapter3.ipynb similarity index 99% rename from tutorials/part4_chapter3.ipynb rename to book/part3_chapter3.ipynb index b0c9975e..9ca84fcb 100644 --- a/tutorials/part4_chapter3.ipynb +++ b/book/part3_chapter3.ipynb @@ -5,8 +5,7 @@ "id": "f6da1ade-1de6-4921-9485-40f1c18b1098", "metadata": {}, "source": [ - "# Part 4: MiMC7 Example\n", - "# Chapter 2: Witness\n", + "# Chapter 3: Witness\n", "As is the best practice with circuit building, we will generate evil witnesses to test the soundness of our circuit constraints. It's always helpful to review the circuit table again:\n", "\n", "|Step Type|Round|||Signals||||||Setups||\n", diff --git a/tutorials/part4_chapter4.ipynb b/book/part3_chapter4.ipynb similarity index 99% rename from tutorials/part4_chapter4.ipynb rename to book/part3_chapter4.ipynb index 9f9792c8..5c9b0863 100644 --- a/tutorials/part4_chapter4.ipynb +++ b/book/part3_chapter4.ipynb @@ -5,7 +5,6 @@ "id": "be7fb603-4820-410e-8250-abc52e81a6b0", "metadata": {}, "source": [ - "# Part 4: MiMC7 Example\n", "# Chapter 4: Fixed Signals, Lookup Tables, and Super Circuit\n", "\n", "## Lookup Tables\n", diff --git a/book/pselogo.png b/book/pselogo.png new file mode 100644 index 0000000000000000000000000000000000000000..a934b013d88226b1f4a3c1f90cf49a7c4624fc63 GIT binary patch literal 75960 zcmeFZX*kvC8vy(qikXO3RF;;ZB%C%O(WWL#B^6~mrOlR|l;t!{R8%t(CxleUdK9ud zGl~pRWXWE3LUxYrz3-pq|1MwN>;3kA7}qt|=vnXmxwoI&M-LxdxM1Z1Lde2HzwSRq z2>%B{1RZ|jgOQS+)feD@KimC!%8?L(rT8C>1c!>jAkFdE!M!B0RJ;rRG4J9Y-93aP z-xruVGoR2l=pNd?=lB)cw+4BZM8_kA?t@Df*Dcr@vSq8b??gm^p^{dJ+aISltvYmI zEB}j|7g);z&R9MzSzYw2`7`Nr!VV$5H;$jVXz2Ua+N&wCvMaFu*la>LH>1DjYGUOb z|LadpM?OvF?%;uY$sxyrGS@(rp1dIq_3

*_rrR4~2=bAm@~l^D~K)&1GKG?`GTc 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򥲢^-z}7cB#H36Y~S!ztfT@O*zu08uq0>ci0h@w;K0q zQrp4F1;yovZ&?%OO_E_Aoz#+4{A`a@p9&JuQ{M4UY*Qs>N)sX#=(rz`N!K@K2v3&b zK8JOHyapg`*j8(V@F{=w4zN%KeIXZ~B*>Av$41?6&O~bvziUSI9okdc(uium`B4(l zC&9*Y@I%nd=W8-FP@Fz{mMMK+Ech8us>8J2QdYG6j+Y= zR6#Rg*=h^)l*50kgV~#xaiSkt3@aI!7tad+P~GnE<$+$yj7Jv-SQ5G>&x4?h;_-nD)20?kIUlC-Ve;ajFgxbMY%E9fdTAhPm~FdCL<9^_!0H+y*`ya?VEG z{Eo|Mxg`lMXOa8PJ?7D1@&TiIZRWOkh_;VQIckZ{yA$a4m-~KlP@mHFHYsArVnrvI z7Imwi(BLcoEZ;qTj~gw7jbp2J|Fv7B7M5 zo*+ISu0-~I{f!-(6@PV924eZW@rOBHOaw;g}`<0t&zULIomnHKPcN6`4PG z&F@1q?{UXdzYx&fZKN6%F^hDlBTOzWRx^rL_&XjSRMOBWA5NU+8-X71BDUD9atYr} z3AW1(KQ-ILzGX}kVn>9GW?6{Ulv>B0o!0K4tQ3SFMGLMTcE-ofbZV_){A9#ThR6?& zo#DnlglDY2uqCqpBrJXq&11B7ebB;Bx9AO;WG-t##0xuZ@d=k;pph2xg1bVo$vNYd z54z{$W`qR93ROu&onw{1c7%xI*PhppKAQf`*Jf@o;fH8R<@=x0HWnuM*l*2o{L^fA zkt^|)s}yULdiH>mJx`u3bxC_qKz>Rf!d@d%LB9 zc!9(Yt$WACk_%!Z{<7}Li4`iu*0t^_O97PuiS1nXbk{@DTldyO+6Zn9uMOnZfY_LG zYd~zQ)-@phH!9h^!F~Uw=yf{YAm+8?wvp1-*3$oRzP%=@CpfKj!*6(1TOXl@bhK5U Q5npmV;c~p{m|xPr0if{x*8l(j literal 0 HcmV?d00001 diff --git a/book/requirements.txt b/book/requirements.txt new file mode 100644 index 00000000..7e821e45 --- /dev/null +++ b/book/requirements.txt @@ -0,0 +1,3 @@ +jupyter-book +matplotlib +numpy diff --git a/examples/factorial.py b/examples/factorial.py index 7a5bc510..6963a3f9 100644 --- a/examples/factorial.py +++ b/examples/factorial.py @@ -40,7 +40,9 @@ def setup(self): # constrain i + 1 == i.next() self.transition(eq(self.circuit.i + 1, self.circuit.i.next())) # constrain the next `x` to be the product of the current `x` and the next `i` - self.transition(eq(self.circuit.x * (self.circuit.i + 1), self.circuit.x.next())) + self.transition( + eq(self.circuit.x * (self.circuit.i + 1), self.circuit.x.next()) + ) def wg(self, i_value, x_value): self.assign(self.circuit.i, F(i_value)) @@ -99,7 +101,9 @@ class Examples: def test_zero(self): factorial = Factorial() factorial_witness = factorial.gen_witness(0) - last_assignments = list(factorial_witness.step_instances[10].assignments.values()) + last_assignments = list( + factorial_witness.step_instances[10].assignments.values() + ) assert last_assignments[0] == 0 # i assert last_assignments[1] == 1 # x factorial.halo2_mock_prover(factorial_witness) @@ -107,7 +111,9 @@ def test_zero(self): def test_basic(self): factorial = Factorial() factorial_witness = factorial.gen_witness(7) - last_assignments = list(factorial_witness.step_instances[10].assignments.values()) + last_assignments = list( + factorial_witness.step_instances[10].assignments.values() + ) assert last_assignments[0] == 7 # i assert last_assignments[1] == 5040 # x factorial.halo2_mock_prover(factorial_witness) @@ -116,6 +122,9 @@ def test_basic(self): if __name__ == "__main__": x = Examples() for method in [ - method for method in dir(x) if callable(getattr(x, method)) if not method.startswith('_') + method + for method in dir(x) + if callable(getattr(x, method)) + if not method.startswith("_") ]: getattr(x, method)() diff --git a/examples/mimc7.py b/examples/mimc7.py index ce3a1ca5..f17028fc 100644 --- a/examples/mimc7.py +++ b/examples/mimc7.py @@ -167,4 +167,4 @@ def mapping(self, x_in_value, k_value): mimc7_super_witness = mimc7.gen_witness(F(1), F(2)) # for key, value in mimc7_super_witness.items(): # print(f"{key}: {str(value)}") -mimc7.halo2_mock_prover(mimc7_super_witness, k = 10) +mimc7.halo2_mock_prover(mimc7_super_witness, k=10) diff --git a/tutorials/part1.ipynb b/tutorials/part1.ipynb deleted file mode 100644 index 064d9234..00000000 --- a/tutorials/part1.ipynb +++ /dev/null @@ -1,48 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "0d703199-2067-4ebc-b6ed-b04470587279", - "metadata": {}, - "source": [ - "# Part 1: Introduction to Chiquito's Python Frontend\n", - "\n", - "Chiquito is a high-level structured language for implementing zero knowledge proof applications, currently being implemented in the DSL Working Group of PSE, Ethereum Foundation. It is a step-based zkDSL (zero knowledge domain specific language) that provides better syntax and abstraction for features like constraint building and column placement when writing PLONKish circuits, which supports custom gates and lookup arguments. Chiquito has a Halo2 backend, which is a low level zkDSL that writes circuits using the PLONKish arithemtization. Chiquito is working on supporting additional backends.\n", - "\n", - "Chiquito can be written in both Python and Rust. This tutorial focuses on Python.\n", - "\n", - "Chiquito's Python frontend is located in a [subfolder here](https://github.com/privacy-scaling-explorations/chiquito/tree/main/src/frontend/python). It exposes Python functions that user writes circuits with. Chiquito's [Rust functions](https://github.com/privacy-scaling-explorations/chiquito/tree/main/src) are called by the Python functions behind the scenes and converts what the user writes into a Halo2 circuit.\n", - "\n", - "The key advantages of Chiquito and its Python frontend include: \n", - "- Abstraction and simplification on the readability and learnability of Halo2 circuits.\n", - "- Composabiity with other Halo2 circuits.\n", - "- Modularity of using multiple frontends (Python and Rust) and backends (Halo2 and beyond).\n", - "- Smooth user experience with a dynamically typed language (Python).\n", - "\n", - "For more on why you need Chiquito and its Python frontend, refer to [What is Chiquito?](https://hackmd.io/h6innd6RTwex4aBExignhw), [Chiquito README](https://github.com/privacy-scaling-explorations/chiquito#readme), and the [Appendix](https://github.com/privacy-scaling-explorations/chiquito/blob/main/Appendix.md/#design-principles) on its design principles. For more on PLONKish concepts and Halo2 circuits, refer to the [Halo2 book](https://zcash.github.io/halo2/index.html)." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "chiquito_kernel", - "language": "python", - "name": "chiquito_kernel" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/tutorials/part2.ipynb b/tutorials/part2.ipynb deleted file mode 100644 index da969fe7..00000000 --- a/tutorials/part2.ipynb +++ /dev/null @@ -1,56 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "11105438-f775-4ff5-a91c-b236aedd9eb1", - "metadata": {}, - "source": [ - "# Part 2: Quick Start\n", - "Chiquito's Python frontend requires using a Python virtual environment for its dependencies, which you should have setup following the [Python README](https://github.com/privacy-scaling-explorations/chiquito/tree/main/src/frontend/python/README.md).\n", - "\n", - "Specifically, after cloning Chiquito, you need to run the following commands in your local machine (NOT in Jupyter):\n", - "```bash\n", - "python3 -m venv .env # create virtual environment\n", - "source .env/bin/activate # activate virtual environment\n", - "pip install -r requirements.txt # install required python dependencies from requirements.txt\n", - "maturin develop # build the project with rust bindings\n", - "```\n", - "\n", - "Then install your local Python virtual environment as a Jupyter Lab Kernel called `chiquito_kernel`. \n", - "```bash\n", - "python -m ipykernel install --user --name=chiquito_kernel\n", - "```\n", - "\n", - "After that, run the following: \n", - "```bash\n", - "cd tutorials # navigate to the tutorials subfolder\n", - "jupyter lab # launch jupyter notebook in browser\n", - "```\n", - "\n", - "In browser, make sure you are using the Kernel we just created called `chiquito_kernel`, which you can change by going to the `Kernel` tab of Jupyter Lab's top menu bar and click `Change Kernel...`\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "chiquito_kernel", - "language": "python", - "name": "chiquito_kernel" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 009690be30e8ba4beddf8397dff63eee3a449e57 Mon Sep 17 00:00:00 2001 From: nullbitx8 <92404251+nullbitx8@users.noreply.github.com> Date: Thu, 26 Oct 2023 07:36:35 -0400 Subject: [PATCH 09/13] Add test coverage reporting (#162) Adds test coverage reporting to the Chiquito project. ## Demo Check out the code coverage report on my fork https://app.codecov.io/github/nullbitx8/chiquito Check out my fork to see how the `Rust` workflow in the project's Github Actions now runs tests with coverage reporting. https://github.com/nullbitx8/chiquito/actions/runs/6653135943/job/18078525896 It looks like Chiquito already has a codecov access token, so we can already see the results here https://app.codecov.io/github/privacy-scaling-explorations/chiquito/tree/add-test-coverage-reporting --- .github/workflows/rust.yml | 8 +++++++- Makefile | 22 +++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 680b88a4..5ae011d0 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -8,6 +8,7 @@ on: env: CARGO_TERM_COLOR: always + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} jobs: skip_check: @@ -30,7 +31,12 @@ jobs: - name: Build run: cargo build --verbose - name: Run tests - run: cargo test --verbose + run: make test + - name: Upload coverage reports to Codecov + run: | + curl -o target/test_coverage/codecov -Os https://uploader.codecov.io/latest/linux/codecov + chmod +x target/test_coverage/codecov + ./target/test_coverage/codecov -t ${CODECOV_TOKEN} clippy_check: needs: [skip_check] diff --git a/Makefile b/Makefile index 26747673..828e6c66 100644 --- a/Makefile +++ b/Makefile @@ -8,4 +8,24 @@ build: cargo build maturin develop -.PHONY: precommit build +test: + # clean test coverage directory + rm -rf ./target/test_coverage + mkdir -p ./target/test_coverage + + # install dependencies + rustup component add llvm-tools-preview + curl -sL https://github.com/mozilla/grcov/releases/download/v0.8.10/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar jxf - -C "./target/test_coverage" + chmod +x ./target/test_coverage/grcov + + # run tests + CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='target/test_coverage/profraw/cargo-test-%p-%m.profraw' cargo test --verbose + + # generate html coverage report + ./target/test_coverage/grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/test_coverage/html + + # generate lcov coverage report + ./target/test_coverage/grcov . --binary-path ./target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/test_coverage/tests.lcov + + +.PHONY: precommit build test From ab6b648eb77826d7c96c705a91b28fdf086ca4db Mon Sep 17 00:00:00 2001 From: nullbitx8 <92404251+nullbitx8@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:22:30 -0400 Subject: [PATCH 10/13] Update part2_chapter5.ipynb (#163) Missing forward signal b in an example --- book/part2_chapter5.ipynb | 1 + 1 file changed, 1 insertion(+) diff --git a/book/part2_chapter5.ipynb b/book/part2_chapter5.ipynb index b01795bc..e12cea90 100644 --- a/book/part2_chapter5.ipynb +++ b/book/part2_chapter5.ipynb @@ -123,6 +123,7 @@ "class Fibonacci(Circuit):\n", " def setup(self):\n", " self.a = self.forward(\"a\")\n", + " self.b = self.forward(\"b\")\n", " self.n = self.forward(\"n\")\n", " \n", " self.fibo_first_step = self.step_type(FiboFirstStep(self, \"fibo_first_step\"))\n", From f3f1b83a6d6d411bae7d8213e9f8021021917b00 Mon Sep 17 00:00:00 2001 From: vuittont60 <81072379+vuittont60@users.noreply.github.com> Date: Fri, 27 Oct 2023 23:37:32 +0800 Subject: [PATCH 11/13] docs: fix typo (#164) fix lenght to length --- book/part1_chapter3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part1_chapter3.md b/book/part1_chapter3.md index c5147876..98432352 100644 --- a/book/part1_chapter3.md +++ b/book/part1_chapter3.md @@ -38,7 +38,7 @@ If we include the states indented: ``` arr = [64, 34, 25, 12, 22, 11, 90] n = ?, i = ?, j = ? -set n to lenght of arr which is 7 +set n to length of arr which is 7 arr = [64, 34, 25, 12, 22, 11, 90] n = 7, i = ?, j = ? set i to 1 From 4bdf8eb412f78faf590e6c68e34f3cfd18cd22de Mon Sep 17 00:00:00 2001 From: sraver Date: Mon, 30 Oct 2023 20:30:35 +0100 Subject: [PATCH 12/13] Small corrections in the tutorials (#145) Changes have been made in three different chapters: - Part 1: Include installation of `jupyterlab` package to allow running the Lab - Part 2/Chapter 3: Remove outdated comment about the parameters of `wg` having to be a single variable - Part 2/Chapter 4: Update the variable and step type to match the newly created type of step --- book/part1_chapter5.ipynb | 1 + book/part2_chapter2.ipynb | 2 -- book/part2_chapter4.ipynb | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/book/part1_chapter5.ipynb b/book/part1_chapter5.ipynb index 9eb33613..2c54cc5d 100644 --- a/book/part1_chapter5.ipynb +++ b/book/part1_chapter5.ipynb @@ -36,6 +36,7 @@ "python3 -m venv .env # create virtual environment\n", "source .env/bin/activate # activate virtual environment\n", "pip install -r requirements.txt # install required python dependencies from requirements.txt\n", + "pip install jupyterlab # install required package to run the Jupyter Lab\n", "maturin develop # build the project with rust bindings\n", "```\n", "\n", diff --git a/book/part2_chapter2.ipynb b/book/part2_chapter2.ipynb index 33e22707..3b293152 100644 --- a/book/part2_chapter2.ipynb +++ b/book/part2_chapter2.ipynb @@ -159,8 +159,6 @@ "\n", "We didn't pass in witness values for \"c\", because they are calculated in `FiboStep` `wg`.\n", "\n", - "Note that we need to pass in witness value assignments in a single argument `args` and therefore we use a tuple in this case. `args` can really be any data type as long as it's one single argument.\n", - "\n", "After creating the first `FiboStep` instance, we loop over `FiboStep` instantiation for 3 more times, each time calculating and passing in a different tuple of assignments. Voila, here's our Fibonacci circuit with 4 `FiboStep` instances:" ] }, diff --git a/book/part2_chapter4.ipynb b/book/part2_chapter4.ipynb index d301f6b4..d3169487 100644 --- a/book/part2_chapter4.ipynb +++ b/book/part2_chapter4.ipynb @@ -43,7 +43,7 @@ "class Fibonacci(Circuit):\n", " def setup(self):\n", " # ...\n", - " self.fibo_step = self.step_type(FiboStep(self, \"fibo_step\"))\n", + " self.fibo_first_step = self.step_type(FiboFirstStep(self, \"fibo_first_step\"))\n", " self.pragma_first_step(self.fibo_first_step)\n", " \n", " def trace(self, args):\n", From 93e0fad13d0f92f7cf32a26b277be640d92461f1 Mon Sep 17 00:00:00 2001 From: Rute Figueiredo Date: Mon, 30 Oct 2023 21:03:27 +0000 Subject: [PATCH 13/13] Feature/optimise step selector builder (#154) ## Optimisation of Step Selector for Log2(n) Columns ### Description - **Goal:** is to optimise the Step Selector Builder to have Log2(n) Columns per n Step Types instead of one Column per each Step Type. - **Current Implementation:** implemented a new type of Selector and implemented the build function for it - **Explanation:** If we use binary representation for the step types instead of using the actual columns we will get an optimised version for the Selector Builder since we can get the total of columns from the following expression ```math n\_cols = \lceil \log_2(n\_step\_types + 1) \rceil ``` We need take into consideration that we also need to save one binary value for the case when there is no step type, because of this we do the math with \(n\_step\_types + 1\). ### What is missing - [x] Ensure compatibility with the backend - [x] Unit tests --------- Co-authored-by: nullbitx8 <92404251+nullbitx8@users.noreply.github.com> Co-authored-by: John Smith Co-authored-by: Leo Lara Co-authored-by: Jaewon In Co-authored-by: Steve Wang --- Cargo.toml | 2 +- src/plonkish/compiler/step_selector.rs | 151 +++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 44a9e6ef..c15cf949 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chiquito" -version = "0.1.2023101700" +version = "0.1.2023101100" edition = "2021" license = "MIT OR Apache-2.0" authors = ["Leo Lara "] diff --git a/src/plonkish/compiler/step_selector.rs b/src/plonkish/compiler/step_selector.rs index a95cdd60..e49b7f92 100644 --- a/src/plonkish/compiler/step_selector.rs +++ b/src/plonkish/compiler/step_selector.rs @@ -188,6 +188,64 @@ impl StepSelectorBuilder for TwoStepsSelectorBuilder { } } +#[derive(Debug, Default, Clone)] +pub struct LogNSelectorBuilder {} + +impl StepSelectorBuilder for LogNSelectorBuilder { + fn build(&self, unit: &mut CompilationUnit) { + let mut selector: StepSelector = StepSelector { + selector_expr: HashMap::new(), + selector_expr_not: HashMap::new(), + selector_assignment: HashMap::new(), + columns: Vec::new(), + }; + + let n_step_types = unit.step_types.len() as u64; + let n_cols = (n_step_types as f64 + 1.0).log2().ceil() as u64; + + let mut annotation; + for index in 0..n_cols { + annotation = format!("'binary selector column {}'", index); + + let column = Column::advice(annotation.clone(), 0); + selector.columns.push(column.clone()); + } + + let mut step_value = 1; + for step in unit.step_types.values() { + let mut combined_expr = PolyExpr::Const(F::ONE); + let mut assignments = Vec::new(); + + for i in 0..n_cols { + let bit = (step_value >> i) & 1; // Extract the i-th bit of step_value + let column = &selector.columns[i as usize]; + + if bit == 1 { + combined_expr = combined_expr * column.query(0, format!("Column {}", i)); + assignments.push((column.query(0, format!("Column {}", i)), F::ONE)); + } else { + combined_expr = combined_expr + * (PolyExpr::Const(F::ONE) - column.query(0, format!("Column {}", i))); + } + } + + selector + .selector_expr + .insert(step.uuid(), combined_expr.clone()); + selector + .selector_expr_not + .insert(step.uuid(), PolyExpr::Const(F::ONE) - combined_expr.clone()); + selector + .selector_assignment + .insert(step.uuid(), assignments); + step_value += 1; + } + + unit.columns.extend_from_slice(&selector.columns); + unit.selector = selector; + } +} + fn other_step_type(unit: &CompilationUnit, uuid: UUID) -> Option>> { for step_type in unit.step_types.values() { if step_type.uuid() != uuid { @@ -197,3 +255,96 @@ fn other_step_type(unit: &CompilationUnit, uuid: UUID) -> Option() -> CompilationUnit { + CompilationUnit::default() + } + + fn add_step_types_to_unit(unit: &mut CompilationUnit, n_step_types: usize) { + for i in 0..n_step_types { + let uuid_value = Uuid::now_v1(&[1, 2, 3, 4, 5, 6]).as_u128(); + unit.step_types.insert( + uuid_value, + Rc::new(StepType::new(uuid_value, format!("StepType{}", i))), + ); + } + } + + fn assert_common_tests(unit: &CompilationUnit, expected_cols: usize) { + assert_eq!(unit.columns.len(), expected_cols); + assert_eq!(unit.selector.columns.len(), expected_cols); + for step_type in unit.step_types.values() { + assert!(unit + .selector + .selector_assignment + .contains_key(&step_type.uuid())); + assert!(unit.selector.selector_expr.contains_key(&step_type.uuid())); + } + } + + #[test] + fn test_log_n_selector_builder_3_step_types() { + let builder = LogNSelectorBuilder {}; + let mut unit = mock_compilation_unit::(); + + add_step_types_to_unit(&mut unit, 3); + builder.build(&mut unit); + assert_common_tests(&unit, 2); + + // Asserts expressions for 3 step types + let expr10_temp = format!( + "(0x1 * {:#?} * (0x1 + (-{:#?})))", + &unit.selector.columns[0].query::(0, "Column 0"), + &unit.selector.columns[1].query::(0, "Column 1") + ); + let expr01_temp = format!( + "(0x1 * (0x1 + (-{:#?})) * {:#?})", + &unit.selector.columns[0].query::(0, "Column 0"), + &unit.selector.columns[1].query::(0, "Column 1") + ); + let expr11_temp = format!( + "(0x1 * {:#?} * {:#?})", + &unit.selector.columns[0].query::(0, "Column 0"), + &unit.selector.columns[1].query::(0, "Column 1") + ); + let expected_exprs = [expr01_temp.trim(), expr10_temp.trim(), expr11_temp.trim()]; + + for expr in unit.selector.selector_expr.values() { + let expr_str = format!("{:#?}", expr); + assert!( + expected_exprs.contains(&expr_str.trim()), + "Unexpected expression: {}", + expr_str + ); + } + } + + #[test] + fn test_log_n_selector_builder_4_step_types() { + let builder = LogNSelectorBuilder {}; + let mut unit = mock_compilation_unit::(); + + add_step_types_to_unit(&mut unit, 4); + builder.build(&mut unit); + assert_common_tests(&unit, 3); + } + + #[test] + fn test_log_n_selector_builder_10_step_types() { + let builder = LogNSelectorBuilder {}; + let mut unit = mock_compilation_unit::(); + + add_step_types_to_unit(&mut unit, 10); + builder.build(&mut unit); + + let expected_cols = (10_f64 + 1.0).log2().ceil() as usize; + assert_common_tests(&unit, expected_cols); + } +}