diff --git a/eigentrust-zk/README.md b/eigentrust-zk/README.md new file mode 100644 index 00000000..51534af0 --- /dev/null +++ b/eigentrust-zk/README.md @@ -0,0 +1,222 @@ +# EigenTrust ZK + +This crate contains all the Chips, Chipsets and Circuit related to EigenTrust protocol. +There are 2 main traits that we use to atomically make chips: + +1) Chip +```rust +/// Trait for an atomic chip implementation +/// Each chip uses common config columns, but has its own selector +pub trait Chip { + /// Output of the synthesis + type Output: Clone; + /// Gate configuration, using common config columns + fn configure(common: &CommonConfig, meta: &mut ConstraintSystem) -> Selector; + /// Chip synthesis. This function can return an assigned cell to be used + /// elsewhere in the circuit + fn synthesize( + self, common: &CommonConfig, selector: &Selector, layouter: impl Layouter, + ) -> Result; +} +``` +Supposed to be the lowest-level primitive and the place where gates are defined. +Specifically, the gates are defined in the `configure` function, +which accepts a `CommonConfig`, and returns a single selector, that will be used to activate this gate. +Example usage: +```rust +/// Structure for the main chip. +pub struct MainChip { + advice: [AssignedCell; NUM_ADVICE], + fixed: [F; NUM_FIXED], +} + +impl MainChip { + /// Assigns a new witness that is equal to boolean AND of `x` and `y` + pub fn new(advice: [AssignedCell; NUM_ADVICE], fixed: [F; NUM_FIXED]) -> Self { + Self { advice, fixed } + } +} + +impl Chip for MainChip { + type Output = (); + + fn configure(common: &CommonConfig, meta: &mut ConstraintSystem) -> Selector { + let selector = meta.selector(); + + meta.create_gate("main gate", |v_cells| { + // MainGate constraints + let a = v_cells.query_advice(common.advice[0], Rotation::cur()); + let b = v_cells.query_advice(common.advice[1], Rotation::cur()); + let c = v_cells.query_advice(common.advice[2], Rotation::cur()); + + ... + }); + selector + } + + fn synthesize( + self, common: &CommonConfig, selector: &Selector, mut layouter: impl Layouter, + ) -> Result { + layouter.assign_region( + || "main gate", + |region| { + let mut ctx = RegionCtx::new(region, 0); + + ctx.enable(*selector)?; + + // e.g.: + ctx.assign_advice(common.advice[0], self.advice[0]); + + ... + }, + ) + } +} +``` + +2) Chipset +```rust +/// Chipset uses a collection of chips as primitives to build more abstract +/// circuits +pub trait Chipset { + /// Config used for synthesis + type Config: Clone; + /// Output of the synthesis + type Output: Clone; + /// Chipset synthesis. This function can have multiple smaller chips + /// synthesised inside. Also can returns an assigned cell. + fn synthesize( + self, common: &CommonConfig, config: &Self::Config, layouter: impl Layouter, + ) -> Result; +} +``` +Is one level higher that a `Chip` and underneath it should call one or multiple chips. +`Config` should contain the selectors for the underlying chips. +It also accepts `CommonConfig` that will be passed down to chips. +```rust +/// Main config for common primitives like `add`, `mul` ... +#[derive(Debug, Clone)] +pub struct MainConfig { + selector: Selector, +} + +impl MainConfig { + /// Initialization function for MainConfig + pub fn new(selector: Selector) -> Self { + Self { selector } + } +} + +/// Chip for addition operation +pub struct AddChipset { + x: AssignedCell, + y: AssignedCell, +} + +impl AddChipset { + /// Create new AddChipset + pub fn new(x: AssignedCell, y: AssignedCell) -> Self { + Self { x, y } + } +} + +impl Chipset for AddChipset { + type Config = MainConfig; + type Output = AssignedCell; + + fn synthesize( + self, common: &CommonConfig, config: &Self::Config, mut layouter: impl Layouter, + ) -> Result { + /// e.g.: + let advices = [self.x, self.y, self.x + self.y, zero, zero]; + let fixed = [F::ONE, F::ONE, -F::ONE, F::ZERO, F::ZERO, F::ZERO, F::ZERO, F::ZERO]; + let main_chip = MainChip::new(advices, fixed); + main_chip.synthesize(common, &config.selector, layouter.namespace(|| "main_add"))?; + + Ok(sum) + } +} +``` + +Finally, `CommonConfig` is a predefined struct containing shared set of advice and fixed columns. +Currently, it is fixed to 20 advice, 10 fixed, 1 table and 1 instance column. +```rust +/// Number of advice columns in common config +pub const ADVICE: usize = 20; +/// Number of fixed columns in common config +pub const FIXED: usize = 10; + +/// Common config for the whole circuit +#[derive(Clone, Debug)] +pub struct CommonConfig { + /// Advice columns + advice: [Column; ADVICE], + /// Fixed columns + fixed: [Column; FIXED], + /// Table column + table: TableColumn, + /// Instance column + instance: Column, +} + +impl CommonConfig { + /// Create a new `CommonConfig` columns + pub fn new(meta: &mut ConstraintSystem) -> Self { + let advice = [(); ADVICE].map(|_| meta.advice_column()); + let fixed = [(); FIXED].map(|_| meta.fixed_column()); + let table = meta.lookup_table_column(); + let instance = meta.instance_column(); + + advice.map(|c| meta.enable_equality(c)); + fixed.map(|c| meta.enable_constant(c)); + meta.enable_equality(instance); + + Self { advice, fixed, table, instance } + } +} +``` + +This config is constructed once in the main circuit and passed down to every chip and chipset. +Example: +```rust +impl SomeCircuit { + type Config = SomeConfig; + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let common = CommonConfig::new(meta); + let main = MainChip::configure(&common, meta); + let bits2num_selector = Bits2NumChip::configure(&common, meta); + let set_selector = SetChip::configure(&common, meta); + ... + } +} +``` +Each chip that accepts `CommonConfig` has the responsability to pick column that it needs for enforcing constraints. +(NOTE: This is a design flaw - the higher-level circuit should be the one picking the columns based on the requirements of the chips/chipsets.) + +Additional utils: `RegionCtx` + +RegionCtx is a wrapper around Halo2's vanilla Region API. Example usage: +```rust +let mut ctx = RegionCtx::new(region, 0); +// Enabling selectors +ctx.enable(*selector)?; +// Assign advice columm from instance +let assigned_inst = ctx.assign_from_instance(common.advice[0], common.instance, 0)?; +// Assign from constant +let assigned_one = ctx.assign_from_constant(common.advice[1], F::ONE)?; +// Assign to advice column +let assigned_res = ctx.assign_advice(common.advice[2], some_value)?; +// Copy assign to advice column +let assigned_x = ctx.copy_assign(common.advice[3], self.x)?; +// Assigned to fixed column +let assigned_zero = ctx.assign_fixed(common.fixed[0], F::ZERO)?; +// Constrain equality +ctx.constrain_equal(assigned_x, assigned_zero)?; +// Constrain to constant +ctx.constrain_to_constant(assigned_one, F::ONE)?; +// Move to next row +ctx.next(); +// Return back to region +let region = ctx.into_region(); +```