From 74e617b46d159edb9a45cab05eaf7afc64f60aae Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 19 Jun 2021 11:20:15 +0800 Subject: [PATCH] chip::generator_table.rs: Load Sinsemilla generator lookup table. The 2^K table of generators used in the Sinsemilla hash. These are loaded into a lookup table. --- src/circuit/gadget/sinsemilla/chip.rs | 12 +- .../gadget/sinsemilla/chip/generator_table.rs | 127 ++++++++++++++++++ src/primitives/sinsemilla/constants.rs | 7 +- 3 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 src/circuit/gadget/sinsemilla/chip/generator_table.rs diff --git a/src/circuit/gadget/sinsemilla/chip.rs b/src/circuit/gadget/sinsemilla/chip.rs index 95c26af3..da6a079b 100644 --- a/src/circuit/gadget/sinsemilla/chip.rs +++ b/src/circuit/gadget/sinsemilla/chip.rs @@ -23,9 +23,9 @@ use pasta_curves::pallas; use std::convert::TryInto; -// mod generator_table; -// pub use generator_table::get_s_by_idx; -// use generator_table::GeneratorTableConfig; +mod generator_table; +pub use generator_table::get_s_by_idx; +use generator_table::GeneratorTableConfig; // mod hash_to_point; @@ -54,6 +54,9 @@ pub struct SinsemillaConfig { // Advice column used to store the lambda_2 intermediate value at each // iteration. lambda_2: Column, + // The lookup table where (idx, x_p, y_p) are loaded for the 2^K generators + // of the Sinsemilla hash. + generator_table: GeneratorTableConfig, // Fixed column shared by the whole circuit. This is used to load the // x-coordinate of the domain Q, which is then constrained to equal the // initial x_a. @@ -89,7 +92,8 @@ impl SinsemillaChip { config: SinsemillaConfig, layouter: &mut impl Layouter, ) -> Result<>::Loaded, Error> { - todo!() + // Load the lookup table. + config.generator_table.load(layouter) } #[allow(clippy::too_many_arguments)] diff --git a/src/circuit/gadget/sinsemilla/chip/generator_table.rs b/src/circuit/gadget/sinsemilla/chip/generator_table.rs new file mode 100644 index 00000000..8104434a --- /dev/null +++ b/src/circuit/gadget/sinsemilla/chip/generator_table.rs @@ -0,0 +1,127 @@ +use crate::primitives::sinsemilla::{self, sinsemilla_s_generators, S_PERSONALIZATION}; +use halo2::{ + circuit::Layouter, + plonk::{Column, ConstraintSystem, Error, Expression, Fixed}, + poly::Rotation, +}; + +use pasta_curves::{ + arithmetic::{CurveAffine, CurveExt, FieldExt}, + pallas, +}; + +use group::Curve; + +/// Table containing independent generators S[0..2^k] +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +pub struct GeneratorTableConfig { + table_idx: Column, + table_x: Column, + table_y: Column, +} + +impl GeneratorTableConfig { + #[allow(clippy::too_many_arguments)] + #[allow(non_snake_case)] + // Even though the lookup table can be used in other parts of the circuit, + // this specific configuration sets up Sinsemilla-specific constraints + // controlled by `q_sinsemilla`, and would likely not apply to other chips. + pub fn configure(meta: &mut ConstraintSystem, config: super::SinsemillaConfig) { + let (table_idx, table_x, table_y) = ( + config.generator_table.table_idx, + config.generator_table.table_x, + config.generator_table.table_y, + ); + + meta.lookup(|meta| { + let q_s1 = meta.query_selector(config.q_sinsemilla1); + let q_s2 = meta.query_fixed(config.q_sinsemilla2, Rotation::cur()); + + let table_idx_cur = meta.query_fixed(table_idx, Rotation::cur()); + let table_x_cur = meta.query_fixed(table_x, Rotation::cur()); + let table_y_cur = meta.query_fixed(table_y, Rotation::cur()); + + // m_{i+1} = z_{i} - 2^K * q_s2 * z_{i + 1} + // Note that the message words m_i's are 1-indexed while the + // running sum z_i's are 0-indexed. + let word = { + let z_cur = meta.query_advice(config.bits, Rotation::cur()); + let z_next = meta.query_advice(config.bits, Rotation::next()); + z_cur - (q_s2 * z_next * pallas::Base::from_u64(1 << sinsemilla::K)) + }; + + let x_p = meta.query_advice(config.x_p, Rotation::cur()); + + // y_{p,i} = (Y_{A,i} / 2) - lambda1 * (x_{A,i} - x_{R,i}), + // where Y_{A,i} = (lambda1_i + lambda2_i) * (x_{A,i} - x_{R,i}), + // x_{R,i} = lambda1^2 - x_{A,i} - x_{P,i} + // + let y_p = { + let lambda1 = meta.query_advice(config.lambda_1, Rotation::cur()); + let lambda2 = meta.query_advice(config.lambda_2, Rotation::cur()); + let x_a = meta.query_advice(config.x_a, Rotation::cur()); + + let x_r = lambda1.clone().square() - x_a.clone() - x_p.clone(); + let Y_A = (lambda1.clone() + lambda2) * (x_a.clone() - x_r.clone()); + + (Y_A * pallas::Base::TWO_INV) * (lambda1 * (x_a - x_r)) + }; + + // Lookup expressions default to the first entry when `q_s1` + // is not enabled. + let (init_x, init_y) = { + let init_p = get_s_by_idx(0).to_affine().coordinates().unwrap(); + (*init_p.x(), *init_p.y()) + }; + let not_q_s1 = Expression::Constant(pallas::Base::one()) - q_s1.clone(); + + let m = q_s1.clone() * word; // The first table index is 0. + let x_p = q_s1.clone() * x_p + not_q_s1.clone() * init_x; + let y_p = q_s1 * y_p + not_q_s1 * init_y; + + vec![(m, table_idx_cur), (x_p, table_x_cur), (y_p, table_y_cur)] + }); + } + + pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + layouter.assign_region( + || "generator_table", + |mut gate| { + // We generate the row values lazily (we only need them during keygen). + let mut rows = sinsemilla_s_generators(); + + for index in 0..(1 << sinsemilla::K) { + let mut row = None; + gate.assign_fixed( + || "table_idx", + self.table_idx, + index, + || { + row = rows.next(); + Ok(pallas::Base::from_u64(index as u64)) + }, + )?; + gate.assign_fixed( + || "table_x", + self.table_x, + index, + || row.map(|(x, _)| x).ok_or(Error::SynthesisError), + )?; + gate.assign_fixed( + || "table_y", + self.table_y, + index, + || row.map(|(_, y)| y).ok_or(Error::SynthesisError), + )?; + } + Ok(()) + }, + ) + } +} + +/// Get generator S by index +pub fn get_s_by_idx(idx: u32) -> pallas::Point { + let hash = pallas::Point::hash_to_curve(S_PERSONALIZATION); + hash(&idx.to_le_bytes()) +} diff --git a/src/primitives/sinsemilla/constants.rs b/src/primitives/sinsemilla/constants.rs index 7b422dbe..5593aab2 100644 --- a/src/primitives/sinsemilla/constants.rs +++ b/src/primitives/sinsemilla/constants.rs @@ -1,6 +1,7 @@ //! Sinsemilla generators use group::Curve; use halo2::arithmetic::{CurveAffine, CurveExt}; +use pasta_curves::pallas; /// Number of bits of each message piece in $\mathsf{SinsemillaHashToPoint}$ pub const K: usize = 10; @@ -62,8 +63,8 @@ pub const Q_MERKLE_CRH: ([u8; 32], [u8; 32]) = ( pub const S_PERSONALIZATION: &str = "z.cash:SinsemillaS"; /// Creates the Sinsemilla S generators used in each round of the Sinsemilla hash -pub fn sinsemilla_s_generators() -> impl Iterator { - let hasher = C::CurveExt::hash_to_curve(S_PERSONALIZATION); +pub fn sinsemilla_s_generators() -> impl Iterator { + let hasher = pallas::Point::hash_to_curve(S_PERSONALIZATION); (0..(1u32 << K)).map(move |j| { let point = hasher(&j.to_le_bytes()).to_affine().coordinates().unwrap(); (*point.x(), *point.y()) @@ -84,7 +85,7 @@ mod tests { #[test] fn sinsemilla_s() { use super::super::sinsemilla_s::SINSEMILLA_S; - let sinsemilla_s: Vec<_> = sinsemilla_s_generators::().collect(); + let sinsemilla_s: Vec<_> = sinsemilla_s_generators().collect(); assert_eq!(sinsemilla_s.len(), SINSEMILLA_S.len()); for (expected, actual) in sinsemilla_s.iter().zip(&SINSEMILLA_S[..]) { assert_eq!(expected, actual);