From 58a6ff1d6f1fa9d752180411c63a3464c86f80a3 Mon Sep 17 00:00:00 2001 From: YaoGalteland Date: Mon, 29 Apr 2024 19:52:32 +0200 Subject: [PATCH] initial modification --- halo2_gadgets/src/ecc.rs | 79 +- halo2_gadgets/src/ecc/chip.rs | 107 ++- halo2_gadgets/src/ecc/chip/mul.rs | 27 +- halo2_gadgets/src/ecc/chip/mul/overflow.rs | 10 +- halo2_gadgets/src/ecc/chip/mul_fixed.rs | 6 +- .../src/ecc/chip/mul_fixed/base_field_elem.rs | 39 +- .../src/ecc/chip/mul_fixed/full_width.rs | 17 +- halo2_gadgets/src/ecc/chip/mul_fixed/short.rs | 326 +------- halo2_gadgets/src/ecc/chip/witness_point.rs | 41 +- halo2_gadgets/src/sinsemilla.rs | 164 ++-- halo2_gadgets/src/sinsemilla/chip.rs | 211 +++--- .../src/sinsemilla/chip/generator_table.rs | 87 +-- .../src/sinsemilla/chip/hash_to_point.rs | 167 ++--- halo2_gadgets/src/sinsemilla/merkle.rs | 42 +- halo2_gadgets/src/sinsemilla/merkle/chip.rs | 100 ++- halo2_gadgets/src/sinsemilla/primitives.rs | 66 +- halo2_gadgets/src/utilities/cond_swap.rs | 346 +-------- .../src/utilities/lookup_range_check.rs | 699 +++++------------- 18 files changed, 623 insertions(+), 1911 deletions(-) diff --git a/halo2_gadgets/src/ecc.rs b/halo2_gadgets/src/ecc.rs index 4861a20e52..97859c3ddd 100644 --- a/halo2_gadgets/src/ecc.rs +++ b/halo2_gadgets/src/ecc.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; use halo2_proofs::{ arithmetic::CurveAffine, - circuit::{AssignedCell, Chip, Layouter, Value}, + circuit::{Chip, Layouter, Value}, plonk::Error, }; @@ -60,15 +60,6 @@ pub trait EccInstructions: value: Value, ) -> Result; - /// Witnesses the given constant point as a private input to the circuit. - /// This allows the point to be the identity, mapped to (0, 0) in - /// affine coordinates. - fn witness_point_from_constant( - &self, - layouter: &mut impl Layouter, - value: C, - ) -> Result; - /// Witnesses the given point as a private input to the circuit. /// This returns an error if the point is the identity. fn witness_point_non_id( @@ -120,15 +111,6 @@ pub trait EccInstructions: b: &B, ) -> Result; - /// Performs variable-base sign-scalar multiplication, returning `[sign] point` - /// `sign` must be in {-1, 1}. - fn mul_sign( - &self, - layouter: &mut impl Layouter, - sign: &AssignedCell, - point: &Self::Point, - ) -> Result; - /// Performs variable-base scalar multiplication, returning `[scalar] base`. fn mul( &self, @@ -249,7 +231,7 @@ impl> ScalarFixed { #[derive(Debug)] pub struct ScalarFixedShort> { chip: EccChip, - inner: EccChip::ScalarFixedShort, + pub(crate) inner: EccChip::ScalarFixedShort, } impl> ScalarFixedShort { @@ -393,8 +375,8 @@ impl + Clone + Debug + Eq> /// A point on a specific elliptic curve. #[derive(Copy, Clone, Debug)] pub struct Point + Clone + Debug + Eq> { - chip: EccChip, - inner: EccChip::Point, + pub(crate) chip: EccChip, + pub(crate) inner: EccChip::Point, } impl + Clone + Debug + Eq> Point { @@ -408,16 +390,6 @@ impl + Clone + Debug + Eq> Point, - value: C, - ) -> Result { - let point = chip.witness_point_from_constant(&mut layouter, value); - point.map(|inner| Point { chip, inner }) - } - /// Constrains this point to be equal in value to another point. pub fn constrain_equal> + Clone>( &self, @@ -460,21 +432,6 @@ impl + Clone + Debug + Eq> Point, - sign: &AssignedCell, - ) -> Result, Error> { - self.chip - .mul_sign(&mut layouter, sign, &self.inner) - .map(|point| Point { - chip: self.chip.clone(), - inner: point, - }) - } } /// The affine short Weierstrass x-coordinate of a point on a specific elliptic curve. @@ -638,7 +595,7 @@ pub(crate) mod tests { }, FixedPoints, }; - use crate::utilities::lookup_range_check::LookupRangeCheckConfig; + use crate::utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig}; #[derive(Debug, Eq, PartialEq, Clone)] pub(crate) struct TestFixedBases; @@ -772,7 +729,10 @@ pub(crate) mod tests { #[allow(non_snake_case)] impl Circuit for MyCircuit { - type Config = EccConfig; + type Config = EccConfig< + TestFixedBases, + LookupRangeCheckConfig, + >; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -793,7 +753,6 @@ pub(crate) mod tests { meta.advice_column(), ]; let lookup_table = meta.lookup_table_column(); - let table_range_check_tag = meta.lookup_table_column(); let lagrange_coeffs = [ meta.fixed_column(), meta.fixed_column(), @@ -808,13 +767,11 @@ pub(crate) mod tests { let constants = meta.fixed_column(); meta.enable_constant(constants); - let range_check = LookupRangeCheckConfig::configure( - meta, - advices[9], - lookup_table, - table_range_check_tag, - ); - EccChip::::configure(meta, advices, lagrange_coeffs, range_check) + let range_check = LookupRangeCheckConfig::configure(meta, advices[9], lookup_table); + EccChip::< + TestFixedBases, + LookupRangeCheckConfig, + >::configure(meta, advices, lagrange_coeffs, range_check) } fn synthesize( @@ -914,14 +871,6 @@ pub(crate) mod tests { )?; } - // Test variable-base sign-scalar multiplication - { - super::chip::mul_fixed::short::tests::test_mul_sign( - chip.clone(), - layouter.namespace(|| "variable-base sign-scalar mul"), - )?; - } - // Test full-width fixed-base scalar multiplication { super::chip::mul_fixed::full_width::tests::test_mul_fixed( diff --git a/halo2_gadgets/src/ecc/chip.rs b/halo2_gadgets/src/ecc/chip.rs index 402020384b..c27a34ca6b 100644 --- a/halo2_gadgets/src/ecc/chip.rs +++ b/halo2_gadgets/src/ecc/chip.rs @@ -1,10 +1,7 @@ //! Chip implementations for the ECC gadgets. use super::{BaseFitsInScalarInstructions, EccInstructions, FixedPoints}; -use crate::{ - sinsemilla::primitives as sinsemilla, - utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, -}; +use crate::utilities::{lookup_range_check::DefaultLookupRangeCheck, UtilitiesInstructions}; use arrayvec::ArrayVec; use ff::PrimeField; @@ -17,12 +14,12 @@ use pasta_curves::{arithmetic::CurveAffine, pallas}; use std::convert::TryInto; -pub(super) mod add; -pub(super) mod add_incomplete; +pub(crate) mod add; +pub(crate) mod add_incomplete; pub mod constants; -pub(super) mod mul; -pub(super) mod mul_fixed; -pub(super) mod witness_point; +pub(crate) mod mul; +pub(crate) mod mul_fixed; +pub(crate) mod witness_point; pub use constants::*; @@ -37,11 +34,11 @@ pub struct EccPoint { /// x-coordinate /// /// Stored as an `Assigned` to enable batching inversions. - x: AssignedCell, pallas::Base>, + pub(crate) x: AssignedCell, pallas::Base>, /// y-coordinate /// /// Stored as an `Assigned` to enable batching inversions. - y: AssignedCell, pallas::Base>, + pub(crate) y: AssignedCell, pallas::Base>, } impl EccPoint { @@ -137,7 +134,10 @@ impl From for EccPoint { /// Configuration for [`EccChip`]. #[derive(Clone, Debug, Eq, PartialEq)] #[allow(non_snake_case)] -pub struct EccConfig> { +pub struct EccConfig< + FixedPoints: super::FixedPoints, + 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 - mul_fixed_short: mul_fixed::short::Config, + 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 - witness_point: witness_point::Config, + 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`. @@ -227,12 +227,15 @@ pub trait FixedPoint: std::fmt::Debug + Eq + Clone { /// An [`EccInstructions`] chip that uses 10 advice columns. #[derive(Clone, Debug, Eq, PartialEq)] -pub struct EccChip> { - config: EccConfig, +pub struct EccChip, Lookup: DefaultLookupRangeCheck> +{ + config: EccConfig, } -impl> Chip for EccChip { - type Config = EccConfig; +impl, Lookup: DefaultLookupRangeCheck> + Chip for EccChip +{ + type Config = EccConfig; type Loaded = (); fn config(&self) -> &Self::Config { @@ -244,13 +247,15 @@ impl> Chip for Ecc } } -impl> UtilitiesInstructions - for EccChip +impl, Lookup: DefaultLookupRangeCheck> + UtilitiesInstructions for EccChip { type Var = AssignedCell; } -impl> EccChip { +impl, Lookup: DefaultLookupRangeCheck> + EccChip +{ /// Reconstructs this chip from the given config. pub fn construct(config: >::Config) -> Self { Self { config } @@ -264,7 +269,7 @@ impl> EccChip { 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]); @@ -301,12 +306,13 @@ impl> EccChip { mul_fixed::short::Config::::configure(meta, mul_fixed.clone()); // 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( - meta, - advices[6..9].try_into().unwrap(), - range_check, - mul_fixed, - ); + let mul_fixed_base_field = + mul_fixed::base_field_elem::Config::::configure( + meta, + advices[6..9].try_into().unwrap(), + range_check, + mul_fixed, + ); EccConfig { advices, @@ -339,7 +345,7 @@ pub struct EccScalarFixed { type MagnitudeCell = AssignedCell; // TODO: Make V an enum Sign { Positive, Negative } type SignCell = AssignedCell; -type MagnitudeSign = (MagnitudeCell, SignCell); +pub(crate) type MagnitudeSign = (MagnitudeCell, SignCell); /// A signed short scalar used for fixed-base scalar multiplication. /// A short scalar must have magnitude in the range [0..2^64), with @@ -407,7 +413,8 @@ pub enum ScalarVar { FullWidth, } -impl> EccInstructions for EccChip +impl, Lookup: DefaultLookupRangeCheck> + EccInstructions for EccChip where >::Base: FixedPoint, @@ -453,18 +460,6 @@ where ) } - fn witness_point_from_constant( - &self, - layouter: &mut impl Layouter, - value: pallas::Affine, - ) -> Result { - let config = self.config().witness_point; - layouter.assign_region( - || "witness point (constant)", - |mut region| config.constant_point(value, 0, &mut region), - ) - } - fn witness_point_non_id( &self, layouter: &mut impl Layouter, @@ -544,24 +539,6 @@ where ) } - /// Performs variable-base sign-scalar multiplication, returning `[sign] point` - /// `sign` must be in {-1, 1}. - fn mul_sign( - &self, - layouter: &mut impl Layouter, - sign: &AssignedCell, - point: &Self::Point, - ) -> Result { - // Multiply point by sign, using the same gate as mul_fixed::short. - // This also constrains sign to be in {-1, 1}. - let config_short = self.config().mul_fixed_short.clone(); - config_short.assign_scalar_sign( - layouter.namespace(|| "variable-base sign-scalar mul"), - sign, - point, - ) - } - fn mul( &self, layouter: &mut impl Layouter, @@ -624,8 +601,8 @@ where } } -impl> 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 8e857ae442..a67b77b0e8 100644 --- a/halo2_gadgets/src/ecc/chip/mul.rs +++ b/halo2_gadgets/src/ecc/chip/mul.rs @@ -1,7 +1,7 @@ use super::{add, EccPoint, NonIdentityEccPoint, ScalarVar, T_Q}; -use crate::{ - sinsemilla::primitives as sinsemilla, - utilities::{bool_check, lookup_range_check::LookupRangeCheckConfig, ternary}, +use crate::utilities::{ + lookup_range_check::DefaultLookupRangeCheck, + {bool_check, ternary}, }; use std::{ convert::TryInto, @@ -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 { - pub(super) fn configure( +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,12 +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; use crate::{ ecc::{ chip::{EccChip, EccPoint}, @@ -476,13 +477,13 @@ 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]; + let column = chip.config.advices[0]; fn constrain_equal_non_id< EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, diff --git a/halo2_gadgets/src/ecc/chip/mul/overflow.rs b/halo2_gadgets/src/ecc/chip/mul/overflow.rs index 12101ae82e..0912bd3a39 100644 --- a/halo2_gadgets/src/ecc/chip/mul/overflow.rs +++ b/halo2_gadgets/src/ecc/chip/mul/overflow.rs @@ -1,6 +1,6 @@ use super::{T_Q, Z}; use crate::{ - sinsemilla::primitives as sinsemilla, utilities::lookup_range_check::LookupRangeCheckConfig, + sinsemilla::primitives as sinsemilla, utilities::lookup_range_check::DefaultLookupRangeCheck, }; use group::ff::PrimeField; @@ -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.rs b/halo2_gadgets/src/ecc/chip/mul_fixed.rs index d0781056b8..686cb6203e 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed.rs @@ -41,11 +41,11 @@ pub struct Config> { fixed_z: Column, // Decomposition of an `n-1`-bit scalar into `k`-bit windows: // a = a_0 + 2^k(a_1) + 2^{2k}(a_2) + ... + 2^{(n-1)k}(a_{n-1}) - window: Column, + pub(crate) window: Column, // y-coordinate of accumulator (only used in the final row). - u: Column, + pub(crate) u: Column, // Configuration for `add` - add_config: add::Config, + pub(crate) add_config: add::Config, // Configuration for `add_incomplete` add_incomplete_config: add_incomplete::Config, _marker: PhantomData, 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 9a2b0c76ce..5ea0b38eef 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 @@ -1,10 +1,8 @@ use super::super::{EccBaseFieldElemFixed, EccPoint, FixedPoints, NUM_WINDOWS, T_P}; use super::H_BASE; -use crate::utilities::bool_check; -use crate::{ - sinsemilla::primitives as sinsemilla, - utilities::{bitrange_subset, lookup_range_check::LookupRangeCheckConfig, range_check}, +use crate::utilities::{ + bitrange_subset, bool_check, lookup_range_check::DefaultLookupRangeCheck, range_check, }; use group::ff::PrimeField; @@ -18,18 +16,18 @@ use pasta_curves::pallas; use std::convert::TryInto; #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Config> { +pub struct Config, Lookup: DefaultLookupRangeCheck> { q_mul_fixed_base_field: Selector, canon_advices: [Column; 3], - lookup_config: LookupRangeCheckConfig, + lookup_config: Lookup, super_config: super::Config, } -impl> 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() { @@ -388,6 +386,7 @@ pub mod tests { use pasta_curves::pallas; use rand::rngs::OsRng; + use crate::utilities::lookup_range_check::DefaultLookupRangeCheck; use crate::{ ecc::{ chip::{EccChip, FixedPoint, H}, @@ -397,8 +396,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( @@ -410,22 +409,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(); @@ -464,10 +463,10 @@ pub mod tests { { let h = pallas::Base::from(H as u64); let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334" - .chars() - .fold(pallas::Base::zero(), |acc, c| { - acc * &h + &pallas::Base::from(c.to_digit(8).unwrap() as u64) - }); + .chars() + .fold(pallas::Base::zero(), |acc, c| { + acc * &h + &pallas::Base::from(c.to_digit(8).unwrap() as u64) + }); let result = { let scalar_fixed = chip.load_private( layouter.namespace(|| "mul with double"), 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 886f86bbba..fe2cc22094 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs @@ -192,9 +192,10 @@ pub mod tests { tests::{FullWidth, TestFixedBases}, FixedPoint, NonIdentityPoint, Point, ScalarFixed, }; + 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(); @@ -209,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 a10c54c1ed..48a1cbf88a 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs @@ -4,7 +4,7 @@ use super::super::{EccPoint, EccScalarFixedShort, FixedPoints, L_SCALAR_SHORT, N use crate::{ecc::chip::MagnitudeSign, utilities::bool_check}; use halo2_proofs::{ - circuit::{AssignedCell, Layouter, Region}, + circuit::{Layouter, Region}, plonk::{ConstraintSystem, Constraints, Error, Expression, Selector}, poly::Rotation, }; @@ -13,8 +13,8 @@ use pasta_curves::pallas; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Config> { // Selector used for fixed-base scalar mul with short signed exponent. - q_mul_fixed_short: Selector, - super_config: super::Config, + pub(crate) q_mul_fixed_short: Selector, + pub(crate) super_config: super::Config, } impl> Config { @@ -241,73 +241,11 @@ impl> Config { Ok((result, scalar)) } - - /// Multiply the point by sign, using the q_mul_fixed_short gate. - /// Constraints `sign` in {-1, 1} - pub fn assign_scalar_sign( - &self, - mut layouter: impl Layouter, - sign: &AssignedCell, - point: &EccPoint, - ) -> Result { - let signed_point = layouter.assign_region( - || "Signed point", - |mut region| { - let offset = 0; - - // Enable mul_fixed_short selector to check the sign logic. - self.q_mul_fixed_short.enable(&mut region, offset)?; - - // Set "last window" to 0 (this field is irrelevant here). - region.assign_advice_from_constant( - || "u=0", - self.super_config.u, - offset, - pallas::Base::zero(), - )?; - - // Copy sign to `window` column - sign.copy_advice(|| "sign", &mut region, self.super_config.window, offset)?; - - // Assign the input y-coordinate. - point.y.copy_advice( - || "unsigned y", - &mut region, - self.super_config.add_config.y_qr, - offset, - )?; - - // Conditionally negate y-coordinate according to the value of sign - let signed_y_val = sign.value().and_then(|sign| { - if sign == &-pallas::Base::one() { - -point.y.value() - } else { - point.y.value().cloned() - } - }); - - // Assign the output signed y-coordinate. - let signed_y = region.assign_advice( - || "signed y", - self.super_config.add_config.y_p, - offset, - || signed_y_val, - )?; - - Ok(EccPoint { - x: point.x.clone(), - y: signed_y, - }) - }, - )?; - - Ok(signed_point) - } } #[cfg(test)] pub mod tests { - use group::{ff::PrimeField, Curve, Group}; + use group::{ff::PrimeField, Curve}; use halo2_proofs::{ arithmetic::CurveAffine, circuit::{AssignedCell, Chip, Layouter, Value}, @@ -315,6 +253,7 @@ pub mod tests { }; use pasta_curves::pallas; + use crate::utilities::lookup_range_check::{DefaultLookupRangeCheck, LookupRangeCheck}; use crate::{ ecc::{ chip::{EccChip, FixedPoint, MagnitudeSign}, @@ -325,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, @@ -351,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, @@ -462,6 +401,8 @@ pub mod tests { Ok(()) } + // todo: fixit + #[test] fn invalid_magnitude_sign() { use crate::{ @@ -487,7 +428,10 @@ pub mod tests { } impl Circuit for MyCircuit { - type Config = EccConfig; + type Config = EccConfig< + TestFixedBases, + LookupRangeCheckConfig, + >; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -508,7 +452,6 @@ pub mod tests { meta.advice_column(), ]; let lookup_table = meta.lookup_table_column(); - let table_range_check_tag = meta.lookup_table_column(); let lagrange_coeffs = [ meta.fixed_column(), meta.fixed_column(), @@ -524,13 +467,11 @@ pub mod tests { let constants = meta.fixed_column(); meta.enable_constant(constants); - let range_check = LookupRangeCheckConfig::configure( - meta, - advices[9], - lookup_table, - table_range_check_tag, - ); - EccChip::::configure(meta, advices, lagrange_coeffs, range_check) + let range_check = LookupRangeCheckConfig::configure(meta, advices[9], lookup_table); + EccChip::< + TestFixedBases, + LookupRangeCheckConfig, + >::configure(meta, advices, lagrange_coeffs, range_check) } fn synthesize( @@ -650,7 +591,7 @@ pub mod tests { )], }, VerifyFailure::Permutation { - column: (Any::Fixed, 10).into(), + column: (Any::Fixed, 9).into(), location: FailureLocation::OutsideRegion { row: 0 }, }, VerifyFailure::Permutation { @@ -725,223 +666,4 @@ pub mod tests { ); } } - - pub(crate) fn test_mul_sign( - chip: EccChip, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - // Generate a random non-identity point P - let p_val = pallas::Point::random(rand::rngs::OsRng).to_affine(); - let p = Point::new( - chip.clone(), - layouter.namespace(|| "P"), - Value::known(p_val), - )?; - - // Create -P - let p_neg_val = -p_val; - let p_neg = Point::new( - chip.clone(), - layouter.namespace(|| "-P"), - Value::known(p_neg_val), - )?; - - // Create the identity point - let identity = Point::new( - chip.clone(), - layouter.namespace(|| "identity"), - Value::known(pallas::Point::identity().to_affine()), - )?; - - // Create -1 and 1 scalars - let pos_sign = chip.load_private( - layouter.namespace(|| "positive sign"), - chip.config().advices[0], - Value::known(pallas::Base::one()), - )?; - let neg_sign = chip.load_private( - layouter.namespace(|| "negative sign"), - chip.config().advices[1], - Value::known(-pallas::Base::one()), - )?; - - // [1] P == P - { - let result = p.mul_sign(layouter.namespace(|| "[1] P"), &pos_sign)?; - result.constrain_equal(layouter.namespace(|| "constrain [1] P"), &p)?; - } - - // [-1] P == -P - { - let result = p.mul_sign(layouter.namespace(|| "[1] P"), &neg_sign)?; - result.constrain_equal(layouter.namespace(|| "constrain [1] P"), &p_neg)?; - } - - // [1] 0 == 0 - { - let result = identity.mul_sign(layouter.namespace(|| "[1] O"), &pos_sign)?; - result.constrain_equal(layouter.namespace(|| "constrain [1] 0"), &identity)?; - } - - // [-1] 0 == 0 - { - let result = identity.mul_sign(layouter.namespace(|| "[-1] O"), &neg_sign)?; - result.constrain_equal(layouter.namespace(|| "constrain [1] 0"), &identity)?; - } - - Ok(()) - } - - #[test] - fn invalid_sign_in_mul_sign() { - use crate::{ecc::chip::EccConfig, utilities::UtilitiesInstructions}; - use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner}, - dev::{FailureLocation, MockProver, VerifyFailure}, - plonk::{Circuit, ConstraintSystem, Error}, - }; - - #[derive(Default)] - struct MyCircuit { - base: Value, - sign: Value, - } - - impl UtilitiesInstructions for MyCircuit { - type Var = AssignedCell; - } - - impl Circuit for MyCircuit { - type Config = EccConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let advices = [ - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - ]; - let lookup_table = meta.lookup_table_column(); - let table_range_check_tag = meta.lookup_table_column(); - let lagrange_coeffs = [ - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - ]; - - // Shared fixed column for loading constants - let constants = meta.fixed_column(); - meta.enable_constant(constants); - - let range_check = LookupRangeCheckConfig::configure( - meta, - advices[9], - lookup_table, - table_range_check_tag, - ); - EccChip::::configure(meta, advices, lagrange_coeffs, range_check) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let chip = EccChip::construct(config.clone()); - - let column = config.advices[0]; - - //let short_config = config.mul_fixed_short.clone(); - let base = Point::new(chip, layouter.namespace(|| "load base"), self.base)?; - - let sign = - self.load_private(layouter.namespace(|| "load sign"), column, self.sign)?; - - base.mul_sign(layouter.namespace(|| "[sign] base"), &sign)?; - - Ok(()) - } - } - - // Copied from halo2_proofs::dev::util - fn format_value(v: pallas::Base) -> String { - use ff::Field; - if v.is_zero_vartime() { - "0".into() - } else if v == pallas::Base::one() { - "1".into() - } else if v == -pallas::Base::one() { - "-1".into() - } else { - // Format value as hex. - let s = format!("{:?}", v); - // Remove leading zeroes. - let s = s.strip_prefix("0x").unwrap(); - let s = s.trim_start_matches('0'); - format!("0x{}", s) - } - } - - // Sign that is not +/- 1 should fail - // Generate a random non-identity point - let point = pallas::Point::random(rand::rngs::OsRng); - let circuit = MyCircuit { - base: Value::known(point.to_affine()), - sign: Value::known(pallas::Base::zero()), - }; - - let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); - assert_eq!( - prover.verify(), - Err(vec![ - VerifyFailure::ConstraintNotSatisfied { - constraint: ((17, "Short fixed-base mul gate").into(), 1, "sign_check").into(), - location: FailureLocation::InRegion { - region: (2, "Signed point").into(), - offset: 0, - }, - cell_values: vec![(((Any::Advice, 4).into(), 0).into(), "0".to_string())], - }, - VerifyFailure::ConstraintNotSatisfied { - constraint: ( - (17, "Short fixed-base mul gate").into(), - 3, - "negation_check" - ) - .into(), - location: FailureLocation::InRegion { - region: (2, "Signed point").into(), - offset: 0, - }, - cell_values: vec![ - ( - ((Any::Advice, 1).into(), 0).into(), - format_value(*point.to_affine().coordinates().unwrap().y()), - ), - ( - ((Any::Advice, 3).into(), 0).into(), - format_value(*point.to_affine().coordinates().unwrap().y()), - ), - (((Any::Advice, 4).into(), 0).into(), "0".to_string()), - ], - } - ]) - ); - } } diff --git a/halo2_gadgets/src/ecc/chip/witness_point.rs b/halo2_gadgets/src/ecc/chip/witness_point.rs index 98f865a6dc..580a07ca1d 100644 --- a/halo2_gadgets/src/ecc/chip/witness_point.rs +++ b/halo2_gadgets/src/ecc/chip/witness_point.rs @@ -12,14 +12,14 @@ use halo2_proofs::{ }; use pasta_curves::{arithmetic::CurveAffine, pallas}; -type Coordinates = ( +pub(crate) type Coordinates = ( AssignedCell, pallas::Base>, AssignedCell, pallas::Base>, ); #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Config { - q_point: Selector, + pub(crate) q_point: Selector, q_point_non_id: Selector, // x-coordinate pub x: Column, @@ -102,21 +102,6 @@ impl Config { Ok((x_var, y_var)) } - fn assign_xy_from_constant( - &self, - value: (Assigned, Assigned), - offset: usize, - region: &mut Region<'_, pallas::Base>, - ) -> Result { - // Assign `x` value - let x_var = region.assign_advice_from_constant(|| "x", self.x, offset, value.0)?; - - // Assign `y` value - let y_var = region.assign_advice_from_constant(|| "y", self.y, offset, value.1)?; - - Ok((x_var, y_var)) - } - /// Assigns a point that can be the identity. pub(super) fn point( &self, @@ -141,28 +126,6 @@ impl Config { .map(|(x, y)| EccPoint::from_coordinates_unchecked(x, y)) } - /// Assigns a constant point that can be the identity. - pub(super) fn constant_point( - &self, - value: pallas::Affine, - offset: usize, - region: &mut Region<'_, pallas::Base>, - ) -> Result { - // Enable `q_point` selector - self.q_point.enable(region, offset)?; - - let value = if value == pallas::Affine::identity() { - // Map the identity to (0, 0). - (Assigned::Zero, Assigned::Zero) - } else { - let value = value.coordinates().unwrap(); - (value.x().into(), value.y().into()) - }; - - self.assign_xy_from_constant(value, offset, region) - .map(|(x, y)| EccPoint::from_coordinates_unchecked(x, y)) - } - /// Assigns a non-identity point. pub(super) fn point_non_id( &self, diff --git a/halo2_gadgets/src/sinsemilla.rs b/halo2_gadgets/src/sinsemilla.rs index e57c0a2129..a3e2dc892b 100644 --- a/halo2_gadgets/src/sinsemilla.rs +++ b/halo2_gadgets/src/sinsemilla.rs @@ -15,7 +15,7 @@ use std::fmt::Debug; pub mod chip; pub mod merkle; -mod message; +pub(crate) mod message; pub mod primitives; /// The set of circuit instructions required to use the [`Sinsemilla`](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html) gadget. @@ -78,7 +78,7 @@ pub trait SinsemillaInstructions Result<(Self::NonIdentityPoint, Vec), Error>; - /// 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 - /// sum. - /// The initial point `Q` is a private point. - #[allow(non_snake_case)] - #[allow(clippy::type_complexity)] - fn hash_to_point_with_private_init( - &self, - layouter: impl Layouter, - Q: &Self::NonIdentityPoint, - message: Self::Message, - ) -> Result<(Self::NonIdentityPoint, Vec), Error>; - /// Extracts the x-coordinate of the output of a Sinsemilla hash. fn extract(point: &Self::NonIdentityPoint) -> Self::X; } @@ -116,8 +102,8 @@ pub struct Message + Clone + Debug + Eq, { - chip: SinsemillaChip, - inner: SinsemillaChip::Message, + pub(crate) chip: SinsemillaChip, + pub(crate) inner: SinsemillaChip::Message, } impl @@ -126,7 +112,7 @@ where SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { #![allow(dead_code)] - fn from_bitstring( + pub(crate) fn from_bitstring( chip: SinsemillaChip, mut layouter: impl Layouter, bitstring: Vec>, @@ -200,7 +186,7 @@ where SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { #![allow(dead_code)] - fn from_bitstring( + pub(crate) fn from_bitstring( chip: SinsemillaChip, layouter: impl Layouter, bitstring: &[Value], @@ -297,9 +283,9 @@ pub struct HashDomain< + Debug + Eq, { - sinsemilla_chip: SinsemillaChip, - ecc_chip: EccChip, - Q: C, + pub(crate) sinsemilla_chip: SinsemillaChip, + pub(crate) ecc_chip: EccChip, + pub(crate) Q: C, } impl @@ -343,21 +329,6 @@ where .map(|(point, zs)| (ecc::NonIdentityPoint::from_inner(self.ecc_chip.clone(), point), zs)) } - #[allow(non_snake_case)] - #[allow(clippy::type_complexity)] - /// Evaluate the Sinsemilla hash of `message` from the private initial point `Q`. - pub fn hash_to_point_with_private_init( - &self, - layouter: impl Layouter, - Q: &>::NonIdentityPoint, - message: Message, - ) -> Result<(ecc::NonIdentityPoint, Vec), Error> { - assert_eq!(self.sinsemilla_chip, message.chip); - self.sinsemilla_chip - .hash_to_point_with_private_init(layouter, Q, message.inner) - .map(|(point, zs)| (ecc::NonIdentityPoint::from_inner(self.ecc_chip.clone(), point), zs)) - } - /// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash]. /// /// [concretesinsemillahash]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash @@ -412,8 +383,8 @@ pub struct CommitDomain< + Debug + Eq, { - M: HashDomain, - R: ecc::FixedPoint, + pub(crate) M: HashDomain, + pub(crate) R: ecc::FixedPoint, } impl @@ -441,63 +412,6 @@ where } } - #[allow(clippy::type_complexity)] - /// Evaluates the Sinsemilla hash of `message` from the public initial point `Q` stored - /// into `CommitDomain`. - pub fn hash( - &self, - layouter: impl Layouter, - message: Message, - ) -> Result< - ( - ecc::NonIdentityPoint, - Vec, - ), - Error, - > { - assert_eq!(self.M.sinsemilla_chip, message.chip); - self.M.hash_to_point(layouter, message) - } - - #[allow(non_snake_case)] - #[allow(clippy::type_complexity)] - /// Evaluates the Sinsemilla hash of `message` from the private initial point `Q`. - pub fn hash_with_private_init( - &self, - layouter: impl Layouter, - Q: &>::NonIdentityPoint, - message: Message, - ) -> Result< - ( - ecc::NonIdentityPoint, - Vec, - ), - Error, - > { - assert_eq!(self.M.sinsemilla_chip, message.chip); - self.M.hash_to_point_with_private_init(layouter, Q, message) - } - - #[allow(clippy::type_complexity)] - /// Returns the public initial point `Q` stored into `CommitDomain`. - pub fn q_init(&self) -> C { - self.M.Q - } - - #[allow(clippy::type_complexity)] - /// Evaluates the blinding factor equal to $\[r\] R$ where `r` is stored in the `CommitDomain`. - pub fn blinding_factor( - &self, - mut layouter: impl Layouter, - r: ecc::ScalarFixed, - ) -> Result< - ecc::Point, - Error, - > { - let (blind, _) = self.R.mul(layouter.namespace(|| "[r] R"), r)?; - Ok(blind) - } - #[allow(clippy::type_complexity)] /// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit]. /// @@ -515,8 +429,14 @@ where Error, > { assert_eq!(self.M.sinsemilla_chip, message.chip); - let blind = self.blinding_factor(layouter.namespace(|| "[r] R"), r)?; - let (p, zs) = self.hash(layouter.namespace(|| "M"), message)?; + + // FIXME: consider returning ZSA version of the following lines. + // It's not a breaking change because `blinding_factor` simply wraps `R.mul` + // and `hash` simply wraps `M.hash_to_point` - are those wrapper really needed? + //let blind = self.blinding_factor(layouter.namespace(|| "[r] R"), r)?; + //let (p, zs) = self.hash(layouter.namespace(|| "M"), message)?; + let (blind, _) = self.R.mul(layouter.namespace(|| "[r] R"), r)?; + let (p, zs) = self.M.hash_to_point(layouter.namespace(|| "M"), message)?; let commitment = p.add(layouter.namespace(|| "M + [r] R"), &blind)?; Ok((commitment, zs)) } @@ -560,7 +480,7 @@ pub(crate) mod tests { tests::{FullWidth, TestFixedBases}, NonIdentityPoint, }, - utilities::lookup_range_check::LookupRangeCheckConfig, + utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig}, }, }; @@ -607,9 +527,22 @@ pub(crate) mod tests { impl Circuit for MyCircuit { #[allow(clippy::type_complexity)] type Config = ( - EccConfig, - SinsemillaConfig, - SinsemillaConfig, + EccConfig< + TestFixedBases, + LookupRangeCheckConfig, + >, + SinsemillaConfig< + TestHashDomain, + TestCommitDomain, + TestFixedBases, + LookupRangeCheckConfig, + >, + SinsemillaConfig< + TestHashDomain, + TestCommitDomain, + TestFixedBases, + LookupRangeCheckConfig, + >, ); type FloorPlanner = SimpleFloorPlanner; @@ -637,7 +570,6 @@ pub(crate) mod tests { meta.enable_constant(constants); let table_idx = meta.lookup_table_column(); - let table_range_check_tag = meta.lookup_table_column(); let lagrange_coeffs = [ meta.fixed_column(), meta.fixed_column(), @@ -654,18 +586,14 @@ pub(crate) mod tests { table_idx, meta.lookup_table_column(), meta.lookup_table_column(), - table_range_check_tag, ); - let range_check = LookupRangeCheckConfig::configure( - meta, - advices[9], - table_idx, - table_range_check_tag, - ); + let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx); - let ecc_config = - EccChip::::configure(meta, advices, lagrange_coeffs, range_check); + let ecc_config = EccChip::< + TestFixedBases, + LookupRangeCheckConfig, + >::configure(meta, advices, lagrange_coeffs, range_check); let config1 = SinsemillaChip::configure( meta, @@ -696,10 +624,12 @@ pub(crate) mod tests { let ecc_chip = EccChip::construct(config.0); // The two `SinsemillaChip`s share the same lookup table. - SinsemillaChip::::load( - config.1.clone(), - &mut layouter, - )?; + SinsemillaChip::< + TestHashDomain, + TestCommitDomain, + TestFixedBases, + LookupRangeCheckConfig, + >::load(config.1.clone(), &mut layouter)?; // This MerkleCRH example is purely for illustrative purposes. // It is not an implementation of the Orchard protocol spec. diff --git a/halo2_gadgets/src/sinsemilla/chip.rs b/halo2_gadgets/src/sinsemilla/chip.rs index c55efd1105..7d96f2c8f3 100644 --- a/halo2_gadgets/src/sinsemilla/chip.rs +++ b/halo2_gadgets/src/sinsemilla/chip.rs @@ -9,7 +9,7 @@ use crate::{ chip::{DoubleAndAdd, NonIdentityEccPoint}, FixedPoints, }, - utilities::lookup_range_check::LookupRangeCheckConfig, + utilities::lookup_range_check::DefaultLookupRangeCheck, }; use std::marker::PhantomData; @@ -23,50 +23,52 @@ use halo2_proofs::{ }; use pasta_curves::pallas; -mod generator_table; +pub(crate) mod generator_table; use generator_table::GeneratorTableConfig; -mod hash_to_point; +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, + Lookup: DefaultLookupRangeCheck, { /// Binary selector used in lookup argument and in the body of the Sinsemilla hash. - q_sinsemilla1: Selector, + pub(crate) q_sinsemilla1: Selector, /// Non-binary selector used in lookup argument and in the body of the Sinsemilla hash. - q_sinsemilla2: Column, + pub(crate) 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, + pub(crate) q_sinsemilla4: Selector, /// Fixed column used to load the y-coordinate of the domain $Q$. - fixed_y_q: Column, + pub(crate) fixed_y_q: Column, /// Logic specific to merged double-and-add. - double_and_add: DoubleAndAdd, + pub(crate) double_and_add: DoubleAndAdd, /// Advice column used to load the message. - bits: Column, + pub(crate) bits: Column, /// Advice column used to witness message pieces. This may or may not be the same /// column as `bits`. - witness_pieces: Column, + pub(crate) 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, + pub(crate) generator_table: GeneratorTableConfig, /// An advice column configured to perform lookup range checks. - lookup_config: LookupRangeCheckConfig, - _marker: PhantomData<(Hash, Commit, F)>, + pub(crate) lookup_config: Lookup, + pub(crate) _marker: PhantomData<(Hash, Commit, F)>, } -impl SinsemillaConfig +impl SinsemillaConfig where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { /// Returns an array of all advice columns in this config, in arbitrary order. pub(super) fn advices(&self) -> [Column; 5] { @@ -80,7 +82,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 } @@ -96,22 +98,24 @@ 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, + Lookup: DefaultLookupRangeCheck, { - config: SinsemillaConfig, + config: SinsemillaConfig, } -impl Chip for SinsemillaChip +impl Chip for SinsemillaChip where Hash: HashDomains, Fixed: FixedPoints, Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { - type Config = SinsemillaConfig; + type Config = SinsemillaConfig; type Loaded = (); fn config(&self) -> &Self::Config { @@ -123,11 +127,12 @@ where } } -impl SinsemillaChip +impl SinsemillaChip where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { /// Reconstructs this chip from the given config. pub fn construct(config: >::Config) -> Self { @@ -136,65 +141,21 @@ 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. + 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, 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, - table_range_check_tag: lookup.3, - }, - lookup_config: range_check, - _marker: PhantomData, - }; - - // Set up lookup argument - GeneratorTableConfig::configure(meta, config.clone()); - + 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) @@ -204,7 +165,7 @@ 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 = meta.query_advice(config.double_and_add.x_p, Rotation::prev()); + 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()); @@ -214,6 +175,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| { @@ -259,18 +239,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 SinsemillaInstructions - for SinsemillaChip +impl + SinsemillaInstructions + for SinsemillaChip where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { type CellValue = AssignedCell; @@ -322,20 +373,6 @@ where ) } - #[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), - ) - } - fn extract(point: &Self::NonIdentityPoint) -> Self::X { point.x() } diff --git a/halo2_gadgets/src/sinsemilla/chip/generator_table.rs b/halo2_gadgets/src/sinsemilla/chip/generator_table.rs index e77928b128..80551a6c91 100644 --- a/halo2_gadgets/src/sinsemilla/chip/generator_table.rs +++ b/halo2_gadgets/src/sinsemilla/chip/generator_table.rs @@ -6,7 +6,10 @@ use halo2_proofs::{ }; use super::{CommitDomains, FixedPoints, HashDomains}; -use crate::sinsemilla::primitives::{self as sinsemilla, K, SINSEMILLA_S}; +use crate::{ + sinsemilla::primitives::{self as sinsemilla, SINSEMILLA_S}, + utilities::lookup_range_check::DefaultLookupRangeCheck, +}; use pasta_curves::pallas; /// Table containing independent generators S[0..2^k] @@ -15,7 +18,6 @@ pub struct GeneratorTableConfig { pub table_idx: TableColumn, pub table_x: TableColumn, pub table_y: TableColumn, - pub table_range_check_tag: TableColumn, } impl GeneratorTableConfig { @@ -24,13 +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, + Lookup: DefaultLookupRangeCheck, { let (table_idx, table_x, table_y) = ( config.generator_table.table_idx, @@ -78,22 +81,6 @@ impl GeneratorTableConfig { }); } - /// Load the generator table into the circuit. - /// - /// | table_idx | table_x | table_y | table_range_check_tag | - /// ------------------------------------------------------------------- - /// | 0 | X(S\[0\]) | Y(S\[0\]) | 0 | - /// | 1 | X(S\[1\]) | Y(S\[1\]) | 0 | - /// | ... | ... | ... | 0 | - /// | 2^10-1 | X(S\[2^10-1\]) | Y(S\[2^10-1\]) | 0 | - /// | 0 | X(S\[0\]) | Y(S\[0\]) | 4 | - /// | 1 | X(S\[1\]) | Y(S\[1\]) | 4 | - /// | ... | ... | ... | 4 | - /// | 2^4-1 | X(S\[2^4-1\]) | Y(S\[2^4-1\]) | 4 | - /// | 0 | X(S\[0\]) | Y(S\[0\]) | 5 | - /// | 1 | X(S\[1\]) | Y(S\[1\]) | 5 | - /// | ... | ... | ... | 5 | - /// | 2^5-1 | X(S\[2^5-1\]) | Y(S\[2^5-1\]) | 5 | pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { layouter.assign_table( || "generator_table", @@ -107,66 +94,6 @@ impl GeneratorTableConfig { )?; table.assign_cell(|| "table_x", self.table_x, index, || Value::known(*x))?; table.assign_cell(|| "table_y", self.table_y, index, || Value::known(*y))?; - table.assign_cell( - || "table_range_check_tag", - self.table_range_check_tag, - index, - || Value::known(pallas::Base::zero()), - )?; - if index < (1 << 4) { - let new_index = index + (1 << K); - table.assign_cell( - || "table_idx", - self.table_idx, - new_index, - || Value::known(pallas::Base::from(index as u64)), - )?; - table.assign_cell( - || "table_x", - self.table_x, - new_index, - || Value::known(*x), - )?; - table.assign_cell( - || "table_y", - self.table_y, - new_index, - || Value::known(*y), - )?; - table.assign_cell( - || "table_range_check_tag", - self.table_range_check_tag, - new_index, - || Value::known(pallas::Base::from(4_u64)), - )?; - } - if index < (1 << 5) { - let new_index = index + (1 << 10) + (1 << 4); - table.assign_cell( - || "table_idx", - self.table_idx, - new_index, - || Value::known(pallas::Base::from(index as u64)), - )?; - table.assign_cell( - || "table_x", - self.table_x, - new_index, - || Value::known(*x), - )?; - table.assign_cell( - || "table_y", - self.table_y, - new_index, - || Value::known(*y), - )?; - table.assign_cell( - || "table_range_check_tag", - self.table_range_check_tag, - new_index, - || Value::known(pallas::Base::from(5_u64)), - )?; - } } Ok(()) }, diff --git a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs index 165615efaa..93b5e29d11 100644 --- a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs @@ -3,6 +3,7 @@ use super::{NonIdentityEccPoint, SinsemillaChip}; use crate::{ ecc::FixedPoints, sinsemilla::primitives::{self as sinsemilla, lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S}, + utilities::lookup_range_check::DefaultLookupRangeCheck, }; use ff::Field; @@ -16,16 +17,24 @@ use pasta_curves::{arithmetic::CurveAffine, pallas}; use std::ops::Deref; -impl SinsemillaChip +/// Define an enum that can hold either type +#[derive(Debug, Clone)] +pub enum EccPointQ<'a> { + PublicPoint(pallas::Affine), + PrivatePoint(&'a NonIdentityEccPoint), +} + +impl SinsemillaChip where Hash: HashDomains, Fixed: FixedPoints, Commit: CommitDomains, + 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, @@ -41,70 +50,27 @@ where ), Error, > { - let (offset, x_a, y_a) = self.public_initialization(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)?; - #[cfg(test)] - #[allow(non_snake_case)] - // Check equivalence to result from primitives::sinsemilla::hash_to_point - { - use crate::sinsemilla::primitives::{K, S_PERSONALIZATION}; - - use group::{prime::PrimeCurveAffine, Curve}; - use pasta_curves::arithmetic::CurveExt; - - let field_elems: Value> = message - .iter() - .map(|piece| piece.field_elem().map(|elem| (elem, piece.num_words()))) - .collect(); - - field_elems - .zip(x_a.value().zip(y_a.value())) - .assert_if_known(|(field_elems, (x_a, y_a))| { - // Get message as a bitstring. - let bitstring: Vec = field_elems - .iter() - .flat_map(|(elem, num_words)| { - elem.to_le_bits().into_iter().take(K * num_words) - }) - .collect(); - - let hasher_S = pallas::Point::hash_to_curve(S_PERSONALIZATION); - let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes()); - - // We can use complete addition here because it differs from - // incomplete addition with negligible probability. - let expected_point = bitstring - .chunks(K) - .fold(Q.to_curve(), |acc, chunk| (acc + S(chunk)) + acc); - let actual_point = - pallas::Affine::from_xy(x_a.evaluate(), y_a.evaluate()).unwrap(); - expected_point.to_affine() == actual_point - }); - } - - x_a.value() - .zip(y_a.value()) - .error_if_known_and(|(x_a, y_a)| x_a.is_zero_vartime() || y_a.is_zero_vartime())?; - Ok(( - NonIdentityEccPoint::from_coordinates_unchecked(x_a.0, y_a), - zs_sum, - )) + 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)] - pub(super) fn hash_message_with_private_init( + #[allow(unused_variables)] + // Check equivalence to result from primitives::sinsemilla::hash_to_point + pub(crate) fn check_hash_result( &self, - region: &mut Region<'_, pallas::Base>, - Q: &NonIdentityEccPoint, + Q: EccPointQ, message: &>::Message, + x_a: X, + y_a: AssignedCell, pallas::Base>, + zs_sum: Vec>>, ) -> Result< ( NonIdentityEccPoint, @@ -112,13 +78,7 @@ where ), Error, > { - let (offset, x_a, y_a) = self.private_initialization(region, Q)?; - - let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?; - #[cfg(test)] - #[allow(non_snake_case)] - // Check equivalence to result from primitives::sinsemilla::hash_to_point { use crate::sinsemilla::primitives::{K, S_PERSONALIZATION}; @@ -130,10 +90,15 @@ where .map(|piece| piece.field_elem().map(|elem| (elem, piece.num_words()))) .collect(); + let value_Q = match Q { + EccPointQ::PublicPoint(p) => Value::known(p), + EccPointQ::PrivatePoint(p) => p.point(), + }; + field_elems .zip(x_a.value().zip(y_a.value())) - .zip(Q.point()) - .assert_if_known(|((field_elems, (x_a, y_a)), Q)| { + .zip(value_Q) + .assert_if_known(|((field_elems, (x_a, y_a)), value_Q)| { // Get message as a bitstring. let bitstring: Vec = field_elems .iter() @@ -149,7 +114,7 @@ where // incomplete addition with negligible probability. let expected_point = bitstring .chunks(K) - .fold(Q.to_curve(), |acc, chunk| (acc + S(chunk)) + acc); + .fold(value_Q.to_curve(), |acc, chunk| (acc + S(chunk)) + acc); let actual_point = pallas::Affine::from_xy(x_a.evaluate(), y_a.evaluate()).unwrap(); expected_point.to_affine() == actual_point @@ -166,19 +131,19 @@ where } #[allow(non_snake_case)] - /// Assign the coordinates of the initial public point `Q` + /// Assign the coordinates of the initial public point `Q`, + /// y_Q to a fixed column /// - /// | offset | x_A | x_P | q_sinsemilla4 | + /// | offset | x_A | q_sinsemilla4 | fixed_y_q | /// -------------------------------------- - /// | 0 | | y_Q | | - /// | 1 | x_Q | | 1 | - fn public_initialization( + /// | 0 | x_Q | 1 | y_Q | + pub(crate) fn public_initialization_vanilla( &self, region: &mut Region<'_, pallas::Base>, Q: pallas::Affine, ) -> Result<(usize, X, Y), Error> { let config = self.config().clone(); - let mut offset = 0; + let offset = 0; // Get the `x`- and `y`-coordinates of the starting `Q` base. let x_q = *Q.coordinates().unwrap().x(); @@ -187,19 +152,17 @@ where // Constrain the initial x_a, lambda_1, lambda_2, x_p using the q_sinsemilla4 // selector. let y_a: Y = { - // Enable `q_sinsemilla4` on the second row. - config.q_sinsemilla4.enable(region, offset + 1)?; - let y_a: AssignedCell, pallas::Base> = region - .assign_advice_from_constant( - || "fixed y_q", - config.double_and_add.x_p, - offset, - y_q.into(), - )?; + // Enable `q_sinsemilla4` on the first row. + config.q_sinsemilla4.enable(region, offset)?; + region.assign_fixed( + || "fixed y_q", + config.fixed_y_q, + offset, + || Value::known(y_q), + )?; - y_a.value_field().into() + Value::known(y_q.into()).into() }; - offset += 1; // Constrain the initial x_q to equal the x-coordinate of the domain's `Q`. let x_a: X = { @@ -216,47 +179,9 @@ where Ok((offset, x_a, y_a)) } - #[allow(non_snake_case)] - /// Assign the coordinates of the initial private point `Q` - /// - /// | offset | x_A | x_P | q_sinsemilla4 | - /// -------------------------------------- - /// | 0 | | y_Q | | - /// | 1 | x_Q | | 1 | - fn private_initialization( - &self, - region: &mut Region<'_, pallas::Base>, - Q: &NonIdentityEccPoint, - ) -> Result<(usize, X, Y), Error> { - let config = self.config().clone(); - let mut offset = 0; - - // Assign `x_Q` and `y_Q` in the region and constrain the initial x_a, lambda_1, lambda_2, - // x_p, y_Q using the q_sinsemilla4 selector. - let y_a: Y = { - // Enable `q_sinsemilla4` on the second row. - config.q_sinsemilla4.enable(region, offset + 1)?; - let q_y: AssignedCell, pallas::Base> = Q.y().into(); - let y_a: AssignedCell, pallas::Base> = - q_y.copy_advice(|| "fixed y_q", region, config.double_and_add.x_p, offset)?; - - y_a.value_field().into() - }; - offset += 1; - - let x_a: X = { - let q_x: AssignedCell, pallas::Base> = Q.x().into(); - let x_a = q_x.copy_advice(|| "fixed x_q", region, config.double_and_add.x_a, offset)?; - - x_a.into() - }; - - Ok((offset, x_a, y_a)) - } - #[allow(clippy::type_complexity)] /// Hash `message` from the initial point `Q`. - fn hash_all_pieces( + pub(crate) fn hash_all_pieces( &self, region: &mut Region<'_, pallas::Base>, mut offset: usize, @@ -534,7 +459,7 @@ where } /// The x-coordinate of the accumulator in a Sinsemilla hash instance. -struct X(AssignedCell, F>); +pub(crate) struct X(pub(crate) AssignedCell, F>); impl From, F>> for X { fn from(cell_value: AssignedCell, F>) -> Self { @@ -555,7 +480,7 @@ impl Deref for X { /// This is never actually witnessed until the last round, since it /// can be derived from other variables. Thus it only exists as a field /// element, not a `CellValue`. -struct Y(Value>); +pub(crate) struct Y(pub(crate) Value>); impl From>> for Y { fn from(value: Value>) -> Self { diff --git a/halo2_gadgets/src/sinsemilla/merkle.rs b/halo2_gadgets/src/sinsemilla/merkle.rs index 1eabfaa870..21561e7316 100644 --- a/halo2_gadgets/src/sinsemilla/merkle.rs +++ b/halo2_gadgets/src/sinsemilla/merkle.rs @@ -56,11 +56,11 @@ pub struct MerklePath< > where MerkleChip: MerkleInstructions + Clone, { - chips: [MerkleChip; PAR], - domain: MerkleChip::HashDomains, - leaf_pos: Value, + pub(crate) chips: [MerkleChip; PAR], + pub(crate) domain: MerkleChip::HashDomains, + pub(crate) leaf_pos: Value, // The Merkle path is ordered from leaves to root. - path: Value<[C::Base; PATH_LENGTH]>, + pub(crate) path: Value<[C::Base; PATH_LENGTH]>, } impl< @@ -184,7 +184,11 @@ pub mod tests { tests::{TestCommitDomain, TestHashDomain}, HashDomains, }, - utilities::{i2lebsp, lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, + utilities::{ + i2lebsp, + lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig}, + UtilitiesInstructions, + }, }; use group::ff::{Field, PrimeField, PrimeFieldBits}; @@ -209,8 +213,18 @@ pub mod tests { impl Circuit for MyCircuit { type Config = ( - MerkleConfig, - MerkleConfig, + MerkleConfig< + TestHashDomain, + TestCommitDomain, + TestFixedBases, + LookupRangeCheckConfig, + >, + MerkleConfig< + TestHashDomain, + TestCommitDomain, + TestFixedBases, + LookupRangeCheckConfig, + >, ); type FloorPlanner = SimpleFloorPlanner; @@ -246,11 +260,9 @@ pub mod tests { meta.lookup_table_column(), meta.lookup_table_column(), meta.lookup_table_column(), - meta.lookup_table_column(), ); - let range_check = - LookupRangeCheckConfig::configure(meta, advices[9], lookup.0, lookup.3); + let range_check = LookupRangeCheckConfig::configure(meta, advices[9], lookup.0); let sinsemilla_config_1 = SinsemillaChip::configure( meta, @@ -281,10 +293,12 @@ pub mod tests { mut layouter: impl Layouter, ) -> Result<(), Error> { // Load generator table (shared across both configs) - SinsemillaChip::::load( - config.0.sinsemilla_config.clone(), - &mut layouter, - )?; + SinsemillaChip::< + TestHashDomain, + TestCommitDomain, + TestFixedBases, + LookupRangeCheckConfig, + >::load(config.0.sinsemilla_config.clone(), &mut layouter)?; // Construct Merkle chips which will be placed side-by-side in the circuit. let chip_1 = MerkleChip::construct(config.0.clone()); diff --git a/halo2_gadgets/src/sinsemilla/merkle/chip.rs b/halo2_gadgets/src/sinsemilla/merkle/chip.rs index cb3c5be4cc..d05d9fc8b3 100644 --- a/halo2_gadgets/src/sinsemilla/merkle/chip.rs +++ b/halo2_gadgets/src/sinsemilla/merkle/chip.rs @@ -11,7 +11,7 @@ use super::MerkleInstructions; use crate::{ sinsemilla::{primitives as sinsemilla, MessagePiece}, - utilities::RangeConstrained, + utilities::{lookup_range_check::DefaultLookupRangeCheck, RangeConstrained}, { ecc::FixedPoints, sinsemilla::{ @@ -28,16 +28,17 @@ use group::ff::PrimeField; /// Configuration for the `MerkleChip` implementation. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct MerkleConfig +pub struct MerkleConfig where Hash: HashDomains, Fixed: FixedPoints, Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { - advices: [Column; 5], - q_decompose: Selector, - pub(super) cond_swap_config: CondSwapConfig, - pub(super) sinsemilla_config: SinsemillaConfig, + pub(crate) advices: [Column; 5], + pub(crate) q_decompose: Selector, + pub(crate) cond_swap_config: CondSwapConfig, + pub(crate) sinsemilla_config: SinsemillaConfig, } /// Chip implementing `MerkleInstructions`. @@ -51,22 +52,24 @@ 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 +pub struct MerkleChip where Hash: HashDomains, Fixed: FixedPoints, Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { - config: MerkleConfig, + pub(crate) config: MerkleConfig, } -impl Chip for MerkleChip +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 { @@ -78,17 +81,18 @@ where } } -impl MerkleChip +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); @@ -191,18 +195,19 @@ where } /// Constructs a [`MerkleChip`] given a [`MerkleConfig`]. - pub fn construct(config: MerkleConfig) -> Self { + pub fn construct(config: MerkleConfig) -> Self { MerkleChip { config } } } -impl +impl MerkleInstructions - for MerkleChip + for MerkleChip where Hash: HashDomains + Eq, F: FixedPoints, Commit: CommitDomains + Eq, + Lookup: DefaultLookupRangeCheck, { #[allow(non_snake_case)] fn hash_layer( @@ -415,20 +420,24 @@ where } } -impl UtilitiesInstructions for MerkleChip +impl UtilitiesInstructions + for MerkleChip where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { type Var = AssignedCell; } -impl CondSwapInstructions for MerkleChip +impl CondSwapInstructions + for MerkleChip where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { #[allow(clippy::type_complexity)] fn swap( @@ -441,71 +450,61 @@ where let chip = CondSwapChip::::construct(config); chip.swap(layouter, pair, swap) } - - fn mux( - &self, - layouter: &mut impl Layouter, - choice: Self::Var, - 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) - } } -impl SinsemillaInstructions - for MerkleChip +impl + SinsemillaInstructions + for MerkleChip where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, + Lookup: DefaultLookupRangeCheck, { - type CellValue = as SinsemillaInstructions< + type CellValue = as SinsemillaInstructions< pallas::Affine, { sinsemilla::K }, { sinsemilla::C }, >>::CellValue; - type Message = as SinsemillaInstructions< + type Message = as SinsemillaInstructions< pallas::Affine, { sinsemilla::K }, { sinsemilla::C }, >>::Message; - type MessagePiece = as SinsemillaInstructions< + type MessagePiece = as SinsemillaInstructions< pallas::Affine, { sinsemilla::K }, { sinsemilla::C }, >>::MessagePiece; - type RunningSum = as SinsemillaInstructions< + type 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< + type NonIdentityPoint = as SinsemillaInstructions< pallas::Affine, { sinsemilla::K }, { sinsemilla::C }, >>::NonIdentityPoint; - type FixedPoints = as SinsemillaInstructions< + type FixedPoints = as SinsemillaInstructions< pallas::Affine, { sinsemilla::K }, { sinsemilla::C }, >>::FixedPoints; - type HashDomains = as SinsemillaInstructions< + type HashDomains = as SinsemillaInstructions< pallas::Affine, { sinsemilla::K }, { sinsemilla::C }, >>::HashDomains; - type CommitDomains = as SinsemillaInstructions< + type CommitDomains = as SinsemillaInstructions< pallas::Affine, { sinsemilla::K }, { sinsemilla::C }, @@ -518,7 +517,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) } @@ -531,24 +530,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) } - #[allow(non_snake_case)] - #[allow(clippy::type_complexity)] - fn hash_to_point_with_private_init( - &self, - layouter: impl Layouter, - Q: &Self::NonIdentityPoint, - message: Self::Message, - ) -> Result<(Self::NonIdentityPoint, Vec>), Error> { - let config = self.config().sinsemilla_config.clone(); - let chip = SinsemillaChip::::construct(config); - chip.hash_to_point_with_private_init(layouter, Q, message) - } - fn extract(point: &Self::NonIdentityPoint) -> Self::X { - SinsemillaChip::::extract(point) + SinsemillaChip::::extract(point) } } diff --git a/halo2_gadgets/src/sinsemilla/primitives.rs b/halo2_gadgets/src/sinsemilla/primitives.rs index ad9e397b5e..2ebde8faf4 100644 --- a/halo2_gadgets/src/sinsemilla/primitives.rs +++ b/halo2_gadgets/src/sinsemilla/primitives.rs @@ -56,7 +56,7 @@ fn extract_p_bottom(point: CtOption) -> CtOption { /// Pads the given iterator (which MUST have length $\leq K * C$) with zero-bits to a /// multiple of $K$ bits. -struct Pad> { +pub(crate) struct Pad> { /// The iterator we are padding. inner: I, /// The measured length of the inner iterator. @@ -184,9 +184,10 @@ impl HashDomain { #[derive(Debug)] #[allow(non_snake_case)] pub struct CommitDomain { + // FIXME: THis comment came from ZSA version /// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can be used - M: HashDomain, - R: pallas::Point, + pub(crate) M: HashDomain, + pub(crate) R: pallas::Point, } impl CommitDomain { @@ -201,17 +202,6 @@ impl CommitDomain { } } - /// Constructs a new `CommitDomain` from different values for `hash_domain` and `blind_domain` - pub fn new_with_personalization(hash_domain: &str, blind_domain: &str) -> Self { - let m_prefix = format!("{}-M", hash_domain); - let r_prefix = format!("{}-r", blind_domain); - let hasher_r = pallas::Point::hash_to_curve(&r_prefix); - CommitDomain { - M: HashDomain::new(&m_prefix), - R: hasher_r(&[]), - } - } - /// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit]. /// /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit @@ -226,26 +216,6 @@ impl CommitDomain { .map(|p| p + Wnaf::new().scalar(r).base(self.R)) } - /// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash]. - /// - /// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash - pub fn hash_to_point(&self, msg: impl Iterator) -> CtOption { - self.M.hash_to_point(msg) - } - - /// Returns `SinsemillaCommit_r(personalization, msg) = hash_point + [r]R` - /// where `SinsemillaHash(personalization, msg) = hash_point` - /// and `R` is derived from the `personalization`. - #[allow(non_snake_case)] - pub fn commit_from_hash_point( - &self, - hash_point: CtOption, - r: &pallas::Scalar, - ) -> CtOption { - // We use complete addition for the blinding factor. - hash_point.map(|p| p + Wnaf::new().scalar(r).base(self.R)) - } - /// $\mathsf{SinsemillaShortCommit}$ from [§ 5.4.8.4][concretesinsemillacommit]. /// /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit @@ -337,32 +307,4 @@ mod tests { assert_eq!(computed, actual); } } - - #[test] - fn commit_in_several_steps() { - use rand::{rngs::OsRng, Rng}; - - use ff::Field; - - use crate::sinsemilla::primitives::CommitDomain; - - let domain = CommitDomain::new("z.cash:ZSA-NoteCommit"); - - let mut os_rng = OsRng::default(); - let msg: Vec = (0..36).map(|_| os_rng.gen::()).collect(); - - let rcm = pallas::Scalar::random(&mut os_rng); - - // Evaluate the commitment with commit function - let commit1 = domain.commit(msg.clone().into_iter(), &rcm); - - // Evaluate the commitment with the following steps - // 1. hash msg - // 2. evaluate the commitment from the hash point - let hash_point = domain.M.hash_to_point(msg.into_iter()); - let commit2 = domain.commit_from_hash_point(hash_point, &rcm); - - // Test equality - assert_eq!(commit1.unwrap(), commit2.unwrap()); - } } diff --git a/halo2_gadgets/src/utilities/cond_swap.rs b/halo2_gadgets/src/utilities/cond_swap.rs index 78049e742a..7712c89b2a 100644 --- a/halo2_gadgets/src/utilities/cond_swap.rs +++ b/halo2_gadgets/src/utilities/cond_swap.rs @@ -2,14 +2,12 @@ use super::{bool_check, ternary, UtilitiesInstructions}; -use crate::ecc::chip::{EccPoint, NonIdentityEccPoint}; use group::ff::{Field, PrimeField}; use halo2_proofs::{ circuit::{AssignedCell, Chip, Layouter, Value}, - plonk::{self, Advice, Column, ConstraintSystem, Constraints, Error, Selector}, + plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, poly::Rotation, }; -use pasta_curves::pallas; use std::marker::PhantomData; /// Instructions for a conditional swap gadget. @@ -26,22 +24,12 @@ pub trait CondSwapInstructions: UtilitiesInstructions { pair: (Self::Var, Value), swap: Value, ) -> Result<(Self::Var, Self::Var), Error>; - - /// Given an input `(choice, left, right)` where `choice` is a boolean flag, - /// returns `left` if `choice` is not set and `right` if `choice` is set. - fn mux( - &self, - layouter: &mut impl Layouter, - choice: Self::Var, - left: Self::Var, - right: Self::Var, - ) -> Result; } /// A chip implementing a conditional swap. #[derive(Clone, Debug)] pub struct CondSwapChip { - config: CondSwapConfig, + pub(crate) config: CondSwapConfig, _marker: PhantomData, } @@ -61,12 +49,12 @@ impl Chip for CondSwapChip { /// Configuration for the [`CondSwapChip`]. #[derive(Clone, Debug, PartialEq, Eq)] pub struct CondSwapConfig { - q_swap: Selector, - a: Column, - b: Column, - a_swapped: Column, - b_swapped: Column, - swap: Column, + pub(crate) q_swap: Selector, + pub(crate) a: Column, + pub(crate) b: Column, + pub(crate) a_swapped: Column, + pub(crate) b_swapped: Column, + pub(crate) swap: Column, } #[cfg(test)] @@ -133,97 +121,6 @@ impl CondSwapInstructions for CondSwapChip { }, ) } - - fn mux( - &self, - layouter: &mut impl Layouter, - choice: Self::Var, - left: Self::Var, - right: Self::Var, - ) -> Result { - let config = self.config(); - - layouter.assign_region( - || "mux", - |mut region| { - // Enable `q_swap` selector - config.q_swap.enable(&mut region, 0)?; - - // Copy in `a` value - let left = left.copy_advice(|| "copy left", &mut region, config.a, 0)?; - - // Copy in `b` value - let right = right.copy_advice(|| "copy right", &mut region, config.b, 0)?; - - // Copy `choice` value - let choice = choice.copy_advice(|| "copy choice", &mut region, config.swap, 0)?; - - let a_swapped = left - .value() - .zip(right.value()) - .zip(choice.value()) - .map(|((left, right), choice)| { - if *choice == F::from(0_u64) { - left - } else { - right - } - }) - .cloned(); - let b_swapped = left - .value() - .zip(right.value()) - .zip(choice.value()) - .map(|((left, right), choice)| { - if *choice == F::from(0_u64) { - right - } else { - left - } - }) - .cloned(); - - region.assign_advice(|| "out b_swap", self.config.b_swapped, 0, || b_swapped)?; - region.assign_advice(|| "out a_swap", self.config.a_swapped, 0, || a_swapped) - }, - ) - } -} - -impl CondSwapChip { - /// Given an input `(choice, left, right)` where `choice` is a boolean flag and `left` and `right` are `EccPoint`, - /// returns `left` if `choice` is not set and `right` if `choice` is set. - pub fn mux_on_points( - &self, - mut layouter: impl Layouter, - choice: &AssignedCell, - left: &EccPoint, - right: &EccPoint, - ) -> Result { - let x_cell = self.mux(&mut layouter, choice.clone(), left.x(), right.x())?; - let y_cell = self.mux(&mut layouter, choice.clone(), left.y(), right.y())?; - Ok(EccPoint::from_coordinates_unchecked( - x_cell.into(), - y_cell.into(), - )) - } - - /// Given an input `(choice, left, right)` where `choice` is a boolean flag and `left` and `right` are - /// `NonIdentityEccPoint`, returns `left` if `choice` is not set and `right` if `choice` is set. - pub fn mux_on_non_identity_points( - &self, - mut layouter: impl Layouter, - choice: &AssignedCell, - left: &NonIdentityEccPoint, - right: &NonIdentityEccPoint, - ) -> Result { - let x_cell = self.mux(&mut layouter, choice.clone(), left.x(), right.x())?; - let y_cell = self.mux(&mut layouter, choice.clone(), left.y(), right.y())?; - Ok(NonIdentityEccPoint::from_coordinates_unchecked( - x_cell.into(), - y_cell.into(), - )) - } } impl CondSwapChip { @@ -394,231 +291,4 @@ mod tests { assert_eq!(prover.verify(), Ok(())); } } - - #[test] - fn test_mux() { - use crate::{ - ecc::{ - chip::{EccChip, EccConfig}, - tests::TestFixedBases, - NonIdentityPoint, Point, - }, - utilities::lookup_range_check::LookupRangeCheckConfig, - }; - - use group::{cofactor::CofactorCurveAffine, Curve, Group}; - use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner, Value}, - dev::MockProver, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance}, - }; - use pasta_curves::arithmetic::CurveAffine; - use pasta_curves::{pallas, EpAffine}; - - use rand::rngs::OsRng; - - #[derive(Clone, Debug)] - pub struct MyConfig { - primary: Column, - advice: Column, - cond_swap_config: CondSwapConfig, - ecc_config: EccConfig, - } - - #[derive(Default)] - struct MyCircuit { - left_point: Value, - right_point: Value, - choice: Value, - } - - impl Circuit for MyCircuit { - type Config = MyConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let advices = [ - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - ]; - - for advice in advices.iter() { - meta.enable_equality(*advice); - } - - // Instance column used for public inputs - let primary = meta.instance_column(); - meta.enable_equality(primary); - - let cond_swap_config = - CondSwapChip::configure(meta, advices[0..5].try_into().unwrap()); - - let table_idx = meta.lookup_table_column(); - let table_range_check_tag = meta.lookup_table_column(); - - let lagrange_coeffs = [ - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - ]; - meta.enable_constant(lagrange_coeffs[0]); - - let range_check = LookupRangeCheckConfig::configure( - meta, - advices[9], - table_idx, - table_range_check_tag, - ); - - let ecc_config = EccChip::::configure( - meta, - advices, - lagrange_coeffs, - range_check, - ); - - MyConfig { - primary, - advice: advices[0], - cond_swap_config, - ecc_config, - } - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - // Construct a CondSwap chip - let cond_swap_chip = CondSwapChip::construct(config.cond_swap_config); - - // Construct an ECC chip - let ecc_chip = EccChip::construct(config.ecc_config); - - // Assign choice - let choice = layouter.assign_region( - || "load private", - |mut region| { - region.assign_advice(|| "load private", config.advice, 0, || self.choice) - }, - )?; - - // Test mux on non identity points - // Assign left point - let left_non_identity_point = NonIdentityPoint::new( - ecc_chip.clone(), - layouter.namespace(|| "left point"), - self.left_point.map(|left_point| left_point), - )?; - - // Assign right point - let right_non_identity_point = NonIdentityPoint::new( - ecc_chip.clone(), - layouter.namespace(|| "right point"), - self.right_point.map(|right_point| right_point), - )?; - - // Apply mux - let result_non_identity_point = cond_swap_chip.mux_on_non_identity_points( - layouter.namespace(|| "MUX"), - &choice, - left_non_identity_point.inner(), - right_non_identity_point.inner(), - )?; - - // Check equality with instance - layouter.constrain_instance( - result_non_identity_point.x().cell(), - config.primary, - 0, - )?; - layouter.constrain_instance( - result_non_identity_point.y().cell(), - config.primary, - 1, - )?; - - // Test mux on points - // Assign left point - let left_point = Point::new( - ecc_chip.clone(), - layouter.namespace(|| "left point"), - self.left_point.map(|left_point| left_point), - )?; - - // Assign right point - let right_point = Point::new( - ecc_chip, - layouter.namespace(|| "right point"), - self.right_point.map(|right_point| right_point), - )?; - - // Apply mux - let result = cond_swap_chip.mux_on_points( - layouter.namespace(|| "MUX"), - &choice, - left_point.inner(), - right_point.inner(), - )?; - - // Check equality with instance - layouter.constrain_instance(result.x().cell(), config.primary, 0)?; - layouter.constrain_instance(result.y().cell(), config.primary, 1) - } - } - - // Test different circuits - let mut circuits = vec![]; - let mut instances = vec![]; - for choice in [false, true] { - let choice_value = if choice { - pallas::Base::one() - } else { - pallas::Base::zero() - }; - let left_point = pallas::Point::random(OsRng).to_affine(); - let right_point = pallas::Point::random(OsRng).to_affine(); - circuits.push(MyCircuit { - left_point: Value::known(left_point), - right_point: Value::known(right_point), - choice: Value::known(choice_value), - }); - let expected_output = if choice { right_point } else { left_point }; - let (expected_x, expected_y) = if bool::from(expected_output.is_identity()) { - (pallas::Base::zero(), pallas::Base::zero()) - } else { - let coords = expected_output.coordinates().unwrap(); - (*coords.x(), *coords.y()) - }; - instances.push([[expected_x, expected_y]]); - } - - for (circuit, instance) in circuits.iter().zip(instances.iter()) { - let prover = MockProver::::run( - 5, - circuit, - instance.iter().map(|p| p.to_vec()).collect(), - ) - .unwrap(); - assert_eq!(prover.verify(), Ok(())); - } - } } diff --git a/halo2_gadgets/src/utilities/lookup_range_check.rs b/halo2_gadgets/src/utilities/lookup_range_check.rs index 64b4ce4c90..a4ab1ba5ab 100644 --- a/halo2_gadgets/src/utilities/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities/lookup_range_check.rs @@ -6,10 +6,14 @@ use halo2_proofs::{ plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector, TableColumn}, poly::Rotation, }; -use std::{convert::TryInto, marker::PhantomData}; +use std::{convert::TryInto, fmt::Debug, marker::PhantomData}; use ff::PrimeFieldBits; +use pasta_curves::pallas; + +use crate::sinsemilla::primitives as sinsemilla; + use super::*; /// The running sum $[z_0, ..., z_W]$. If created in strict mode, $z_W = 0$. @@ -30,8 +34,8 @@ impl RangeConstrained> { /// # Panics /// /// Panics if `bitrange.len() >= K`. - pub fn witness_short( - lookup_config: &LookupRangeCheckConfig, + pub fn witness_short>( + lookup_config: &L, layouter: impl Layouter, value: Value<&F>, bitrange: Range, @@ -49,7 +53,7 @@ impl RangeConstrained> { .map(|inner| Self { inner, num_bits, - _phantom: PhantomData::default(), + _phantom: PhantomData, }) } } @@ -57,18 +61,19 @@ impl RangeConstrained> { /// Configuration that provides methods for a lookup range check. #[derive(Eq, PartialEq, Debug, Clone, Copy)] pub struct LookupRangeCheckConfig { - q_lookup: Selector, - q_running: Selector, - q_bitshift: Selector, - q_range_check_4: Selector, - q_range_check_5: Selector, - running_sum: Column, - table_idx: TableColumn, - table_range_check_tag: TableColumn, - _marker: PhantomData, + pub(crate) q_lookup: Selector, + pub(crate) q_running: Selector, + pub(crate) q_bitshift: Selector, + pub(crate) running_sum: Column, + pub(crate) table_idx: TableColumn, + pub(crate) _marker: PhantomData, } -impl LookupRangeCheckConfig { +/// Trait that provides common methods for a lookup range check. +pub trait LookupRangeCheck { + /// Returns a reference to the `LookupRangeCheckConfig` instance. + fn config(&self) -> &LookupRangeCheckConfig; + /// 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. @@ -80,175 +85,35 @@ impl LookupRangeCheckConfig { /// # Side-effects /// /// Both the `running_sum` and `constants` columns will be equality-enabled. - pub fn configure( + fn configure( meta: &mut ConstraintSystem, running_sum: Column, table_idx: TableColumn, - table_range_check_tag: TableColumn, - ) -> Self { - meta.enable_equality(running_sum); - - let q_lookup = meta.complex_selector(); - let q_running = meta.complex_selector(); - let q_bitshift = meta.selector(); - let q_range_check_4 = meta.complex_selector(); - let q_range_check_5 = meta.complex_selector(); - let config = LookupRangeCheckConfig { - q_lookup, - q_running, - q_bitshift, - q_range_check_4, - q_range_check_5, - running_sum, - table_idx, - table_range_check_tag, - _marker: PhantomData, - }; - - // https://p.z.cash/halo2-0.1:decompose-combined-lookup - meta.lookup(|meta| { - let q_lookup = meta.query_selector(config.q_lookup); - let q_running = meta.query_selector(config.q_running); - let q_range_check_4 = meta.query_selector(config.q_range_check_4); - let q_range_check_5 = meta.query_selector(config.q_range_check_5); - let z_cur = meta.query_advice(config.running_sum, Rotation::cur()); - let one = Expression::Constant(F::ONE); - - // In the case of a running sum decomposition, we recover the word from - // the difference of the running sums: - // z_i = 2^{K}⋅z_{i + 1} + a_i - // => a_i = z_i - 2^{K}⋅z_{i + 1} - let running_sum_lookup = { - let running_sum_word = { - let z_next = meta.query_advice(config.running_sum, Rotation::next()); - z_cur.clone() - z_next * F::from(1 << K) - }; - - q_running.clone() * running_sum_word - }; - - // In the short range check, the word is directly witnessed. - let short_lookup = { - let short_word = z_cur.clone(); - let q_short = one.clone() - q_running; - - q_short * short_word - }; - - // q_range_check is equal to - // - 1 if q_range_check_4 = 1 or q_range_check_5 = 1 - // - 0 otherwise - let q_range_check = one.clone() - - (one.clone() - q_range_check_4.clone()) * (one.clone() - q_range_check_5.clone()); - - // num_bits is equal to - // - 5 if q_range_check_5 = 1 - // - 4 if q_range_check_4 = 1 and q_range_check_5 = 0 - // - 0 otherwise - let num_bits = q_range_check_5.clone() * Expression::Constant(F::from(5_u64)) - + (one.clone() - q_range_check_5) - * q_range_check_4 - * Expression::Constant(F::from(4_u64)); - - // Combine the running sum, short lookups and optimized range checks: - vec![ - ( - q_lookup.clone() - * ((one - q_range_check.clone()) * (running_sum_lookup + short_lookup) - + q_range_check.clone() * z_cur), - config.table_idx, - ), - ( - q_lookup * q_range_check * num_bits, - config.table_range_check_tag, - ), - ] - }); - - // For short lookups, check that the word has been shifted by the correct number of bits. - // https://p.z.cash/halo2-0.1:decompose-short-lookup - meta.create_gate("Short lookup bitshift", |meta| { - let q_bitshift = meta.query_selector(config.q_bitshift); - let word = meta.query_advice(config.running_sum, Rotation::prev()); - let shifted_word = meta.query_advice(config.running_sum, Rotation::cur()); - let inv_two_pow_s = meta.query_advice(config.running_sum, Rotation::next()); - - let two_pow_k = F::from(1 << K); - - // shifted_word = word * 2^{K-s} - // = word * 2^K * inv_two_pow_s - Constraints::with_selector( - q_bitshift, - Some(word * two_pow_k * inv_two_pow_s - shifted_word), - ) - }); - - config - } + ) -> Self + where + 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. - pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - layouter.assign_table( - || "table_idx", - |mut table| { - // We generate the row values lazily (we only need them during keygen). - for index in 0..(1 << K) { - table.assign_cell( - || "table_idx", - self.table_idx, - index, - || Value::known(F::from(index as u64)), - )?; - table.assign_cell( - || "table_range_check_tag", - self.table_range_check_tag, - index, - || Value::known(F::ZERO), - )?; - } - for index in 0..(1 << 4) { - let new_index = index + (1 << K); - table.assign_cell( - || "table_idx", - self.table_idx, - new_index, - || Value::known(F::from(index as u64)), - )?; - table.assign_cell( - || "table_range_check_tag", - self.table_range_check_tag, - new_index, - || Value::known(F::from(4_u64)), - )?; - } - for index in 0..(1 << 5) { - let new_index = index + (1 << K) + (1 << 4); - table.assign_cell( - || "table_idx", - self.table_idx, - new_index, - || Value::known(F::from(index as u64)), - )?; - table.assign_cell( - || "table_range_check_tag", - self.table_range_check_tag, - new_index, - || Value::known(F::from(5_u64)), - )?; - } - Ok(()) - }, - ) - } + fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error>; + + /// 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>, + element: AssignedCell, + num_bits: usize, + ) -> Result<(), Error>; /// Range check on an existing cell that is copied into this helper. /// /// Returns an error if `element` is not in a column that was passed to /// [`ConstraintSystem::enable_equality`] during circuit configuration. - pub fn copy_check( + fn copy_check( &self, mut layouter: impl Layouter, element: AssignedCell, @@ -259,14 +124,15 @@ impl LookupRangeCheckConfig { || format!("{:?} words range check", num_words), |mut region| { // Copy `element` and initialize running sum `z_0 = element` to decompose it. - let z_0 = element.copy_advice(|| "z_0", &mut region, self.running_sum, 0)?; + let z_0 = + element.copy_advice(|| "z_0", &mut region, self.config().running_sum, 0)?; self.range_check(&mut region, z_0, num_words, strict) }, ) } /// Range check on a value that is witnessed in this helper. - pub fn witness_check( + fn witness_check( &self, mut layouter: impl Layouter, value: Value, @@ -276,8 +142,12 @@ impl LookupRangeCheckConfig { layouter.assign_region( || "Witness element", |mut region| { - let z_0 = - region.assign_advice(|| "Witness element", self.running_sum, 0, || value)?; + let z_0 = region.assign_advice( + || "Witness element", + self.config().running_sum, + 0, + || value, + )?; self.range_check(&mut region, z_0, num_words, strict) }, ) @@ -332,9 +202,9 @@ impl LookupRangeCheckConfig { let inv_two_pow_k = F::from(1u64 << K).invert().unwrap(); for (idx, word) in words.iter().enumerate() { // Enable q_lookup on this row - self.q_lookup.enable(region, idx)?; + self.config().q_lookup.enable(region, idx)?; // Enable q_running on this row - self.q_running.enable(region, idx)?; + self.config().q_running.enable(region, idx)?; // z_next = (z_cur - m_cur) / 2^K z = { @@ -346,7 +216,7 @@ impl LookupRangeCheckConfig { // Assign z_next region.assign_advice( || format!("z_{:?}", idx + 1), - self.running_sum, + self.config().running_sum, idx + 1, || z_val, )? @@ -367,7 +237,7 @@ impl LookupRangeCheckConfig { /// # Panics /// /// Panics if NUM_BITS is equal to or larger than K. - pub fn copy_short_check( + fn copy_short_check( &self, mut layouter: impl Layouter, element: AssignedCell, @@ -379,8 +249,7 @@ impl LookupRangeCheckConfig { |mut region| { // Copy `element` to use in the k-bit lookup. let element = - element.copy_advice(|| "element", &mut region, self.running_sum, 0)?; - + element.copy_advice(|| "element", &mut region, self.config().running_sum, 0)?; self.short_range_check(&mut region, element, num_bits) }, ) @@ -391,7 +260,7 @@ impl LookupRangeCheckConfig { /// # Panics /// /// Panics if num_bits is larger than K. - pub fn witness_short_check( + fn witness_short_check( &self, mut layouter: impl Layouter, element: Value, @@ -402,8 +271,12 @@ impl LookupRangeCheckConfig { || format!("Range check {:?} bits", num_bits), |mut region| { // Witness `element` to use in the k-bit lookup. - let element = - region.assign_advice(|| "Witness element", self.running_sum, 0, || element)?; + let element = region.assign_advice( + || "Witness element", + self.config().running_sum, + 0, + || element, + )?; self.short_range_check(&mut region, element.clone(), num_bits)?; @@ -411,362 +284,158 @@ impl LookupRangeCheckConfig { }, ) } +} - /// 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>, - element: AssignedCell, - num_bits: usize, - ) -> Result<(), Error> { - // Enable lookup for `element`. - self.q_lookup.enable(region, 0)?; - - match num_bits { - 4 => { - self.q_range_check_4.enable(region, 0)?; - } - 5 => { - self.q_range_check_5.enable(region, 0)?; - } - _ => { - // Enable lookup for shifted element, to constrain it to 10 bits. - self.q_lookup.enable(region, 1)?; +impl LookupRangeCheck for LookupRangeCheckConfig { + fn config(&self) -> &LookupRangeCheckConfig { + self + } - // Check element has been shifted by the correct number of bits. - self.q_bitshift.enable(region, 1)?; + fn configure( + meta: &mut ConstraintSystem, + running_sum: Column, + table_idx: TableColumn, + ) -> Self { + meta.enable_equality(running_sum); - // Assign shifted `element * 2^{K - num_bits}` - let shifted = element.value().into_field() * F::from(1 << (K - num_bits)); + let q_lookup = meta.complex_selector(); + let q_running = meta.complex_selector(); + let q_bitshift = meta.selector(); - region.assign_advice( - || format!("element * 2^({}-{})", K, num_bits), - self.running_sum, - 1, - || shifted, - )?; + // if the order of the creation makes a difference + let config = LookupRangeCheckConfig { + q_lookup, + q_running, + q_bitshift, + running_sum, + table_idx, + _marker: PhantomData, + }; - // Assign 2^{-num_bits} from a fixed column. - let inv_two_pow_s = F::from(1 << num_bits).invert().unwrap(); - region.assign_advice_from_constant( - || format!("2^(-{})", num_bits), - self.running_sum, - 2, - inv_two_pow_s, - )?; - } - } + // https://p.z.cash/halo2-0.1:decompose-combined-lookup + meta.lookup(|meta| { + let q_lookup = meta.query_selector(config.q_lookup); + let q_running = meta.query_selector(config.q_running); + // 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); - Ok(()) - } -} + // In the case of a running sum decomposition, we recover the word from + // the difference of the running sums: + // z_i = 2^{K}⋅z_{i + 1} + a_i + // => a_i = z_i - 2^{K}⋅z_{i + 1} + let running_sum_lookup = { + let running_sum_word = { + let z_next = meta.query_advice(config.running_sum, Rotation::next()); + z_cur.clone() - z_next * F::from(1 << K) + }; -#[cfg(test)] -mod tests { - use super::LookupRangeCheckConfig; - - use super::super::lebs2ip; - use crate::sinsemilla::primitives::K; - - use ff::{Field, PrimeFieldBits}; - use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner, Value}, - dev::{FailureLocation, MockProver, VerifyFailure}, - plonk::{Circuit, ConstraintSystem, Error}, - }; - use pasta_curves::pallas; - - use std::{convert::TryInto, marker::PhantomData}; - - #[test] - fn lookup_range_check() { - #[derive(Clone, Copy)] - struct MyCircuit { - num_words: usize, - _marker: PhantomData, - } + q_running.clone() * running_sum_word + }; - impl Circuit for MyCircuit { - type Config = LookupRangeCheckConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - *self - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let running_sum = meta.advice_column(); - let table_idx = meta.lookup_table_column(); - let table_range_check_tag = meta.lookup_table_column(); - let constants = meta.fixed_column(); - meta.enable_constant(constants); - - LookupRangeCheckConfig::::configure( - meta, - running_sum, - table_idx, - table_range_check_tag, - ) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - // Load table_idx - config.load(&mut layouter)?; - - // Lookup constraining element to be no longer than num_words * K bits. - let elements_and_expected_final_zs = [ - (F::from((1 << (self.num_words * K)) - 1), F::ZERO, true), // a word that is within self.num_words * K bits long - (F::from(1 << (self.num_words * K)), F::ONE, false), // a word that is just over self.num_words * K bits long - ]; - - fn expected_zs( - element: F, - num_words: usize, - ) -> Vec { - let chunks = { - element - .to_le_bits() - .iter() - .by_vals() - .take(num_words * K) - .collect::>() - .chunks_exact(K) - .map(|chunk| F::from(lebs2ip::(chunk.try_into().unwrap()))) - .collect::>() - }; - let expected_zs = { - let inv_two_pow_k = F::from(1 << K).invert().unwrap(); - chunks.iter().fold(vec![element], |mut zs, a_i| { - // z_{i + 1} = (z_i - a_i) / 2^{K} - let z = (zs[zs.len() - 1] - a_i) * inv_two_pow_k; - zs.push(z); - zs - }) - }; - expected_zs - } + // In the short range check, the word is directly witnessed. + let short_lookup = { + let short_word = z_cur.clone(); + let q_short = one.clone() - q_running; - for (element, expected_final_z, strict) in elements_and_expected_final_zs.iter() { - let expected_zs = expected_zs::(*element, self.num_words); + q_short * short_word + }; - let zs = config.witness_check( - layouter.namespace(|| format!("Lookup {:?}", self.num_words)), - Value::known(*element), - self.num_words, - *strict, - )?; + vec![( + q_lookup * (running_sum_lookup + short_lookup), + config.table_idx, + )] + }); - assert_eq!(*expected_zs.last().unwrap(), *expected_final_z); + // For short lookups, check that the word has been shifted by the correct number of bits. + // https://p.z.cash/halo2-0.1:decompose-short-lookup + meta.create_gate("Short lookup bitshift", |meta| { + let q_bitshift = meta.query_selector(config.q_bitshift); + let word = meta.query_advice(config.running_sum, Rotation::prev()); + let shifted_word = meta.query_advice(config.running_sum, Rotation::cur()); + let inv_two_pow_s = meta.query_advice(config.running_sum, Rotation::next()); - for (expected_z, z) in expected_zs.into_iter().zip(zs.iter()) { - z.value().assert_if_known(|z| &&expected_z == z); - } - } - Ok(()) - } - } + let two_pow_k = F::from(1 << K); - { - let circuit: MyCircuit = MyCircuit { - num_words: 6, - _marker: PhantomData, - }; + // shifted_word = word * 2^{K-s} + // = word * 2^K * inv_two_pow_s + Constraints::with_selector( + q_bitshift, + Some(word * two_pow_k * inv_two_pow_s - shifted_word), + ) + }); - let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - } + config } - #[test] - fn short_range_check() { - struct MyCircuit { - element: Value, - num_bits: usize, - } - - impl Circuit for MyCircuit { - type Config = LookupRangeCheckConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - MyCircuit { - element: Value::unknown(), - num_bits: self.num_bits, + #[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> { + layouter.assign_table( + || "table_idx", + |mut table| { + // We generate the row values lazily (we only need them during keygen). + for index in 0..(1 << K) { + table.assign_cell( + || "table_idx", + self.table_idx, + index, + || Value::known(F::from(index as u64)), + )?; } - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let running_sum = meta.advice_column(); - let table_idx = meta.lookup_table_column(); - let table_range_check_tag = meta.lookup_table_column(); - let constants = meta.fixed_column(); - meta.enable_constant(constants); - - LookupRangeCheckConfig::::configure( - meta, - running_sum, - table_idx, - table_range_check_tag, - ) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - // Load table_idx - config.load(&mut layouter)?; - - // Lookup constraining element to be no longer than num_bits. - config.witness_short_check( - layouter.namespace(|| format!("Lookup {:?} bits", self.num_bits)), - self.element, - self.num_bits, - )?; - Ok(()) - } - } - - // Edge case: zero bits - { - let circuit: MyCircuit = MyCircuit { - element: Value::known(pallas::Base::ZERO), - num_bits: 0, - }; - let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - } + }, + ) + } - // Edge case: K bits - { - let circuit: MyCircuit = MyCircuit { - element: Value::known(pallas::Base::from((1 << K) - 1)), - num_bits: K, - }; - let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - } + /// 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>, + element: AssignedCell, + num_bits: usize, + ) -> Result<(), Error> { + // Enable lookup for `element`. + self.q_lookup.enable(region, 0)?; - // Element within `num_bits` - { - let circuit: MyCircuit = MyCircuit { - element: Value::known(pallas::Base::from((1 << 6) - 1)), - num_bits: 6, - }; - let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - } + // Enable lookup for shifted element, to constrain it to 10 bits. + self.q_lookup.enable(region, 1)?; - // Element larger than `num_bits` but within K bits - { - let circuit: MyCircuit = MyCircuit { - element: Value::known(pallas::Base::from(1 << 6)), - num_bits: 6, - }; - let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); - assert_eq!( - prover.verify(), - Err(vec![VerifyFailure::Lookup { - lookup_index: 0, - location: FailureLocation::InRegion { - region: (1, "Range check 6 bits").into(), - offset: 1, - }, - }]) - ); - } + // Check element has been shifted by the correct number of bits. + self.q_bitshift.enable(region, 1)?; - // Element larger than K bits - { - let circuit: MyCircuit = MyCircuit { - element: Value::known(pallas::Base::from(1 << K)), - num_bits: 6, - }; - let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); - assert_eq!( - prover.verify(), - Err(vec![ - VerifyFailure::Lookup { - lookup_index: 0, - location: FailureLocation::InRegion { - region: (1, "Range check 6 bits").into(), - offset: 0, - }, - }, - VerifyFailure::Lookup { - lookup_index: 0, - location: FailureLocation::InRegion { - region: (1, "Range check 6 bits").into(), - offset: 1, - }, - }, - ]) - ); - } + // Assign shifted `element * 2^{K - num_bits}` + let shifted = element.value().into_field() * F::from(1 << (K - num_bits)); - // Element which is not within `num_bits`, but which has a shifted value within - // num_bits - { - let num_bits = 6; - let shifted = pallas::Base::from((1 << num_bits) - 1); - // Recall that shifted = element * 2^{K-s} - // => element = shifted * 2^{s-K} - let element = shifted - * pallas::Base::from(1 << (K as u64 - num_bits)) - .invert() - .unwrap(); - let circuit: MyCircuit = MyCircuit { - element: Value::known(element), - num_bits: num_bits as usize, - }; - let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); - assert_eq!( - prover.verify(), - Err(vec![VerifyFailure::Lookup { - lookup_index: 0, - location: FailureLocation::InRegion { - region: (1, "Range check 6 bits").into(), - offset: 0, - }, - }]) - ); - } + region.assign_advice( + || format!("element * 2^({}-{})", K, num_bits), + self.running_sum, + 1, + || shifted, + )?; - // Element within 4 bits - { - let circuit: MyCircuit = MyCircuit { - element: Value::known(pallas::Base::from((1 << 4) - 1)), - num_bits: 4, - }; - let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - } + // Assign 2^{-num_bits} from a fixed column. + let inv_two_pow_s = F::from(1 << num_bits).invert().unwrap(); + region.assign_advice_from_constant( + || format!("2^(-{})", num_bits), + self.running_sum, + 2, + inv_two_pow_s, + )?; - // Element larger than 5 bits - { - let circuit: MyCircuit = MyCircuit { - element: Value::known(pallas::Base::from(1 << 5)), - num_bits: 5, - }; - let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); - assert_eq!( - prover.verify(), - Err(vec![VerifyFailure::Lookup { - lookup_index: 0, - location: FailureLocation::InRegion { - region: (1, "Range check 5 bits").into(), - offset: 0, - }, - }]) - ); - } + Ok(()) } } + +/// 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 {}