diff --git a/.gitignore b/.gitignore index 123de7b25..11874446a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ Cargo.lock .DS_Store layout.png -serialization-test.pk +serialization-example.vk +serialization-example.pk diff --git a/halo2_backend/src/plonk/circuit.rs b/halo2_backend/src/plonk/circuit.rs index a754bf4bf..5dd6aa06e 100644 --- a/halo2_backend/src/plonk/circuit.rs +++ b/halo2_backend/src/plonk/circuit.rs @@ -366,3 +366,47 @@ fn shuffle_argument_required_degree(arg: &shuffle::Argume // (1 - (l_last + l_blind)) (z(\omega X) (s(X) + \gamma) - z(X) (a(X) + \gamma)) std::cmp::max(2 + shuffle_degree, 2 + input_degree) } + +#[cfg(test)] +mod tests { + use super::ExpressionBack; + use halo2curves::bn256::Fr; + + #[test] + fn expressionback_iter_sum() { + let exprs: Vec> = vec![ + ExpressionBack::Constant(1.into()), + ExpressionBack::Constant(2.into()), + ExpressionBack::Constant(3.into()), + ]; + let happened: ExpressionBack = exprs.into_iter().sum(); + let expected: ExpressionBack = ExpressionBack::Sum( + Box::new(ExpressionBack::Sum( + Box::new(ExpressionBack::Constant(1.into())), + Box::new(ExpressionBack::Constant(2.into())), + )), + Box::new(ExpressionBack::Constant(3.into())), + ); + + assert_eq!(happened, expected); + } + + #[test] + fn expressionback_iter_product() { + let exprs: Vec> = vec![ + ExpressionBack::Constant(1.into()), + ExpressionBack::Constant(2.into()), + ExpressionBack::Constant(3.into()), + ]; + let happened: ExpressionBack = exprs.into_iter().product(); + let expected: ExpressionBack = ExpressionBack::Product( + Box::new(ExpressionBack::Product( + Box::new(ExpressionBack::Constant(1.into())), + Box::new(ExpressionBack::Constant(2.into())), + )), + Box::new(ExpressionBack::Constant(3.into())), + ); + + assert_eq!(happened, expected); + } +} diff --git a/halo2_frontend/Cargo.toml b/halo2_frontend/Cargo.toml index 4b3b91662..cecb8896d 100644 --- a/halo2_frontend/Cargo.toml +++ b/halo2_frontend/Cargo.toml @@ -43,6 +43,9 @@ tabbycat = { version = "0.1", features = ["attributes"], optional = true } proptest = "1" rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } serde_json = "1" +tracing-subscriber = { version = "0.3" } +tracing = "0.1" +tracing-capture = "0.1" [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/halo2_frontend/src/dev/cost.rs b/halo2_frontend/src/dev/cost.rs index 870a7e600..68d883e18 100644 --- a/halo2_frontend/src/dev/cost.rs +++ b/halo2_frontend/src/dev/cost.rs @@ -555,6 +555,12 @@ mod tests { Ok(()) } } - CircuitCost::::measure(K, &MyCircuit).proof_size(1); + let cost = CircuitCost::::measure(K, &MyCircuit); + + let marginal_proof_size = cost.marginal_proof_size(); + assert_eq!(usize::from(marginal_proof_size), 0); + + let proof_size = cost.proof_size(1); + assert_eq!(usize::from(proof_size), 608); } } diff --git a/halo2_frontend/src/dev/failure.rs b/halo2_frontend/src/dev/failure.rs index a11d382af..ad7c76235 100644 --- a/halo2_frontend/src/dev/failure.rs +++ b/halo2_frontend/src/dev/failure.rs @@ -876,3 +876,445 @@ impl VerifyFailure { } } } + +#[cfg(test)] +mod tests { + use halo2_middleware::poly::Rotation; + use halo2curves::pasta::Fp; + + use crate::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Error, Fixed, Selector}, + }; + + use super::*; + + #[test] + fn test_failure_constraint_not_satisfied() { + const K: u32 = 4; + + #[derive(Clone)] + struct FaultyCircuitConfig { + a: Column, + b: Column, + c: Column, + d: Column, + q: Selector, + } + + struct FaultyCircuit {} + + impl Circuit for FaultyCircuit { + type Config = FaultyCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let a = meta.advice_column(); + let b = meta.advice_column(); + let c = meta.advice_column(); + let d = meta.fixed_column(); + let q = meta.selector(); + + meta.create_gate("Equality check", |cells| { + let a = cells.query_advice(a, Rotation::cur()); + let b = cells.query_advice(b, Rotation::cur()); + let c = cells.query_advice(c, Rotation::cur()); + let d = cells.query_fixed(d, Rotation::cur()); + let q = cells.query_selector(q); + + // If q is enabled, a and b must be assigned to. + vec![q * (a - b) * (c - d)] + }); + + FaultyCircuitConfig { a, b, c, d, q } + } + + fn without_witnesses(&self) -> Self { + Self {} + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "Correct synthesis", + |mut region| { + // Enable the equality gate. + config.q.enable(&mut region, 0)?; + + // Assign a = 1. + region.assign_advice(|| "a", config.a, 0, || Value::known(Fp::one()))?; + + // Assign b = 1. + region.assign_advice(|| "b", config.b, 0, || Value::known(Fp::one()))?; + + // Assign c = 5. + region.assign_advice( + || "c", + config.c, + 0, + || Value::known(Fp::from(5u64)), + )?; + // Assign d = 7. + region.assign_fixed( + || "d", + config.d, + 0, + || Value::known(Fp::from(7u64)), + )?; + Ok(()) + }, + )?; + layouter.assign_region( + || "Wrong synthesis", + |mut region| { + // Enable the equality gate. + config.q.enable(&mut region, 0)?; + + // Assign a = 1. + region.assign_advice(|| "a", config.a, 0, || Value::known(Fp::one()))?; + + // Assign b = 0. + region.assign_advice(|| "b", config.b, 0, || Value::known(Fp::zero()))?; + + // Name Column a + region.name_column(|| "This is Advice!", config.a); + // Name Column b + region.name_column(|| "This is Advice too!", config.b); + + // Assign c = 5. + region.assign_advice( + || "c", + config.c, + 0, + || Value::known(Fp::from(5u64)), + )?; + // Assign d = 7. + region.assign_fixed( + || "d", + config.d, + 0, + || Value::known(Fp::from(7u64)), + )?; + + // Name Column c + region.name_column(|| "Another one!", config.c); + // Name Column d + region.name_column(|| "This is a Fixed!", config.d); + + // Note that none of the terms cancel eachother. Therefore we will have a constraint that is non satisfied for + // the `Equalty check` gate. + Ok(()) + }, + ) + } + } + + let prover = MockProver::run(K, &FaultyCircuit {}, vec![]).unwrap(); + let errs = prover.verify().unwrap_err(); + + assert_eq!(errs.len(), 1); + // debug printing + assert_eq!( + format!("{:?}", errs[0]), + r#####"ConstraintCaseDebug { + constraint: Constraint { + gate: Gate { + index: 0, + name: "Equality check", + }, + index: 0, + name: "", + }, + location: InRegion { + region: Region 1 ('Wrong synthesis'), + offset: 0, + }, + cell_values: [ + ( + DebugVirtualCell { + name: "", + column: DebugColumn { + column_type: Advice, + index: 0, + annotation: "This is Advice!", + }, + rotation: 0, + }, + "1", + ), + ( + DebugVirtualCell { + name: "", + column: DebugColumn { + column_type: Advice, + index: 1, + annotation: "This is Advice too!", + }, + rotation: 0, + }, + "0", + ), + ( + DebugVirtualCell { + name: "", + column: DebugColumn { + column_type: Advice, + index: 2, + annotation: "Another one!", + }, + rotation: 0, + }, + "0x5", + ), + ( + DebugVirtualCell { + name: "", + column: DebugColumn { + column_type: Fixed, + index: 0, + annotation: "This is a Fixed!", + }, + rotation: 0, + }, + "0x7", + ), + ], +}"##### + ); + // display printing + assert_eq!( + format!("{}", errs[0]), + r#####"Constraint 0 in gate 0 ('Equality check') is not satisfied in Region 1 ('Wrong synthesis') at offset 0 +- Column('Advice', 0 - This is Advice!)@0 = 1 +- Column('Advice', 1 - This is Advice too!)@0 = 0 +- Column('Advice', 2 - Another one!)@0 = 0x5 +- Column('Fixed', 0 - This is a Fixed!)@0 = 0x7 +"##### + ); + } + + #[test] + fn test_failure_cell_not_assigned() { + const K: u32 = 4; + + #[derive(Clone)] + struct FaultyCircuitConfig { + a: Column, + b: Column, + c: Column, + d: Column, + q: Selector, + } + + struct FaultyCircuit {} + + impl Circuit for FaultyCircuit { + type Config = FaultyCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let a = meta.advice_column(); + let b = meta.advice_column(); + let c = meta.advice_column(); + let d = meta.fixed_column(); + let q = meta.selector(); + + meta.create_gate("Equality check", |cells| { + let a = cells.query_advice(a, Rotation::cur()); + let b = cells.query_advice(b, Rotation::cur()); + let c = cells.query_advice(c, Rotation::cur()); + let d = cells.query_fixed(d, Rotation::cur()); + let q = cells.query_selector(q); + + // If q is enabled, a and b must be assigned to. + vec![q * (a - b) * (c - d)] + }); + + FaultyCircuitConfig { a, b, c, d, q } + } + + fn without_witnesses(&self) -> Self { + Self {} + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "Correct synthesis", + |mut region| { + // Enable the equality gate. + config.q.enable(&mut region, 0)?; + + // Assign a = 1. + region.assign_advice(|| "a", config.a, 0, || Value::known(Fp::one()))?; + + // Assign b = 1. + region.assign_advice(|| "b", config.b, 0, || Value::known(Fp::one()))?; + + // Assign c = 5. + region.assign_advice( + || "c", + config.c, + 0, + || Value::known(Fp::from(5u64)), + )?; + // Assign d = 7. + region.assign_fixed( + || "d", + config.d, + 0, + || Value::known(Fp::from(7u64)), + )?; + Ok(()) + }, + )?; + layouter.assign_region( + || "Wrong synthesis", + |mut region| { + // Enable the equality gate. + config.q.enable(&mut region, 0)?; + + // Assign a = 1. + region.assign_advice(|| "a", config.a, 0, || Value::known(Fp::one()))?; + + // Name Column a + region.name_column(|| "This is Advice!", config.a); + // Name Column b + region.name_column(|| "This is Advice too!", config.b); + + // Assign c = 5. + region.assign_advice( + || "c", + config.c, + 0, + || Value::known(Fp::from(5u64)), + )?; + // Assign d = 7. + region.assign_fixed( + || "d", + config.d, + 0, + || Value::known(Fp::from(7u64)), + )?; + + // Name Column c + region.name_column(|| "Another one!", config.c); + // Name Column d + region.name_column(|| "This is a Fixed!", config.d); + + // Note that none of the terms cancel eachother. Therefore we will have a constraint that is non satisfied for + // the `Equalty check` gate. + Ok(()) + }, + ) + } + } + + let prover = MockProver::run(K, &FaultyCircuit {}, vec![]).unwrap(); + let errs = prover.verify().unwrap_err(); + + assert_eq!(errs.len(), 2); + assert_eq!( + format!("{}", errs[0]), + r#####"Region 1 ('Wrong synthesis') uses Gate 0 ('Equality check') at offset 1, which requires cell in column Column { index: 1, column_type: Advice } at offset 0 with annotation Some("This is Advice too!") to be assigned."##### + ); + } + + #[test] + fn test_failure_instance_cell_not_assigned() { + const K: u32 = 4; + + #[derive(Clone)] + struct FaultyCircuitConfig { + a: Column, + b: Column, + c: Column, + d: Column, + q: Selector, + } + + struct FaultyCircuit {} + + impl Circuit for FaultyCircuit { + type Config = FaultyCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let a = meta.advice_column(); + let b = meta.advice_column(); + let c = meta.advice_column(); + let d = meta.instance_column(); + let q = meta.selector(); + + meta.enable_equality(c); + meta.enable_equality(d); + + meta.create_gate("Addition check", |cells| { + let a = cells.query_advice(a, Rotation::cur()); + let b = cells.query_advice(b, Rotation::cur()); + let c = cells.query_advice(c, Rotation::cur()); + let q = cells.query_selector(q); + + vec![q * (a + b - c)] + }); + + FaultyCircuitConfig { a, b, c, d, q } + } + + fn without_witnesses(&self) -> Self { + Self {} + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "Instance value not assigned", + |mut region| { + // Enable the equality gate. + config.q.enable(&mut region, 0)?; + + // Assign a = 1. + region.assign_advice(|| "a", config.a, 0, || Value::known(Fp::one()))?; + + // Assign b = 1. + region.assign_advice(|| "b", config.b, 0, || Value::known(Fp::one()))?; + + // Assign & constraint c = 2. + region.assign_advice_from_instance(|| "c", config.d, 0, config.c, 0)?; + + Ok(()) + }, + )?; + Ok(()) + } + } + + let prover = MockProver::run(K, &FaultyCircuit {}, vec![vec![]]).unwrap(); + let errs = prover.verify().unwrap_err(); + + assert_eq!(errs.len(), 1); + assert_eq!( + format!("{}", errs[0]), + r#####"Constraint 0 in gate 0 ('Addition check') is not satisfied in Region 0 ('Instance value not assigned') at offset 0 +- Column('Advice', 0 - )@0 = 1 +- Column('Advice', 1 - )@0 = 1 +- Column('Advice', 2 - )@0 = 0 +"##### + ); + } +} diff --git a/halo2_frontend/src/dev/gates.rs b/halo2_frontend/src/dev/gates.rs index 5c48bbed4..6ca620463 100644 --- a/halo2_frontend/src/dev/gates.rs +++ b/halo2_frontend/src/dev/gates.rs @@ -308,3 +308,83 @@ impl fmt::Display for CircuitGates { writeln!(f, "Total multiplications: {}", self.total_multiplications) } } + +#[cfg(test)] +mod tests { + use halo2_middleware::poly::Rotation; + use halo2curves::pasta::Fp; + + use crate::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Advice, Column, Error, Fixed, Selector}, + }; + + use super::*; + + #[test] + fn test_circuit_gates() { + #[allow(unused)] + #[derive(Clone)] + struct TestCircuitConfig { + a: Column, + b: Column, + c: Column, + d: Column, + q: Selector, + } + + struct TestCircuit {} + + impl Circuit for TestCircuit { + type Config = TestCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let a = meta.advice_column(); + let b = meta.advice_column(); + let c = meta.advice_column(); + let d = meta.fixed_column(); + let q = meta.selector(); + + meta.create_gate("Equality check", |cells| { + let a = cells.query_advice(a, Rotation::cur()); + let b = cells.query_advice(b, Rotation::cur()); + let c = cells.query_advice(c, Rotation::cur()); + let d = cells.query_fixed(d, Rotation::cur()); + let q = cells.query_selector(q); + + // If q is enabled, a and b must be assigned to. + vec![q * (a - b) * (c - d)] + }); + + TestCircuitConfig { a, b, c, d, q } + } + + fn without_witnesses(&self) -> Self { + Self {} + } + + fn synthesize(&self, _: Self::Config, _: impl Layouter) -> Result<(), Error> { + unreachable!(); + } + } + + let gates = CircuitGates::collect::( + #[cfg(feature = "circuit-params")] + (), + ); + assert_eq!( + format!("{}", gates), + r#####"Equality check: +- (S0 * (A0@0 - A1@0)) * (A2@0 - F0@0) +Total gates: 1 +Total custom constraint polynomials: 1 +Total negations: 2 +Total additions: 2 +Total multiplications: 2 +"##### + ) + } +} diff --git a/halo2_frontend/src/dev/tfp.rs b/halo2_frontend/src/dev/tfp.rs index 0d02d1d2b..cee9a4128 100644 --- a/halo2_frontend/src/dev/tfp.rs +++ b/halo2_frontend/src/dev/tfp.rs @@ -509,3 +509,128 @@ impl<'cs, F: Field, CS: Assignment> Assignment for TracingAssignment<'cs, // We exit namespace spans in TracingLayouter. } } + +#[cfg(test)] +mod tests { + use halo2_middleware::poly::Rotation; + use halo2curves::pasta::Fp; + + use crate::{circuit::SimpleFloorPlanner, dev::MockProver}; + + use super::*; + + #[test] + fn test_tracing_floor_planner() { + use tracing_capture::{CaptureLayer, SharedStorage}; + use tracing_subscriber::layer::SubscriberExt; + + const K: u32 = 4; + + #[derive(Clone)] + struct TestCircuitConfig { + a: Column, + b: Column, + c: Column, + d: Column, + q: Selector, + } + + struct TestCircuit {} + + impl Circuit for TestCircuit { + type Config = TestCircuitConfig; + type FloorPlanner = TracingFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let a = meta.advice_column(); + let b = meta.advice_column(); + let c = meta.advice_column(); + let d = meta.fixed_column(); + let q = meta.selector(); + + meta.create_gate("Equality check", |cells| { + let a = cells.query_advice(a, Rotation::cur()); + let b = cells.query_advice(b, Rotation::cur()); + let c = cells.query_advice(c, Rotation::cur()); + let d = cells.query_fixed(d, Rotation::cur()); + let q = cells.query_selector(q); + + // If q is enabled, a and b must be assigned to. + vec![q * (a - b) * (c - d)] + }); + + TestCircuitConfig { a, b, c, d, q } + } + + fn without_witnesses(&self) -> Self { + Self {} + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "Correct synthesis", + |mut region| { + // Enable the equality gate. + config.q.enable(&mut region, 0)?; + + // Assign a = 1. + region.assign_advice(|| "a", config.a, 0, || Value::known(Fp::one()))?; + + // Assign b = 1. + region.assign_advice(|| "b", config.b, 0, || Value::known(Fp::one()))?; + + // Assign c = 5. + region.assign_advice( + || "c", + config.c, + 0, + || Value::known(Fp::from(5u64)), + )?; + // Assign d = 7. + region.assign_fixed( + || "d", + config.d, + 0, + || Value::known(Fp::from(7u64)), + )?; + Ok(()) + }, + )?; + + Ok(()) + } + } + + // At the start of your test, enable tracing. + // + // Following setup of `SharedStorage` and `tracing_subscriber::fmt` is just for this test. + // For real tests, you don't need to do this. + // Check the example in the doc of [`TracingFloorPlanner`] for details. + let subscriber = tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .with_ansi(false) + .without_time() + .finish(); + let storage = SharedStorage::default(); + let subscriber = subscriber.with(CaptureLayer::new(&storage)); + tracing::subscriber::set_global_default(subscriber).unwrap(); + + // run the prover to check if every step is traced. + let prover = MockProver::run(K, &TestCircuit {}, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + + // check if all tracing logs are captured + let storage = storage.lock(); + assert_eq!(storage.all_spans().len(), 12); + for span in storage.all_spans() { + let metadata = span.metadata(); + assert_eq!(*metadata.level(), tracing::Level::DEBUG); + } + } +} diff --git a/halo2_middleware/src/expression.rs b/halo2_middleware/src/expression.rs index 1cbb557ed..f0c202f13 100644 --- a/halo2_middleware/src/expression.rs +++ b/halo2_middleware/src/expression.rs @@ -65,7 +65,7 @@ impl Expression { } } - pub fn write_identifier(&self, writer: &mut W) -> std::io::Result<()> { + fn write_identifier(&self, writer: &mut W) -> std::io::Result<()> { match self { Expression::Constant(scalar) => write!(writer, "{scalar:?}"), Expression::Var(v) => v.write_identifier(writer), @@ -172,3 +172,48 @@ impl Product for Expression { .unwrap_or(Expression::Constant(F::ONE)) } } + +#[cfg(test)] +mod tests { + + use crate::circuit::ExpressionMid; + use halo2curves::bn256::Fr; + + #[test] + fn iter_sum() { + let exprs: Vec> = vec![ + ExpressionMid::Constant(1.into()), + ExpressionMid::Constant(2.into()), + ExpressionMid::Constant(3.into()), + ]; + let happened: ExpressionMid = exprs.into_iter().sum(); + let expected: ExpressionMid = ExpressionMid::Sum( + Box::new(ExpressionMid::Sum( + Box::new(ExpressionMid::Constant(1.into())), + Box::new(ExpressionMid::Constant(2.into())), + )), + Box::new(ExpressionMid::Constant(3.into())), + ); + + assert_eq!(happened, expected); + } + + #[test] + fn iter_product() { + let exprs: Vec> = vec![ + ExpressionMid::Constant(1.into()), + ExpressionMid::Constant(2.into()), + ExpressionMid::Constant(3.into()), + ]; + let happened: ExpressionMid = exprs.into_iter().product(); + let expected: ExpressionMid = ExpressionMid::Product( + Box::new(ExpressionMid::Product( + Box::new(ExpressionMid::Constant(1.into())), + Box::new(ExpressionMid::Constant(2.into())), + )), + Box::new(ExpressionMid::Constant(3.into())), + ); + + assert_eq!(happened, expected); + } +} diff --git a/halo2_proofs/examples/circuit-cost.rs b/halo2_proofs/examples/circuit-cost.rs new file mode 100644 index 000000000..bafcf6afa --- /dev/null +++ b/halo2_proofs/examples/circuit-cost.rs @@ -0,0 +1,122 @@ +use halo2_frontend::dev::CircuitCost; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, ErrorFront, Fixed, Instance}, + poly::Rotation, +}; +use halo2curves::pasta::{Eq, Fp}; + +#[derive(Clone, Copy)] +struct StandardPlonkConfig { + a: Column, + b: Column, + c: Column, + q_a: Column, + q_b: Column, + q_c: Column, + q_ab: Column, + constant: Column, + #[allow(dead_code)] + instance: Column, +} + +impl StandardPlonkConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [a, b, c] = [(); 3].map(|_| meta.advice_column()); + let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); + let instance = meta.instance_column(); + + [a, b, c].map(|column| meta.enable_equality(column)); + + meta.create_gate( + "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", + |meta| { + let [a, b, c] = [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); + let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] + .map(|column| meta.query_fixed(column, Rotation::cur())); + let instance = meta.query_instance(instance, Rotation::cur()); + Some( + q_a * a.clone() + + q_b * b.clone() + + q_c * c + + q_ab * a * b + + constant + + instance, + ) + }, + ); + + StandardPlonkConfig { + a, + b, + c, + q_a, + q_b, + q_c, + q_ab, + constant, + instance, + } + } +} + +#[derive(Clone, Default)] +struct StandardPlonk(Fp); + +impl Circuit for StandardPlonk { + type Config = StandardPlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + StandardPlonkConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), ErrorFront> { + layouter.assign_region( + || "", + |mut region| { + region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; + region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fp::one()))?; + + region.assign_advice(|| "", config.a, 1, || Value::known(-Fp::from(5u64)))?; + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed(|| "", column, 1, || Value::known(Fp::from(idx as u64)))?; + } + + let a = region.assign_advice(|| "", config.a, 2, || Value::known(Fp::one()))?; + a.copy_advice(|| "", &mut region, config.b, 3)?; + a.copy_advice(|| "", &mut region, config.c, 4)?; + Ok(()) + }, + ) + } +} + +fn main() { + const K: u32 = 6; + let circuit = StandardPlonk(Fp::one()); + + let cost = CircuitCost::::measure(K, &circuit); + + let marginal_proof_size = cost.marginal_proof_size(); + println!("Marginal proof size: {}", usize::from(marginal_proof_size)); + + let proof_size = cost.proof_size(1); + println!("Proof size: {}", usize::from(proof_size)); +} diff --git a/halo2_proofs/examples/serialization.rs b/halo2_proofs/examples/serialization.rs new file mode 100644 index 000000000..e1cdd9ee4 --- /dev/null +++ b/halo2_proofs/examples/serialization.rs @@ -0,0 +1,171 @@ +use std::{ + fs::File, + io::{BufReader, BufWriter, Write}, +}; + +use ff::Field; +use halo2_debug::test_rng; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{ + keygen_pk_custom, keygen_vk_custom, pk_read, vk_read, Advice, Circuit, Column, + ConstraintSystem, ErrorFront, Fixed, Instance, ProvingKey, VerifyingKey, + }, + poly::{kzg::commitment::ParamsKZG, Rotation}, + SerdeFormat, +}; +use halo2curves::bn256::{Bn256, Fr, G1Affine}; + +#[derive(Clone, Copy)] +struct StandardPlonkConfig { + a: Column, + b: Column, + c: Column, + q_a: Column, + q_b: Column, + q_c: Column, + q_ab: Column, + constant: Column, + #[allow(dead_code)] + instance: Column, +} + +impl StandardPlonkConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [a, b, c] = [(); 3].map(|_| meta.advice_column()); + let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); + let instance = meta.instance_column(); + + [a, b, c].map(|column| meta.enable_equality(column)); + + meta.create_gate( + "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", + |meta| { + let [a, b, c] = [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); + let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] + .map(|column| meta.query_fixed(column, Rotation::cur())); + let instance = meta.query_instance(instance, Rotation::cur()); + Some( + q_a * a.clone() + + q_b * b.clone() + + q_c * c + + q_ab * a * b + + constant + + instance, + ) + }, + ); + + StandardPlonkConfig { + a, + b, + c, + q_a, + q_b, + q_c, + q_ab, + constant, + instance, + } + } +} + +#[derive(Clone, Default)] +struct StandardPlonk(Fr); + +impl Circuit for StandardPlonk { + type Config = StandardPlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + StandardPlonkConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), ErrorFront> { + layouter.assign_region( + || "", + |mut region| { + region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; + region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fr::one()))?; + + region.assign_advice(|| "", config.a, 1, || Value::known(-Fr::from(5u64)))?; + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed(|| "", column, 1, || Value::known(Fr::from(idx as u64)))?; + } + + let a = region.assign_advice(|| "", config.a, 2, || Value::known(Fr::one()))?; + a.copy_advice(|| "", &mut region, config.b, 3)?; + a.copy_advice(|| "", &mut region, config.c, 4)?; + Ok(()) + }, + ) + } +} + +fn setup( + k: u32, + compress_selectors: bool, +) -> (StandardPlonk, VerifyingKey, ProvingKey) { + let mut rng = test_rng(); + + let circuit = StandardPlonk(Fr::random(&mut rng)); + let params = ParamsKZG::::setup(k, &mut rng); + + let vk = keygen_vk_custom(¶ms, &circuit, compress_selectors).expect("vk should not fail"); + let pk = keygen_pk_custom(¶ms, vk.clone(), &circuit, compress_selectors) + .expect("pk should not fail"); + + (circuit, vk, pk) +} + +fn main() { + let k = 4; + let compress_selectors = true; + + let (circuit, vk, pk) = setup(k, compress_selectors); + + // choose (de)serialization format + let format = SerdeFormat::RawBytes; + + // serialization for vk + let f = File::create("serialization-example.vk").unwrap(); + let mut writer = BufWriter::new(f); + vk.write(&mut writer, format).unwrap(); + writer.flush().unwrap(); + + // deserialization for vk + let f = File::open("serialization-example.vk").unwrap(); + let mut reader = BufReader::new(f); + let _vk = + vk_read::(&mut reader, format, k, &circuit, compress_selectors) + .unwrap(); + + // serialization for pk + let f = File::create("serialization-example.pk").unwrap(); + let mut writer = BufWriter::new(f); + pk.write(&mut writer, format).unwrap(); + writer.flush().unwrap(); + + // deserialization for pk + let f = File::open("serialization-example.pk").unwrap(); + let mut reader = BufReader::new(f); + let _pk = + pk_read::(&mut reader, format, k, &circuit, compress_selectors) + .unwrap(); +} diff --git a/halo2_proofs/tests/serialization.rs b/halo2_proofs/tests/serialization.rs index becb80d86..b0e8ef4c4 100644 --- a/halo2_proofs/tests/serialization.rs +++ b/halo2_proofs/tests/serialization.rs @@ -8,8 +8,8 @@ use halo2_debug::test_rng; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, plonk::{ - create_proof, keygen_pk, keygen_vk_custom, pk_read, verify_proof, Advice, Circuit, Column, - ConstraintSystem, ErrorFront, Fixed, Instance, + create_proof, keygen_pk, keygen_vk_custom, pk_read, verify_proof, vk_read, Advice, Circuit, + Column, ConstraintSystem, ErrorFront, Fixed, Instance, }, poly::{ kzg::{ @@ -141,8 +141,29 @@ fn test_serialization() { let compress_selectors = true; let vk = keygen_vk_custom(¶ms, &circuit, compress_selectors) .expect("vk should not fail"); - let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail"); + let pk = keygen_pk(¶ms, vk.clone(), &circuit).expect("pk should not fail"); + // serialization test for vk + let f = File::create("serialization-test.vk").unwrap(); + let mut writer = BufWriter::new(f); + vk.write(&mut writer, SerdeFormat::RawBytes).unwrap(); + writer.flush().unwrap(); + + let f = File::open("serialization-test.vk").unwrap(); + let mut reader = BufReader::new(f); + #[allow(clippy::unit_arg)] + let vk = vk_read::( + &mut reader, + SerdeFormat::RawBytes, + k, + &circuit, + compress_selectors, + ) + .unwrap(); + + std::fs::remove_file("serialization-test.vk").unwrap(); + + // serialization test for pk let f = File::create("serialization-test.pk").unwrap(); let mut writer = BufWriter::new(f); pk.write(&mut writer, SerdeFormat::RawBytes).unwrap(); @@ -162,6 +183,7 @@ fn test_serialization() { std::fs::remove_file("serialization-test.pk").unwrap(); + // create & verify proof let instances: Vec>> = vec![vec![vec![circuit.0]]]; let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); create_proof::< @@ -193,7 +215,7 @@ fn test_serialization() { SingleStrategy, >( &verifier_params, - pk.get_vk(), + &vk, strategy, instances.as_slice(), &mut transcript