//! Chip implementations for the Sinsemilla gadgets. use super::{ message::{Message, MessagePiece}, primitives as sinsemilla, CommitDomains, HashDomains, SinsemillaInstructions, }; use crate::{ ecc::{ chip::{DoubleAndAdd, NonIdentityEccPoint}, FixedPoints, }, utilities::lookup_range_check::LookupRangeCheckConfig, }; use std::marker::PhantomData; use halo2_proofs::{ circuit::{AssignedCell, Chip, Layouter, Value}, plonk::{ Advice, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector, TableColumn, VirtualCells, }, poly::Rotation, }; use pasta_curves::pallas; mod generator_table; use generator_table::GeneratorTableConfig; mod hash_to_point; /// Configuration for the Sinsemilla hash chip #[derive(Eq, PartialEq, Clone, Debug)] pub struct SinsemillaConfig where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, { /// Binary selector used in lookup argument and in the body of the Sinsemilla hash. q_sinsemilla1: Selector, /// Non-binary selector used in lookup argument and in the body of the Sinsemilla hash. q_sinsemilla2: Column, /// q_sinsemilla2 is used to define a synthetic selector, /// q_sinsemilla3 = (q_sinsemilla2) ⋅ (q_sinsemilla2 - 1) /// Simple selector used to constrain hash initialization to be consistent with /// the y-coordinate of the domain $Q$. q_sinsemilla4: Selector, /// Fixed column used to load the y-coordinate of the domain $Q$. fixed_y_q: Column, /// Logic specific to merged double-and-add. double_and_add: DoubleAndAdd, /// Advice column used to load the message. bits: Column, /// Advice column used to witness message pieces. This may or may not be the same /// column as `bits`. witness_pieces: Column, /// The lookup table where $(\mathsf{idx}, x_p, y_p)$ are loaded for the $2^K$ /// generators of the Sinsemilla hash. pub(super) generator_table: GeneratorTableConfig, /// An advice column configured to perform lookup range checks. lookup_config: LookupRangeCheckConfig, _marker: PhantomData<(Hash, Commit, F)>, } impl SinsemillaConfig where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, { /// Returns an array of all advice columns in this config, in arbitrary order. pub(super) fn advices(&self) -> [Column; 5] { [ self.double_and_add.x_a, self.double_and_add.x_p, self.bits, self.double_and_add.lambda_1, self.double_and_add.lambda_2, ] } /// Returns the lookup range check config used in this config. pub fn lookup_config(&self) -> LookupRangeCheckConfig { self.lookup_config } /// Derives the expression `q_s3 = (q_s2) * (q_s2 - 1)`. fn q_s3(&self, meta: &mut VirtualCells) -> Expression { let one = Expression::Constant(pallas::Base::one()); let q_s2 = meta.query_fixed(self.q_sinsemilla2); q_s2.clone() * (q_s2 - one) } } /// A chip that implements 10-bit Sinsemilla using a lookup table and 5 advice columns. /// /// [Chip description](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html#plonk--halo-2-constraints). #[derive(Eq, PartialEq, Clone, Debug)] pub struct SinsemillaChip where Hash: HashDomains, Fixed: FixedPoints, Commit: CommitDomains, { config: SinsemillaConfig, } impl Chip for SinsemillaChip where Hash: HashDomains, Fixed: FixedPoints, Commit: CommitDomains, { type Config = SinsemillaConfig; type Loaded = (); fn config(&self) -> &Self::Config { &self.config } fn loaded(&self) -> &Self::Loaded { &() } } impl SinsemillaChip where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, { /// Reconstructs this chip from the given config. pub fn construct(config: >::Config) -> Self { Self { config } } /// Loads the lookup table required by this chip into the circuit. pub fn load( config: SinsemillaConfig, layouter: &mut impl Layouter, ) -> Result<>::Loaded, Error> { // Load the lookup table. config.generator_table.load(layouter) } /// # Side-effects /// /// All columns in `advices` and will be equality-enabled. #[allow(clippy::too_many_arguments)] #[allow(non_snake_case)] pub fn configure( meta: &mut ConstraintSystem, advices: [Column; 5], witness_pieces: Column, fixed_y_q: Column, lookup: (TableColumn, TableColumn, TableColumn), range_check: LookupRangeCheckConfig, ) -> >::Config { // Enable equality on all advice columns for advice in advices.iter() { meta.enable_equality(*advice); } let config = SinsemillaConfig:: { q_sinsemilla1: meta.complex_selector(), q_sinsemilla2: meta.fixed_column(), q_sinsemilla4: meta.selector(), fixed_y_q, double_and_add: DoubleAndAdd { x_a: advices[0], x_p: advices[1], lambda_1: advices[3], lambda_2: advices[4], }, bits: advices[2], witness_pieces, generator_table: GeneratorTableConfig { table_idx: lookup.0, table_x: lookup.1, table_y: lookup.2, }, lookup_config: range_check, _marker: PhantomData, }; // Set up lookup argument GeneratorTableConfig::configure(meta, config.clone()); let two = pallas::Base::from(2); // Closures for expressions that are derived multiple times // x_r = lambda_1^2 - x_a - x_p let x_r = |meta: &mut VirtualCells, rotation| { config.double_and_add.x_r(meta, rotation) }; // Y_A = (lambda_1 + lambda_2) * (x_a - x_r) let Y_A = |meta: &mut VirtualCells, rotation| { config.double_and_add.Y_A(meta, rotation) }; // Check that the initial x_A, x_P, lambda_1, lambda_2 are consistent with y_Q. // https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial meta.create_gate("Initial y_Q", |meta| { let q_s4 = meta.query_selector(config.q_sinsemilla4); let y_q = meta.query_fixed(config.fixed_y_q); // Y_A = (lambda_1 + lambda_2) * (x_a - x_r) let Y_A_cur = Y_A(meta, Rotation::cur()); // 2 * y_q - Y_{A,0} = 0 let init_y_q_check = y_q * two - Y_A_cur; Constraints::with_selector(q_s4, Some(("init_y_q_check", init_y_q_check))) }); // https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial meta.create_gate("Sinsemilla gate", |meta| { let q_s1 = meta.query_selector(config.q_sinsemilla1); let q_s3 = config.q_s3(meta); let lambda_1_next = meta.query_advice(config.double_and_add.lambda_1, Rotation::next()); let lambda_2_cur = meta.query_advice(config.double_and_add.lambda_2, Rotation::cur()); let x_a_cur = meta.query_advice(config.double_and_add.x_a, Rotation::cur()); let x_a_next = meta.query_advice(config.double_and_add.x_a, Rotation::next()); // x_r = lambda_1^2 - x_a_cur - x_p let x_r = x_r(meta, Rotation::cur()); // Y_A = (lambda_1 + lambda_2) * (x_a - x_r) let Y_A_cur = Y_A(meta, Rotation::cur()); // Y_A = (lambda_1 + lambda_2) * (x_a - x_r) let Y_A_next = Y_A(meta, Rotation::next()); // lambda2^2 - (x_a_next + x_r + x_a_cur) = 0 let secant_line = lambda_2_cur.clone().square() - (x_a_next.clone() + x_r + x_a_cur.clone()); // lhs - rhs = 0, where // - lhs = 4 * lambda_2_cur * (x_a_cur - x_a_next) // - rhs = (2 * Y_A_cur + (2 - q_s3) * Y_A_next + 2 * q_s3 * y_a_final) let y_check = { // lhs = 4 * lambda_2_cur * (x_a_cur - x_a_next) let lhs = lambda_2_cur * pallas::Base::from(4) * (x_a_cur - x_a_next); // rhs = 2 * Y_A_cur + (2 - q_s3) * Y_A_next + 2 * q_s3 * y_a_final let rhs = { // y_a_final is assigned to the lambda1 column on the next offset. let y_a_final = lambda_1_next; Y_A_cur * two + (Expression::Constant(two) - q_s3.clone()) * Y_A_next + q_s3 * two * y_a_final }; lhs - rhs }; Constraints::with_selector(q_s1, [("Secant line", secant_line), ("y check", y_check)]) }); config } } // Implement `SinsemillaInstructions` for `SinsemillaChip` impl SinsemillaInstructions for SinsemillaChip where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, { type CellValue = AssignedCell; type Message = Message; type MessagePiece = MessagePiece; type RunningSum = Vec; type X = AssignedCell; type NonIdentityPoint = NonIdentityEccPoint; type FixedPoints = F; type HashDomains = Hash; type CommitDomains = Commit; fn witness_message_piece( &self, mut layouter: impl Layouter, field_elem: Value, num_words: usize, ) -> Result { let config = self.config().clone(); let cell = layouter.assign_region( || "witness message piece", |mut region| { region.assign_advice( || "witness message piece", config.witness_pieces, 0, || field_elem, ) }, )?; Ok(MessagePiece::new(cell, num_words)) } #[allow(non_snake_case)] #[allow(clippy::type_complexity)] fn hash_to_point( &self, mut layouter: impl Layouter, Q: pallas::Affine, message: Self::Message, ) -> Result<(Self::NonIdentityPoint, Vec), Error> { layouter.assign_region( || "hash_to_point", |mut region| self.hash_message(&mut region, Q, &message), ) } fn extract(point: &Self::NonIdentityPoint) -> Self::X { point.x() } }