diff --git a/halo2_gadgets/src/ecc.rs b/halo2_gadgets/src/ecc.rs index a03eaa141a..1d1a4a48e3 100644 --- a/halo2_gadgets/src/ecc.rs +++ b/halo2_gadgets/src/ecc.rs @@ -596,7 +596,6 @@ pub(crate) mod tests { FixedPoints, }; use crate::{ - sinsemilla::primitives as sinsemilla, utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig}, }; diff --git a/halo2_gadgets/src/ecc/chip.rs b/halo2_gadgets/src/ecc/chip.rs index df8e1bb124..76d5a019a7 100644 --- a/halo2_gadgets/src/ecc/chip.rs +++ b/halo2_gadgets/src/ecc/chip.rs @@ -136,7 +136,7 @@ impl From for EccPoint { #[allow(non_snake_case)] pub struct EccConfig< FixedPoints: super::FixedPoints, - LookupRangeCheckConfig: DefaultLookupRangeCheck, + Lookup: DefaultLookupRangeCheck, > { /// Advice columns needed by instructions in the ECC chip. pub advices: [Column; 10], @@ -148,20 +148,20 @@ pub struct EccConfig< add: add::Config, /// Variable-base scalar multiplication - mul: mul::Config, + mul: mul::Config, /// Fixed-base full-width scalar multiplication mul_fixed_full: mul_fixed::full_width::Config, /// Fixed-base signed short scalar multiplication pub(crate) mul_fixed_short: mul_fixed::short::Config, /// Fixed-base mul using a base field element as a scalar - mul_fixed_base_field: mul_fixed::base_field_elem::Config, + mul_fixed_base_field: mul_fixed::base_field_elem::Config, /// Witness point pub(crate) witness_point: witness_point::Config, /// Lookup range check using 10-bit lookup table - pub lookup_config: LookupRangeCheckConfig, + pub lookup_config: Lookup, } /// A trait representing the kind of scalar used with a particular `FixedPoint`. @@ -229,17 +229,17 @@ pub trait FixedPoint: std::fmt::Debug + Eq + Clone { #[derive(Clone, Debug, Eq, PartialEq)] pub struct EccChip< FixedPoints: super::FixedPoints, - LookupRangeCheckConfig: DefaultLookupRangeCheck, + Lookup: DefaultLookupRangeCheck, > { - config: EccConfig, + config: EccConfig, } impl< FixedPoints: super::FixedPoints, - LookupRangeCheckConfig: DefaultLookupRangeCheck, - > Chip for EccChip + Lookup: DefaultLookupRangeCheck, + > Chip for EccChip { - type Config = EccConfig; + type Config = EccConfig; type Loaded = (); fn config(&self) -> &Self::Config { @@ -253,16 +253,16 @@ impl< impl< Fixed: super::FixedPoints, - LookupRangeCheckConfig: DefaultLookupRangeCheck, - > UtilitiesInstructions for EccChip + Lookup: DefaultLookupRangeCheck, + > UtilitiesInstructions for EccChip { type Var = AssignedCell; } impl< FixedPoints: super::FixedPoints, - LookupRangeCheckConfig: DefaultLookupRangeCheck, - > EccChip + Lookup: DefaultLookupRangeCheck, + > EccChip { /// Reconstructs this chip from the given config. pub fn construct(config: >::Config) -> Self { @@ -277,7 +277,7 @@ impl< meta: &mut ConstraintSystem, advices: [Column; 10], lagrange_coeffs: [Column; 8], - range_check: LookupRangeCheckConfig, + range_check: Lookup, ) -> >::Config { // Create witness point gate let witness_point = witness_point::Config::configure(meta, advices[0], advices[1]); @@ -315,7 +315,7 @@ impl< // Create gate that is only used in fixed-base mul using a base field element. let mul_fixed_base_field = - mul_fixed::base_field_elem::Config::::configure( + mul_fixed::base_field_elem::Config::::configure( meta, advices[6..9].try_into().unwrap(), range_check, @@ -421,8 +421,8 @@ pub enum ScalarVar { FullWidth, } -impl, LookupRangeCheckConfig: DefaultLookupRangeCheck> - EccInstructions for EccChip +impl, Lookup: DefaultLookupRangeCheck> + EccInstructions for EccChip where >::Base: FixedPoint, @@ -609,8 +609,8 @@ where } } -impl, LookupRangeCheckConfig: DefaultLookupRangeCheck> - BaseFitsInScalarInstructions for EccChip +impl, Lookup: DefaultLookupRangeCheck> + BaseFitsInScalarInstructions for EccChip where >::Base: FixedPoint, diff --git a/halo2_gadgets/src/ecc/chip/mul.rs b/halo2_gadgets/src/ecc/chip/mul.rs index b4b6d55518..61f36f88e9 100644 --- a/halo2_gadgets/src/ecc/chip/mul.rs +++ b/halo2_gadgets/src/ecc/chip/mul.rs @@ -46,7 +46,7 @@ const INCOMPLETE_LO_LEN: usize = INCOMPLETE_LEN - INCOMPLETE_HI_LEN; const COMPLETE_RANGE: Range = INCOMPLETE_LEN..(INCOMPLETE_LEN + NUM_COMPLETE_BITS); #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Config { +pub struct Config { // Selector used to check switching logic on LSB q_mul_lsb: Selector, // Configuration used in complete addition @@ -58,14 +58,14 @@ pub struct Config { // Configuration used for complete addition part of double-and-add algorithm complete_config: complete::Config, // Configuration used to check for overflow - overflow_config: overflow::Config, + overflow_config: overflow::Config, } -impl Config { +impl Config { pub(crate) fn configure( meta: &mut ConstraintSystem, add_config: add::Config, - lookup_config: LookupRangeCheckConfig, + lookup_config: Lookup, advices: [Column; 10], ) -> Self { let hi_config = incomplete::Config::configure( @@ -461,13 +461,13 @@ pub mod tests { Curve, }; use halo2_proofs::{ - circuit::{Chip, Layouter, Value}, + circuit::{Layouter, Value}, plonk::Error, }; use pasta_curves::pallas; use rand::rngs::OsRng; - use crate::utilities::lookup_range_check::{DefaultLookupRangeCheck, LookupRangeCheckConfig}; + use crate::utilities::lookup_range_check::{DefaultLookupRangeCheck}; use crate::{ ecc::{ chip::{EccChip, EccPoint}, @@ -477,10 +477,10 @@ pub mod tests { utilities::UtilitiesInstructions, }; - pub(crate) fn test_mul( - chip: EccChip, + pub(crate) fn test_mul( + chip: EccChip, mut layouter: impl Layouter, - p: &NonIdentityPoint>, + p: &NonIdentityPoint>, p_val: pallas::Affine, ) -> Result<(), Error> { let column = chip.config.advices[0]; diff --git a/halo2_gadgets/src/ecc/chip/mul/overflow.rs b/halo2_gadgets/src/ecc/chip/mul/overflow.rs index 8adaefa311..0912bd3a39 100644 --- a/halo2_gadgets/src/ecc/chip/mul/overflow.rs +++ b/halo2_gadgets/src/ecc/chip/mul/overflow.rs @@ -15,19 +15,19 @@ use pasta_curves::pallas; use std::iter; #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Config { +pub struct Config { // Selector to check z_0 = alpha + t_q (mod p) q_mul_overflow: Selector, // 10-bit lookup table - lookup_config: LookupRangeCheckConfig, + lookup_config: Lookup, // Advice columns advices: [Column; 3], } -impl Config { +impl Config { pub(super) fn configure( meta: &mut ConstraintSystem, - lookup_config: LookupRangeCheckConfig, + lookup_config: Lookup, advices: [Column; 3], ) -> Self { for advice in advices.iter() { diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed/base_field_elem.rs b/halo2_gadgets/src/ecc/chip/mul_fixed/base_field_elem.rs index 254ba6804c..1298140e43 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/base_field_elem.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/base_field_elem.rs @@ -18,21 +18,21 @@ use std::convert::TryInto; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Config< Fixed: FixedPoints, - LookupRangeCheckConfig: DefaultLookupRangeCheck, + Lookup: DefaultLookupRangeCheck, > { q_mul_fixed_base_field: Selector, canon_advices: [Column; 3], - lookup_config: LookupRangeCheckConfig, + lookup_config: Lookup, super_config: super::Config, } -impl, LookupRangeCheckConfig: DefaultLookupRangeCheck> - Config +impl, Lookup: DefaultLookupRangeCheck> + Config { pub(crate) fn configure( meta: &mut ConstraintSystem, canon_advices: [Column; 3], - lookup_config: LookupRangeCheckConfig, + lookup_config: Lookup, super_config: super::Config, ) -> Self { for advice in canon_advices.iter() { @@ -401,8 +401,8 @@ pub mod tests { utilities::UtilitiesInstructions, }; - pub(crate) fn test_mul_fixed_base_field( - chip: EccChip, + pub(crate) fn test_mul_fixed_base_field( + chip: EccChip, mut layouter: impl Layouter, ) -> Result<(), Error> { test_single_base( @@ -414,22 +414,22 @@ pub mod tests { } #[allow(clippy::op_ref)] - fn test_single_base( - chip: EccChip, + fn test_single_base( + chip: EccChip, mut layouter: impl Layouter, - base: FixedPointBaseField>, + base: FixedPointBaseField>, base_val: pallas::Affine, ) -> Result<(), Error> { let rng = OsRng; let column = chip.config().advices[0]; - fn constrain_equal_non_id( - chip: EccChip, + fn constrain_equal_non_id( + chip: EccChip, mut layouter: impl Layouter, base_val: pallas::Affine, scalar_val: pallas::Base, - result: Point>, + result: Point>, ) -> Result<(), Error> { // Move scalar from base field into scalar field (which always fits for Pallas). let scalar = pallas::Scalar::from_repr(scalar_val.to_repr()).unwrap(); diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs b/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs index a06b1b9394..fe2cc22094 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs @@ -194,8 +194,8 @@ pub mod tests { }; use crate::utilities::lookup_range_check::DefaultLookupRangeCheck; - pub(crate) fn test_mul_fixed( - chip: EccChip, + pub(crate) fn test_mul_fixed( + chip: EccChip, mut layouter: impl Layouter, ) -> Result<(), Error> { let test_base = FullWidth::from_pallas_generator(); @@ -210,18 +210,18 @@ pub mod tests { } #[allow(clippy::op_ref)] - fn test_single_base( - chip: EccChip, + fn test_single_base( + chip: EccChip, mut layouter: impl Layouter, - base: FixedPoint>, + base: FixedPoint>, base_val: pallas::Affine, ) -> Result<(), Error> { - fn constrain_equal_non_id( - chip: EccChip, + fn constrain_equal_non_id( + chip: EccChip, mut layouter: impl Layouter, base_val: pallas::Affine, scalar_val: pallas::Scalar, - result: Point>, + result: Point>, ) -> Result<(), Error> { let expected = NonIdentityPoint::new( chip, diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs index c746bd5f97..48a1cbf88a 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs @@ -264,16 +264,16 @@ pub mod tests { }; #[allow(clippy::op_ref)] - pub(crate) fn test_mul_fixed_short( - chip: EccChip, + pub(crate) fn test_mul_fixed_short( + chip: EccChip, mut layouter: impl Layouter, ) -> Result<(), Error> { // test_short let base_val = Short.generator(); let test_short = FixedPointShort::from_inner(chip.clone(), Short); - fn load_magnitude_sign( - chip: EccChip, + fn load_magnitude_sign( + chip: EccChip, mut layouter: impl Layouter, magnitude: pallas::Base, sign: pallas::Base, @@ -290,12 +290,12 @@ pub mod tests { Ok((magnitude, sign)) } - fn constrain_equal_non_id( - chip: EccChip, + fn constrain_equal_non_id( + chip: EccChip, mut layouter: impl Layouter, base_val: pallas::Affine, scalar_val: pallas::Scalar, - result: Point>, + result: Point>, ) -> Result<(), Error> { let expected = NonIdentityPoint::new( chip, diff --git a/halo2_gadgets/src/ecc_opt.rs b/halo2_gadgets/src/ecc_opt.rs index 253662a7ca..b7a7dca5a4 100644 --- a/halo2_gadgets/src/ecc_opt.rs +++ b/halo2_gadgets/src/ecc_opt.rs @@ -99,19 +99,6 @@ pub(crate) mod tests { find_zs_and_us(*BASE, NUM_WINDOWS_SHORT).unwrap(); } - impl FullWidth { - pub(crate) fn from_pallas_generator() -> Self { - FullWidth(*BASE, &ZS_AND_US) - } - - pub(crate) fn from_parts( - base: pallas::Affine, - zs_and_us: &'static [(u64, [pallas::Base; H])], - ) -> Self { - FullWidth(base, zs_and_us) - } - } - impl FixedPoint for FullWidth { type FixedScalarKind = FullScalar; diff --git a/halo2_gadgets/src/ecc_opt/chip.rs b/halo2_gadgets/src/ecc_opt/chip.rs index ed391881b3..c2c70bc17f 100644 --- a/halo2_gadgets/src/ecc_opt/chip.rs +++ b/halo2_gadgets/src/ecc_opt/chip.rs @@ -19,8 +19,8 @@ use super::EccInstructionsOptimized; pub(crate) mod mul_fixed; pub(super) mod witness_point; -impl, LookupRangeCheckConfig: DefaultLookupRangeCheck> - EccInstructionsOptimized for EccChip +impl, Lookup: DefaultLookupRangeCheck> + EccInstructionsOptimized for EccChip where >::Base: FixedPoint, diff --git a/halo2_gadgets/src/ecc_opt/chip/mul_fixed/short.rs b/halo2_gadgets/src/ecc_opt/chip/mul_fixed/short.rs index 927aea4858..b0487b5d73 100644 --- a/halo2_gadgets/src/ecc_opt/chip/mul_fixed/short.rs +++ b/halo2_gadgets/src/ecc_opt/chip/mul_fixed/short.rs @@ -92,13 +92,12 @@ pub mod tests { use crate::{ ecc::{chip::EccChip, tests::TestFixedBases, Point}, utilities::{ - lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig}, UtilitiesInstructions, }, }; - pub(crate) fn test_mul_sign( - chip: EccChip, + pub(crate) fn test_mul_sign( + chip: EccChip, mut layouter: impl Layouter, ) -> Result<(), Error> { // Generate a random non-identity point P diff --git a/halo2_gadgets/src/sinsemilla/chip.rs b/halo2_gadgets/src/sinsemilla/chip.rs index 4793a99da2..fa64deedfe 100644 --- a/halo2_gadgets/src/sinsemilla/chip.rs +++ b/halo2_gadgets/src/sinsemilla/chip.rs @@ -30,12 +30,12 @@ pub(crate) mod hash_to_point; /// Configuration for the Sinsemilla hash chip #[derive(Eq, PartialEq, Clone, Debug)] -pub struct SinsemillaConfig +pub struct SinsemillaConfig where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, + Lookup: DefaultLookupRangeCheck, { /// Binary selector used in lookup argument and in the body of the Sinsemilla hash. pub(crate) q_sinsemilla1: Selector, @@ -59,17 +59,17 @@ where /// generators of the Sinsemilla hash. pub(crate) generator_table: GeneratorTableConfig, /// An advice column configured to perform lookup range checks. - pub(crate) lookup_config: LookupRangeCheckConfig, + pub(crate) lookup_config: Lookup, pub(crate) _marker: PhantomData<(Hash, Commit, F)>, } -impl - SinsemillaConfig +impl + SinsemillaConfig where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, + Lookup: DefaultLookupRangeCheck, { /// Returns an array of all advice columns in this config, in arbitrary order. pub(super) fn advices(&self) -> [Column; 5] { @@ -83,7 +83,7 @@ where } /// Returns the lookup range check config used in this config. - pub fn lookup_config(&self) -> LookupRangeCheckConfig { + pub fn lookup_config(&self) -> Lookup { self.lookup_config } @@ -99,25 +99,25 @@ where /// /// [Chip description](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html#plonk--halo-2-constraints). #[derive(Eq, PartialEq, Clone, Debug)] -pub struct SinsemillaChip +pub struct SinsemillaChip where Hash: HashDomains, Fixed: FixedPoints, Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, + Lookup: DefaultLookupRangeCheck, { - config: SinsemillaConfig, + config: SinsemillaConfig, } -impl Chip - for SinsemillaChip +impl Chip + for SinsemillaChip where Hash: HashDomains, Fixed: FixedPoints, Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, + Lookup: DefaultLookupRangeCheck, { - type Config = SinsemillaConfig; + type Config = SinsemillaConfig; type Loaded = (); fn config(&self) -> &Self::Config { @@ -129,13 +129,13 @@ where } } -impl - SinsemillaChip +impl + SinsemillaChip where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, + Lookup: DefaultLookupRangeCheck, { /// Reconstructs this chip from the given config. pub fn construct(config: >::Config) -> Self { @@ -144,7 +144,7 @@ where /// Loads the lookup table required by this chip into the circuit. pub fn load( - config: SinsemillaConfig, + config: SinsemillaConfig, layouter: &mut impl Layouter, ) -> Result<>::Loaded, Error> { // Load the lookup table. @@ -152,59 +152,13 @@ where 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( + pub(crate) fn create_initial_y_q_gate( meta: &mut ConstraintSystem, - advices: [Column; 5], - witness_pieces: Column, - fixed_y_q: Column, - lookup: (TableColumn, TableColumn, TableColumn), - range_check: LookupRangeCheckConfig, - ) -> >::Config { - // FIXME: add comments - - // 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); - + config: &SinsemillaConfig, + ) { 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) @@ -214,11 +168,8 @@ where // 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 = if LookupRangeCheckConfig::is_optimized() { - meta.query_advice(config.double_and_add.x_p, Rotation::prev()) - } else { - meta.query_fixed(config.fixed_y_q) - }; + 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()); @@ -227,6 +178,25 @@ where Constraints::with_selector(q_s4, Some(("init_y_q_check", init_y_q_check))) }); + } + + #[allow(non_snake_case)] + pub(crate) fn create_sinsemilla_gate( + meta: &mut ConstraintSystem, + config: &SinsemillaConfig, + ) { + 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) + }; // https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial meta.create_gate("Sinsemilla gate", |meta| { @@ -272,20 +242,89 @@ where Constraints::with_selector(q_s1, [("Secant line", secant_line), ("y check", y_check)]) }); + } + + pub(crate) fn create_config( + meta: &mut ConstraintSystem, + advices: [Column; 5], + witness_pieces: Column, + fixed_y_q: Column, + lookup: (TableColumn, TableColumn, TableColumn), + range_check: Lookup, + ) -> >::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); + + config + } + + /// # 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: Lookup, + ) -> >::Config { + // create SinsemillaConfig + let config = Self::create_config( + meta, + advices, + witness_pieces, + fixed_y_q, + lookup, + range_check, + ); + + Self::create_initial_y_q_gate(meta, &config); + + Self::create_sinsemilla_gate(meta, &config); config } } // Implement `SinsemillaInstructions` for `SinsemillaChip` -impl +impl SinsemillaInstructions - for SinsemillaChip + for SinsemillaChip where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, + Lookup: DefaultLookupRangeCheck, { type CellValue = AssignedCell; diff --git a/halo2_gadgets/src/sinsemilla/chip/generator_table.rs b/halo2_gadgets/src/sinsemilla/chip/generator_table.rs index ed7cc9e554..80551a6c91 100644 --- a/halo2_gadgets/src/sinsemilla/chip/generator_table.rs +++ b/halo2_gadgets/src/sinsemilla/chip/generator_table.rs @@ -6,7 +6,6 @@ use halo2_proofs::{ }; use super::{CommitDomains, FixedPoints, HashDomains}; -use crate::utilities::lookup_range_check::LookupRangeCheckConfig; use crate::{ sinsemilla::primitives::{self as sinsemilla, SINSEMILLA_S}, utilities::lookup_range_check::DefaultLookupRangeCheck, @@ -27,14 +26,14 @@ impl GeneratorTableConfig { /// 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( + pub fn configure( meta: &mut ConstraintSystem, - config: &super::SinsemillaConfig, + config: &super::SinsemillaConfig, ) where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, + Lookup: DefaultLookupRangeCheck, { let (table_idx, table_x, table_y) = ( config.generator_table.table_idx, diff --git a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs index 1bb1ce9ed6..0456fc0893 100644 --- a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs @@ -1,5 +1,5 @@ use super::super::{CommitDomains, HashDomains, SinsemillaInstructions}; -use super::{NonIdentityEccPoint, SinsemillaChip, SinsemillaConfig}; +use super::{NonIdentityEccPoint, SinsemillaChip}; use crate::{ ecc::FixedPoints, sinsemilla::primitives::{self as sinsemilla, lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S}, @@ -24,18 +24,18 @@ pub enum EccPointQ<'a> { PrivatePoint(&'a NonIdentityEccPoint), } -impl - SinsemillaChip +impl + SinsemillaChip where Hash: HashDomains, Fixed: FixedPoints, Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, + Lookup: DefaultLookupRangeCheck, { /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] #[allow(clippy::type_complexity)] - pub(super) fn hash_message( + pub(crate) fn hash_message( &self, region: &mut Region<'_, pallas::Base>, Q: pallas::Affine, @@ -51,13 +51,7 @@ where ), Error, > { - // todo: add doc about LookupRangeCheckConfig::is_optimized() - //let (offset, x_a, y_a) = self.public_initialization(region, Q)?; - let (offset, x_a, y_a) = if LookupRangeCheckConfig::is_optimized() { - self.public_initialization(region, Q)? - } else { - self.public_initialization_vanilla(region, Q)? - }; + let (offset, x_a, y_a) = self.public_initialization_vanilla(region, Q)?; let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?; @@ -65,6 +59,7 @@ where } #[allow(non_snake_case)] + #[allow(unused_variables)] // Check equivalence to result from primitives::sinsemilla::hash_to_point pub(crate) fn check_hash_result( &self, diff --git a/halo2_gadgets/src/sinsemilla/merkle/chip.rs b/halo2_gadgets/src/sinsemilla/merkle/chip.rs index dc7fb3f6f8..c54133e291 100644 --- a/halo2_gadgets/src/sinsemilla/merkle/chip.rs +++ b/halo2_gadgets/src/sinsemilla/merkle/chip.rs @@ -28,17 +28,17 @@ use group::ff::PrimeField; /// Configuration for the `MerkleChip` implementation. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct MerkleConfig -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, +pub struct MerkleConfig + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { - advices: [Column; 5], - q_decompose: Selector, + pub(crate) advices: [Column; 5], + pub(crate) q_decompose: Selector, pub(crate) cond_swap_config: CondSwapConfig, - pub(crate) sinsemilla_config: SinsemillaConfig, + pub(crate) sinsemilla_config: SinsemillaConfig, } /// Chip implementing `MerkleInstructions`. @@ -52,25 +52,25 @@ where /// This chip does **NOT** constrain `left⋆` and `right⋆` to be canonical encodings of /// `left` and `right`. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct MerkleChip -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, +pub struct MerkleChip + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { - config: MerkleConfig, + pub(crate) config: MerkleConfig, } -impl Chip - for MerkleChip -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, +impl Chip +for MerkleChip + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { - type Config = MerkleConfig; + type Config = MerkleConfig; type Loaded = (); fn config(&self) -> &Self::Config { @@ -82,18 +82,18 @@ where } } -impl MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, +impl MerkleChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { /// Configures the [`MerkleChip`]. pub fn configure( meta: &mut ConstraintSystem, - sinsemilla_config: SinsemillaConfig, - ) -> MerkleConfig { + sinsemilla_config: SinsemillaConfig, + ) -> MerkleConfig { // All five advice columns are equality-enabled by SinsemillaConfig. let advices = sinsemilla_config.advices(); let cond_swap_config = CondSwapChip::configure(meta, advices); @@ -196,19 +196,19 @@ where } /// Constructs a [`MerkleChip`] given a [`MerkleConfig`]. - pub fn construct(config: MerkleConfig) -> Self { + pub fn construct(config: MerkleConfig) -> Self { MerkleChip { config } } } -impl - MerkleInstructions - for MerkleChip -where - Hash: HashDomains + Eq, - F: FixedPoints, - Commit: CommitDomains + Eq, - LookupRangeCheckConfig: DefaultLookupRangeCheck, +impl +MerkleInstructions +for MerkleChip + where + Hash: HashDomains + Eq, + F: FixedPoints, + Commit: CommitDomains + Eq, + Lookup: DefaultLookupRangeCheck, { #[allow(non_snake_case)] fn hash_layer( @@ -421,24 +421,24 @@ where } } -impl UtilitiesInstructions - for MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, +impl UtilitiesInstructions +for MerkleChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { type Var = AssignedCell; } -impl CondSwapInstructions - for MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, +impl CondSwapInstructions +for MerkleChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { #[allow(clippy::type_complexity)] fn swap( @@ -453,71 +453,71 @@ where } } -impl - SinsemillaInstructions - for MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, +impl +SinsemillaInstructions +for MerkleChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { type CellValue = - as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::CellValue; + as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::CellValue; type Message = - as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::Message; + as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::Message; type MessagePiece = - as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::MessagePiece; + as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::MessagePiece; type RunningSum = - as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::RunningSum; + as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::RunningSum; - type X = as SinsemillaInstructions< + type X = as SinsemillaInstructions< pallas::Affine, { sinsemilla::K }, { sinsemilla::C }, >>::X; type NonIdentityPoint = - as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::NonIdentityPoint; + as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::NonIdentityPoint; type FixedPoints = - as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::FixedPoints; + as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::FixedPoints; type HashDomains = - as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::HashDomains; + as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::HashDomains; type CommitDomains = - as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::CommitDomains; + as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::CommitDomains; fn witness_message_piece( &self, @@ -526,7 +526,7 @@ where num_words: usize, ) -> Result { let config = self.config().sinsemilla_config.clone(); - let chip = SinsemillaChip::::construct(config); + let chip = SinsemillaChip::::construct(config); chip.witness_message_piece(layouter, value, num_words) } @@ -539,11 +539,11 @@ where message: Self::Message, ) -> Result<(Self::NonIdentityPoint, Vec>), Error> { let config = self.config().sinsemilla_config.clone(); - let chip = SinsemillaChip::::construct(config); + let chip = SinsemillaChip::::construct(config); chip.hash_to_point(layouter, Q, message) } fn extract(point: &Self::NonIdentityPoint) -> Self::X { - SinsemillaChip::::extract(point) + SinsemillaChip::::extract(point) } -} +} \ No newline at end of file diff --git a/halo2_gadgets/src/sinsemilla_opt.rs b/halo2_gadgets/src/sinsemilla_opt.rs index cca2af19f8..397209e80c 100644 --- a/halo2_gadgets/src/sinsemilla_opt.rs +++ b/halo2_gadgets/src/sinsemilla_opt.rs @@ -5,7 +5,6 @@ use std::fmt::Debug; use pasta_curves::arithmetic::CurveAffine; -use pasta_curves::pallas; use halo2_proofs::{circuit::Layouter, plonk::Error}; @@ -18,10 +17,15 @@ pub mod chip; pub mod merkle; pub mod primitives; -/// FIXME: add a doc +/// `SinsemillaInstructionsOptimized` provides an optimized set of instructions +/// for implementing the Sinsemilla hash function and commitment scheme +/// on elliptic curves. This trait is an extension of the `SinsemillaInstructions` trait, +/// designed to enhance performance in specific cryptographic scenarios.ld + pub trait SinsemillaInstructionsOptimized: - SinsemillaInstructions +SinsemillaInstructions { + /// Hashes a message to an ECC curve point. /// This returns both the resulting point, as well as the message /// decomposition in the form of intermediate values in a cumulative @@ -159,7 +163,6 @@ pub(crate) mod tests { tests::{FullWidth, TestFixedBases}, NonIdentityPoint, }, - utilities::lookup_range_check::LookupRangeCheckConfig, }, }; @@ -310,7 +313,6 @@ pub(crate) mod tests { let ecc_chip = EccChip::construct(config.0); - // todo: check SinsemillaChipOptimized::load // The two `SinsemillaChip`s share the same lookup table. SinsemillaChipOptimized::::load( config.1.clone(), diff --git a/halo2_gadgets/src/sinsemilla_opt/chip.rs b/halo2_gadgets/src/sinsemilla_opt/chip.rs index 909ad21f7c..d08c76153d 100644 --- a/halo2_gadgets/src/sinsemilla_opt/chip.rs +++ b/halo2_gadgets/src/sinsemilla_opt/chip.rs @@ -1,7 +1,6 @@ //! Chip implementations for the Sinsemilla gadgets. use super::SinsemillaInstructionsOptimized; -use crate::utilities::lookup_range_check::DefaultLookupRangeCheck; use crate::{ ecc::{chip::NonIdentityEccPoint, FixedPoints}, sinsemilla::{ @@ -11,7 +10,6 @@ use crate::{ }, utilities_opt::lookup_range_check::DefaultLookupRangeCheckConfigOptimized, }; -use halo2_proofs::plonk::Expression; use halo2_proofs::{ circuit::{AssignedCell, Chip, Layouter, Value}, plonk::{ @@ -20,56 +18,29 @@ use halo2_proofs::{ poly::Rotation, }; use pasta_curves::pallas; -use pasta_curves::pallas::Base; pub(crate) mod generator_table; mod hash_to_point; -// Implement `SinsemillaInstructionsOptimized` for `SinsemillaChip` -impl - SinsemillaInstructionsOptimized - for SinsemillaChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, -{ - #[allow(non_snake_case)] - #[allow(clippy::type_complexity)] - fn hash_to_point_with_private_init( - &self, - mut layouter: impl Layouter, - Q: &Self::NonIdentityPoint, - message: Self::Message, - ) -> Result<(Self::NonIdentityPoint, Vec), Error> { - layouter.assign_region( - || "hash_to_point", - |mut region| self.hash_message_with_private_init(&mut region, Q, &message), - ) - } -} - /// 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 SinsemillaChipOptimized -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { inner: SinsemillaChip, } -// FIXME: is this needed? impl Chip for SinsemillaChipOptimized -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { type Config = SinsemillaConfig; type Loaded = (); @@ -84,11 +55,21 @@ where } impl SinsemillaChipOptimized -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { + /// Reconstructs this chip from the given config. + pub fn construct(config: >::Config) -> Self { + Self { + inner: + SinsemillaChip::::construct( + config, + ), + } + } + /// Loads the lookup table required by this chip into the circuit. pub fn load( config: SinsemillaConfig, @@ -97,9 +78,142 @@ where // Load the lookup table. generator_table::load_with_tag( &config.generator_table, - // FIXME: consider to remove Option arount tag config.lookup_config.table_range_check_tag(), layouter, ) } + + #[allow(non_snake_case)] + fn create_initial_y_q_gate( + meta: &mut ConstraintSystem, + config: &SinsemillaConfig, + ) { + let two = pallas::Base::from(2); + + // 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_advice(config.double_and_add.x_p, Rotation::prev()); + + // 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))) + }); + } + + /// # 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: DefaultLookupRangeCheckConfigOptimized, + ) -> >::Config { + let config = SinsemillaChip::::create_config( + meta, + advices, + witness_pieces, + fixed_y_q, + lookup, + range_check, + ); + + Self::create_initial_y_q_gate(meta, &config); + + SinsemillaChip::::create_sinsemilla_gate( + meta, &config, + ); + + config + } +} + +// Implement `SinsemillaInstructions` for `SinsemillaChip` +impl SinsemillaInstructions +for SinsemillaChipOptimized + 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, + layouter: impl Layouter, + field_elem: Value, + num_words: usize, + ) -> Result { + self.inner + .witness_message_piece(layouter, field_elem, 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.inner.hash_message_zsa(&mut region, Q, &message), + ) + } + + fn extract(point: &Self::NonIdentityPoint) -> Self::X { + point.x() + } } + +// Implement `SinsemillaInstructions` for `SinsemillaChip` +impl +SinsemillaInstructionsOptimized +for SinsemillaChipOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + fn hash_to_point_with_private_init( + &self, + mut layouter: impl Layouter, + Q: &Self::NonIdentityPoint, + message: Self::Message, + ) -> Result<(Self::NonIdentityPoint, Vec), Error> { + layouter.assign_region( + || "hash_to_point", + |mut region| self.inner.hash_message_with_private_init(&mut region, Q, &message), + ) + } +} \ No newline at end of file diff --git a/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs index 469a5991e1..5f513ba5df 100644 --- a/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs @@ -17,14 +17,39 @@ use crate::{ }, }; -impl - SinsemillaChip +impl + SinsemillaChip where Hash: HashDomains, Fixed: FixedPoints, Commit: CommitDomains, - LookupRangeCheckConfig: DefaultLookupRangeCheck, + Lookup: DefaultLookupRangeCheck, { + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + pub(crate) fn hash_message_zsa( + &self, + region: &mut Region<'_, pallas::Base>, + Q: pallas::Affine, + message: &>::Message, + ) -> Result< + ( + NonIdentityEccPoint, + Vec>>, + ), + Error, + > { + // Coordinates of the initial point `Q` are assigned to advice columns + let (offset, x_a, y_a) = self.public_initialization(region, Q)?; + + let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?; + + self.check_hash_result(EccPointQ::PublicPoint(Q), message, x_a, y_a, zs_sum) + } /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] #[allow(clippy::type_complexity)] @@ -46,7 +71,6 @@ where > { let (offset, x_a, y_a) = self.private_initialization(region, Q)?; - // FIXME: calling construct_base is possibly(!) non-optimal let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?; self.check_hash_result(EccPointQ::PrivatePoint(Q), message, x_a, y_a, zs_sum) diff --git a/halo2_gadgets/src/sinsemilla_opt/merkle.rs b/halo2_gadgets/src/sinsemilla_opt/merkle.rs index 69d43a5c6d..95e4d103cd 100644 --- a/halo2_gadgets/src/sinsemilla_opt/merkle.rs +++ b/halo2_gadgets/src/sinsemilla_opt/merkle.rs @@ -8,11 +8,10 @@ pub mod tests { use crate::{ ecc::tests::TestFixedBases, sinsemilla::{ - chip::SinsemillaChip, tests::{TestCommitDomain, TestHashDomain}, HashDomains, }, - utilities::{i2lebsp, lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, + utilities::{i2lebsp, UtilitiesInstructions}, }; use group::ff::{Field, PrimeField, PrimeFieldBits}; @@ -23,13 +22,13 @@ pub mod tests { plonk::{Circuit, ConstraintSystem, Error}, }; - use crate::sinsemilla::merkle::chip::{MerkleChip, MerkleConfig}; + use crate::sinsemilla::merkle::chip::{MerkleConfig}; use crate::sinsemilla::merkle::MerklePath; use crate::sinsemilla_opt::chip::SinsemillaChipOptimized; - use crate::utilities::lookup_range_check::LookupRangeCheck; use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; use rand::{rngs::OsRng, RngCore}; use std::{convert::TryInto, iter}; + use crate::sinsemilla_opt::merkle::chip::MerkleChipOptimized; const MERKLE_DEPTH: usize = 32; @@ -99,7 +98,7 @@ pub mod tests { table_range_check_tag, ); - let sinsemilla_config_1 = SinsemillaChip::configure( + let sinsemilla_config_1 = SinsemillaChipOptimized::configure( meta, advices[5..].try_into().unwrap(), advices[7], @@ -107,9 +106,9 @@ pub mod tests { lookup, range_check, ); - let config1 = MerkleChip::configure(meta, sinsemilla_config_1); + let config1 = MerkleChipOptimized::configure(meta, sinsemilla_config_1); - let sinsemilla_config_2 = SinsemillaChip::configure( + let sinsemilla_config_2 = SinsemillaChipOptimized::configure( meta, advices[..5].try_into().unwrap(), advices[2], @@ -117,7 +116,7 @@ pub mod tests { lookup, range_check, ); - let config2 = MerkleChip::configure(meta, sinsemilla_config_2); + let config2 = MerkleChipOptimized::configure(meta, sinsemilla_config_2); (config1, config2) } @@ -134,8 +133,8 @@ pub mod tests { )?; // Construct Merkle chips which will be placed side-by-side in the circuit. - let chip_1 = MerkleChip::construct(config.0.clone()); - let chip_2 = MerkleChip::construct(config.1.clone()); + let chip_1 = MerkleChipOptimized::construct(config.0.clone()); + let chip_2 = MerkleChipOptimized::construct(config.1.clone()); let leaf = chip_1.load_private( layouter.namespace(|| ""), diff --git a/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs b/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs index 1eaf2c0853..18240919d4 100644 --- a/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs +++ b/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs @@ -1,69 +1,428 @@ //! Chip implementing a Merkle hash using Sinsemilla as the hash function. +use ff::PrimeField; use halo2_proofs::{ - circuit::{Chip, Layouter}, - plonk::Error, + circuit::{AssignedCell, Chip, Layouter, Value}, + plonk::{ConstraintSystem, Error}, }; use pasta_curves::pallas; -use crate::sinsemilla::chip::SinsemillaChip; -use crate::sinsemilla::SinsemillaInstructions; -use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; use crate::{ - sinsemilla::{merkle::chip::MerkleChip, primitives as sinsemilla}, - sinsemilla_opt::SinsemillaInstructionsOptimized, + sinsemilla::{ + merkle::{ + chip::{MerkleChip, MerkleConfig}, + MerkleInstructions, + }, + primitives as sinsemilla, + }, + sinsemilla_opt::chip::SinsemillaChipOptimized, utilities_opt::lookup_range_check::DefaultLookupRangeCheckConfigOptimized, { ecc::FixedPoints, - sinsemilla::{CommitDomains, HashDomains}, - utilities::cond_swap::CondSwapChip, - utilities_opt::cond_swap::CondSwapInstructionsOptimized, + sinsemilla::{chip::SinsemillaConfig, CommitDomains, HashDomains, SinsemillaInstructions}, + utilities::{cond_swap::CondSwapInstructions, UtilitiesInstructions}, }, }; +use crate::sinsemilla::MessagePiece; +use crate::utilities::RangeConstrained; + +/// Chip implementing `MerkleInstructions`. +/// +/// This chip specifically implements `MerkleInstructions::hash_layer` as the `MerkleCRH` +/// function `hash = SinsemillaHash(Q, 𝑙⋆ || left⋆ || right⋆)`, where: +/// - `𝑙⋆ = I2LEBSP_10(l)` +/// - `left⋆ = I2LEBSP_255(left)` +/// - `right⋆ = I2LEBSP_255(right)` +/// +/// This chip does **NOT** constrain `left⋆` and `right⋆` to be canonical encodings of +/// `left` and `right`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MerkleChipOptimized + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + base: MerkleChip, +} + +impl Chip for MerkleChipOptimized + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + type Config = MerkleConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.base.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} -impl CondSwapInstructionsOptimized - for MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +impl MerkleChipOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { - fn mux( + /// Configures the [`MerkleChip`]. + pub fn configure( + meta: &mut ConstraintSystem, + sinsemilla_config: SinsemillaConfig< + Hash, + Commit, + F, + DefaultLookupRangeCheckConfigOptimized, + >, + ) -> MerkleConfig { + MerkleChip::configure(meta, sinsemilla_config) + } + + /// Constructs a [`MerkleChip`] given a [`MerkleConfig`]. + pub fn construct( + config: MerkleConfig, + ) -> Self { + MerkleChipOptimized { + base: MerkleChip { config }, + } + } +} + +impl +MerkleInstructions +for MerkleChipOptimized + where + Hash: HashDomains + Eq, + F: FixedPoints, + Commit: CommitDomains + Eq, +{ + // Todo: simplify the body? + #[allow(non_snake_case)] + fn hash_layer( &self, - layouter: &mut impl Layouter, - choice: Self::Var, + mut layouter: impl Layouter, + Q: pallas::Affine, + // l = MERKLE_DEPTH - layer - 1 + l: usize, left: Self::Var, right: Self::Var, ) -> Result { - let config = self.config().cond_swap_config.clone(); - let chip = CondSwapChip::::construct(config); - chip.mux(layouter, choice, left, right) + // Todo: simplify the body? copied and pasted from the body of the hash_layer function in halo2_gadgets/src/sinsemilla/merkle/chip.rs + + let config = self.config().clone(); + + // We need to hash `l || left || right`, where `l` is a 10-bit value. + // We allow `left` and `right` to be non-canonical 255-bit encodings. + // + // a = a_0||a_1 = l || (bits 0..=239 of left) + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // c = bits 5..=254 of right + // + // We start by witnessing all of the individual pieces, and range-constraining the + // short pieces b_1 and b_2. + // + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-bit-lengths?partial + + // `a = a_0||a_1` = `l` || (bits 0..=239 of `left`) + let a = MessagePiece::from_subpieces( + self.clone(), + layouter.namespace(|| "Witness a = a_0 || a_1"), + [ + RangeConstrained::bitrange_of(Value::known(&pallas::Base::from(l as u64)), 0..10), + RangeConstrained::bitrange_of(left.value(), 0..240), + ], + )?; + + // b = b_0 || b_1 || b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + let (b_1, b_2, b) = { + // b_0 = (bits 240..=249 of `left`) + let b_0 = RangeConstrained::bitrange_of(left.value(), 240..250); + + // b_1 = (bits 250..=254 of `left`) + // Constrain b_1 to 5 bits. + let b_1 = RangeConstrained::witness_short( + &config.sinsemilla_config.lookup_config(), + layouter.namespace(|| "b_1"), + left.value(), + 250..(pallas::Base::NUM_BITS as usize), + )?; + + // b_2 = (bits 0..=4 of `right`) + // Constrain b_2 to 5 bits. + let b_2 = RangeConstrained::witness_short( + &config.sinsemilla_config.lookup_config(), + layouter.namespace(|| "b_2"), + right.value(), + 0..5, + )?; + + let b = MessagePiece::from_subpieces( + self.clone(), + layouter.namespace(|| "Witness b = b_0 || b_1 || b_2"), + [b_0, b_1.value(), b_2.value()], + )?; + + (b_1, b_2, b) + }; + + // c = bits 5..=254 of `right` + let c = MessagePiece::from_subpieces( + self.clone(), + layouter.namespace(|| "Witness c"), + [RangeConstrained::bitrange_of( + right.value(), + 5..(pallas::Base::NUM_BITS as usize), + )], + )?; + + // hash = SinsemillaHash(Q, 𝑙⋆ || left⋆ || right⋆) + // + // `hash = ⊥` is handled internally to `SinsemillaChip::hash_to_point`: incomplete + // addition constraints allows ⊥ to occur, and then during synthesis it detects + // these edge cases and raises an error (aborting proof creation). + // + // Note that MerkleCRH as-defined maps ⊥ to 0. This is for completeness outside + // the circuit (so that the ⊥ does not propagate into the type system). The chip + // explicitly doesn't map ⊥ to 0; in fact it cannot, as doing so would require + // constraints that amount to using complete addition. The rationale for excluding + // this map is the same as why Sinsemilla uses incomplete addition: this situation + // yields a nontrivial discrete log relation, and by assumption it is hard to find + // these. + // + // https://p.z.cash/proto:merkle-crh-orchard + let (point, zs) = self.hash_to_point( + layouter.namespace(|| format!("hash at l = {}", l)), + Q, + vec![a.inner(), b.inner(), c.inner()].into(), + )?; + let hash = Self::extract(&point); + + // `SinsemillaChip::hash_to_point` returns the running sum for each `MessagePiece`. + // Grab the outputs we need for the decomposition constraints. + let z1_a = zs[0][1].clone(); + let z1_b = zs[1][1].clone(); + + // Check that the pieces have been decomposed properly. + // + // The pieces and subpieces are arranged in the following configuration: + // | A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose | + // ------------------------------------------------------- + // | a | b | c | left | right | 1 | + // | z1_a | z1_b | b_1 | b_2 | l | 0 | + { + layouter.assign_region( + || "Check piece decomposition", + |mut region| { + // Set the fixed column `l` to the current l. + // Recall that l = MERKLE_DEPTH - layer - 1. + // The layer with 2^n nodes is called "layer n". + config.q_decompose.enable(&mut region, 0)?; + region.assign_advice_from_constant( + || format!("l {}", l), + config.advices[4], + 1, + pallas::Base::from(l as u64), + )?; + + // Offset 0 + // Copy and assign `a` at the correct position. + a.inner().cell_value().copy_advice( + || "copy a", + &mut region, + config.advices[0], + 0, + )?; + // Copy and assign `b` at the correct position. + b.inner().cell_value().copy_advice( + || "copy b", + &mut region, + config.advices[1], + 0, + )?; + // Copy and assign `c` at the correct position. + c.inner().cell_value().copy_advice( + || "copy c", + &mut region, + config.advices[2], + 0, + )?; + // Copy and assign the left node at the correct position. + left.copy_advice(|| "left", &mut region, config.advices[3], 0)?; + // Copy and assign the right node at the correct position. + right.copy_advice(|| "right", &mut region, config.advices[4], 0)?; + + // Offset 1 + // Copy and assign z_1 of SinsemillaHash(a) = a_1 + z1_a.copy_advice(|| "z1_a", &mut region, config.advices[0], 1)?; + // Copy and assign z_1 of SinsemillaHash(b) = b_1 + z1_b.copy_advice(|| "z1_b", &mut region, config.advices[1], 1)?; + // Copy `b_1`, which has been constrained to be a 5-bit value + b_1.inner() + .copy_advice(|| "b_1", &mut region, config.advices[2], 1)?; + // Copy `b_2`, which has been constrained to be a 5-bit value + b_2.inner() + .copy_advice(|| "b_2", &mut region, config.advices[3], 1)?; + + Ok(()) + }, + )?; + } + // Check layer hash output against Sinsemilla primitives hash + #[cfg(test)] + { + use crate::{sinsemilla::primitives::HashDomain, utilities::i2lebsp}; + + use group::ff::PrimeFieldBits; + + left.value() + .zip(right.value()) + .zip(hash.value()) + .assert_if_known(|((left, right), hash)| { + let l = i2lebsp::<10>(l as u64); + let left: Vec<_> = left + .to_le_bits() + .iter() + .by_vals() + .take(pallas::Base::NUM_BITS as usize) + .collect(); + let right: Vec<_> = right + .to_le_bits() + .iter() + .by_vals() + .take(pallas::Base::NUM_BITS as usize) + .collect(); + let merkle_crh = HashDomain::from_Q(Q.into()); + + let mut message = l.to_vec(); + message.extend_from_slice(&left); + message.extend_from_slice(&right); + + let expected = merkle_crh.hash(message.into_iter()).unwrap(); + + expected.to_repr() == hash.to_repr() + }); + } + + Ok(hash) + + + } +} + +impl UtilitiesInstructions for MerkleChipOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + type Var = AssignedCell; +} + +impl CondSwapInstructions for MerkleChipOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + #[allow(clippy::type_complexity)] + fn swap( + &self, + layouter: impl Layouter, + pair: (Self::Var, Value), + swap: Value, + ) -> Result<(Self::Var, Self::Var), Error> { + self.base.swap(layouter, pair, swap) } } -impl - SinsemillaInstructionsOptimized - for MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +impl SinsemillaInstructions +for MerkleChipOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { + type CellValue = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::CellValue; + + type Message = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::Message; + type MessagePiece = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::MessagePiece; + type RunningSum = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::RunningSum; + + type X = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::X; + type NonIdentityPoint = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::NonIdentityPoint; + type FixedPoints = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::FixedPoints; + + type HashDomains = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::HashDomains; + type CommitDomains = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::CommitDomains; + + fn witness_message_piece( + &self, + layouter: impl Layouter, + value: Value, + num_words: usize, + ) -> Result { + let config = self.config().sinsemilla_config.clone(); + let chip = SinsemillaChipOptimized::::construct(config); + chip.witness_message_piece(layouter, value, num_words) + } + #[allow(non_snake_case)] #[allow(clippy::type_complexity)] - fn hash_to_point_with_private_init( + fn hash_to_point( &self, layouter: impl Layouter, - Q: &Self::NonIdentityPoint, + Q: pallas::Affine, message: Self::Message, ) -> Result<(Self::NonIdentityPoint, Vec>), Error> { let config = self.config().sinsemilla_config.clone(); - let chip = SinsemillaChip::< - Hash, - Commit, - F, - LookupRangeCheckConfigOptimized, - >::construct(config); - chip.hash_to_point_with_private_init(layouter, Q, message) + let chip = SinsemillaChipOptimized::::construct(config); + chip.hash_to_point(layouter, Q, message) } -} + + fn extract(point: &Self::NonIdentityPoint) -> Self::X { + SinsemillaChipOptimized::::extract(point) + } +} \ No newline at end of file diff --git a/halo2_gadgets/src/utilities/lookup_range_check.rs b/halo2_gadgets/src/utilities/lookup_range_check.rs index f553920036..35dcd6ecf3 100644 --- a/halo2_gadgets/src/utilities/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities/lookup_range_check.rs @@ -69,14 +69,22 @@ pub struct LookupRangeCheckConfig { pub(crate) _marker: PhantomData, } -/// FIXME: add doc +/// Trait that provides common methods for a lookup range check. pub trait LookupRangeCheck { - /// FIXME: add doc - fn is_optimized() -> bool; - /// FIXME: add doc + /// Returns a reference to the `LookupRangeCheckConfig` instance. fn config(&self) -> &LookupRangeCheckConfig; - /// FIXME: add doc + /// The `running_sum` advice column breaks the field element into `K`-bit + /// words. It is used to construct the input expression to the lookup + /// argument. + /// + /// The `table_idx` fixed column contains values from [0..2^K). Looking up + /// a value in `table_idx` constrains it to be within this range. The table + /// can be loaded outside this helper. + /// + /// # Side-effects + /// + /// Both the `running_sum` and `constants` columns will be equality-enabled. fn configure( meta: &mut ConstraintSystem, running_sum: Column, @@ -86,9 +94,14 @@ pub trait LookupRangeCheck { Self: Sized; #[cfg(test)] + // Fill `table_idx` and `table_range_check_tag`. + // This is only used in testing for now, since the Sinsemilla chip provides a pre-loaded table + // in the Orchard context. fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error>; - /// FIXME: add doc + /// Constrain `x` to be a NUM_BITS word. + /// + /// `element` must have been assigned to `self.running_sum` at offset 0. fn short_range_check( &self, region: &mut Region<'_, F>, @@ -274,9 +287,6 @@ pub trait LookupRangeCheck { } impl LookupRangeCheck for LookupRangeCheckConfig { - fn is_optimized() -> bool { - false - } fn config(&self) -> &LookupRangeCheckConfig { self @@ -293,7 +303,6 @@ impl LookupRangeCheck for LookupRangeCh let q_running = meta.complex_selector(); let q_bitshift = meta.selector(); - // FIXME: q_range_check_4 and q_range_check_5 need to be created here // if the order of the creation makes a difference let config = LookupRangeCheckConfig { q_lookup, @@ -308,7 +317,6 @@ impl LookupRangeCheck for LookupRangeCh meta.lookup(|meta| { let q_lookup = meta.query_selector(config.q_lookup); let q_running = meta.query_selector(config.q_running); - // FIXME: q_range_check_4 and q_range_check_5 need to be created here // if the order of the creation makes a difference let z_cur = meta.query_advice(config.running_sum, Rotation::cur()); let one = Expression::Constant(F::ONE); @@ -424,10 +432,13 @@ impl LookupRangeCheck for LookupRangeCh } } -/// FIXME: add doc +/// The `DefaultLookupRangeCheck` trait extends the `LookupRangeCheck` with additional +/// standard traits necessary for effective use in cryptographic contexts. pub trait DefaultLookupRangeCheck: LookupRangeCheck + Eq + PartialEq + Clone + Copy + Debug { } impl DefaultLookupRangeCheck for LookupRangeCheckConfig {} + + diff --git a/halo2_gadgets/src/utilities_opt/cond_swap.rs b/halo2_gadgets/src/utilities_opt/cond_swap.rs index d14e5407ba..ffe9d45838 100644 --- a/halo2_gadgets/src/utilities_opt/cond_swap.rs +++ b/halo2_gadgets/src/utilities_opt/cond_swap.rs @@ -131,7 +131,7 @@ mod tests { tests::TestFixedBases, NonIdentityPoint, Point, }, - utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig}, + utilities::lookup_range_check::{LookupRangeCheck}, }; use group::{cofactor::CofactorCurveAffine, Curve, Group}; diff --git a/halo2_gadgets/src/utilities_opt/lookup_range_check.rs b/halo2_gadgets/src/utilities_opt/lookup_range_check.rs index 6306e25bfb..97ab50c97e 100644 --- a/halo2_gadgets/src/utilities_opt/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities_opt/lookup_range_check.rs @@ -171,10 +171,6 @@ impl LookupRangeCheckConfigOptimized { impl LookupRangeCheck for LookupRangeCheckConfigOptimized { - fn is_optimized() -> bool { - true - } - fn config(&self) -> &LookupRangeCheckConfig { &self.base }