extern crate halo2; use std::marker::PhantomData; use halo2::{ arithmetic::FieldExt, circuit::{Cell, Chip, Layouter, Region, SimpleFloorPlanner}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, poly::Rotation, }; // ANCHOR: field-instructions /// A variable representing a number. #[derive(Clone)] struct Number { cell: Cell, value: Option, } trait FieldInstructions: AddInstructions + MulInstructions { /// Variable representing a number. type Num; /// Loads a number into the circuit as a private input. fn load_private( &self, layouter: impl Layouter, a: Option, ) -> Result<>::Num, Error>; /// Returns `d = (a + b) * c`. fn add_and_mul( &self, layouter: &mut impl Layouter, a: >::Num, b: >::Num, c: >::Num, ) -> Result<>::Num, Error>; /// Exposes a number as a public input to the circuit. fn expose_public( &self, layouter: impl Layouter, num: >::Num, row: usize, ) -> Result<(), Error>; } // ANCHOR_END: field-instructions // ANCHOR: add-instructions trait AddInstructions: Chip { /// Variable representing a number. type Num; /// Returns `c = a + b`. fn add( &self, layouter: impl Layouter, a: Self::Num, b: Self::Num, ) -> Result; } // ANCHOR_END: add-instructions // ANCHOR: mul-instructions trait MulInstructions: Chip { /// Variable representing a number. type Num; /// Returns `c = a * b`. fn mul( &self, layouter: impl Layouter, a: Self::Num, b: Self::Num, ) -> Result; } // ANCHOR_END: mul-instructions // ANCHOR: field-config // The top-level config that provides all necessary columns and permutations // for the other configs. #[derive(Clone, Debug)] struct FieldConfig { /// For this chip, we will use two advice columns to implement our instructions. /// These are also the columns through which we communicate with other parts of /// the circuit. advice: [Column; 2], /// Public inputs instance: Column, add_config: AddConfig, mul_config: MulConfig, } // ANCHOR END: field-config // ANCHOR: add-config #[derive(Clone, Debug)] struct AddConfig { advice: [Column; 2], s_add: Selector, } // ANCHOR_END: add-config // ANCHOR: mul-config #[derive(Clone, Debug)] struct MulConfig { advice: [Column; 2], s_mul: Selector, } // ANCHOR END: mul-config // ANCHOR: field-chip /// The top-level chip that will implement the `FieldInstructions`. struct FieldChip { config: FieldConfig, _marker: PhantomData, } // ANCHOR_END: field-chip // ANCHOR: add-chip struct AddChip { config: AddConfig, _marker: PhantomData, } // ANCHOR END: add-chip // ANCHOR: mul-chip struct MulChip { config: MulConfig, _marker: PhantomData, } // ANCHOR_END: mul-chip // ANCHOR: add-chip-trait-impl impl Chip for AddChip { type Config = AddConfig; type Loaded = (); fn config(&self) -> &Self::Config { &self.config } fn loaded(&self) -> &Self::Loaded { &() } } // ANCHOR END: add-chip-trait-impl // ANCHOR: add-chip-impl impl AddChip { fn construct(config: >::Config, _loaded: >::Loaded) -> Self { Self { config, _marker: PhantomData, } } fn configure( meta: &mut ConstraintSystem, advice: [Column; 2], ) -> >::Config { let s_add = meta.selector(); // Define our addition gate! meta.create_gate("add", |meta| { let lhs = meta.query_advice(advice[0], Rotation::cur()); let rhs = meta.query_advice(advice[1], Rotation::cur()); let out = meta.query_advice(advice[0], Rotation::next()); let s_add = meta.query_selector(s_add); vec![s_add * (lhs + rhs - out)] }); AddConfig { advice, s_add } } } // ANCHOR END: add-chip-impl // ANCHOR: add-instructions-impl impl AddInstructions for FieldChip { type Num = Number; fn add( &self, layouter: impl Layouter, a: Self::Num, b: Self::Num, ) -> Result { let config = self.config().add_config.clone(); let add_chip = AddChip::::construct(config, ()); add_chip.add(layouter, a, b) } } impl AddInstructions for AddChip { type Num = Number; fn add( &self, mut layouter: impl Layouter, a: Self::Num, b: Self::Num, ) -> Result { let config = self.config(); let mut out = None; layouter.assign_region( || "add", |mut region: Region<'_, F>| { // We only want to use a single multiplication gate in this region, // so we enable it at region offset 0; this means it will constrain // cells at offsets 0 and 1. config.s_add.enable(&mut region, 0)?; // The inputs we've been given could be located anywhere in the circuit, // but we can only rely on relative offsets inside this region. So we // assign new cells inside the region and constrain them to have the // same values as the inputs. let lhs = region.assign_advice( || "lhs", config.advice[0], 0, || a.value.ok_or(Error::SynthesisError), )?; let rhs = region.assign_advice( || "rhs", config.advice[1], 0, || b.value.ok_or(Error::SynthesisError), )?; region.constrain_equal(a.cell, lhs)?; region.constrain_equal(b.cell, rhs)?; // Now we can assign the multiplication result into the output position. let value = a.value.and_then(|a| b.value.map(|b| a + b)); let cell = region.assign_advice( || "lhs * rhs", config.advice[0], 1, || value.ok_or(Error::SynthesisError), )?; // Finally, we return a variable representing the output, // to be used in another part of the circuit. out = Some(Number { cell, value }); Ok(()) }, )?; Ok(out.unwrap()) } } // ANCHOR END: add-instructions-impl // ANCHOR: mul-chip-trait-impl impl Chip for MulChip { type Config = MulConfig; type Loaded = (); fn config(&self) -> &Self::Config { &self.config } fn loaded(&self) -> &Self::Loaded { &() } } // ANCHOR END: mul-chip-trait-impl // ANCHOR: mul-chip-impl impl MulChip { fn construct(config: >::Config, _loaded: >::Loaded) -> Self { Self { config, _marker: PhantomData, } } fn configure( meta: &mut ConstraintSystem, advice: [Column; 2], ) -> >::Config { for column in &advice { meta.enable_equality((*column).into()); } let s_mul = meta.selector(); // Define our multiplication gate! meta.create_gate("mul", |meta| { // To implement multiplication, we need three advice cells and a selector // cell. We arrange them like so: // // | a0 | a1 | s_mul | // |-----|-----|-------| // | lhs | rhs | s_mul | // | out | | | // // Gates may refer to any relative offsets we want, but each distinct // offset adds a cost to the proof. The most common offsets are 0 (the // current row), 1 (the next row), and -1 (the previous row), for which // `Rotation` has specific constructors. let lhs = meta.query_advice(advice[0], Rotation::cur()); let rhs = meta.query_advice(advice[1], Rotation::cur()); let out = meta.query_advice(advice[0], Rotation::next()); let s_mul = meta.query_selector(s_mul); // The polynomial expression returned from `create_gate` will be // constrained by the proving system to equal zero. Our expression // has the following properties: // - When s_mul = 0, any value is allowed in lhs, rhs, and out. // - When s_mul != 0, this constrains lhs * rhs = out. vec![s_mul * (lhs * rhs - out)] }); MulConfig { advice, s_mul } } } // ANCHOR_END: mul-chip-impl // ANCHOR: mul-instructions-impl impl MulInstructions for FieldChip { type Num = Number; fn mul( &self, layouter: impl Layouter, a: Self::Num, b: Self::Num, ) -> Result { let config = self.config().mul_config.clone(); let mul_chip = MulChip::::construct(config, ()); mul_chip.mul(layouter, a, b) } } impl MulInstructions for MulChip { type Num = Number; fn mul( &self, mut layouter: impl Layouter, a: Self::Num, b: Self::Num, ) -> Result { let config = self.config(); let mut out = None; layouter.assign_region( || "mul", |mut region: Region<'_, F>| { // We only want to use a single multiplication gate in this region, // so we enable it at region offset 0; this means it will constrain // cells at offsets 0 and 1. config.s_mul.enable(&mut region, 0)?; // The inputs we've been given could be located anywhere in the circuit, // but we can only rely on relative offsets inside this region. So we // assign new cells inside the region and constrain them to have the // same values as the inputs. let lhs = region.assign_advice( || "lhs", config.advice[0], 0, || a.value.ok_or(Error::SynthesisError), )?; let rhs = region.assign_advice( || "rhs", config.advice[1], 0, || b.value.ok_or(Error::SynthesisError), )?; region.constrain_equal(a.cell, lhs)?; region.constrain_equal(b.cell, rhs)?; // Now we can assign the multiplication result into the output position. let value = a.value.and_then(|a| b.value.map(|b| a * b)); let cell = region.assign_advice( || "lhs * rhs", config.advice[0], 1, || value.ok_or(Error::SynthesisError), )?; // Finally, we return a variable representing the output, // to be used in another part of the circuit. out = Some(Number { cell, value }); Ok(()) }, )?; Ok(out.unwrap()) } } // ANCHOR END: mul-instructions-impl // ANCHOR: field-chip-trait-impl impl Chip for FieldChip { type Config = FieldConfig; type Loaded = (); fn config(&self) -> &Self::Config { &self.config } fn loaded(&self) -> &Self::Loaded { &() } } // ANCHOR_END: field-chip-trait-impl // ANCHOR: field-chip-impl impl FieldChip { fn construct(config: >::Config, _loaded: >::Loaded) -> Self { Self { config, _marker: PhantomData, } } fn configure( meta: &mut ConstraintSystem, advice: [Column; 2], instance: Column, ) -> >::Config { let add_config = AddChip::configure(meta, advice); let mul_config = MulChip::configure(meta, advice); meta.enable_equality(instance.into()); FieldConfig { advice, instance, add_config, mul_config, } } } // ANCHOR_END: field-chip-impl // ANCHOR: field-instructions-impl impl FieldInstructions for FieldChip { type Num = Number; fn load_private( &self, mut layouter: impl Layouter, value: Option, ) -> Result<>::Num, Error> { let config = self.config(); let mut num = None; layouter.assign_region( || "load private", |mut region| { let cell = region.assign_advice( || "private input", config.advice[0], 0, || value.ok_or(Error::SynthesisError), )?; num = Some(Number { cell, value }); Ok(()) }, )?; Ok(num.unwrap()) } /// Returns `d = (a + b) * c`. fn add_and_mul( &self, layouter: &mut impl Layouter, a: >::Num, b: >::Num, c: >::Num, ) -> Result<>::Num, Error> { let ab = self.add(layouter.namespace(|| "a + b"), a, b)?; self.mul(layouter.namespace(|| "(a + b) * c"), ab, c) } fn expose_public( &self, mut layouter: impl Layouter, num: >::Num, row: usize, ) -> Result<(), Error> { let config = self.config(); layouter.constrain_instance(num.cell, config.instance, row) } } // ANCHOR_END: field-instructions-impl // ANCHOR: circuit /// The full circuit implementation. /// /// In this struct we store the private input variables. We use `Option` because /// they won't have any value during key generation. During proving, if any of these /// were `None` we would get an error. #[derive(Default)] struct MyCircuit { a: Option, b: Option, c: Option, } impl Circuit for MyCircuit { // Since we are using a single chip for everything, we can just reuse its config. type Config = FieldConfig; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { Self::default() } fn configure(meta: &mut ConstraintSystem) -> Self::Config { // We create the two advice columns that FieldChip uses for I/O. let advice = [meta.advice_column(), meta.advice_column()]; // We also need an instance column to store public inputs. let instance = meta.instance_column(); FieldChip::configure(meta, advice, instance) } fn synthesize( &self, config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { let field_chip = FieldChip::::construct(config, ()); // Load our private values into the circuit. let a = field_chip.load_private(layouter.namespace(|| "load a"), self.a)?; let b = field_chip.load_private(layouter.namespace(|| "load b"), self.b)?; let c = field_chip.load_private(layouter.namespace(|| "load c"), self.c)?; // Use `add_and_mul` to get `d = (a + b) * c`. let d = field_chip.add_and_mul(&mut layouter, a, b, c)?; // Expose the result as a public input to the circuit. field_chip.expose_public(layouter.namespace(|| "expose d"), d, 0) } } // ANCHOR_END: circuit #[allow(clippy::many_single_char_names)] fn main() { use halo2::{dev::MockProver, pasta::Fp}; // ANCHOR: test-circuit // The number of rows in our circuit cannot exceed 2^k. Since our example // circuit is very small, we can pick a very small value here. let k = 4; // Prepare the private and public inputs to the circuit! let a = Fp::rand(); let b = Fp::rand(); let c = Fp::rand(); let d = (a + b) * c; // Instantiate the circuit with the private inputs. let circuit = MyCircuit { a: Some(a), b: Some(b), c: Some(c), }; // Arrange the public input. We expose the multiplication result in row 0 // of the instance column, so we position it there in our public inputs. let mut public_inputs = vec![d]; // Given the correct public input, our circuit will verify. let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); assert_eq!(prover.verify(), Ok(())); // If we try some other public input, the proof will fail! public_inputs[0] += Fp::one(); let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); assert!(prover.verify().is_err()); // ANCHOR_END: test-circuit }