Skip to content
This repository has been archived by the owner on Nov 23, 2023. It is now read-only.

Latest commit

 

History

History

eigentrust-zk

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

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
/// Trait for an atomic chip implementation
/// Each chip uses common config columns, but has its own selector
pub trait Chip<F: FieldExt> {
	/// Output of the synthesis
	type Output: Clone;
	/// Gate configuration, using common config columns
	fn configure(common: &CommonConfig, meta: &mut ConstraintSystem<F>) -> 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<F>,
	) -> Result<Self::Output, Error>;
}

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:

/// Structure for the main chip.
pub struct MainChip<F: FieldExt> {
	advice: [AssignedCell<F, F>; NUM_ADVICE],
	fixed: [F; NUM_FIXED],
}

impl<F: FieldExt> MainChip<F> {
	/// Assigns a new witness that is equal to boolean AND of `x` and `y`
	pub fn new(advice: [AssignedCell<F, F>; NUM_ADVICE], fixed: [F; NUM_FIXED]) -> Self {
		Self { advice, fixed }
	}
}

impl<F: FieldExt> Chip<F> for MainChip<F> {
	type Output = ();

	fn configure(common: &CommonConfig, meta: &mut ConstraintSystem<F>) -> 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<F>,
	) -> Result<Self::Output, Error> {
		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]);

				...
			},
		)
	}
}
  1. Chipset
/// Chipset uses a collection of chips as primitives to build more abstract
/// circuits
pub trait Chipset<F: FieldExt> {
	/// 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<F>,
	) -> Result<Self::Output, Error>;
}

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.

/// 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<F: FieldExt> {
	x: AssignedCell<F, F>,
	y: AssignedCell<F, F>,
}

impl<F: FieldExt> AddChipset<F> {
	/// Create new AddChipset
	pub fn new(x: AssignedCell<F, F>, y: AssignedCell<F, F>) -> Self {
		Self { x, y }
	}
}

impl<F: FieldExt> Chipset<F> for AddChipset<F> {
	type Config = MainConfig;
	type Output = AssignedCell<F, F>;

	fn synthesize(
		self, common: &CommonConfig, config: &Self::Config, mut layouter: impl Layouter<F>,
	) -> Result<Self::Output, Error> {
	    /// 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.

/// 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>; ADVICE],
	/// Fixed columns
	fixed: [Column<Fixed>; FIXED],
	/// Table column
	table: TableColumn,
	/// Instance column
	instance: Column<Instance>,
}

impl CommonConfig {
	/// Create a new `CommonConfig` columns
	pub fn new<F: FieldExt>(meta: &mut ConstraintSystem<F>) -> 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:

impl SomeCircuit {
    type Config = SomeConfig;

    fn configure(meta: &mut ConstraintSystem<N>) -> 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:

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();