From ec1ebfd6ea554fd85e630ddb1d5dbb3c1e24e364 Mon Sep 17 00:00:00 2001 From: YaoGalteland Date: Mon, 15 Apr 2024 13:49:20 +0200 Subject: [PATCH 1/7] initial doc --- halo2_gadgets/src/ecc.rs | 51 +- halo2_gadgets/src/ecc/chip.rs | 54 +- halo2_gadgets/src/ecc/chip/mul/overflow.rs | 1 + .../src/ecc/chip/mul_fixed/base_field_elem.rs | 1 + halo2_gadgets/src/ecc/chip/mul_fixed/short.rs | 64 ++ halo2_gadgets/src/ecc/chip/witness_point.rs | 41 +- halo2_gadgets/src/lib.rs | 2 + halo2_gadgets/src/sinsemilla.rs | 178 +++-- halo2_gadgets/src/sinsemilla/chip.rs | 415 ++++++++---- .../src/sinsemilla/chip/generator_table.rs | 20 +- .../src/sinsemilla/chip/hash_to_point.rs | 345 ++++++++-- halo2_gadgets/src/sinsemilla/merkle.rs | 2 + halo2_gadgets/src/sinsemilla/merkle/chip.rs | 104 +-- halo2_gadgets/src/sinsemilla/primitives.rs | 66 +- halo2_gadgets/src/sinsemilla_opt.rs | 2 + halo2_gadgets/src/sinsemilla_opt/chip.rs | 248 +++++++ .../sinsemilla_opt/chip/generator_table.rs | 176 +++++ .../src/sinsemilla_opt/chip/hash_to_point.rs | 620 ++++++++++++++++++ halo2_gadgets/src/sinsemilla_opt/merkle.rs | 407 ++++++++++++ .../src/sinsemilla_opt/merkle/chip.rs | 557 ++++++++++++++++ halo2_gadgets/src/utilities/cond_swap.rs | 107 ++- .../src/utilities/lookup_range_check.rs | 339 +++++----- halo2_gadgets/src/utilities_opt.rs | 4 + halo2_gadgets/src/utilities_opt/cond_swap.rs | 0 .../src/utilities_opt/lookup_range_check.rs | 563 ++++++++++++++++ 25 files changed, 3891 insertions(+), 476 deletions(-) create mode 100644 halo2_gadgets/src/sinsemilla_opt.rs create mode 100644 halo2_gadgets/src/sinsemilla_opt/chip.rs create mode 100644 halo2_gadgets/src/sinsemilla_opt/chip/generator_table.rs create mode 100644 halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs create mode 100644 halo2_gadgets/src/sinsemilla_opt/merkle.rs create mode 100644 halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs create mode 100644 halo2_gadgets/src/utilities_opt.rs create mode 100644 halo2_gadgets/src/utilities_opt/cond_swap.rs create mode 100644 halo2_gadgets/src/utilities_opt/lookup_range_check.rs diff --git a/halo2_gadgets/src/ecc.rs b/halo2_gadgets/src/ecc.rs index 38fab0a49a..b5cc37406a 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::{Chip, Layouter, Value}, + circuit::{AssignedCell, Chip, Layouter, Value}, plonk::Error, }; @@ -14,7 +14,7 @@ pub mod chip; /// The set of circuit instructions required to use the ECC gadgets. pub trait EccInstructions: - Chip + UtilitiesInstructions + Clone + Debug + Eq +Chip + UtilitiesInstructions + Clone + Debug + Eq { /// Variable representing a scalar used in variable-base scalar mul. /// @@ -60,6 +60,15 @@ 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( @@ -111,6 +120,15 @@ 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, @@ -362,7 +380,7 @@ impl> NonIdentityPoint { } impl + Clone + Debug + Eq> - From> for Point +From> for Point { fn from(non_id_point: NonIdentityPoint) -> Self { Self { @@ -390,6 +408,16 @@ 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, @@ -432,6 +460,21 @@ 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. @@ -595,7 +638,7 @@ pub(crate) mod tests { }, FixedPoints, }; - use crate::utilities::lookup_range_check::LookupRangeCheckConfig; + use crate::utilities::lookup_range_check::{LookupRangeCheckConfig, LookupRangeCheck}; #[derive(Debug, Eq, PartialEq, Clone)] pub(crate) struct TestFixedBases; diff --git a/halo2_gadgets/src/ecc/chip.rs b/halo2_gadgets/src/ecc/chip.rs index 4d12057afb..8036b08338 100644 --- a/halo2_gadgets/src/ecc/chip.rs +++ b/halo2_gadgets/src/ecc/chip.rs @@ -245,7 +245,7 @@ impl> Chip for Ecc } impl> UtilitiesInstructions - for EccChip +for EccChip { type Var = AssignedCell; } @@ -360,7 +360,7 @@ pub struct EccScalarFixedShort { /// The circuit-assigned running sum constraining this signed short scalar, or `None` /// if the scalar has not been used yet. running_sum: - Option, { NUM_WINDOWS_SHORT + 1 }>>, + Option, { NUM_WINDOWS_SHORT + 1 }>>, } /// A base field element used for fixed-base scalar multiplication. @@ -408,12 +408,12 @@ pub enum ScalarVar { } impl> EccInstructions for EccChip -where - >::Base: + where + >::Base: FixedPoint, - >::FullScalar: + >::FullScalar: FixedPoint, - >::ShortScalar: + >::ShortScalar: FixedPoint, { type ScalarFixed = EccScalarFixed; @@ -453,6 +453,18 @@ 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, @@ -532,6 +544,24 @@ 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, @@ -595,13 +625,13 @@ where } impl> BaseFitsInScalarInstructions - for EccChip -where - >::Base: +for EccChip + where + >::Base: FixedPoint, - >::FullScalar: + >::FullScalar: FixedPoint, - >::ShortScalar: + >::ShortScalar: FixedPoint, { fn scalar_var_from_base( @@ -611,4 +641,4 @@ where ) -> Result { Ok(ScalarVar::BaseFieldElem(base.clone())) } -} +} \ No newline at end of file diff --git a/halo2_gadgets/src/ecc/chip/mul/overflow.rs b/halo2_gadgets/src/ecc/chip/mul/overflow.rs index 12101ae82e..ba2d876e3c 100644 --- a/halo2_gadgets/src/ecc/chip/mul/overflow.rs +++ b/halo2_gadgets/src/ecc/chip/mul/overflow.rs @@ -13,6 +13,7 @@ use halo2_proofs::{ use pasta_curves::pallas; use std::iter; +use crate::utilities::lookup_range_check::LookupRangeCheck; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Config { 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..758a35107e 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 @@ -16,6 +16,7 @@ use halo2_proofs::{ use pasta_curves::pallas; use std::convert::TryInto; +use crate::utilities::lookup_range_check::LookupRangeCheck; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Config> { diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs index bfdc735f70..98c4805f1d 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs @@ -9,6 +9,7 @@ use halo2_proofs::{ poly::Rotation, }; use pasta_curves::pallas; +use halo2_proofs::circuit::AssignedCell; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Config> { @@ -241,6 +242,68 @@ 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)] @@ -261,6 +324,7 @@ pub mod tests { }, utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, }; + use crate::utilities::lookup_range_check::LookupRangeCheck; #[allow(clippy::op_ref)] pub(crate) fn test_mul_fixed_short( diff --git a/halo2_gadgets/src/ecc/chip/witness_point.rs b/halo2_gadgets/src/ecc/chip/witness_point.rs index 7cba8d6f87..bb21bbb511 100644 --- a/halo2_gadgets/src/ecc/chip/witness_point.rs +++ b/halo2_gadgets/src/ecc/chip/witness_point.rs @@ -102,6 +102,21 @@ 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, @@ -126,6 +141,28 @@ 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, @@ -169,6 +206,6 @@ pub mod tests { layouter.namespace(|| "witness identity"), Value::known(pallas::Affine::identity()), ) - .expect_err("witnessing 𝒪 should return an error"); + .expect_err("witnessing 𝒪 should return an error"); } -} +} \ No newline at end of file diff --git a/halo2_gadgets/src/lib.rs b/halo2_gadgets/src/lib.rs index 2ac2623a99..b4564dc84f 100644 --- a/halo2_gadgets/src/lib.rs +++ b/halo2_gadgets/src/lib.rs @@ -28,3 +28,5 @@ pub mod poseidon; pub mod sha256; pub mod sinsemilla; pub mod utilities; +pub mod utilities_opt; +pub mod sinsemilla_opt; diff --git a/halo2_gadgets/src/sinsemilla.rs b/halo2_gadgets/src/sinsemilla.rs index 3f06315a77..f626252ad2 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; } @@ -99,17 +115,17 @@ pub trait SinsemillaInstructions -where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { chip: SinsemillaChip, inner: SinsemillaChip::Message, } impl - Message -where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +Message + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { #![allow(dead_code)] fn from_bitstring( @@ -163,16 +179,16 @@ where /// A message piece with a bitlength of some multiple of `K`. #[derive(Copy, Clone, Debug)] pub struct MessagePiece -where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { inner: SinsemillaChip::MessagePiece, } impl - MessagePiece -where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +MessagePiece + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { /// Returns the inner MessagePiece contained in this gadget. pub fn inner(&self) -> SinsemillaChip::MessagePiece { @@ -181,9 +197,9 @@ where } impl - MessagePiece -where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +MessagePiece + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { #![allow(dead_code)] fn from_bitstring( @@ -276,12 +292,12 @@ pub struct HashDomain< > where SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, EccChip: EccInstructions< - C, - NonIdentityPoint = >::NonIdentityPoint, - FixedPoints = >::FixedPoints, - > + Clone - + Debug - + Eq, + C, + NonIdentityPoint = >::NonIdentityPoint, + FixedPoints = >::FixedPoints, + > + Clone + + Debug + + Eq, { sinsemilla_chip: SinsemillaChip, ecc_chip: EccChip, @@ -289,10 +305,10 @@ pub struct HashDomain< } impl - HashDomain -where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, - EccChip: EccInstructions< +HashDomain + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + EccChip: EccInstructions< C, NonIdentityPoint = >::NonIdentityPoint, FixedPoints = >::FixedPoints, @@ -314,6 +330,7 @@ where } } + #[allow(clippy::type_complexity)] /// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash]. /// @@ -329,6 +346,24 @@ 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 @@ -346,7 +381,7 @@ where /// Trait allowing circuit's Sinsemilla CommitDomains to be enumerated. pub trait CommitDomains, H: HashDomains>: - Clone + Debug +Clone + Debug { /// Returns the fixed point corresponding to the R constant used for /// randomization in this CommitDomain. @@ -376,22 +411,22 @@ pub struct CommitDomain< > where SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, EccChip: EccInstructions< - C, - NonIdentityPoint = >::NonIdentityPoint, - FixedPoints = >::FixedPoints, - > + Clone - + Debug - + Eq, + C, + NonIdentityPoint = >::NonIdentityPoint, + FixedPoints = >::FixedPoints, + > + Clone + + Debug + + Eq, { M: HashDomain, R: ecc::FixedPoint, } impl - CommitDomain -where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, - EccChip: EccInstructions< +CommitDomain + where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + EccChip: EccInstructions< C, NonIdentityPoint = >::NonIdentityPoint, FixedPoints = >::FixedPoints, @@ -412,6 +447,67 @@ 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]. /// @@ -429,8 +525,8 @@ where Error, > { assert_eq!(self.M.sinsemilla_chip, message.chip); - let (blind, _) = self.R.mul(layouter.namespace(|| "[r] R"), r)?; - let (p, zs) = self.M.hash_to_point(layouter.namespace(|| "M"), message)?; + let blind = self.blinding_factor(layouter.namespace(|| "[r] R"), r)?; + let (p, zs) = self.hash(layouter.namespace(|| "M"), message)?; let commitment = p.add(layouter.namespace(|| "M + [r] R"), &blind)?; Ok((commitment, zs)) } @@ -483,6 +579,8 @@ pub(crate) mod tests { use pasta_curves::pallas; use std::convert::TryInto; + use crate::sinsemilla::chip::SinsemillaChipProps; + use crate::utilities::lookup_range_check::LookupRangeCheck; pub(crate) const PERSONALIZATION: &str = "MerkleCRH"; @@ -551,6 +649,8 @@ pub(crate) mod tests { meta.enable_constant(constants); let table_idx = meta.lookup_table_column(); + #[cfg(feature = "zsa")] + let table_range_check_tag = meta.lookup_table_column(); let lagrange_coeffs = [ meta.fixed_column(), meta.fixed_column(), @@ -567,9 +667,11 @@ pub(crate) mod tests { table_idx, meta.lookup_table_column(), meta.lookup_table_column(), + //TODO: table_range_check_tag, ); let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx); + //TODO: test for zsa let ecc_config = EccChip::::configure(meta, advices, lagrange_coeffs, range_check); diff --git a/halo2_gadgets/src/sinsemilla/chip.rs b/halo2_gadgets/src/sinsemilla/chip.rs index ac4c34f781..5ef8eab48f 100644 --- a/halo2_gadgets/src/sinsemilla/chip.rs +++ b/halo2_gadgets/src/sinsemilla/chip.rs @@ -12,6 +12,8 @@ use crate::{ utilities::lookup_range_check::LookupRangeCheckConfig, }; use std::marker::PhantomData; +use ff::PrimeField; +use pasta_curves::arithmetic::CurveAffine; use halo2_proofs::{ circuit::{AssignedCell, Chip, Layouter, Value}, @@ -22,188 +24,116 @@ use halo2_proofs::{ poly::Rotation, }; use pasta_curves::pallas; +use pasta_curves::pallas::Base; +use proptest::test_runner::Config; mod generator_table; use generator_table::GeneratorTableConfig; +use halo2_proofs::circuit::Region; +use halo2_proofs::plonk::Assigned; +use crate::sinsemilla::primitives::{INV_TWO_POW_K, lebs2ip_k, SINSEMILLA_S}; mod hash_to_point; /// Configuration for the Sinsemilla hash chip #[derive(Eq, PartialEq, Clone, Debug)] -pub struct SinsemillaConfig -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +pub struct SinsemillaConfigCommon + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { /// 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, + _marker: PhantomData<(Hash, Commit, F)>, +} +/// Configuration for the Sinsemilla hash chip +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct SinsemillaConfig + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + base: SinsemillaConfigCommon, /// The lookup table where $(\mathsf{idx}, x_p, y_p)$ are loaded for the $2^K$ /// generators of the Sinsemilla hash. pub(super) generator_table: GeneratorTableConfig, /// An advice column configured to perform lookup range checks. lookup_config: LookupRangeCheckConfig, - _marker: PhantomData<(Hash, Commit, F)>, } - -impl SinsemillaConfig -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +// TODO: add doc, rename it to SinsemillaConfigProps +pub trait SinsemillaConfigProps + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { + type LookupConfigType; + fn base(&self) -> &SinsemillaConfigCommon; + /// Returns an array of all advice columns in this config, in arbitrary order. - pub(super) fn advices(&self) -> [Column; 5] { + fn advices(&self) -> [Column; 5] { [ - self.double_and_add.x_a, - self.double_and_add.x_p, - self.bits, - self.double_and_add.lambda_1, - self.double_and_add.lambda_2, + self.base().double_and_add.x_a, + self.base().double_and_add.x_p, + self.base().bits, + self.base().double_and_add.lambda_1, + self.base().double_and_add.lambda_2, ] } /// Returns the lookup range check config used in this config. - pub fn lookup_config(&self) -> LookupRangeCheckConfig { - self.lookup_config - } + fn lookup_config(&self) -> Self::LookupConfigType; /// Derives the expression `q_s3 = (q_s2) * (q_s2 - 1)`. fn q_s3(&self, meta: &mut VirtualCells) -> Expression { let one = Expression::Constant(pallas::Base::one()); - let q_s2 = meta.query_fixed(self.q_sinsemilla2); + let q_s2 = meta.query_fixed(self.base().q_sinsemilla2); q_s2.clone() * (q_s2 - one) } -} - -/// A chip that implements 10-bit Sinsemilla using a lookup table and 5 advice columns. -/// -/// [Chip description](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html#plonk--halo-2-constraints). -#[derive(Eq, PartialEq, Clone, Debug)] -pub struct SinsemillaChip -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, -{ - config: SinsemillaConfig, -} - -impl Chip for SinsemillaChip -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, -{ - type Config = SinsemillaConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} - -impl SinsemillaChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, -{ - /// Reconstructs this chip from the given config. - pub fn construct(config: >::Config) -> Self { - Self { config } - } - /// Loads the lookup table required by this chip into the circuit. - pub fn load( - config: SinsemillaConfig, - layouter: &mut impl Layouter, - ) -> Result<>::Loaded, Error> { - // Load the lookup table. - config.generator_table.load(layouter) - } + fn get_y_q(&self, meta: &mut VirtualCells) -> Expression; - /// # Side-effects - /// - /// All columns in `advices` and will be equality-enabled. - #[allow(clippy::too_many_arguments)] - #[allow(non_snake_case)] - pub fn configure( + fn configure_from_y_q( + &self, meta: &mut ConstraintSystem, - advices: [Column; 5], - witness_pieces: Column, - fixed_y_q: Column, - lookup: (TableColumn, TableColumn, TableColumn), - range_check: LookupRangeCheckConfig, - ) -> >::Config { - // Enable equality on all advice columns - for advice in advices.iter() { - meta.enable_equality(*advice); - } - - let config = SinsemillaConfig:: { - q_sinsemilla1: meta.complex_selector(), - q_sinsemilla2: meta.fixed_column(), - q_sinsemilla4: meta.selector(), - fixed_y_q, - double_and_add: DoubleAndAdd { - x_a: advices[0], - x_p: advices[1], - lambda_1: advices[3], - lambda_2: advices[4], - }, - bits: advices[2], - witness_pieces, - generator_table: GeneratorTableConfig { - table_idx: lookup.0, - table_x: lookup.1, - table_y: lookup.2, - }, - lookup_config: range_check, - _marker: PhantomData, - }; - - // Set up lookup argument - GeneratorTableConfig::configure(meta, config.clone()); - + ) where + { 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) + self.base().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) + self.base().double_and_add.Y_A(meta, rotation) }; // Check that the initial x_A, x_P, lambda_1, lambda_2 are consistent with y_Q. // https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial meta.create_gate("Initial y_Q", |meta| { - let q_s4 = meta.query_selector(config.q_sinsemilla4); - let y_q = meta.query_fixed(config.fixed_y_q); + let q_s4 = meta.query_selector(self.base().q_sinsemilla4); + let y_q = self.get_y_q(meta); // We use the closure we passed to get the y_q. // Y_A = (lambda_1 + lambda_2) * (x_a - x_r) let Y_A_cur = Y_A(meta, Rotation::cur()); @@ -216,13 +146,13 @@ where // https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial meta.create_gate("Sinsemilla gate", |meta| { - let q_s1 = meta.query_selector(config.q_sinsemilla1); - let q_s3 = config.q_s3(meta); + let q_s1 = meta.query_selector(self.base().q_sinsemilla1); + let q_s3 = self.q_s3(meta); - let lambda_1_next = meta.query_advice(config.double_and_add.lambda_1, Rotation::next()); - let lambda_2_cur = meta.query_advice(config.double_and_add.lambda_2, Rotation::cur()); - let x_a_cur = meta.query_advice(config.double_and_add.x_a, Rotation::cur()); - let x_a_next = meta.query_advice(config.double_and_add.x_a, Rotation::next()); + let lambda_1_next = meta.query_advice(self.base().double_and_add.lambda_1, Rotation::next()); + let lambda_2_cur = meta.query_advice(self.base().double_and_add.lambda_2, Rotation::cur()); + let x_a_cur = meta.query_advice(self.base().double_and_add.x_a, Rotation::cur()); + let x_a_next = meta.query_advice(self.base().double_and_add.x_a, Rotation::next()); // x_r = lambda_1^2 - x_a_cur - x_p let x_r = x_r(meta, Rotation::cur()); @@ -258,18 +188,209 @@ where Constraints::with_selector(q_s1, [("Secant line", secant_line), ("y check", y_check)]) }); + } + + +} +// TODO: add doc +impl SinsemillaConfigProps for SinsemillaConfig + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + type LookupConfigType = LookupRangeCheckConfig; + fn base(&self) -> &SinsemillaConfigCommon { + &self.base + } + + fn lookup_config(&self) -> Self::LookupConfigType { + self.lookup_config + } + + // todo: add doc + fn get_y_q(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.base.fixed_y_q) + } +} + + +/// A chip that implements 10-bit Sinsemilla using a lookup table and 5 advice columns. +/// +/// [Chip description](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html#plonk--halo-2-constraints). +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct SinsemillaChip + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + config: SinsemillaConfig, +} + +// TODO: add doc,rename it to SinsemillaChipProps +pub trait SinsemillaChipProps + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + type Loaded; + + type RangeCheckConfigType; + type SinsemillaConfigType: SinsemillaConfigProps; + type LookupType; + + fn base(&self) -> &SinsemillaConfigCommon; + + /// Reconstructs this chip from the given config. + fn construct(config: Self::SinsemillaConfigType) -> Self; + + /// Loads the lookup table required by this chip into the circuit. + fn load( + config: Self::SinsemillaConfigType, + layouter: &mut impl Layouter, + ) -> Result; + fn configure( + meta: &mut ConstraintSystem, + advices: [Column; 5], + witness_pieces: Column, + fixed_y_q: Column, + lookup: Self::LookupType, + range_check: Self::RangeCheckConfigType, + ) -> Self::SinsemillaConfigType; + +} + +impl SinsemillaChipProps for SinsemillaChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + type Loaded = >::Loaded; + + type RangeCheckConfigType = LookupRangeCheckConfig; + + type SinsemillaConfigType = SinsemillaConfig; + + type LookupType = (TableColumn, TableColumn, TableColumn); + + fn base(&self) -> &SinsemillaConfigCommon { + &self.config.base + } + + /// Reconstructs this chip from the given config. + fn construct(config: Self::SinsemillaConfigType) -> Self { + Self { config } + } + + /// Loads the lookup table required by this chip into the circuit. + fn load( + config: Self::SinsemillaConfigType, + 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)] + fn configure( + meta: &mut ConstraintSystem, + advices: [Column; 5], + witness_pieces: Column, + fixed_y_q: Column, + lookup: Self::LookupType, + range_check: Self::RangeCheckConfigType, + ) -> Self::SinsemillaConfigType { + // Enable equality on all advice columns + for advice in advices.iter() { + meta.enable_equality(*advice); + } + + let base_config = create_common_config(meta, advices, witness_pieces, fixed_y_q); + let config = SinsemillaConfig:: { + base: base_config, + generator_table: GeneratorTableConfig { + table_idx: lookup.0, + table_x: lookup.1, + table_y: lookup.2, + }, + lookup_config: range_check, + }; + + // Set up lookup argument + GeneratorTableConfig::configure(meta, config.clone()); + config.configure_from_y_q(meta); config } } +// TODO: add doc +pub fn create_common_config( + meta: &mut ConstraintSystem, + advices: [Column; 5], + witness_pieces: Column, + fixed_y_q: Column, +) -> SinsemillaConfigCommon + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + SinsemillaConfigCommon { + 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, + _marker: PhantomData, + } +} + + +// TODO: remove duplicate? +impl Chip for SinsemillaChip + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + type Config = SinsemillaConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + + + +// TODO: remove duplicate? + // Implement `SinsemillaInstructions` for `SinsemillaChip` impl SinsemillaInstructions - for SinsemillaChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +for SinsemillaChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type CellValue = AssignedCell; @@ -298,7 +419,7 @@ where |mut region| { region.assign_advice( || "witness message piece", - config.witness_pieces, + config.base.witness_pieces, 0, || field_elem, ) @@ -307,6 +428,7 @@ where Ok(MessagePiece::new(cell, num_words)) } + // TODO: in the opt version: hash_message_vanilla -> hash_message #[allow(non_snake_case)] #[allow(clippy::type_complexity)] fn hash_to_point( @@ -317,11 +439,24 @@ where ) -> Result<(Self::NonIdentityPoint, Vec), Error> { layouter.assign_region( || "hash_to_point", - |mut region| self.hash_message(&mut region, Q, &message), + |mut region| self.hash_message_vanilla(&mut region, Q, &message), ) } + #[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() } -} +} \ No newline at end of file diff --git a/halo2_gadgets/src/sinsemilla/chip/generator_table.rs b/halo2_gadgets/src/sinsemilla/chip/generator_table.rs index fd0ff03f91..9193f85722 100644 --- a/halo2_gadgets/src/sinsemilla/chip/generator_table.rs +++ b/halo2_gadgets/src/sinsemilla/chip/generator_table.rs @@ -5,7 +5,7 @@ use halo2_proofs::{ poly::Rotation, }; -use super::{CommitDomains, FixedPoints, HashDomains}; +use super::{CommitDomains, FixedPoints, HashDomains, SinsemillaConfigProps}; use crate::sinsemilla::primitives::{self as sinsemilla, SINSEMILLA_S}; use pasta_curves::pallas; @@ -39,8 +39,8 @@ impl GeneratorTableConfig { // https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial meta.lookup(|meta| { - let q_s1 = meta.query_selector(config.q_sinsemilla1); - let q_s2 = meta.query_fixed(config.q_sinsemilla2); + let q_s1 = meta.query_selector(config.base.q_sinsemilla1); + let q_s2 = meta.query_fixed(config.base.q_sinsemilla2); let q_s3 = config.q_s3(meta); let q_run = q_s2 - q_s3; @@ -48,18 +48,18 @@ impl GeneratorTableConfig { // Note that the message words m_i's are 1-indexed while the // running sum z_i's are 0-indexed. let word = { - let z_cur = meta.query_advice(config.bits, Rotation::cur()); - let z_next = meta.query_advice(config.bits, Rotation::next()); + let z_cur = meta.query_advice(config.base.bits, Rotation::cur()); + let z_next = meta.query_advice(config.base.bits, Rotation::next()); z_cur - (q_run * z_next * pallas::Base::from(1 << sinsemilla::K)) }; - let x_p = meta.query_advice(config.double_and_add.x_p, Rotation::cur()); + let x_p = meta.query_advice(config.base.double_and_add.x_p, Rotation::cur()); // y_{p,i} = (Y_{A,i} / 2) - lambda1 * (x_{A,i} - x_{P,i}) let y_p = { - let lambda1 = meta.query_advice(config.double_and_add.lambda_1, Rotation::cur()); - let x_a = meta.query_advice(config.double_and_add.x_a, Rotation::cur()); - let Y_A = config.double_and_add.Y_A(meta, Rotation::cur()); + let lambda1 = meta.query_advice(config.base.double_and_add.lambda_1, Rotation::cur()); + let x_a = meta.query_advice(config.base.double_and_add.x_a, Rotation::cur()); + let Y_A = config.base.double_and_add.Y_A(meta, Rotation::cur()); (Y_A * pallas::Base::TWO_INV) - (lambda1 * (x_a - x_p.clone())) }; @@ -95,4 +95,4 @@ impl GeneratorTableConfig { }, ) } -} +} \ No newline at end of file diff --git a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs index 44beaa4259..8f2e34c441 100644 --- a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs @@ -15,17 +15,59 @@ use group::ff::{PrimeField, PrimeFieldBits}; use pasta_curves::{arithmetic::CurveAffine, pallas}; use std::ops::Deref; +// TODO: SinsemillaChip to SinsemillaChipTraits -impl SinsemillaChip -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, + + + +impl SinsemillaChip + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { + + // TODO: simplify three hash_message functions + /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + pub(crate) fn hash_message_vanilla( + &self, + region: &mut Region<'_, pallas::Base>, + Q: pallas::Affine, + message: &>::Message, + ) -> Result< + ( + NonIdentityEccPoint, + Vec>>, + ), + Error, + > { + 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)?; + + // todo: add test + + 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, + )) + + } + + /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] #[allow(clippy::type_complexity)] - pub(super) fn hash_message( + fn hash_message( &self, region: &mut Region<'_, pallas::Base>, Q: pallas::Affine, @@ -41,6 +83,101 @@ where ), Error, > { + let (offset, x_a, y_a) = self.public_initialization(region, Q)?; + + let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?; + + // todo: add test + + 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, + )) + } + + + /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + pub(crate) fn hash_message_with_private_init( + &self, + region: &mut Region<'_, pallas::Base>, + Q: &NonIdentityEccPoint, + message: &>::Message, + ) -> Result< + ( + NonIdentityEccPoint, + Vec>>, + ), + 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}; + + 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())) + .zip(Q.point()) + .assert_if_known(|((field_elems, (x_a, y_a)), Q)| { + // 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, + )) + } + + + #[allow(non_snake_case)] + 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; @@ -51,11 +188,11 @@ where // Constrain the initial x_a, lambda_1, lambda_2, x_p using the q_sinsemilla4 // selector. let mut y_a: Y = { - // Enable `q_sinsemilla4` on the first row. - config.q_sinsemilla4.enable(region, offset)?; +// Enable `q_sinsemilla4` on the first row. + config.base.q_sinsemilla4.enable(region, offset)?; region.assign_fixed( || "fixed y_q", - config.fixed_y_q, + config.base.fixed_y_q, offset, || Value::known(y_q), )?; @@ -67,7 +204,56 @@ where let mut x_a: X = { let x_a = region.assign_advice_from_constant( || "fixed x_q", - config.double_and_add.x_a, + config.base.double_and_add.x_a, + offset, + x_q.into(), + )?; + + x_a.into() + }; + Ok((offset, x_a, y_a)) + } + #[allow(non_snake_case)] + /// Assign the coordinates of the initial public point `Q` + /// + /// | offset | x_A | x_P | q_sinsemilla4 | + /// -------------------------------------- + /// | 0 | | y_Q | | + /// | 1 | x_Q | | 1 | + fn public_initialization( + &self, + region: &mut Region<'_, pallas::Base>, + Q: pallas::Affine, + ) -> Result<(usize, X, Y), Error> { + let config = self.config().clone(); + let mut offset = 0; + + // Get the `x`- and `y`-coordinates of the starting `Q` base. + let x_q = *Q.coordinates().unwrap().x(); + let y_q = *Q.coordinates().unwrap().y(); + + // 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.base.q_sinsemilla4.enable(region, offset + 1)?; + let y_a: AssignedCell, pallas::Base> = region + .assign_advice_from_constant( + || "fixed y_q", + config.base.double_and_add.x_p, + offset, + y_q.into(), + )?; + + y_a.value_field().into() + }; + offset += 1; + + // Constrain the initial x_q to equal the x-coordinate of the domain's `Q`. + let x_a: X = { + let x_a = region.assign_advice_from_constant( + || "fixed x_q", + config.base.double_and_add.x_a, offset, x_q.into(), )?; @@ -75,6 +261,70 @@ where x_a.into() }; + 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.base.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.base.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.base.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( + &self, + region: &mut Region<'_, pallas::Base>, + mut offset: usize, + message: &>::Message, + mut x_a: X, + mut y_a: Y, + ) -> Result< + ( + X, + AssignedCell, pallas::Base>, + Vec>>, + ), + Error, + > { + let config = self.config().clone(); + let mut zs_sum: Vec>> = Vec::new(); // Hash each piece in the message. @@ -98,7 +348,7 @@ where let y_a = { // Assign the final y_a. let y_a_cell = - region.assign_advice(|| "y_a", config.double_and_add.lambda_1, offset, || y_a.0)?; + region.assign_advice(|| "y_a", config.base.double_and_add.lambda_1, offset, || y_a.0)?; // Assign lambda_2 and x_p zero values since they are queried // in the gate. (The actual values do not matter since they are @@ -106,13 +356,13 @@ where { region.assign_advice( || "dummy lambda2", - config.double_and_add.lambda_2, + config.base.double_and_add.lambda_2, offset, || Value::known(pallas::Base::zero()), )?; region.assign_advice( || "dummy x_p", - config.double_and_add.x_p, + config.base.double_and_add.x_p, offset, || Value::known(pallas::Base::zero()), )?; @@ -121,52 +371,7 @@ where y_a_cell }; - #[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, - )) + Ok((x_a, y_a, zs_sum)) } #[allow(clippy::type_complexity)] @@ -202,14 +407,14 @@ where { // Enable `q_sinsemilla1` selector on every row. for row in 0..piece.num_words() { - config.q_sinsemilla1.enable(region, offset + row)?; + config.base.q_sinsemilla1.enable(region, offset + row)?; } // Set `q_sinsemilla2` fixed column to 1 on every row but the last. for row in 0..(piece.num_words() - 1) { region.assign_fixed( || "q_s2 = 1", - config.q_sinsemilla2, + config.base.q_sinsemilla2, offset + row, || Value::known(pallas::Base::one()), )?; @@ -225,7 +430,7 @@ where "q_s2 between pieces" } }, - config.q_sinsemilla2, + config.base.q_sinsemilla2, offset + piece.num_words() - 1, || { Value::known(if final_piece { @@ -272,7 +477,7 @@ where let initial_z = piece.cell_value().copy_advice( || "z_0 (copy of message piece)", region, - config.bits, + config.base.bits, offset, )?; zs.push(initial_z); @@ -294,7 +499,7 @@ where z = (z - word) * inv_2_k; let cell = region.assign_advice( || format!("z_{:?}", idx + 1), - config.bits, + config.base.bits, offset + idx + 1, || z, )?; @@ -314,7 +519,7 @@ where let y_p = gen.map(|gen| gen.1); // Assign `x_p` - region.assign_advice(|| "x_p", config.double_and_add.x_p, offset + row, || x_p)?; + region.assign_advice(|| "x_p", config.base.double_and_add.x_p, offset + row, || x_p)?; // Compute and assign `lambda_1` let lambda_1 = { @@ -323,7 +528,7 @@ where // Assign lambda_1 region.assign_advice( || "lambda_1", - config.double_and_add.lambda_1, + config.base.double_and_add.lambda_1, offset + row, || lambda_1, )?; @@ -341,7 +546,7 @@ where region.assign_advice( || "lambda_2", - config.double_and_add.lambda_2, + config.base.double_and_add.lambda_2, offset + row, || lambda_2, )?; @@ -355,7 +560,7 @@ where let x_a_cell = region.assign_advice( || "x_a", - config.double_and_add.x_a, + config.base.double_and_add.x_a, offset + row + 1, || x_a_new, )?; @@ -377,7 +582,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 { @@ -398,7 +603,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 02a3bdaf7c..91461639d2 100644 --- a/halo2_gadgets/src/sinsemilla/merkle.rs +++ b/halo2_gadgets/src/sinsemilla/merkle.rs @@ -197,6 +197,8 @@ pub mod tests { use rand::{rngs::OsRng, RngCore}; use std::{convert::TryInto, iter}; + use crate::sinsemilla::chip::SinsemillaChipProps; + use crate::utilities::lookup_range_check::LookupRangeCheck; const MERKLE_DEPTH: usize = 32; diff --git a/halo2_gadgets/src/sinsemilla/merkle/chip.rs b/halo2_gadgets/src/sinsemilla/merkle/chip.rs index 2c37fe924b..b132b03e4b 100644 --- a/halo2_gadgets/src/sinsemilla/merkle/chip.rs +++ b/halo2_gadgets/src/sinsemilla/merkle/chip.rs @@ -25,14 +25,16 @@ use crate::{ }, }; use group::ff::PrimeField; +use crate::sinsemilla::chip::{SinsemillaChipProps, SinsemillaConfigProps}; +use crate::utilities::lookup_range_check::witness_short_generic; /// Configuration for the `MerkleChip` implementation. #[derive(Clone, Debug, PartialEq, Eq)] pub struct MerkleConfig -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { advices: [Column; 5], q_decompose: Selector, @@ -52,19 +54,19 @@ where /// `left` and `right`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct MerkleChip -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { config: MerkleConfig, } impl Chip for MerkleChip -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { type Config = MerkleConfig; type Loaded = (); @@ -79,10 +81,10 @@ where } impl MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { /// Configures the [`MerkleChip`]. pub fn configure( @@ -197,12 +199,12 @@ where } impl - MerkleInstructions - for MerkleChip -where - Hash: HashDomains + Eq, - F: FixedPoints, - Commit: CommitDomains + Eq, +MerkleInstructions +for MerkleChip + where + Hash: HashDomains + Eq, + F: FixedPoints, + Commit: CommitDomains + Eq, { #[allow(non_snake_case)] fn hash_layer( @@ -247,7 +249,7 @@ where // b_1 = (bits 250..=254 of `left`) // Constrain b_1 to 5 bits. - let b_1 = RangeConstrained::witness_short( + let b_1 = witness_short_generic( &config.sinsemilla_config.lookup_config(), layouter.namespace(|| "b_1"), left.value(), @@ -256,7 +258,7 @@ where // b_2 = (bits 0..=4 of `right`) // Constrain b_2 to 5 bits. - let b_2 = RangeConstrained::witness_short( + let b_2 = witness_short_generic( &config.sinsemilla_config.lookup_config(), layouter.namespace(|| "b_2"), right.value(), @@ -416,19 +418,19 @@ where } impl UtilitiesInstructions for MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type Var = AssignedCell; } impl CondSwapInstructions for MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { #[allow(clippy::type_complexity)] fn swap( @@ -441,14 +443,26 @@ 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 -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +for MerkleChip + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type CellValue = as SinsemillaInstructions< pallas::Affine, @@ -523,7 +537,21 @@ where 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) } -} +} \ No newline at end of file diff --git a/halo2_gadgets/src/sinsemilla/primitives.rs b/halo2_gadgets/src/sinsemilla/primitives.rs index 9bf6a72332..668558fed9 100644 --- a/halo2_gadgets/src/sinsemilla/primitives.rs +++ b/halo2_gadgets/src/sinsemilla/primitives.rs @@ -184,6 +184,7 @@ impl HashDomain { #[derive(Debug)] #[allow(non_snake_case)] pub struct CommitDomain { + /// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can be used M: HashDomain, R: pallas::Point, } @@ -200,6 +201,17 @@ 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 @@ -214,6 +226,26 @@ 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 @@ -271,7 +303,7 @@ mod tests { .iter() .cloned() ) - .collect::>(), + .collect::>(), vec![true, true, false, true, false, true, false, true, false, true] ); assert_eq!( @@ -280,7 +312,7 @@ mod tests { .iter() .cloned() ) - .collect::>(), + .collect::>(), vec![ true, true, false, true, false, true, false, true, false, true, true, false, false, false, false, false, false, false, false, false @@ -305,4 +337,32 @@ 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()); + } +} \ No newline at end of file diff --git a/halo2_gadgets/src/sinsemilla_opt.rs b/halo2_gadgets/src/sinsemilla_opt.rs new file mode 100644 index 0000000000..bf4dc43e4e --- /dev/null +++ b/halo2_gadgets/src/sinsemilla_opt.rs @@ -0,0 +1,2 @@ +pub mod chip; +pub mod merkle; diff --git a/halo2_gadgets/src/sinsemilla_opt/chip.rs b/halo2_gadgets/src/sinsemilla_opt/chip.rs new file mode 100644 index 0000000000..073e2141d6 --- /dev/null +++ b/halo2_gadgets/src/sinsemilla_opt/chip.rs @@ -0,0 +1,248 @@ +mod generator_table; +mod hash_to_point; + +use crate::sinsemilla::{ + primitives as sinsemilla, +}; +use crate::{ecc::{ + chip::{DoubleAndAdd, NonIdentityEccPoint}, + FixedPoints, +}, utilities::lookup_range_check::LookupRangeCheckConfig}; +use std::marker::PhantomData; +use crate::sinsemilla::{ + message::{Message, MessagePiece}, +}; +use halo2_proofs::{ + circuit::{AssignedCell, Chip, Layouter, Value}, + plonk::{ + Advice, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector, + TableColumn, VirtualCells, + }, + poly::Rotation, +}; +use pasta_curves::pallas; +use pasta_curves::pallas::Base; + +use generator_table::GeneratorTableConfigOptimized; +use crate::sinsemilla::{CommitDomains, HashDomains, SinsemillaInstructions}; +use crate::sinsemilla::chip::{create_common_config, SinsemillaChip, SinsemillaChipProps, SinsemillaConfigCommon, SinsemillaConfigProps}; +use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; + +/// Configuration for the SinsemillaConfigOptimized hash chip +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct SinsemillaConfigOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + base: SinsemillaConfigCommon, + + /// 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: GeneratorTableConfigOptimized, + /// An advice column configured to perform lookup range checks. + lookup_config: LookupRangeCheckConfigOptimized, +} +impl SinsemillaConfigProps for SinsemillaConfigOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + type LookupConfigType = LookupRangeCheckConfigOptimized; + fn base(&self) -> &SinsemillaConfigCommon { + &self.base + } + + fn lookup_config(&self) -> Self::LookupConfigType { + self.lookup_config + } + + // todo: add doc + fn get_y_q(&self, meta: &mut VirtualCells) -> Expression { + meta.query_advice(self.base.double_and_add.x_p, Rotation::prev()) + } +} + +/// A chip that implements 10-bit Sinsemilla using a lookup table and 5 advice columns. +/// +/// [Chip description](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html#plonk--halo-2-constraints). +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct SinsemillaChipOptimized + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + config: SinsemillaConfigOptimized, +} + + +impl SinsemillaChipProps for SinsemillaChipOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + type Loaded = >::Loaded; + + type RangeCheckConfigType = LookupRangeCheckConfigOptimized; + + type SinsemillaConfigType = SinsemillaConfigOptimized; + + type LookupType = (TableColumn, TableColumn, TableColumn, TableColumn); + + fn base(&self) -> &SinsemillaConfigCommon { + &self.config.base + } + + /// Reconstructs this chip from the given config. + fn construct(config: Self::SinsemillaConfigType) -> Self { + Self { config } + } + + /// Loads the lookup table required by this chip into the circuit. + fn load( + config: Self::SinsemillaConfigType, + 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)] + fn configure( + meta: &mut ConstraintSystem, + advices: [Column; 5], + witness_pieces: Column, + fixed_y_q: Column, + lookup: Self::LookupType, + range_check: Self::RangeCheckConfigType, + ) -> Self::SinsemillaConfigType { + // Enable equality on all advice columns + for advice in advices.iter() { + meta.enable_equality(*advice); + } + + let base_config = create_common_config(meta, advices, witness_pieces, fixed_y_q); + let config = SinsemillaConfigOptimized:: { + base: base_config, + generator_table: GeneratorTableConfigOptimized { + table_idx: lookup.0, + table_x: lookup.1, + table_y: lookup.2, + table_range_check_tag: lookup.3, + }, + lookup_config: range_check, + }; + + // Set up lookup argument + GeneratorTableConfigOptimized::configure(meta, config.clone()); + + config.configure_from_y_q(meta); + config + } +} + +// TODO: remove duplicate? +impl Chip for SinsemillaChipOptimized + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + type Config = SinsemillaConfigOptimized; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +// TODO: remove duplicate? + +// Implement `SinsemillaInstructions` for `SinsemillaChip` +impl SinsemillaInstructions +for SinsemillaChipOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + type CellValue = AssignedCell; + + type Message = Message; + type MessagePiece = MessagePiece; + + type RunningSum = Vec; + + type X = AssignedCell; + type NonIdentityPoint = NonIdentityEccPoint; + type FixedPoints = F; + + type HashDomains = Hash; + type CommitDomains = Commit; + + fn witness_message_piece( + &self, + mut layouter: impl Layouter, + field_elem: Value, + num_words: usize, + ) -> Result { + let config = self.config().clone(); + + let cell = layouter.assign_region( + || "witness message piece", + |mut region| { + region.assign_advice( + || "witness message piece", + config.base.witness_pieces, + 0, + || field_elem, + ) + }, + )?; + Ok(MessagePiece::new(cell, num_words)) + } + + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + fn hash_to_point( + &self, + mut layouter: impl Layouter, + Q: pallas::Affine, + message: Self::Message, + ) -> Result<(Self::NonIdentityPoint, Vec), Error> { + layouter.assign_region( + || "hash_to_point", + |mut region| self.hash_message(&mut region, Q, &message), + ) + } + + #[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_opt/chip/generator_table.rs b/halo2_gadgets/src/sinsemilla_opt/chip/generator_table.rs new file mode 100644 index 0000000000..12616a3b0a --- /dev/null +++ b/halo2_gadgets/src/sinsemilla_opt/chip/generator_table.rs @@ -0,0 +1,176 @@ +use group::ff::PrimeField; +use halo2_proofs::{ + circuit::{Layouter, Value}, + plonk::{ConstraintSystem, Error, Expression, TableColumn}, + poly::Rotation, +}; + +use super::{CommitDomains, FixedPoints, HashDomains}; +use crate::sinsemilla::primitives::{self as sinsemilla, K, SINSEMILLA_S}; +use pasta_curves::pallas; +use crate::sinsemilla::chip::SinsemillaConfigProps; + +/// Table containing independent generators S[0..2^k] +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +pub struct GeneratorTableConfigOptimized { + pub table_idx: TableColumn, + pub table_x: TableColumn, + pub table_y: TableColumn, + pub table_range_check_tag: TableColumn, +} + +impl GeneratorTableConfigOptimized { + #[allow(clippy::too_many_arguments)] + #[allow(non_snake_case)] + /// Even though the lookup table can be used in other parts of the circuit, + /// this specific configuration sets up Sinsemilla-specific constraints + /// controlled by `q_sinsemilla`, and would likely not apply to other chips. + pub fn configure( + meta: &mut ConstraintSystem, + config: super::SinsemillaConfigOptimized, + ) where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, + { + let (table_idx, table_x, table_y) = ( + config.generator_table.table_idx, + config.generator_table.table_x, + config.generator_table.table_y, + ); + + // https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial + meta.lookup(|meta| { + let q_s1 = meta.query_selector(config.base.q_sinsemilla1); + let q_s2 = meta.query_fixed(config.base.q_sinsemilla2); + let q_s3 = config.q_s3(meta); + let q_run = q_s2 - q_s3; + + // m_{i+1} = z_{i} - 2^K * q_{run,i} * z_{i + 1} + // Note that the message words m_i's are 1-indexed while the + // running sum z_i's are 0-indexed. + let word = { + let z_cur = meta.query_advice(config.base.bits, Rotation::cur()); + let z_next = meta.query_advice(config.base.bits, Rotation::next()); + z_cur - (q_run * z_next * pallas::Base::from(1 << sinsemilla::K)) + }; + + let x_p = meta.query_advice(config.base.double_and_add.x_p, Rotation::cur()); + + // y_{p,i} = (Y_{A,i} / 2) - lambda1 * (x_{A,i} - x_{P,i}) + let y_p = { + let lambda1 = meta.query_advice(config.base.double_and_add.lambda_1, Rotation::cur()); + let x_a = meta.query_advice(config.base.double_and_add.x_a, Rotation::cur()); + let Y_A = config.base.double_and_add.Y_A(meta, Rotation::cur()); + + (Y_A * pallas::Base::TWO_INV) - (lambda1 * (x_a - x_p.clone())) + }; + + // Lookup expressions default to the first entry when `q_s1` + // is not enabled. + let (init_x, init_y) = SINSEMILLA_S[0]; + let not_q_s1 = Expression::Constant(pallas::Base::one()) - q_s1.clone(); + + let m = q_s1.clone() * word; // The first table index is 0. + let x_p = q_s1.clone() * x_p + not_q_s1.clone() * init_x; + let y_p = q_s1 * y_p + not_q_s1 * init_y; + + vec![(m, table_idx), (x_p, table_x), (y_p, table_y)] + }); + } + + /// 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", + |mut table| { + for (index, (x, y)) in SINSEMILLA_S.iter().enumerate() { + table.assign_cell( + || "table_idx", + self.table_idx, + index, + || Value::known(pallas::Base::from(index as u64)), + )?; + 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(()) + }, + ) + } +} \ No newline at end of file diff --git a/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs new file mode 100644 index 0000000000..d4c1167445 --- /dev/null +++ b/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs @@ -0,0 +1,620 @@ +use super::{NonIdentityEccPoint, SinsemillaChip, SinsemillaChipOptimized}; +use crate::{ + ecc::FixedPoints, + sinsemilla::primitives::{self as sinsemilla, lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S}, +}; + +use ff::Field; +use halo2_proofs::{ + circuit::{AssignedCell, Chip, Region, Value}, + plonk::{Assigned, Error}, +}; + +use group::ff::{PrimeField, PrimeFieldBits}; +use pasta_curves::{arithmetic::CurveAffine, pallas}; + +use std::ops::Deref; +use crate::sinsemilla::{CommitDomains, HashDomains, SinsemillaInstructions}; +// TODO: SinsemillaChip to SinsemillaChipTraits + + + + +impl SinsemillaChipOptimized + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + + // TODO: simplify three hash_message functions + /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + pub(crate) fn hash_message_vanilla( + &self, + region: &mut Region<'_, pallas::Base>, + Q: pallas::Affine, + message: &>::Message, + ) -> Result< + ( + NonIdentityEccPoint, + Vec>>, + ), + Error, + > { + 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)?; + + // todo: add test + + 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, + )) + + } + + + /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + pub(crate) fn hash_message( + &self, + region: &mut Region<'_, pallas::Base>, + Q: pallas::Affine, + message: &>::Message, + ) -> Result< + ( + NonIdentityEccPoint, + Vec>>, + ), + Error, + > { + let (offset, x_a, y_a) = self.public_initialization(region, Q)?; + + let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?; + + // todo: add test + + 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, + )) + } + + + /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + pub(crate) fn hash_message_with_private_init( + &self, + region: &mut Region<'_, pallas::Base>, + Q: &NonIdentityEccPoint, + message: &>::Message, + ) -> Result< + ( + NonIdentityEccPoint, + Vec>>, + ), + 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}; + + 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())) + .zip(Q.point()) + .assert_if_known(|((field_elems, (x_a, y_a)), Q)| { + // 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, + )) + } + + + #[allow(non_snake_case)] + 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; + + // Get the `x`- and `y`-coordinates of the starting `Q` base. + let x_q = *Q.coordinates().unwrap().x(); + let y_q = *Q.coordinates().unwrap().y(); + + // Constrain the initial x_a, lambda_1, lambda_2, x_p using the q_sinsemilla4 + // selector. + let mut y_a: Y = { +// Enable `q_sinsemilla4` on the first row. + config.base.q_sinsemilla4.enable(region, offset)?; + region.assign_fixed( + || "fixed y_q", + config.base.fixed_y_q, + offset, + || Value::known(y_q), + )?; + + Value::known(y_q.into()).into() + }; + + // Constrain the initial x_q to equal the x-coordinate of the domain's `Q`. + let mut x_a: X = { + let x_a = region.assign_advice_from_constant( + || "fixed x_q", + config.base.double_and_add.x_a, + offset, + x_q.into(), + )?; + + x_a.into() + }; + Ok((offset, x_a, y_a)) + } + #[allow(non_snake_case)] + /// Assign the coordinates of the initial public point `Q` + /// + /// | offset | x_A | x_P | q_sinsemilla4 | + /// -------------------------------------- + /// | 0 | | y_Q | | + /// | 1 | x_Q | | 1 | + fn public_initialization( + &self, + region: &mut Region<'_, pallas::Base>, + Q: pallas::Affine, + ) -> Result<(usize, X, Y), Error> { + let config = self.config().clone(); + let mut offset = 0; + + // Get the `x`- and `y`-coordinates of the starting `Q` base. + let x_q = *Q.coordinates().unwrap().x(); + let y_q = *Q.coordinates().unwrap().y(); + + // 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.base.q_sinsemilla4.enable(region, offset + 1)?; + let y_a: AssignedCell, pallas::Base> = region + .assign_advice_from_constant( + || "fixed y_q", + config.base.double_and_add.x_p, + offset, + y_q.into(), + )?; + + y_a.value_field().into() + }; + offset += 1; + + // Constrain the initial x_q to equal the x-coordinate of the domain's `Q`. + let x_a: X = { + let x_a = region.assign_advice_from_constant( + || "fixed x_q", + config.base.double_and_add.x_a, + offset, + x_q.into(), + )?; + + x_a.into() + }; + + 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.base.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.base.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.base.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( + &self, + region: &mut Region<'_, pallas::Base>, + mut offset: usize, + message: &>::Message, + mut x_a: X, + mut y_a: Y, + ) -> Result< + ( + X, + AssignedCell, pallas::Base>, + Vec>>, + ), + Error, + > { + let config = self.config().clone(); + + let mut zs_sum: Vec>> = Vec::new(); + + // Hash each piece in the message. + for (idx, piece) in message.iter().enumerate() { + let final_piece = idx == message.len() - 1; + + // The value of the accumulator after this piece is processed. + let (x, y, zs) = self.hash_piece(region, offset, piece, x_a, y_a, final_piece)?; + + // Since each message word takes one row to process, we increase + // the offset by `piece.num_words` on each iteration. + offset += piece.num_words(); + + // Update the accumulator to the latest value. + x_a = x; + y_a = y; + zs_sum.push(zs); + } + + // Assign the final y_a. + let y_a = { + // Assign the final y_a. + let y_a_cell = + region.assign_advice(|| "y_a", config.base.double_and_add.lambda_1, offset, || y_a.0)?; + + // Assign lambda_2 and x_p zero values since they are queried + // in the gate. (The actual values do not matter since they are + // multiplied by zero.) + { + region.assign_advice( + || "dummy lambda2", + config.base.double_and_add.lambda_2, + offset, + || Value::known(pallas::Base::zero()), + )?; + region.assign_advice( + || "dummy x_p", + config.base.double_and_add.x_p, + offset, + || Value::known(pallas::Base::zero()), + )?; + } + + y_a_cell + }; + + Ok((x_a, y_a, zs_sum)) + } + + #[allow(clippy::type_complexity)] + /// Hashes a message piece containing `piece.length` number of `K`-bit words. + /// + /// To avoid a duplicate assignment, the accumulator x-coordinate provided + /// by the caller is not copied. This only works because `hash_piece()` is + /// an internal API. Before this call to `hash_piece()`, x_a MUST have been + /// already assigned within this region at the correct offset. + fn hash_piece( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + piece: &>::MessagePiece, + mut x_a: X, + mut y_a: Y, + final_piece: bool, + ) -> Result< + ( + X, + Y, + Vec>, + ), + Error, + > { + let config = self.config().clone(); + + // Selector assignments + { + // Enable `q_sinsemilla1` selector on every row. + for row in 0..piece.num_words() { + config.base.q_sinsemilla1.enable(region, offset + row)?; + } + + // Set `q_sinsemilla2` fixed column to 1 on every row but the last. + for row in 0..(piece.num_words() - 1) { + region.assign_fixed( + || "q_s2 = 1", + config.base.q_sinsemilla2, + offset + row, + || Value::known(pallas::Base::one()), + )?; + } + + // Set `q_sinsemilla2` fixed column to 0 on the last row if this is + // not the final piece, or to 2 on the last row of the final piece. + region.assign_fixed( + || { + if final_piece { + "q_s2 for final piece" + } else { + "q_s2 between pieces" + } + }, + config.base.q_sinsemilla2, + offset + piece.num_words() - 1, + || { + Value::known(if final_piece { + pallas::Base::from(2) + } else { + pallas::Base::zero() + }) + }, + )?; + } + + // Message piece as K * piece.length bitstring + let bitstring: Value> = piece.field_elem().map(|value| { + value + .to_le_bits() + .into_iter() + .take(sinsemilla::K * piece.num_words()) + .collect() + }); + + let words: Value> = bitstring.map(|bitstring| { + bitstring + .chunks_exact(sinsemilla::K) + .map(lebs2ip_k) + .collect() + }); + + // Get (x_p, y_p) for each word. + let generators: Value> = words.clone().map(|words| { + words + .iter() + .map(|word| SINSEMILLA_S[*word as usize]) + .collect() + }); + + // Convert `words` from `Value>` to `Vec>` + let words = words.transpose_vec(piece.num_words()); + + // Decompose message piece into `K`-bit pieces with a running sum `z`. + let zs = { + let mut zs = Vec::with_capacity(piece.num_words() + 1); + + // Copy message and initialize running sum `z` to decompose message in-circuit + let initial_z = piece.cell_value().copy_advice( + || "z_0 (copy of message piece)", + region, + config.base.bits, + offset, + )?; + zs.push(initial_z); + + // Assign cumulative sum such that for 0 <= i < n, + // z_i = 2^K * z_{i + 1} + m_{i + 1} + // => z_{i + 1} = (z_i - m_{i + 1}) / 2^K + // + // For a message piece m = m_1 + 2^K m_2 + ... + 2^{K(n-1)} m_n}, initialize z_0 = m. + // We end up with z_n = 0. (z_n is not directly encoded as a cell value; + // it is implicitly taken as 0 by adjusting the definition of m_{i+1}.) + let mut z = piece.field_elem(); + let inv_2_k = Value::known(pallas::Base::from_repr(INV_TWO_POW_K).unwrap()); + + // We do not assign the final z_n as it is constrained to be zero. + for (idx, word) in words[0..(words.len() - 1)].iter().enumerate() { + let word = word.map(|word| pallas::Base::from(word as u64)); + // z_{i + 1} = (z_i - m_{i + 1}) / 2^K + z = (z - word) * inv_2_k; + let cell = region.assign_advice( + || format!("z_{:?}", idx + 1), + config.base.bits, + offset + idx + 1, + || z, + )?; + zs.push(cell) + } + + zs + }; + + // The accumulator x-coordinate provided by the caller MUST have been assigned + // within this region. + + let generators = generators.transpose_vec(piece.num_words()); + + for (row, gen) in generators.iter().enumerate() { + let x_p = gen.map(|gen| gen.0); + let y_p = gen.map(|gen| gen.1); + + // Assign `x_p` + region.assign_advice(|| "x_p", config.base.double_and_add.x_p, offset + row, || x_p)?; + + // Compute and assign `lambda_1` + let lambda_1 = { + let lambda_1 = (y_a.0 - y_p) * (x_a.value() - x_p).invert(); + + // Assign lambda_1 + region.assign_advice( + || "lambda_1", + config.base.double_and_add.lambda_1, + offset + row, + || lambda_1, + )?; + + lambda_1 + }; + + // Compute `x_r` + let x_r = lambda_1.square() - x_a.value() - x_p; + + // Compute and assign `lambda_2` + let lambda_2 = { + let lambda_2 = + y_a.0 * pallas::Base::from(2) * (x_a.value() - x_r).invert() - lambda_1; + + region.assign_advice( + || "lambda_2", + config.base.double_and_add.lambda_2, + offset + row, + || lambda_2, + )?; + + lambda_2 + }; + + // Compute and assign `x_a` for the next row. + let x_a_new: X = { + let x_a_new = lambda_2.square() - x_a.value() - x_r; + + let x_a_cell = region.assign_advice( + || "x_a", + config.base.double_and_add.x_a, + offset + row + 1, + || x_a_new, + )?; + + x_a_cell.into() + }; + + // Compute y_a for the next row. + let y_a_new: Y = + (lambda_2 * (x_a.value() - x_a_new.value()) - y_a.0).into(); + + // Update the mutable `x_a`, `y_a` variables. + x_a = x_a_new; + y_a = y_a_new; + } + + Ok((x_a, y_a, zs)) + } +} + +/// The x-coordinate of the accumulator in a Sinsemilla hash instance. +pub(crate) struct X(pub(crate) AssignedCell, F>); + +impl From, F>> for X { + fn from(cell_value: AssignedCell, F>) -> Self { + X(cell_value) + } +} + +impl Deref for X { + type Target = AssignedCell, F>; + + fn deref(&self) -> &AssignedCell, F> { + &self.0 + } +} + +/// The y-coordinate of the accumulator in a Sinsemilla hash instance. +/// +/// 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`. +pub(crate) struct Y(pub(crate) Value>); + +impl From>> for Y { + fn from(value: Value>) -> Self { + Y(value) + } +} + +impl Deref for Y { + type Target = Value>; + + fn deref(&self) -> &Value> { + &self.0 + } +} diff --git a/halo2_gadgets/src/sinsemilla_opt/merkle.rs b/halo2_gadgets/src/sinsemilla_opt/merkle.rs new file mode 100644 index 0000000000..a6d35d7f2a --- /dev/null +++ b/halo2_gadgets/src/sinsemilla_opt/merkle.rs @@ -0,0 +1,407 @@ +//! Gadgets for implementing a Merkle tree with Sinsemilla. + +use halo2_proofs::{ + circuit::{Chip, Layouter, Value}, + plonk::Error, +}; +use pasta_curves::arithmetic::CurveAffine; + +use crate::sinsemilla::{HashDomains, SinsemillaInstructions}; + +use crate::utilities::{cond_swap::CondSwapInstructions, i2lebsp, UtilitiesInstructions}; +pub mod chip; + + +/// SWU hash-to-curve personalization for the Merkle CRH generator +pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH"; + +/// Instructions to check the validity of a Merkle path of a given `PATH_LENGTH`. +/// The hash function used is a Sinsemilla instance with `K`-bit words. +/// The hash function can process `MAX_WORDS` words. +pub trait MerkleInstructions< + C: CurveAffine, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, +>: +SinsemillaInstructions ++ CondSwapInstructions ++ UtilitiesInstructions ++ Chip +{ + /// Compute MerkleCRH for a given `layer`. The hash that computes the root + /// is at layer 0, and the hashes that are applied to two leaves are at + /// layer `MERKLE_DEPTH - 1` = layer 31. + #[allow(non_snake_case)] + fn hash_layer( + &self, + layouter: impl Layouter, + Q: C, + l: usize, + left: Self::Var, + right: Self::Var, + ) -> Result; +} + +/// Gadget representing a Merkle path that proves a leaf exists in a Merkle tree at a +/// specific position. +#[derive(Clone, Debug)] +pub struct MerklePath< + C: CurveAffine, + MerkleChipOptimized, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, + const PAR: usize, +> where + MerkleChipOptimized: MerkleInstructions + Clone, +{ + chips: [MerkleChipOptimized; PAR], + domain: MerkleChipOptimized::HashDomains, + leaf_pos: Value, + // The Merkle path is ordered from leaves to root. + path: Value<[C::Base; PATH_LENGTH]>, +} + +impl< + C: CurveAffine, + MerkleChipOptimized, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, + const PAR: usize, +> MerklePath + where + MerkleChipOptimized: MerkleInstructions + Clone, +{ + /// Constructs a [`MerklePath`]. + /// + /// A circuit may have many more columns available than are required by a single + /// `MerkleChipOptimized`. To make better use of the available circuit area, the `MerklePath` + /// gadget will distribute its path hashing across each `MerkleChipOptimized` in `chips`, such + /// that each chip processes `ceil(PATH_LENGTH / PAR)` layers (with the last chip + /// processing fewer layers if the division is inexact). + pub fn construct( + chips: [MerkleChipOptimized; PAR], + domain: MerkleChipOptimized::HashDomains, + leaf_pos: Value, + path: Value<[C::Base; PATH_LENGTH]>, + ) -> Self { + assert_ne!(PAR, 0); + Self { + chips, + domain, + leaf_pos, + path, + } + } +} + +#[allow(non_snake_case)] +impl< + C: CurveAffine, + MerkleChipOptimized, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, + const PAR: usize, +> MerklePath + where + MerkleChipOptimized: MerkleInstructions + Clone, +{ + /// Calculates the root of the tree containing the given leaf at this Merkle path. + /// + /// Implements [Zcash Protocol Specification Section 4.9: Merkle Path Validity][merklepath]. + /// + /// [merklepath]: https://zips.z.cash/protocol/protocol.pdf#merklepath + pub fn calculate_root( + &self, + mut layouter: impl Layouter, + leaf: MerkleChipOptimized::Var, + ) -> Result { + // Each chip processes `ceil(PATH_LENGTH / PAR)` layers. + let layers_per_chip = (PATH_LENGTH + PAR - 1) / PAR; + + // Assign each layer to a chip. + let chips = (0..PATH_LENGTH).map(|i| self.chips[i / layers_per_chip].clone()); + + // The Merkle path is ordered from leaves to root, which is consistent with the + // little-endian representation of `pos` below. + let path = self.path.transpose_array(); + + // Get position as a PATH_LENGTH-bit bitstring (little-endian bit order). + let pos: [Value; PATH_LENGTH] = { + let pos: Value<[bool; PATH_LENGTH]> = self.leaf_pos.map(|pos| i2lebsp(pos as u64)); + pos.transpose_array() + }; + + let Q = self.domain.Q(); + + let mut node = leaf; + for (l, ((sibling, pos), chip)) in path.iter().zip(pos.iter()).zip(chips).enumerate() { + // `l` = MERKLE_DEPTH - layer - 1, which is the index obtained from + // enumerating this Merkle path (going from leaf to root). + // For example, when `layer = 31` (the first sibling on the Merkle path), + // we have `l` = 32 - 31 - 1 = 0. + // On the other hand, when `layer = 0` (the final sibling on the Merkle path), + // we have `l` = 32 - 0 - 1 = 31. + + // Constrain which of (node, sibling) is (left, right) with a conditional swap + // tied to the current bit of the position. + let pair = { + let pair = (node, *sibling); + + // Swap node and sibling if needed + chip.swap(layouter.namespace(|| "node position"), pair, *pos)? + }; + + // Compute the node in layer l from its children: + // M^l_i = MerkleCRH(l, M^{l+1}_{2i}, M^{l+1}_{2i+1}) + node = chip.hash_layer( + layouter.namespace(|| format!("MerkleCRH({}, left, right)", l)), + Q, + l, + pair.0, + pair.1, + )?; + } + + Ok(node) + } +} + +#[cfg(test)] +pub mod tests { + use super::{ + chip::{MerkleChipOptimized, MerkleConfigOptimized}, + MerklePath, + }; + + use crate::{ + ecc::tests::TestFixedBases, + sinsemilla::{ + tests::{TestCommitDomain, TestHashDomain}, + HashDomains, + }, + utilities::{i2lebsp, lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, + }; + use crate::{ + sinsemilla_opt::{ + chip::SinsemillaChipOptimized, + }, + }; + + use group::ff::{Field, PrimeField, PrimeFieldBits}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + pasta::pallas, + plonk::{Circuit, ConstraintSystem, Error}, + }; + + use rand::{rngs::OsRng, RngCore}; + use std::{convert::TryInto, iter}; + use crate::sinsemilla::chip::SinsemillaChipProps; + use crate::utilities::lookup_range_check::LookupRangeCheck; + use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; + + const MERKLE_DEPTH: usize = 32; + + #[derive(Default)] + struct MyCircuit { + leaf: Value, + leaf_pos: Value, + merkle_path: Value<[pallas::Base; MERKLE_DEPTH]>, + } + + impl Circuit for MyCircuit { + type Config = ( + MerkleConfigOptimized, + MerkleConfigOptimized, + ); + 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(), + ]; + + // Shared fixed column for loading constants + let constants = meta.fixed_column(); + meta.enable_constant(constants); + + // NB: In the actual Action circuit, these fixed columns will be reused + // by other chips. For this test, we are creating new fixed columns. + let fixed_y_q_1 = meta.fixed_column(); + let fixed_y_q_2 = meta.fixed_column(); + + // Fixed columns for the Sinsemilla generator lookup table + let lookup = ( + meta.lookup_table_column(), + meta.lookup_table_column(), + meta.lookup_table_column(), + meta.lookup_table_column(), + ); + + let range_check = + LookupRangeCheckConfigOptimized::configure(meta, advices[9], lookup.0); + + let sinsemilla_config_1 = SinsemillaChipOptimized::configure( + meta, + advices[5..].try_into().unwrap(), + advices[7], + fixed_y_q_1, + lookup, + range_check, + ); + let config1 = MerkleChipOptimized::configure(meta, sinsemilla_config_1); + + let sinsemilla_config_2 = SinsemillaChipOptimized::configure( + meta, + advices[..5].try_into().unwrap(), + advices[2], + fixed_y_q_2, + lookup, + range_check, + ); + let config2 = MerkleChipOptimized::configure(meta, sinsemilla_config_2); + + (config1, config2) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // Load generator table (shared across both configs) + SinsemillaChipOptimized::::load( + config.0.sinsemilla_config.clone(), + &mut layouter, + )?; + + // Construct Merkle chips which will be placed side-by-side in the circuit. + let chip_1 = MerkleChipOptimized::construct(config.0.clone()); + let chip_2 = MerkleChipOptimized::construct(config.1.clone()); + + let leaf = chip_1.load_private( + layouter.namespace(|| ""), + config.0.cond_swap_config.a(), + self.leaf, + )?; + + let path = MerklePath { + chips: [chip_1, chip_2], + domain: TestHashDomain, + leaf_pos: self.leaf_pos, + path: self.merkle_path, + }; + + let computed_final_root = + path.calculate_root(layouter.namespace(|| "calculate root"), leaf)?; + + self.leaf + .zip(self.leaf_pos) + .zip(self.merkle_path) + .zip(computed_final_root.value()) + .assert_if_known(|(((leaf, leaf_pos), merkle_path), computed_final_root)| { + // The expected final root + let final_root = + merkle_path + .iter() + .enumerate() + .fold(*leaf, |node, (l, sibling)| { + let l = l as u8; + let (left, right) = if leaf_pos & (1 << l) == 0 { + (&node, sibling) + } else { + (sibling, &node) + }; + + use crate::sinsemilla::primitives as sinsemilla; + let merkle_crh = + sinsemilla::HashDomain::from_Q(TestHashDomain.Q().into()); + + merkle_crh + .hash( + iter::empty() + .chain(i2lebsp::<10>(l as u64).iter().copied()) + .chain( + left.to_le_bits() + .iter() + .by_vals() + .take(pallas::Base::NUM_BITS as usize), + ) + .chain( + right + .to_le_bits() + .iter() + .by_vals() + .take(pallas::Base::NUM_BITS as usize), + ), + ) + .unwrap_or(pallas::Base::zero()) + }); + + // Check the computed final root against the expected final root. + computed_final_root == &&final_root + }); + + Ok(()) + } + } + + #[test] + fn merkle_chip() { + let mut rng = OsRng; + + // Choose a random leaf and position + let leaf = pallas::Base::random(rng); + let pos = rng.next_u32(); + + // Choose a path of random inner nodes + let path: Vec<_> = (0..(MERKLE_DEPTH)) + .map(|_| pallas::Base::random(rng)) + .collect(); + + // The root is provided as a public input in the Orchard circuit. + + let circuit = MyCircuit { + leaf: Value::known(leaf), + leaf_pos: Value::known(pos), + merkle_path: Value::known(path.try_into().unwrap()), + }; + + let prover = MockProver::run(11, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } + + #[cfg(feature = "test-dev-graph")] + #[test] + fn print_merkle_chip() { + use plotters::prelude::*; + + let root = BitMapBackend::new("merkle-path-layout.png", (1024, 7680)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root.titled("MerkleCRH Path", ("sans-serif", 60)).unwrap(); + + let circuit = MyCircuit::default(); + halo2_proofs::dev::CircuitLayout::default() + .show_labels(false) + .render(11, &circuit, &root) + .unwrap(); + } +} \ No newline at end of file diff --git a/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs b/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs new file mode 100644 index 0000000000..ef88473cb9 --- /dev/null +++ b/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs @@ -0,0 +1,557 @@ +//! Chip implementing a Merkle hash using Sinsemilla as the hash function. + +use halo2_proofs::{ + circuit::{AssignedCell, Chip, Layouter, Value}, + plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, + poly::Rotation, +}; +use pasta_curves::pallas; + +use super::MerkleInstructions; + +use crate::{ + sinsemilla::{primitives as sinsemilla, MessagePiece}, + utilities::RangeConstrained, + { + ecc::FixedPoints, + sinsemilla::{ + CommitDomains, HashDomains, SinsemillaInstructions, + }, + utilities::{ + cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions}, + UtilitiesInstructions, + }, + }, +}; +use group::ff::PrimeField; +use crate::sinsemilla::chip::{SinsemillaChipProps, SinsemillaConfigProps}; +use crate::sinsemilla_opt::chip::{SinsemillaChipOptimized, SinsemillaConfigOptimized}; +use crate::utilities::lookup_range_check::witness_short_generic; + +/// Configuration for the `MerkleChipOptimized` implementation. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MerkleConfigOptimized + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + advices: [Column; 5], + q_decompose: Selector, + pub(super) cond_swap_config: CondSwapConfig, + pub(super) sinsemilla_config: SinsemillaConfigOptimized, +} + +/// Chip implementing `MerkleInstructions`. +/// +/// This chip specifically implements `MerkleInstructions::hash_layer` as the `MerkleCRH` +/// function `hash = SinsemillaHash(Q, 𝑙⋆ || left⋆ || right⋆)`, where: +/// - `𝑙⋆ = I2LEBSP_10(l)` +/// - `left⋆ = I2LEBSP_255(left)` +/// - `right⋆ = I2LEBSP_255(right)` +/// +/// This chip does **NOT** constrain `left⋆` and `right⋆` to be canonical encodings of +/// `left` and `right`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MerkleChipOptimized + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + config: MerkleConfigOptimized, +} + +impl Chip for MerkleChipOptimized + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, +{ + type Config = MerkleConfigOptimized; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +impl MerkleChipOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + /// Configures the [`MerkleChipOptimized`]. + pub fn configure( + meta: &mut ConstraintSystem, + sinsemilla_config: SinsemillaConfigOptimized, + ) -> MerkleConfigOptimized { + // All five advice columns are equality-enabled by SinsemillaConfigOptimized. + let advices = sinsemilla_config.advices(); + let cond_swap_config = CondSwapChip::configure(meta, advices); + + // This selector enables the decomposition gate. + let q_decompose = meta.selector(); + + // Check that pieces have been decomposed correctly for Sinsemilla hash. + // + // + // a = a_0||a_1 = l || (bits 0..=239 of left) + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // c = bits 5..=254 of right + // + // The message pieces `a`, `b`, `c` are constrained by Sinsemilla to be + // 250 bits, 20 bits, and 250 bits respectively. + // + // The pieces and subpieces are arranged in the following configuration: + // | A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose | + // ------------------------------------------------------- + // | a | b | c | left | right | 1 | + // | z1_a | z1_b | b_1 | b_2 | l | 0 | + meta.create_gate("Decomposition check", |meta| { + let q_decompose = meta.query_selector(q_decompose); + let l_whole = meta.query_advice(advices[4], Rotation::next()); + + let two_pow_5 = pallas::Base::from(1 << 5); + let two_pow_10 = two_pow_5.square(); + + // a_whole is constrained by Sinsemilla to be 250 bits. + let a_whole = meta.query_advice(advices[0], Rotation::cur()); + // b_whole is constrained by Sinsemilla to be 20 bits. + let b_whole = meta.query_advice(advices[1], Rotation::cur()); + // c_whole is constrained by Sinsemilla to be 250 bits. + let c_whole = meta.query_advice(advices[2], Rotation::cur()); + let left_node = meta.query_advice(advices[3], Rotation::cur()); + let right_node = meta.query_advice(advices[4], Rotation::cur()); + + // a = a_0||a_1 = l || (bits 0..=239 of left) + // + // z_1 of SinsemillaHash(a) = a_1 + // => a_0 = a - (a_1 * 2^10) + let z1_a = meta.query_advice(advices[0], Rotation::next()); + let a_1 = z1_a; + // Derive a_0 (constrained by SinsemillaHash to be 10 bits) + let a_0 = a_whole - a_1.clone() * two_pow_10; + + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // The Orchard specification allows this representation to be non-canonical. + // + // + // z_1 of SinsemillaHash(b) = b_1 + 2^5 b_2 + // => b_0 = b - (z1_b * 2^10) + let z1_b = meta.query_advice(advices[1], Rotation::next()); + // b_1 has been constrained to be 5 bits outside this gate. + let b_1 = meta.query_advice(advices[2], Rotation::next()); + // b_2 has been constrained to be 5 bits outside this gate. + let b_2 = meta.query_advice(advices[3], Rotation::next()); + // Constrain b_1 + 2^5 b_2 = z1_b + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-bit-lengths?partial + let b1_b2_check = z1_b.clone() - (b_1.clone() + b_2.clone() * two_pow_5); + // Derive b_0 (constrained by SinsemillaHash to be 10 bits) + let b_0 = b_whole - (z1_b * two_pow_10); + + // Check that left = a_1 (240 bits) || b_0 (10 bits) || b_1 (5 bits) + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-decomposition?partial + let left_check = { + let reconstructed = { + let two_pow_240 = pallas::Base::from_u128(1 << 120).square(); + a_1 + (b_0 + b_1 * two_pow_10) * two_pow_240 + }; + reconstructed - left_node + }; + + // Check that right = b_2 (5 bits) || c (250 bits) + // The Orchard specification allows this representation to be non-canonical. + // + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-decomposition?partial + let right_check = b_2 + c_whole * two_pow_5 - right_node; + + Constraints::with_selector( + q_decompose, + [ + ("l_check", a_0 - l_whole), + ("left_check", left_check), + ("right_check", right_check), + ("b1_b2_check", b1_b2_check), + ], + ) + }); + + MerkleConfigOptimized { + advices, + q_decompose, + cond_swap_config, + sinsemilla_config, + } + } + + /// Constructs a [`MerkleChipOptimized`] given a [`MerkleConfigOptimized`]. + pub fn construct(config: MerkleConfigOptimized) -> Self { + MerkleChipOptimized { config } + } +} + +impl +MerkleInstructions +for MerkleChipOptimized + where + Hash: HashDomains + Eq, + F: FixedPoints, + Commit: CommitDomains + Eq, +{ + #[allow(non_snake_case)] + fn hash_layer( + &self, + mut layouter: impl Layouter, + Q: pallas::Affine, + // l = MERKLE_DEPTH - layer - 1 + l: usize, + left: Self::Var, + right: Self::Var, + ) -> Result { + let config = self.config().clone(); + + // We need to hash `l || left || right`, where `l` is a 10-bit value. + // We allow `left` and `right` to be non-canonical 255-bit encodings. + // + // a = a_0||a_1 = l || (bits 0..=239 of left) + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // c = bits 5..=254 of right + // + // We start by witnessing all of the individual pieces, and range-constraining the + // short pieces b_1 and b_2. + // + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-bit-lengths?partial + + // `a = a_0||a_1` = `l` || (bits 0..=239 of `left`) + let a = MessagePiece::from_subpieces( + self.clone(), + layouter.namespace(|| "Witness a = a_0 || a_1"), + [ + RangeConstrained::bitrange_of(Value::known(&pallas::Base::from(l as u64)), 0..10), + RangeConstrained::bitrange_of(left.value(), 0..240), + ], + )?; + + // b = b_0 || b_1 || b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + let (b_1, b_2, b) = { + // b_0 = (bits 240..=249 of `left`) + let b_0 = RangeConstrained::bitrange_of(left.value(), 240..250); + + // b_1 = (bits 250..=254 of `left`) + // Constrain b_1 to 5 bits. + let b_1 = witness_short_generic( + &config.sinsemilla_config.lookup_config(), + layouter.namespace(|| "b_1"), + left.value(), + 250..(pallas::Base::NUM_BITS as usize), + )?; + + // b_2 = (bits 0..=4 of `right`) + // Constrain b_2 to 5 bits. + let b_2 = witness_short_generic( + &config.sinsemilla_config.lookup_config(), + layouter.namespace(|| "b_2"), + right.value(), + 0..5, + )?; + + let b = MessagePiece::from_subpieces( + self.clone(), + layouter.namespace(|| "Witness b = b_0 || b_1 || b_2"), + [b_0, b_1.value(), b_2.value()], + )?; + + (b_1, b_2, b) + }; + + // c = bits 5..=254 of `right` + let c = MessagePiece::from_subpieces( + self.clone(), + layouter.namespace(|| "Witness c"), + [RangeConstrained::bitrange_of( + right.value(), + 5..(pallas::Base::NUM_BITS as usize), + )], + )?; + + // hash = SinsemillaHash(Q, 𝑙⋆ || left⋆ || right⋆) + // + // `hash = ⊥` is handled internally to `SinsemillaChipOptimized::hash_to_point`: incomplete + // addition constraints allows ⊥ to occur, and then during synthesis it detects + // these edge cases and raises an error (aborting proof creation). + // + // Note that MerkleCRH as-defined maps ⊥ to 0. This is for completeness outside + // the circuit (so that the ⊥ does not propagate into the type system). The chip + // explicitly doesn't map ⊥ to 0; in fact it cannot, as doing so would require + // constraints that amount to using complete addition. The rationale for excluding + // this map is the same as why Sinsemilla uses incomplete addition: this situation + // yields a nontrivial discrete log relation, and by assumption it is hard to find + // these. + // + // https://p.z.cash/proto:merkle-crh-orchard + let (point, zs) = self.hash_to_point( + layouter.namespace(|| format!("hash at l = {}", l)), + Q, + vec![a.inner(), b.inner(), c.inner()].into(), + )?; + let hash = Self::extract(&point); + + // `SinsemillaChipOptimized::hash_to_point` returns the running sum for each `MessagePiece`. + // Grab the outputs we need for the decomposition constraints. + let z1_a = zs[0][1].clone(); + let z1_b = zs[1][1].clone(); + + // Check that the pieces have been decomposed properly. + // + // The pieces and subpieces are arranged in the following configuration: + // | A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose | + // ------------------------------------------------------- + // | a | b | c | left | right | 1 | + // | z1_a | z1_b | b_1 | b_2 | l | 0 | + { + layouter.assign_region( + || "Check piece decomposition", + |mut region| { + // Set the fixed column `l` to the current l. + // Recall that l = MERKLE_DEPTH - layer - 1. + // The layer with 2^n nodes is called "layer n". + config.q_decompose.enable(&mut region, 0)?; + region.assign_advice_from_constant( + || format!("l {}", l), + config.advices[4], + 1, + pallas::Base::from(l as u64), + )?; + + // Offset 0 + // Copy and assign `a` at the correct position. + a.inner().cell_value().copy_advice( + || "copy a", + &mut region, + config.advices[0], + 0, + )?; + // Copy and assign `b` at the correct position. + b.inner().cell_value().copy_advice( + || "copy b", + &mut region, + config.advices[1], + 0, + )?; + // Copy and assign `c` at the correct position. + c.inner().cell_value().copy_advice( + || "copy c", + &mut region, + config.advices[2], + 0, + )?; + // Copy and assign the left node at the correct position. + left.copy_advice(|| "left", &mut region, config.advices[3], 0)?; + // Copy and assign the right node at the correct position. + right.copy_advice(|| "right", &mut region, config.advices[4], 0)?; + + // Offset 1 + // Copy and assign z_1 of SinsemillaHash(a) = a_1 + z1_a.copy_advice(|| "z1_a", &mut region, config.advices[0], 1)?; + // Copy and assign z_1 of SinsemillaHash(b) = b_1 + z1_b.copy_advice(|| "z1_b", &mut region, config.advices[1], 1)?; + // Copy `b_1`, which has been constrained to be a 5-bit value + b_1.inner() + .copy_advice(|| "b_1", &mut region, config.advices[2], 1)?; + // Copy `b_2`, which has been constrained to be a 5-bit value + b_2.inner() + .copy_advice(|| "b_2", &mut region, config.advices[3], 1)?; + + Ok(()) + }, + )?; + } + + // Check layer hash output against Sinsemilla primitives hash + #[cfg(test)] + { + use crate::{sinsemilla::primitives::HashDomain, utilities::i2lebsp}; + + use group::ff::PrimeFieldBits; + + left.value() + .zip(right.value()) + .zip(hash.value()) + .assert_if_known(|((left, right), hash)| { + let l = i2lebsp::<10>(l as u64); + let left: Vec<_> = left + .to_le_bits() + .iter() + .by_vals() + .take(pallas::Base::NUM_BITS as usize) + .collect(); + let right: Vec<_> = right + .to_le_bits() + .iter() + .by_vals() + .take(pallas::Base::NUM_BITS as usize) + .collect(); + let merkle_crh = HashDomain::from_Q(Q.into()); + + let mut message = l.to_vec(); + message.extend_from_slice(&left); + message.extend_from_slice(&right); + + let expected = merkle_crh.hash(message.into_iter()).unwrap(); + + expected.to_repr() == hash.to_repr() + }); + } + + Ok(hash) + } +} + +impl UtilitiesInstructions for MerkleChipOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + type Var = AssignedCell; +} + +impl CondSwapInstructions for MerkleChipOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + #[allow(clippy::type_complexity)] + fn swap( + &self, + layouter: impl Layouter, + pair: (Self::Var, Value), + swap: Value, + ) -> Result<(Self::Var, Self::Var), Error> { + let config = self.config().cond_swap_config.clone(); + 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 MerkleChipOptimized + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + type CellValue = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::CellValue; + + type Message = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::Message; + type MessagePiece = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::MessagePiece; + type RunningSum = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::RunningSum; + + type X = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::X; + type NonIdentityPoint = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::NonIdentityPoint; + type FixedPoints = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::FixedPoints; + + type HashDomains = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::HashDomains; + type CommitDomains = as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::CommitDomains; + + fn witness_message_piece( + &self, + layouter: impl Layouter, + value: Value, + num_words: usize, + ) -> Result { + let config = self.config().sinsemilla_config.clone(); + let chip = SinsemillaChipOptimized::::construct(config); + chip.witness_message_piece(layouter, value, num_words) + } + + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + fn hash_to_point( + &self, + layouter: impl Layouter, + Q: pallas::Affine, + message: Self::Message, + ) -> Result<(Self::NonIdentityPoint, Vec>), Error> { + let config = self.config().sinsemilla_config.clone(); + let chip = SinsemillaChipOptimized::::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 = SinsemillaChipOptimized::::construct(config); + chip.hash_to_point_with_private_init(layouter, Q, message) + } + + fn extract(point: &Self::NonIdentityPoint) -> Self::X { + SinsemillaChipOptimized::::extract(point) + } +} + diff --git a/halo2_gadgets/src/utilities/cond_swap.rs b/halo2_gadgets/src/utilities/cond_swap.rs index d733e6c4fb..2e292f3195 100644 --- a/halo2_gadgets/src/utilities/cond_swap.rs +++ b/halo2_gadgets/src/utilities/cond_swap.rs @@ -3,12 +3,10 @@ use super::{bool_check, ternary, UtilitiesInstructions}; use group::ff::{Field, PrimeField}; -use halo2_proofs::{ - circuit::{AssignedCell, Chip, Layouter, Value}, - plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, - poly::Rotation, -}; +use halo2_proofs::{circuit::{AssignedCell, Chip, Layouter, Value}, plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, plonk, poly::Rotation}; use std::marker::PhantomData; +use pasta_curves::pallas; +use crate::ecc::chip::{EccPoint, NonIdentityEccPoint}; /// Instructions for a conditional swap gadget. pub trait CondSwapInstructions: UtilitiesInstructions { @@ -24,6 +22,15 @@ pub trait CondSwapInstructions: UtilitiesInstructions { pair: (Self::Var, Value), swap: Value, ) -> Result<(Self::Var, Self::Var), Error>; + + fn mux( + &self, + layouter: &mut impl Layouter, + choice: Self::Var, + left: Self::Var, + right: Self::Var, + ) -> Result; + } /// A chip implementing a conditional swap. @@ -121,6 +128,96 @@ 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 { diff --git a/halo2_gadgets/src/utilities/lookup_range_check.rs b/halo2_gadgets/src/utilities/lookup_range_check.rs index b26a89a884..047c52afe3 100644 --- a/halo2_gadgets/src/utilities/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities/lookup_range_check.rs @@ -9,6 +9,8 @@ use halo2_proofs::{ use std::{convert::TryInto, marker::PhantomData}; use ff::PrimeFieldBits; +use crate::sinsemilla::chip::{SinsemillaConfig, SinsemillaConfigProps}; +use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; use super::*; @@ -23,161 +25,73 @@ impl std::ops::Deref for RunningSum { } } -impl RangeConstrained> { - /// Witnesses a subset of the bits in `value` and constrains them to be the correct - /// number of bits. - /// - /// # Panics - /// - /// Panics if `bitrange.len() >= K`. - pub fn witness_short( - lookup_config: &LookupRangeCheckConfig, - layouter: impl Layouter, - value: Value<&F>, - bitrange: Range, - ) -> Result { - let num_bits = bitrange.len(); - assert!(num_bits < K); - - // Witness the subset and constrain it to be the correct number of bits. - lookup_config - .witness_short_check( - layouter, - value.map(|value| bitrange_subset(value, bitrange)), - num_bits, - ) - .map(|inner| Self { - inner, - num_bits, - _phantom: PhantomData::default(), - }) - } +// Create a Generic Function. The function will use the LookupRangeCheck trait, +// allowing it to work with any configuration type that implements LookupRangeCheck: +pub fn witness_short_generic( + lookup_config: &C, + layouter: impl Layouter, + value: Value<&F>, + bitrange: Range, +) -> Result>, Error> + where + F: PrimeFieldBits, + C: LookupRangeCheck, +{ + let num_bits = bitrange.len(); + assert!(num_bits < K); + + lookup_config.witness_short_check( + layouter, + value.map(|value| bitrange_subset(value, bitrange)), + num_bits, + ).map(|inner| RangeConstrained { + inner, + num_bits, + _phantom: PhantomData::default(), + }) } /// 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, - running_sum: Column, - table_idx: 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 { - /// The `running_sum` advice column breaks the field element into `K`-bit - /// words. It is used to construct the input expression to the lookup - /// argument. - /// - /// The `table_idx` fixed column contains values from [0..2^K). Looking up - /// a value in `table_idx` constrains it to be within this range. The table - /// can be loaded outside this helper. - /// - /// # Side-effects - /// - /// Both the `running_sum` and `constants` columns will be equality-enabled. - pub fn configure( +/// FIXME: add doc +pub trait LookupRangeCheck { + /// FIXME: add doc + fn base(&self) -> &LookupRangeCheckConfig; + + /// FIXME: add doc + fn configure( meta: &mut ConstraintSystem, running_sum: Column, table_idx: 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 config = LookupRangeCheckConfig { - q_lookup, - q_running, - q_bitshift, - running_sum, - table_idx, - _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 z_cur = meta.query_advice(config.running_sum, Rotation::cur()); - - // 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; - let q_short = Expression::Constant(F::ONE) - q_running; - - q_short * short_word - }; - - // Combine the running sum and short lookups: - vec![( - q_lookup * (running_sum_lookup + short_lookup), - config.table_idx, - )] - }); - - // 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)] - // Loads the values [0..2^K) into `table_idx`. 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)), - )?; - } - Ok(()) - }, - ) - } + fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error>; + + /// FIXME: add doc + 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, @@ -188,14 +102,14 @@ 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.base().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, @@ -205,8 +119,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.base().running_sum, + 0, + || value, + )?; self.range_check(&mut region, z_0, num_words, strict) }, ) @@ -246,7 +164,7 @@ impl LookupRangeCheckConfig { .map(|word| F::from(lebs2ip::(&(word.try_into().unwrap())))) .collect::>() }) - .transpose_vec(num_words) + .transpose_vec(num_words) }; let mut zs = vec![element.clone()]; @@ -261,9 +179,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.base().q_lookup.enable(region, idx)?; // Enable q_running on this row - self.q_running.enable(region, idx)?; + self.base().q_running.enable(region, idx)?; // z_next = (z_cur - m_cur) / 2^K z = { @@ -275,7 +193,7 @@ impl LookupRangeCheckConfig { // Assign z_next region.assign_advice( || format!("z_{:?}", idx + 1), - self.running_sum, + self.base().running_sum, idx + 1, || z_val, )? @@ -296,7 +214,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, @@ -308,7 +226,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.base().running_sum, 0)?; self.short_range_check(&mut region, element, num_bits) }, @@ -320,7 +238,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, @@ -331,8 +249,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.base().running_sum, + 0, + || element, + )?; self.short_range_check(&mut region, element.clone(), num_bits)?; @@ -341,6 +263,116 @@ impl LookupRangeCheckConfig { ) } +} + +impl LookupRangeCheck +for LookupRangeCheckConfig +{ + fn base(&self) -> &LookupRangeCheckConfig { + self + } + + fn configure( + meta: &mut ConstraintSystem, + running_sum: Column, + table_idx: TableColumn, + ) -> Self { + meta.enable_equality(running_sum); + + let q_lookup = meta.complex_selector(); + let q_running = meta.complex_selector(); + let q_bitshift = meta.selector(); + + // FIXME: q_range_check_4 and q_range_check_5 need to be created here + // if the order of the creation makes a difference + let config = LookupRangeCheckConfig { + q_lookup, + q_running, + q_bitshift, + running_sum, + table_idx, + _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); + // FIXME: q_range_check_4 and q_range_check_5 need to be created here + // if the order of the creation makes a difference + let z_cur = meta.query_advice(config.running_sum, Rotation::cur()); + let one = Expression::Constant(F::ONE); + + // 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 + }; + + vec![( + q_lookup * (running_sum_lookup + short_lookup), + config.table_idx, + )] + }); + + // 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 + } + + #[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)), + )?; + } + Ok(()) + }, + ) + } + /// Constrain `x` to be a NUM_BITS word. /// /// `element` must have been assigned to `self.running_sum` at offset 0. @@ -350,7 +382,7 @@ impl LookupRangeCheckConfig { element: AssignedCell, num_bits: usize, ) -> Result<(), Error> { - // Enable lookup for `element`, to constrain it to 10 bits. + // Enable lookup for `element`. self.q_lookup.enable(region, 0)?; // Enable lookup for shifted element, to constrain it to 10 bits. @@ -381,10 +413,9 @@ impl LookupRangeCheckConfig { Ok(()) } } - #[cfg(test)] mod tests { - use super::LookupRangeCheckConfig; + use super::{LookupRangeCheckConfig, LookupRangeCheck}; use super::super::lebs2ip; use crate::sinsemilla::primitives::K; diff --git a/halo2_gadgets/src/utilities_opt.rs b/halo2_gadgets/src/utilities_opt.rs new file mode 100644 index 0000000000..ddd75ce739 --- /dev/null +++ b/halo2_gadgets/src/utilities_opt.rs @@ -0,0 +1,4 @@ +//! Utility gadgets. + +pub mod cond_swap; +pub mod lookup_range_check; diff --git a/halo2_gadgets/src/utilities_opt/cond_swap.rs b/halo2_gadgets/src/utilities_opt/cond_swap.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/halo2_gadgets/src/utilities_opt/lookup_range_check.rs b/halo2_gadgets/src/utilities_opt/lookup_range_check.rs new file mode 100644 index 0000000000..b7e1583557 --- /dev/null +++ b/halo2_gadgets/src/utilities_opt/lookup_range_check.rs @@ -0,0 +1,563 @@ +//! Make use of a K-bit lookup table to decompose a field element into K-bit +//! words. + +use std::marker::PhantomData; +use std::ops::Range; + +use halo2_proofs::{ + circuit::{AssignedCell, Region}, + plonk::{ + Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector, TableColumn, + }, + poly::Rotation, +}; + +#[cfg(test)] +use halo2_proofs::circuit::{Layouter, Value}; + +use ff::PrimeFieldBits; + +use crate::utilities::lookup_range_check::{LookupRangeCheckConfig, LookupRangeCheck}; +use crate::utilities::{bitrange_subset, RangeConstrained}; + + + +/// Configuration that provides methods for a lookup range check. +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub struct LookupRangeCheckConfigOptimized { + base: LookupRangeCheckConfig, + + q_range_check_4: Selector, + q_range_check_5: Selector, + // FIXME: Instead of making it pub, add a method in LookupRangeCheckConfig that returns table_range_check_tag + //pub(crate) + table_range_check_tag: TableColumn, +} + +impl LookupRangeCheckConfigOptimized { + /// The `running_sum` advice column breaks the field element into `K`-bit + /// words. It is used to construct the input expression to the lookup + /// argument. + /// + /// The `table_idx` fixed column contains values from [0..2^K). Looking up + /// a value in `table_idx` constrains it to be within this range. The table + /// can be loaded outside this helper. + /// + /// # Side-effects + /// + /// Both the `running_sum` and `constants` columns will be equality-enabled. + fn configure_with_tag( + 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(); + + // FIXME: q_range_check_4 and q_range_check_5 need to be created here + // if the order of the creation makes a difference + let config = LookupRangeCheckConfigOptimized { + base: LookupRangeCheckConfig { + q_lookup, + q_running, + q_bitshift, + running_sum, + table_idx, + _marker: PhantomData, + }, + q_range_check_4, + q_range_check_5, + table_range_check_tag, + }; + + // https://p.z.cash/halo2-0.1:decompose-combined-lookup + meta.lookup(|meta| { + let q_lookup = meta.query_selector(config.base.q_lookup); + let q_running = meta.query_selector(config.base.q_running); + // FIXME: q_range_check_4 and q_range_check_5 need to be created here + // if the order of the creation makes a difference + let z_cur = meta.query_advice(config.base.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.base.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 + }; + + 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); + + // 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.base.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.base.q_bitshift); + let word = meta.query_advice(config.base.running_sum, Rotation::prev()); + let shifted_word = meta.query_advice(config.base.running_sum, Rotation::cur()); + let inv_two_pow_s = meta.query_advice(config.base.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 + } + + pub(crate) fn table_range_check_tag(&self) -> TableColumn { + self.table_range_check_tag + } +} + +impl LookupRangeCheck +for LookupRangeCheckConfigOptimized +{ + fn base(&self) -> &LookupRangeCheckConfig { + &self.base + } + + fn configure( + meta: &mut ConstraintSystem, + running_sum: Column, + table_idx: TableColumn, + ) -> Self { + let table_range_check_tag = meta.lookup_table_column(); + Self::configure_with_tag(meta, running_sum, table_idx, table_range_check_tag) + } + + #[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| { + let mut assign_cells = + |offset: usize, table_size, value: u64| -> Result { + for index in 0..table_size { + let new_index = index + offset; + table.assign_cell( + || "table_idx", + self.base.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(value)), + )?; + } + Ok(offset + table_size) + }; + + // We generate the row values lazily (we only need them during keygen). + let mut offset = 0; + + //annotation: &'v (dyn Fn() -> String + 'v), + //column: TableColumn, + //offset: usize, + //to: &'v mut (dyn FnMut() -> Value> + 'v), + + offset = assign_cells(offset, 1 << K, 0)?; + offset = assign_cells(offset, 1 << 4, 4)?; + assign_cells(offset, 1 << 5, 5)?; + + 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> { + match num_bits { + 4 => { + self.base.q_lookup.enable(region, 0)?; + self.q_range_check_4.enable(region, 0)?; + Ok(()) + } + + 5 => { + self.base.q_lookup.enable(region, 0)?; + self.q_range_check_5.enable(region, 0)?; + Ok(()) + } + + _ => self.base.short_range_check(region, element, num_bits), + } + } +} + +#[cfg(test)] +mod tests { + use super::{LookupRangeCheck, LookupRangeCheckConfigOptimized}; + + use crate::sinsemilla::primitives::K; + use crate::utilities::lebs2ip; + + 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, + } + + impl Circuit for MyCircuit { + type Config = LookupRangeCheckConfigOptimized; + 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); + + LookupRangeCheckConfigOptimized::::configure_with_tag( + 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 + } + + for (element, expected_final_z, strict) in elements_and_expected_final_zs.iter() { + let expected_zs = expected_zs::(*element, self.num_words); + + let zs = config.witness_check( + layouter.namespace(|| format!("Lookup {:?}", self.num_words)), + Value::known(*element), + self.num_words, + *strict, + )?; + + assert_eq!(*expected_zs.last().unwrap(), *expected_final_z); + + for (expected_z, z) in expected_zs.into_iter().zip(zs.iter()) { + z.value().assert_if_known(|z| &&expected_z == z); + } + } + Ok(()) + } + } + + { + let circuit: MyCircuit = MyCircuit { + num_words: 6, + _marker: PhantomData, + }; + + let prover = MockProver::::run(11, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())); + } + } + + #[test] + fn short_range_check() { + struct MyCircuit { + element: Value, + num_bits: usize, + } + + impl Circuit for MyCircuit { + type Config = LookupRangeCheckConfigOptimized; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + MyCircuit { + element: Value::unknown(), + num_bits: self.num_bits, + } + } + + 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); + + LookupRangeCheckConfigOptimized::::configure_with_tag( + 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(())); + } + + // 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(())); + } + + // 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, + }, + }]) + ); + } + + // 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, + }, + }, + ]) + ); + } + + // 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, + }, + }]) + ); + } + + // 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(())); + } + + // 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, + }, + }]) + ); + } + } +} \ No newline at end of file From fc375084e443f7601d7915b9ca2d3e3ad6c2671e Mon Sep 17 00:00:00 2001 From: YaoGalteland Date: Mon, 15 Apr 2024 13:49:39 +0200 Subject: [PATCH 2/7] initial doc --- halo2_gadgets/src/ecc.rs | 6 +- halo2_gadgets/src/ecc/chip.rs | 24 ++-- halo2_gadgets/src/ecc/chip/mul/overflow.rs | 2 +- .../src/ecc/chip/mul_fixed/base_field_elem.rs | 2 +- halo2_gadgets/src/ecc/chip/mul_fixed/short.rs | 4 +- halo2_gadgets/src/ecc/chip/witness_point.rs | 4 +- halo2_gadgets/src/lib.rs | 2 +- halo2_gadgets/src/sinsemilla.rs | 34 +++--- halo2_gadgets/src/sinsemilla/chip.rs | 107 ++++++++---------- .../src/sinsemilla/chip/generator_table.rs | 5 +- .../src/sinsemilla/chip/hash_to_point.rs | 50 ++++---- halo2_gadgets/src/sinsemilla/merkle.rs | 4 +- halo2_gadgets/src/sinsemilla/merkle/chip.rs | 77 +++++++------ halo2_gadgets/src/sinsemilla/primitives.rs | 6 +- halo2_gadgets/src/sinsemilla_opt/chip.rs | 84 +++++++------- .../sinsemilla_opt/chip/generator_table.rs | 7 +- .../src/sinsemilla_opt/chip/hash_to_point.rs | 52 +++++---- halo2_gadgets/src/sinsemilla_opt/merkle.rs | 57 +++++----- .../src/sinsemilla_opt/merkle/chip.rs | 81 +++++++------ halo2_gadgets/src/utilities/cond_swap.rs | 12 +- .../src/utilities/lookup_range_check.rs | 41 ++++--- halo2_gadgets/src/utilities_opt/cond_swap.rs | 1 + .../src/utilities_opt/lookup_range_check.rs | 18 ++- halo2_proofs/src/circuit.rs | 9 ++ 24 files changed, 352 insertions(+), 337 deletions(-) diff --git a/halo2_gadgets/src/ecc.rs b/halo2_gadgets/src/ecc.rs index b5cc37406a..c6130a7574 100644 --- a/halo2_gadgets/src/ecc.rs +++ b/halo2_gadgets/src/ecc.rs @@ -14,7 +14,7 @@ pub mod chip; /// The set of circuit instructions required to use the ECC gadgets. pub trait EccInstructions: -Chip + UtilitiesInstructions + Clone + Debug + Eq + Chip + UtilitiesInstructions + Clone + Debug + Eq { /// Variable representing a scalar used in variable-base scalar mul. /// @@ -380,7 +380,7 @@ impl> NonIdentityPoint { } impl + Clone + Debug + Eq> -From> for Point + From> for Point { fn from(non_id_point: NonIdentityPoint) -> Self { Self { @@ -638,7 +638,7 @@ pub(crate) mod tests { }, FixedPoints, }; - use crate::utilities::lookup_range_check::{LookupRangeCheckConfig, LookupRangeCheck}; + use crate::utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig}; #[derive(Debug, Eq, PartialEq, Clone)] pub(crate) struct TestFixedBases; diff --git a/halo2_gadgets/src/ecc/chip.rs b/halo2_gadgets/src/ecc/chip.rs index 8036b08338..402020384b 100644 --- a/halo2_gadgets/src/ecc/chip.rs +++ b/halo2_gadgets/src/ecc/chip.rs @@ -245,7 +245,7 @@ impl> Chip for Ecc } impl> UtilitiesInstructions -for EccChip + for EccChip { type Var = AssignedCell; } @@ -360,7 +360,7 @@ pub struct EccScalarFixedShort { /// The circuit-assigned running sum constraining this signed short scalar, or `None` /// if the scalar has not been used yet. running_sum: - Option, { NUM_WINDOWS_SHORT + 1 }>>, + Option, { NUM_WINDOWS_SHORT + 1 }>>, } /// A base field element used for fixed-base scalar multiplication. @@ -408,12 +408,12 @@ pub enum ScalarVar { } impl> EccInstructions for EccChip - where - >::Base: +where + >::Base: FixedPoint, - >::FullScalar: + >::FullScalar: FixedPoint, - >::ShortScalar: + >::ShortScalar: FixedPoint, { type ScalarFixed = EccScalarFixed; @@ -625,13 +625,13 @@ impl> EccInstructions for Ecc } impl> BaseFitsInScalarInstructions -for EccChip - where - >::Base: + for EccChip +where + >::Base: FixedPoint, - >::FullScalar: + >::FullScalar: FixedPoint, - >::ShortScalar: + >::ShortScalar: FixedPoint, { fn scalar_var_from_base( @@ -641,4 +641,4 @@ for EccChip ) -> Result { Ok(ScalarVar::BaseFieldElem(base.clone())) } -} \ No newline at end of file +} diff --git a/halo2_gadgets/src/ecc/chip/mul/overflow.rs b/halo2_gadgets/src/ecc/chip/mul/overflow.rs index ba2d876e3c..89852693a7 100644 --- a/halo2_gadgets/src/ecc/chip/mul/overflow.rs +++ b/halo2_gadgets/src/ecc/chip/mul/overflow.rs @@ -12,8 +12,8 @@ use halo2_proofs::{ }; use pasta_curves::pallas; -use std::iter; use crate::utilities::lookup_range_check::LookupRangeCheck; +use std::iter; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Config { 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 758a35107e..d939b0e00e 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 @@ -15,8 +15,8 @@ use halo2_proofs::{ }; use pasta_curves::pallas; -use std::convert::TryInto; use crate::utilities::lookup_range_check::LookupRangeCheck; +use std::convert::TryInto; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Config> { diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs index 98c4805f1d..056e716e1e 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs @@ -3,13 +3,13 @@ use std::convert::TryInto; use super::super::{EccPoint, EccScalarFixedShort, FixedPoints, L_SCALAR_SHORT, NUM_WINDOWS_SHORT}; use crate::{ecc::chip::MagnitudeSign, utilities::bool_check}; +use halo2_proofs::circuit::AssignedCell; use halo2_proofs::{ circuit::{Layouter, Region}, plonk::{ConstraintSystem, Constraints, Error, Expression, Selector}, poly::Rotation, }; use pasta_curves::pallas; -use halo2_proofs::circuit::AssignedCell; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Config> { @@ -316,6 +316,7 @@ pub mod tests { }; use pasta_curves::pallas; + use crate::utilities::lookup_range_check::LookupRangeCheck; use crate::{ ecc::{ chip::{EccChip, FixedPoint, MagnitudeSign}, @@ -324,7 +325,6 @@ pub mod tests { }, utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, }; - use crate::utilities::lookup_range_check::LookupRangeCheck; #[allow(clippy::op_ref)] pub(crate) fn test_mul_fixed_short( diff --git a/halo2_gadgets/src/ecc/chip/witness_point.rs b/halo2_gadgets/src/ecc/chip/witness_point.rs index bb21bbb511..98f865a6dc 100644 --- a/halo2_gadgets/src/ecc/chip/witness_point.rs +++ b/halo2_gadgets/src/ecc/chip/witness_point.rs @@ -206,6 +206,6 @@ pub mod tests { layouter.namespace(|| "witness identity"), Value::known(pallas::Affine::identity()), ) - .expect_err("witnessing 𝒪 should return an error"); + .expect_err("witnessing 𝒪 should return an error"); } -} \ No newline at end of file +} diff --git a/halo2_gadgets/src/lib.rs b/halo2_gadgets/src/lib.rs index b4564dc84f..eadea1bb65 100644 --- a/halo2_gadgets/src/lib.rs +++ b/halo2_gadgets/src/lib.rs @@ -27,6 +27,6 @@ pub mod poseidon; #[cfg_attr(docsrs, doc(cfg(feature = "unstable-sha256-gadget")))] pub mod sha256; pub mod sinsemilla; +pub mod sinsemilla_opt; pub mod utilities; pub mod utilities_opt; -pub mod sinsemilla_opt; diff --git a/halo2_gadgets/src/sinsemilla.rs b/halo2_gadgets/src/sinsemilla.rs index f626252ad2..a7028052bf 100644 --- a/halo2_gadgets/src/sinsemilla.rs +++ b/halo2_gadgets/src/sinsemilla.rs @@ -88,7 +88,6 @@ 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 @@ -103,7 +102,6 @@ pub trait SinsemillaInstructions Result<(Self::NonIdentityPoint, Vec), Error>; - /// Extracts the x-coordinate of the output of a Sinsemilla hash. fn extract(point: &Self::NonIdentityPoint) -> Self::X; } @@ -115,17 +113,17 @@ pub trait SinsemillaInstructions - where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { chip: SinsemillaChip, inner: SinsemillaChip::Message, } impl -Message - where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + Message +where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { #![allow(dead_code)] fn from_bitstring( @@ -179,16 +177,16 @@ Message /// A message piece with a bitlength of some multiple of `K`. #[derive(Copy, Clone, Debug)] pub struct MessagePiece - where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, +where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { inner: SinsemillaChip::MessagePiece, } impl -MessagePiece - where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + MessagePiece +where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { /// Returns the inner MessagePiece contained in this gadget. pub fn inner(&self) -> SinsemillaChip::MessagePiece { @@ -197,9 +195,9 @@ MessagePiece } impl -MessagePiece - where - SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, + MessagePiece +where + SinsemillaChip: SinsemillaInstructions + Clone + Debug + Eq, { #![allow(dead_code)] fn from_bitstring( @@ -381,7 +379,7 @@ HashDomain /// Trait allowing circuit's Sinsemilla CommitDomains to be enumerated. pub trait CommitDomains, H: HashDomains>: -Clone + Debug + Clone + Debug { /// Returns the fixed point corresponding to the R constant used for /// randomization in this CommitDomain. @@ -578,9 +576,9 @@ pub(crate) mod tests { use lazy_static::lazy_static; use pasta_curves::pallas; - use std::convert::TryInto; use crate::sinsemilla::chip::SinsemillaChipProps; use crate::utilities::lookup_range_check::LookupRangeCheck; + use std::convert::TryInto; pub(crate) const PERSONALIZATION: &str = "MerkleCRH"; @@ -650,7 +648,7 @@ pub(crate) mod tests { let table_idx = meta.lookup_table_column(); #[cfg(feature = "zsa")] - let table_range_check_tag = meta.lookup_table_column(); + let table_range_check_tag = meta.lookup_table_column(); let lagrange_coeffs = [ meta.fixed_column(), meta.fixed_column(), diff --git a/halo2_gadgets/src/sinsemilla/chip.rs b/halo2_gadgets/src/sinsemilla/chip.rs index 5ef8eab48f..7ab26bec82 100644 --- a/halo2_gadgets/src/sinsemilla/chip.rs +++ b/halo2_gadgets/src/sinsemilla/chip.rs @@ -11,9 +11,9 @@ use crate::{ }, utilities::lookup_range_check::LookupRangeCheckConfig, }; -use std::marker::PhantomData; use ff::PrimeField; use pasta_curves::arithmetic::CurveAffine; +use std::marker::PhantomData; use halo2_proofs::{ circuit::{AssignedCell, Chip, Layouter, Value}, @@ -28,20 +28,20 @@ use pasta_curves::pallas::Base; use proptest::test_runner::Config; mod generator_table; +use crate::sinsemilla::primitives::{lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S}; use generator_table::GeneratorTableConfig; use halo2_proofs::circuit::Region; use halo2_proofs::plonk::Assigned; -use crate::sinsemilla::primitives::{INV_TWO_POW_K, lebs2ip_k, SINSEMILLA_S}; mod hash_to_point; /// Configuration for the Sinsemilla hash chip #[derive(Eq, PartialEq, Clone, Debug)] pub struct SinsemillaConfigCommon - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { /// Binary selector used in lookup argument and in the body of the Sinsemilla hash. pub(crate) q_sinsemilla1: Selector, @@ -66,10 +66,10 @@ pub struct SinsemillaConfigCommon /// Configuration for the Sinsemilla hash chip #[derive(Eq, PartialEq, Clone, Debug)] pub struct SinsemillaConfig - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { base: SinsemillaConfigCommon, /// The lookup table where $(\mathsf{idx}, x_p, y_p)$ are loaded for the $2^K$ @@ -80,10 +80,10 @@ pub struct SinsemillaConfig } // TODO: add doc, rename it to SinsemillaConfigProps pub trait SinsemillaConfigProps - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type LookupConfigType; fn base(&self) -> &SinsemillaConfigCommon; @@ -111,11 +111,7 @@ pub trait SinsemillaConfigProps fn get_y_q(&self, meta: &mut VirtualCells) -> Expression; - fn configure_from_y_q( - &self, - meta: &mut ConstraintSystem, - ) where - { + fn configure_from_y_q(&self, meta: &mut ConstraintSystem) { let two = pallas::Base::from(2); // Closures for expressions that are derived multiple times @@ -149,8 +145,10 @@ pub trait SinsemillaConfigProps let q_s1 = meta.query_selector(self.base().q_sinsemilla1); let q_s3 = self.q_s3(meta); - let lambda_1_next = meta.query_advice(self.base().double_and_add.lambda_1, Rotation::next()); - let lambda_2_cur = meta.query_advice(self.base().double_and_add.lambda_2, Rotation::cur()); + let lambda_1_next = + meta.query_advice(self.base().double_and_add.lambda_1, Rotation::next()); + let lambda_2_cur = + meta.query_advice(self.base().double_and_add.lambda_2, Rotation::cur()); let x_a_cur = meta.query_advice(self.base().double_and_add.x_a, Rotation::cur()); let x_a_next = meta.query_advice(self.base().double_and_add.x_a, Rotation::next()); @@ -189,15 +187,13 @@ pub trait SinsemillaConfigProps Constraints::with_selector(q_s1, [("Secant line", secant_line), ("y check", y_check)]) }); } - - } // TODO: add doc impl SinsemillaConfigProps for SinsemillaConfig - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type LookupConfigType = LookupRangeCheckConfig; fn base(&self) -> &SinsemillaConfigCommon { @@ -214,26 +210,25 @@ impl SinsemillaConfigProps for SinsemillaConfi } } - /// A chip that implements 10-bit Sinsemilla using a lookup table and 5 advice columns. /// /// [Chip description](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html#plonk--halo-2-constraints). #[derive(Eq, PartialEq, Clone, Debug)] pub struct SinsemillaChip - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { config: SinsemillaConfig, } // TODO: add doc,rename it to SinsemillaChipProps pub trait SinsemillaChipProps - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type Loaded; @@ -259,14 +254,13 @@ pub trait SinsemillaChipProps lookup: Self::LookupType, range_check: Self::RangeCheckConfigType, ) -> Self::SinsemillaConfigType; - } impl SinsemillaChipProps for SinsemillaChip - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type Loaded = >::Loaded; @@ -338,10 +332,10 @@ pub fn create_common_config( witness_pieces: Column, fixed_y_q: Column, ) -> SinsemillaConfigCommon - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { SinsemillaConfigCommon { q_sinsemilla1: meta.complex_selector(), @@ -360,13 +354,12 @@ pub fn create_common_config( } } - // TODO: remove duplicate? impl Chip for SinsemillaChip - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { type Config = SinsemillaConfig; type Loaded = (); @@ -380,17 +373,15 @@ impl Chip for SinsemillaChip SinsemillaInstructions -for SinsemillaChip - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, + for SinsemillaChip +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type CellValue = AssignedCell; @@ -459,4 +450,4 @@ for SinsemillaChip fn extract(point: &Self::NonIdentityPoint) -> Self::X { point.x() } -} \ No newline at end of file +} diff --git a/halo2_gadgets/src/sinsemilla/chip/generator_table.rs b/halo2_gadgets/src/sinsemilla/chip/generator_table.rs index 9193f85722..16194ffc8e 100644 --- a/halo2_gadgets/src/sinsemilla/chip/generator_table.rs +++ b/halo2_gadgets/src/sinsemilla/chip/generator_table.rs @@ -57,7 +57,8 @@ impl GeneratorTableConfig { // y_{p,i} = (Y_{A,i} / 2) - lambda1 * (x_{A,i} - x_{P,i}) let y_p = { - let lambda1 = meta.query_advice(config.base.double_and_add.lambda_1, Rotation::cur()); + let lambda1 = + meta.query_advice(config.base.double_and_add.lambda_1, Rotation::cur()); let x_a = meta.query_advice(config.base.double_and_add.x_a, Rotation::cur()); let Y_A = config.base.double_and_add.Y_A(meta, Rotation::cur()); @@ -95,4 +96,4 @@ impl GeneratorTableConfig { }, ) } -} \ No newline at end of file +} diff --git a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs index 8f2e34c441..9ca6f448a0 100644 --- a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs @@ -17,16 +17,12 @@ use pasta_curves::{arithmetic::CurveAffine, pallas}; use std::ops::Deref; // TODO: SinsemillaChip to SinsemillaChipTraits - - - -impl SinsemillaChip - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +impl SinsemillaChip +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { - // TODO: simplify three hash_message functions /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] @@ -60,10 +56,8 @@ impl SinsemillaChip NonIdentityEccPoint::from_coordinates_unchecked(x_a.0, y_a), zs_sum, )) - } - /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] #[allow(clippy::type_complexity)] @@ -98,7 +92,6 @@ impl SinsemillaChip )) } - /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] #[allow(clippy::type_complexity)] @@ -171,7 +164,6 @@ impl SinsemillaChip )) } - #[allow(non_snake_case)] fn public_initialization_vanilla( &self, @@ -188,7 +180,7 @@ impl SinsemillaChip // Constrain the initial x_a, lambda_1, lambda_2, x_p using the q_sinsemilla4 // selector. let mut y_a: Y = { -// Enable `q_sinsemilla4` on the first row. + // Enable `q_sinsemilla4` on the first row. config.base.q_sinsemilla4.enable(region, offset)?; region.assign_fixed( || "fixed y_q", @@ -285,8 +277,12 @@ impl SinsemillaChip // Enable `q_sinsemilla4` on the second row. config.base.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.base.double_and_add.x_p, offset)?; + let y_a: AssignedCell, pallas::Base> = q_y.copy_advice( + || "fixed y_q", + region, + config.base.double_and_add.x_p, + offset, + )?; y_a.value_field().into() }; @@ -294,7 +290,12 @@ impl SinsemillaChip 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.base.double_and_add.x_a, offset)?; + let x_a = q_x.copy_advice( + || "fixed x_q", + region, + config.base.double_and_add.x_a, + offset, + )?; x_a.into() }; @@ -347,8 +348,12 @@ impl SinsemillaChip // Assign the final y_a. let y_a = { // Assign the final y_a. - let y_a_cell = - region.assign_advice(|| "y_a", config.base.double_and_add.lambda_1, offset, || y_a.0)?; + let y_a_cell = region.assign_advice( + || "y_a", + config.base.double_and_add.lambda_1, + offset, + || y_a.0, + )?; // Assign lambda_2 and x_p zero values since they are queried // in the gate. (The actual values do not matter since they are @@ -519,7 +524,12 @@ impl SinsemillaChip let y_p = gen.map(|gen| gen.1); // Assign `x_p` - region.assign_advice(|| "x_p", config.base.double_and_add.x_p, offset + row, || x_p)?; + region.assign_advice( + || "x_p", + config.base.double_and_add.x_p, + offset + row, + || x_p, + )?; // Compute and assign `lambda_1` let lambda_1 = { diff --git a/halo2_gadgets/src/sinsemilla/merkle.rs b/halo2_gadgets/src/sinsemilla/merkle.rs index 91461639d2..58f7851f45 100644 --- a/halo2_gadgets/src/sinsemilla/merkle.rs +++ b/halo2_gadgets/src/sinsemilla/merkle.rs @@ -195,10 +195,10 @@ pub mod tests { plonk::{Circuit, ConstraintSystem, Error}, }; - use rand::{rngs::OsRng, RngCore}; - use std::{convert::TryInto, iter}; use crate::sinsemilla::chip::SinsemillaChipProps; use crate::utilities::lookup_range_check::LookupRangeCheck; + use rand::{rngs::OsRng, RngCore}; + use std::{convert::TryInto, iter}; const MERKLE_DEPTH: usize = 32; diff --git a/halo2_gadgets/src/sinsemilla/merkle/chip.rs b/halo2_gadgets/src/sinsemilla/merkle/chip.rs index b132b03e4b..0d57fd3249 100644 --- a/halo2_gadgets/src/sinsemilla/merkle/chip.rs +++ b/halo2_gadgets/src/sinsemilla/merkle/chip.rs @@ -9,6 +9,8 @@ use pasta_curves::pallas; use super::MerkleInstructions; +use crate::sinsemilla::chip::{SinsemillaChipProps, SinsemillaConfigProps}; +use crate::utilities::lookup_range_check::witness_short_generic; use crate::{ sinsemilla::{primitives as sinsemilla, MessagePiece}, utilities::RangeConstrained, @@ -25,16 +27,14 @@ use crate::{ }, }; use group::ff::PrimeField; -use crate::sinsemilla::chip::{SinsemillaChipProps, SinsemillaConfigProps}; -use crate::utilities::lookup_range_check::witness_short_generic; /// Configuration for the `MerkleChip` implementation. #[derive(Clone, Debug, PartialEq, Eq)] pub struct MerkleConfig - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { advices: [Column; 5], q_decompose: Selector, @@ -54,19 +54,19 @@ pub struct MerkleConfig /// `left` and `right`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct MerkleChip - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { config: MerkleConfig, } impl Chip for MerkleChip - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { type Config = MerkleConfig; type Loaded = (); @@ -81,10 +81,10 @@ impl Chip for MerkleChip } impl MerkleChip - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { /// Configures the [`MerkleChip`]. pub fn configure( @@ -199,12 +199,12 @@ impl MerkleChip } impl -MerkleInstructions -for MerkleChip - where - Hash: HashDomains + Eq, - F: FixedPoints, - Commit: CommitDomains + Eq, + MerkleInstructions + for MerkleChip +where + Hash: HashDomains + Eq, + F: FixedPoints, + Commit: CommitDomains + Eq, { #[allow(non_snake_case)] fn hash_layer( @@ -418,19 +418,19 @@ for MerkleChip } impl UtilitiesInstructions for MerkleChip - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type Var = AssignedCell; } impl CondSwapInstructions for MerkleChip - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { #[allow(clippy::type_complexity)] fn swap( @@ -458,11 +458,11 @@ impl CondSwapInstructions for MerkleChip SinsemillaInstructions -for MerkleChip - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, + for MerkleChip +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type CellValue = as SinsemillaInstructions< pallas::Affine, @@ -537,7 +537,6 @@ for MerkleChip chip.hash_to_point(layouter, Q, message) } - #[allow(non_snake_case)] #[allow(clippy::type_complexity)] fn hash_to_point_with_private_init( @@ -554,4 +553,4 @@ for MerkleChip fn extract(point: &Self::NonIdentityPoint) -> Self::X { SinsemillaChip::::extract(point) } -} \ No newline at end of file +} diff --git a/halo2_gadgets/src/sinsemilla/primitives.rs b/halo2_gadgets/src/sinsemilla/primitives.rs index 668558fed9..ad9e397b5e 100644 --- a/halo2_gadgets/src/sinsemilla/primitives.rs +++ b/halo2_gadgets/src/sinsemilla/primitives.rs @@ -303,7 +303,7 @@ mod tests { .iter() .cloned() ) - .collect::>(), + .collect::>(), vec![true, true, false, true, false, true, false, true, false, true] ); assert_eq!( @@ -312,7 +312,7 @@ mod tests { .iter() .cloned() ) - .collect::>(), + .collect::>(), vec![ true, true, false, true, false, true, false, true, false, true, true, false, false, false, false, false, false, false, false, false @@ -365,4 +365,4 @@ mod tests { // Test equality assert_eq!(commit1.unwrap(), commit2.unwrap()); } -} \ No newline at end of file +} diff --git a/halo2_gadgets/src/sinsemilla_opt/chip.rs b/halo2_gadgets/src/sinsemilla_opt/chip.rs index 073e2141d6..73b29ba77a 100644 --- a/halo2_gadgets/src/sinsemilla_opt/chip.rs +++ b/halo2_gadgets/src/sinsemilla_opt/chip.rs @@ -1,16 +1,14 @@ mod generator_table; mod hash_to_point; -use crate::sinsemilla::{ - primitives as sinsemilla, -}; -use crate::{ecc::{ - chip::{DoubleAndAdd, NonIdentityEccPoint}, - FixedPoints, -}, utilities::lookup_range_check::LookupRangeCheckConfig}; -use std::marker::PhantomData; -use crate::sinsemilla::{ - message::{Message, MessagePiece}, +use crate::sinsemilla::message::{Message, MessagePiece}; +use crate::sinsemilla::primitives as sinsemilla; +use crate::{ + ecc::{ + chip::{DoubleAndAdd, NonIdentityEccPoint}, + FixedPoints, + }, + utilities::lookup_range_check::LookupRangeCheckConfig, }; use halo2_proofs::{ circuit::{AssignedCell, Chip, Layouter, Value}, @@ -22,19 +20,23 @@ use halo2_proofs::{ }; use pasta_curves::pallas; use pasta_curves::pallas::Base; +use std::marker::PhantomData; -use generator_table::GeneratorTableConfigOptimized; +use crate::sinsemilla::chip::{ + create_common_config, SinsemillaChip, SinsemillaChipProps, SinsemillaConfigCommon, + SinsemillaConfigProps, +}; use crate::sinsemilla::{CommitDomains, HashDomains, SinsemillaInstructions}; -use crate::sinsemilla::chip::{create_common_config, SinsemillaChip, SinsemillaChipProps, SinsemillaConfigCommon, SinsemillaConfigProps}; use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; +use generator_table::GeneratorTableConfigOptimized; /// Configuration for the SinsemillaConfigOptimized hash chip #[derive(Eq, PartialEq, Clone, Debug)] pub struct SinsemillaConfigOptimized - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { base: SinsemillaConfigCommon, @@ -44,11 +46,12 @@ pub struct SinsemillaConfigOptimized /// An advice column configured to perform lookup range checks. lookup_config: LookupRangeCheckConfigOptimized, } -impl SinsemillaConfigProps for SinsemillaConfigOptimized - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +impl SinsemillaConfigProps + for SinsemillaConfigOptimized +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type LookupConfigType = LookupRangeCheckConfigOptimized; fn base(&self) -> &SinsemillaConfigCommon { @@ -70,20 +73,20 @@ impl SinsemillaConfigProps for SinsemillaConfi /// [Chip description](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html#plonk--halo-2-constraints). #[derive(Eq, PartialEq, Clone, Debug)] pub struct SinsemillaChipOptimized - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { config: SinsemillaConfigOptimized, } - -impl SinsemillaChipProps for SinsemillaChipOptimized - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +impl SinsemillaChipProps + for SinsemillaChipOptimized +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type Loaded = >::Loaded; @@ -151,10 +154,10 @@ impl SinsemillaChipProps for SinsemillaChipOpt // TODO: remove duplicate? impl Chip for SinsemillaChipOptimized - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { type Config = SinsemillaConfigOptimized; type Loaded = (); @@ -172,11 +175,11 @@ impl Chip for SinsemillaChipOptimized SinsemillaInstructions -for SinsemillaChipOptimized - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, + for SinsemillaChipOptimized +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type CellValue = AssignedCell; @@ -245,4 +248,3 @@ for SinsemillaChipOptimized point.x() } } - diff --git a/halo2_gadgets/src/sinsemilla_opt/chip/generator_table.rs b/halo2_gadgets/src/sinsemilla_opt/chip/generator_table.rs index 12616a3b0a..1c6f1293d2 100644 --- a/halo2_gadgets/src/sinsemilla_opt/chip/generator_table.rs +++ b/halo2_gadgets/src/sinsemilla_opt/chip/generator_table.rs @@ -6,9 +6,9 @@ use halo2_proofs::{ }; use super::{CommitDomains, FixedPoints, HashDomains}; +use crate::sinsemilla::chip::SinsemillaConfigProps; use crate::sinsemilla::primitives::{self as sinsemilla, K, SINSEMILLA_S}; use pasta_curves::pallas; -use crate::sinsemilla::chip::SinsemillaConfigProps; /// Table containing independent generators S[0..2^k] #[derive(Eq, PartialEq, Copy, Clone, Debug)] @@ -59,7 +59,8 @@ impl GeneratorTableConfigOptimized { // y_{p,i} = (Y_{A,i} / 2) - lambda1 * (x_{A,i} - x_{P,i}) let y_p = { - let lambda1 = meta.query_advice(config.base.double_and_add.lambda_1, Rotation::cur()); + let lambda1 = + meta.query_advice(config.base.double_and_add.lambda_1, Rotation::cur()); let x_a = meta.query_advice(config.base.double_and_add.x_a, Rotation::cur()); let Y_A = config.base.double_and_add.Y_A(meta, Rotation::cur()); @@ -173,4 +174,4 @@ impl GeneratorTableConfigOptimized { }, ) } -} \ No newline at end of file +} diff --git a/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs index d4c1167445..e4f1558e6e 100644 --- a/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs @@ -13,20 +13,16 @@ use halo2_proofs::{ use group::ff::{PrimeField, PrimeFieldBits}; use pasta_curves::{arithmetic::CurveAffine, pallas}; -use std::ops::Deref; use crate::sinsemilla::{CommitDomains, HashDomains, SinsemillaInstructions}; +use std::ops::Deref; // TODO: SinsemillaChip to SinsemillaChipTraits - - - -impl SinsemillaChipOptimized - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +impl SinsemillaChipOptimized +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { - // TODO: simplify three hash_message functions /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] @@ -60,10 +56,8 @@ impl SinsemillaChipOptimized NonIdentityEccPoint::from_coordinates_unchecked(x_a.0, y_a), zs_sum, )) - } - /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] #[allow(clippy::type_complexity)] @@ -98,7 +92,6 @@ impl SinsemillaChipOptimized )) } - /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] #[allow(clippy::type_complexity)] @@ -171,7 +164,6 @@ impl SinsemillaChipOptimized )) } - #[allow(non_snake_case)] fn public_initialization_vanilla( &self, @@ -188,7 +180,7 @@ impl SinsemillaChipOptimized // Constrain the initial x_a, lambda_1, lambda_2, x_p using the q_sinsemilla4 // selector. let mut y_a: Y = { -// Enable `q_sinsemilla4` on the first row. + // Enable `q_sinsemilla4` on the first row. config.base.q_sinsemilla4.enable(region, offset)?; region.assign_fixed( || "fixed y_q", @@ -285,8 +277,12 @@ impl SinsemillaChipOptimized // Enable `q_sinsemilla4` on the second row. config.base.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.base.double_and_add.x_p, offset)?; + let y_a: AssignedCell, pallas::Base> = q_y.copy_advice( + || "fixed y_q", + region, + config.base.double_and_add.x_p, + offset, + )?; y_a.value_field().into() }; @@ -294,7 +290,12 @@ impl SinsemillaChipOptimized 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.base.double_and_add.x_a, offset)?; + let x_a = q_x.copy_advice( + || "fixed x_q", + region, + config.base.double_and_add.x_a, + offset, + )?; x_a.into() }; @@ -347,8 +348,12 @@ impl SinsemillaChipOptimized // Assign the final y_a. let y_a = { // Assign the final y_a. - let y_a_cell = - region.assign_advice(|| "y_a", config.base.double_and_add.lambda_1, offset, || y_a.0)?; + let y_a_cell = region.assign_advice( + || "y_a", + config.base.double_and_add.lambda_1, + offset, + || y_a.0, + )?; // Assign lambda_2 and x_p zero values since they are queried // in the gate. (The actual values do not matter since they are @@ -519,7 +524,12 @@ impl SinsemillaChipOptimized let y_p = gen.map(|gen| gen.1); // Assign `x_p` - region.assign_advice(|| "x_p", config.base.double_and_add.x_p, offset + row, || x_p)?; + region.assign_advice( + || "x_p", + config.base.double_and_add.x_p, + offset + row, + || x_p, + )?; // Compute and assign `lambda_1` let lambda_1 = { diff --git a/halo2_gadgets/src/sinsemilla_opt/merkle.rs b/halo2_gadgets/src/sinsemilla_opt/merkle.rs index a6d35d7f2a..8dd8967ed6 100644 --- a/halo2_gadgets/src/sinsemilla_opt/merkle.rs +++ b/halo2_gadgets/src/sinsemilla_opt/merkle.rs @@ -11,7 +11,6 @@ use crate::sinsemilla::{HashDomains, SinsemillaInstructions}; use crate::utilities::{cond_swap::CondSwapInstructions, i2lebsp, UtilitiesInstructions}; pub mod chip; - /// SWU hash-to-curve personalization for the Merkle CRH generator pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH"; @@ -24,10 +23,10 @@ pub trait MerkleInstructions< const K: usize, const MAX_WORDS: usize, >: -SinsemillaInstructions -+ CondSwapInstructions -+ UtilitiesInstructions -+ Chip + SinsemillaInstructions + + CondSwapInstructions + + UtilitiesInstructions + + Chip { /// Compute MerkleCRH for a given `layer`. The hash that computes the root /// is at layer 0, and the hashes that are applied to two leaves are at @@ -64,15 +63,15 @@ pub struct MerklePath< } impl< - C: CurveAffine, - MerkleChipOptimized, - const PATH_LENGTH: usize, - const K: usize, - const MAX_WORDS: usize, - const PAR: usize, -> MerklePath - where - MerkleChipOptimized: MerkleInstructions + Clone, + C: CurveAffine, + MerkleChipOptimized, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, + const PAR: usize, + > MerklePath +where + MerkleChipOptimized: MerkleInstructions + Clone, { /// Constructs a [`MerklePath`]. /// @@ -99,15 +98,15 @@ impl< #[allow(non_snake_case)] impl< - C: CurveAffine, - MerkleChipOptimized, - const PATH_LENGTH: usize, - const K: usize, - const MAX_WORDS: usize, - const PAR: usize, -> MerklePath - where - MerkleChipOptimized: MerkleInstructions + Clone, + C: CurveAffine, + MerkleChipOptimized, + const PATH_LENGTH: usize, + const K: usize, + const MAX_WORDS: usize, + const PAR: usize, + > MerklePath +where + MerkleChipOptimized: MerkleInstructions + Clone, { /// Calculates the root of the tree containing the given leaf at this Merkle path. /// @@ -177,6 +176,7 @@ pub mod tests { MerklePath, }; + use crate::sinsemilla_opt::chip::SinsemillaChipOptimized; use crate::{ ecc::tests::TestFixedBases, sinsemilla::{ @@ -185,11 +185,6 @@ pub mod tests { }, utilities::{i2lebsp, lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, }; - use crate::{ - sinsemilla_opt::{ - chip::SinsemillaChipOptimized, - }, - }; use group::ff::{Field, PrimeField, PrimeFieldBits}; use halo2_proofs::{ @@ -199,11 +194,11 @@ pub mod tests { plonk::{Circuit, ConstraintSystem, Error}, }; - use rand::{rngs::OsRng, RngCore}; - use std::{convert::TryInto, iter}; use crate::sinsemilla::chip::SinsemillaChipProps; use crate::utilities::lookup_range_check::LookupRangeCheck; use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; + use rand::{rngs::OsRng, RngCore}; + use std::{convert::TryInto, iter}; const MERKLE_DEPTH: usize = 32; @@ -404,4 +399,4 @@ pub mod tests { .render(11, &circuit, &root) .unwrap(); } -} \ No newline at end of file +} diff --git a/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs b/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs index ef88473cb9..474de3044d 100644 --- a/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs +++ b/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs @@ -9,14 +9,15 @@ use pasta_curves::pallas; use super::MerkleInstructions; +use crate::sinsemilla::chip::{SinsemillaChipProps, SinsemillaConfigProps}; +use crate::sinsemilla_opt::chip::{SinsemillaChipOptimized, SinsemillaConfigOptimized}; +use crate::utilities::lookup_range_check::witness_short_generic; use crate::{ sinsemilla::{primitives as sinsemilla, MessagePiece}, utilities::RangeConstrained, { ecc::FixedPoints, - sinsemilla::{ - CommitDomains, HashDomains, SinsemillaInstructions, - }, + sinsemilla::{CommitDomains, HashDomains, SinsemillaInstructions}, utilities::{ cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions}, UtilitiesInstructions, @@ -24,17 +25,14 @@ use crate::{ }, }; use group::ff::PrimeField; -use crate::sinsemilla::chip::{SinsemillaChipProps, SinsemillaConfigProps}; -use crate::sinsemilla_opt::chip::{SinsemillaChipOptimized, SinsemillaConfigOptimized}; -use crate::utilities::lookup_range_check::witness_short_generic; /// Configuration for the `MerkleChipOptimized` implementation. #[derive(Clone, Debug, PartialEq, Eq)] pub struct MerkleConfigOptimized - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { advices: [Column; 5], q_decompose: Selector, @@ -54,19 +52,19 @@ pub struct MerkleConfigOptimized /// `left` and `right`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct MerkleChipOptimized - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { config: MerkleConfigOptimized, } impl Chip for MerkleChipOptimized - where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, { type Config = MerkleConfigOptimized; type Loaded = (); @@ -81,10 +79,10 @@ impl Chip for MerkleChipOptimized MerkleChipOptimized - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { /// Configures the [`MerkleChipOptimized`]. pub fn configure( @@ -199,12 +197,12 @@ impl MerkleChipOptimized } impl -MerkleInstructions -for MerkleChipOptimized - where - Hash: HashDomains + Eq, - F: FixedPoints, - Commit: CommitDomains + Eq, + MerkleInstructions + for MerkleChipOptimized +where + Hash: HashDomains + Eq, + F: FixedPoints, + Commit: CommitDomains + Eq, { #[allow(non_snake_case)] fn hash_layer( @@ -418,19 +416,19 @@ for MerkleChipOptimized } impl UtilitiesInstructions for MerkleChipOptimized - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type Var = AssignedCell; } impl CondSwapInstructions for MerkleChipOptimized - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { #[allow(clippy::type_complexity)] fn swap( @@ -458,11 +456,11 @@ impl CondSwapInstructions for MerkleChipOptimized } impl SinsemillaInstructions -for MerkleChipOptimized - where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, + for MerkleChipOptimized +where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { type CellValue = as SinsemillaInstructions< pallas::Affine, @@ -554,4 +552,3 @@ for MerkleChipOptimized SinsemillaChipOptimized::::extract(point) } } - diff --git a/halo2_gadgets/src/utilities/cond_swap.rs b/halo2_gadgets/src/utilities/cond_swap.rs index 2e292f3195..504685800e 100644 --- a/halo2_gadgets/src/utilities/cond_swap.rs +++ b/halo2_gadgets/src/utilities/cond_swap.rs @@ -2,11 +2,16 @@ 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::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, plonk, poly::Rotation}; -use std::marker::PhantomData; +use halo2_proofs::{ + circuit::{AssignedCell, Chip, Layouter, Value}, + plonk, + plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, + poly::Rotation, +}; use pasta_curves::pallas; -use crate::ecc::chip::{EccPoint, NonIdentityEccPoint}; +use std::marker::PhantomData; /// Instructions for a conditional swap gadget. pub trait CondSwapInstructions: UtilitiesInstructions { @@ -30,7 +35,6 @@ pub trait CondSwapInstructions: UtilitiesInstructions { left: Self::Var, right: Self::Var, ) -> Result; - } /// A chip implementing a conditional swap. diff --git a/halo2_gadgets/src/utilities/lookup_range_check.rs b/halo2_gadgets/src/utilities/lookup_range_check.rs index 047c52afe3..de9ddaa1df 100644 --- a/halo2_gadgets/src/utilities/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities/lookup_range_check.rs @@ -8,9 +8,9 @@ use halo2_proofs::{ }; use std::{convert::TryInto, marker::PhantomData}; -use ff::PrimeFieldBits; use crate::sinsemilla::chip::{SinsemillaConfig, SinsemillaConfigProps}; use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; +use ff::PrimeFieldBits; use super::*; @@ -33,22 +33,24 @@ pub fn witness_short_generic( value: Value<&F>, bitrange: Range, ) -> Result>, Error> - where - F: PrimeFieldBits, - C: LookupRangeCheck, +where + F: PrimeFieldBits, + C: LookupRangeCheck, { let num_bits = bitrange.len(); assert!(num_bits < K); - lookup_config.witness_short_check( - layouter, - value.map(|value| bitrange_subset(value, bitrange)), - num_bits, - ).map(|inner| RangeConstrained { - inner, - num_bits, - _phantom: PhantomData::default(), - }) + lookup_config + .witness_short_check( + layouter, + value.map(|value| bitrange_subset(value, bitrange)), + num_bits, + ) + .map(|inner| RangeConstrained { + inner, + num_bits, + _phantom: PhantomData::default(), + }) } /// Configuration that provides methods for a lookup range check. @@ -73,8 +75,8 @@ pub trait LookupRangeCheck { running_sum: Column, table_idx: TableColumn, ) -> Self - where - Self: Sized; + where + Self: Sized; #[cfg(test)] fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error>; @@ -164,7 +166,7 @@ pub trait LookupRangeCheck { .map(|word| F::from(lebs2ip::(&(word.try_into().unwrap())))) .collect::>() }) - .transpose_vec(num_words) + .transpose_vec(num_words) }; let mut zs = vec![element.clone()]; @@ -262,12 +264,9 @@ pub trait LookupRangeCheck { }, ) } - } -impl LookupRangeCheck -for LookupRangeCheckConfig -{ +impl LookupRangeCheck for LookupRangeCheckConfig { fn base(&self) -> &LookupRangeCheckConfig { self } @@ -415,7 +414,7 @@ for LookupRangeCheckConfig } #[cfg(test)] mod tests { - use super::{LookupRangeCheckConfig, LookupRangeCheck}; + use super::{LookupRangeCheck, LookupRangeCheckConfig}; use super::super::lebs2ip; use crate::sinsemilla::primitives::K; diff --git a/halo2_gadgets/src/utilities_opt/cond_swap.rs b/halo2_gadgets/src/utilities_opt/cond_swap.rs index e69de29bb2..8b13789179 100644 --- a/halo2_gadgets/src/utilities_opt/cond_swap.rs +++ b/halo2_gadgets/src/utilities_opt/cond_swap.rs @@ -0,0 +1 @@ + diff --git a/halo2_gadgets/src/utilities_opt/lookup_range_check.rs b/halo2_gadgets/src/utilities_opt/lookup_range_check.rs index b7e1583557..d3e17320f7 100644 --- a/halo2_gadgets/src/utilities_opt/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities_opt/lookup_range_check.rs @@ -17,11 +17,9 @@ use halo2_proofs::circuit::{Layouter, Value}; use ff::PrimeFieldBits; -use crate::utilities::lookup_range_check::{LookupRangeCheckConfig, LookupRangeCheck}; +use crate::utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig}; use crate::utilities::{bitrange_subset, RangeConstrained}; - - /// Configuration that provides methods for a lookup range check. #[derive(Eq, PartialEq, Debug, Clone, Copy)] pub struct LookupRangeCheckConfigOptimized { @@ -122,15 +120,15 @@ impl LookupRangeCheckConfigOptimized { // - 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)); + * 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), + + q_range_check.clone() * z_cur), config.base.table_idx, ), ( @@ -167,7 +165,7 @@ impl LookupRangeCheckConfigOptimized { } impl LookupRangeCheck -for LookupRangeCheckConfigOptimized + for LookupRangeCheckConfigOptimized { fn base(&self) -> &LookupRangeCheckConfig { &self.base @@ -512,8 +510,8 @@ mod tests { // => element = shifted * 2^{s-K} let element = shifted * pallas::Base::from(1 << (K as u64 - num_bits)) - .invert() - .unwrap(); + .invert() + .unwrap(); let circuit: MyCircuit = MyCircuit { element: Value::known(element), num_bits: num_bits as usize, @@ -560,4 +558,4 @@ mod tests { ); } } -} \ No newline at end of file +} diff --git a/halo2_proofs/src/circuit.rs b/halo2_proofs/src/circuit.rs index 0822d8d8aa..9a792942cc 100644 --- a/halo2_proofs/src/circuit.rs +++ b/halo2_proofs/src/circuit.rs @@ -139,6 +139,15 @@ impl AssignedCell, F> { } } +impl From> for AssignedCell, F> { + fn from(ac: AssignedCell) -> Self { + AssignedCell { + value: ac.value.map(|a| a.into()), + cell: ac.cell, + _marker: Default::default(), + } + } +} impl AssignedCell where for<'v> Assigned: From<&'v V>, From 08c554bf49011832195b9a212e1d14f730d1f646 Mon Sep 17 00:00:00 2001 From: YaoGalteland Date: Mon, 15 Apr 2024 15:36:06 +0200 Subject: [PATCH 3/7] add comments --- halo2_gadgets/src/ecc_opt.rs | 1 + halo2_gadgets/src/ecc_opt/chip.rs | 1 + halo2_gadgets/src/lib.rs | 1 + halo2_gadgets/src/sinsemilla/chip.rs | 41 ++- .../src/sinsemilla/chip/hash_to_point.rs | 88 +----- halo2_gadgets/src/sinsemilla_opt/chip.rs | 24 +- .../src/sinsemilla_opt/chip/hash_to_point.rs | 76 +----- halo2_gadgets/src/sinsemilla_opt/merkle.rs | 2 +- .../src/utilities/lookup_range_check.rs | 35 ++- halo2_gadgets/src/utilities_opt.rs | 4 +- halo2_gadgets/src/utilities_opt/cond_swap.rs | 258 ++++++++++++++++++ .../src/utilities_opt/lookup_range_check.rs | 9 +- 12 files changed, 317 insertions(+), 223 deletions(-) create mode 100644 halo2_gadgets/src/ecc_opt.rs create mode 100644 halo2_gadgets/src/ecc_opt/chip.rs diff --git a/halo2_gadgets/src/ecc_opt.rs b/halo2_gadgets/src/ecc_opt.rs new file mode 100644 index 0000000000..3ef3167b1d --- /dev/null +++ b/halo2_gadgets/src/ecc_opt.rs @@ -0,0 +1 @@ +pub mod chip; diff --git a/halo2_gadgets/src/ecc_opt/chip.rs b/halo2_gadgets/src/ecc_opt/chip.rs new file mode 100644 index 0000000000..225a3ef493 --- /dev/null +++ b/halo2_gadgets/src/ecc_opt/chip.rs @@ -0,0 +1 @@ +// TODO: EccConfig has LookupRangeCheckConfig diff --git a/halo2_gadgets/src/lib.rs b/halo2_gadgets/src/lib.rs index eadea1bb65..e404097d81 100644 --- a/halo2_gadgets/src/lib.rs +++ b/halo2_gadgets/src/lib.rs @@ -22,6 +22,7 @@ #![deny(unsafe_code)] pub mod ecc; +mod ecc_opt; pub mod poseidon; #[cfg(feature = "unstable-sha256-gadget")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-sha256-gadget")))] diff --git a/halo2_gadgets/src/sinsemilla/chip.rs b/halo2_gadgets/src/sinsemilla/chip.rs index 7ab26bec82..9b74ef1979 100644 --- a/halo2_gadgets/src/sinsemilla/chip.rs +++ b/halo2_gadgets/src/sinsemilla/chip.rs @@ -11,8 +11,6 @@ use crate::{ }, utilities::lookup_range_check::LookupRangeCheckConfig, }; -use ff::PrimeField; -use pasta_curves::arithmetic::CurveAffine; use std::marker::PhantomData; use halo2_proofs::{ @@ -25,17 +23,12 @@ use halo2_proofs::{ }; use pasta_curves::pallas; use pasta_curves::pallas::Base; -use proptest::test_runner::Config; mod generator_table; -use crate::sinsemilla::primitives::{lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S}; use generator_table::GeneratorTableConfig; -use halo2_proofs::circuit::Region; -use halo2_proofs::plonk::Assigned; - mod hash_to_point; -/// Configuration for the Sinsemilla hash chip +/// Configuration for the Sinsemilla hash chip common parts #[derive(Eq, PartialEq, Clone, Debug)] pub struct SinsemillaConfigCommon where @@ -78,7 +71,8 @@ where /// An advice column configured to perform lookup range checks. lookup_config: LookupRangeCheckConfig, } -// TODO: add doc, rename it to SinsemillaConfigProps + +/// Trait that provides common methods for SinsemillaConfig and SinsemillaConfigOptimized pub trait SinsemillaConfigProps where Hash: HashDomains, @@ -86,6 +80,8 @@ where Commit: CommitDomains, { type LookupConfigType; + + /// Returns a reference to the `SinsemillaConfigCommon` instance. fn base(&self) -> &SinsemillaConfigCommon; /// Returns an array of all advice columns in this config, in arbitrary order. @@ -109,8 +105,12 @@ where q_s2.clone() * (q_s2 - one) } + /// querying a value 'y_q' from certain column fn get_y_q(&self, meta: &mut VirtualCells) -> Expression; + /// Configures constraints in the constraint system `meta` using the value 'y_q' + /// This function sets up various gates within the circuit to enforce the correct relationships + /// between variables according to elliptic curve arithmetic and the Sinsemilla hash function. fn configure_from_y_q(&self, meta: &mut ConstraintSystem) { let two = pallas::Base::from(2); @@ -188,7 +188,7 @@ where }); } } -// TODO: add doc + impl SinsemillaConfigProps for SinsemillaConfig where Hash: HashDomains, @@ -204,7 +204,7 @@ where self.lookup_config } - // todo: add doc + /// Query a fixed value from the circuit's fixed column using the configuration `fixed_y_q`. fn get_y_q(&self, meta: &mut VirtualCells) -> Expression { meta.query_fixed(self.base.fixed_y_q) } @@ -223,7 +223,7 @@ where config: SinsemillaConfig, } -// TODO: add doc,rename it to SinsemillaChipProps +/// Trait that provides common methods for SinsemillaChip and SinsemillaChipOptimized pub trait SinsemillaChipProps where Hash: HashDomains, @@ -246,6 +246,10 @@ where config: Self::SinsemillaConfigType, layouter: &mut impl Layouter, ) -> Result; + + /// # Side-effects + /// + /// All columns in `advices` and will be equality-enabled. fn configure( meta: &mut ConstraintSystem, advices: [Column; 5], @@ -274,12 +278,10 @@ where &self.config.base } - /// Reconstructs this chip from the given config. fn construct(config: Self::SinsemillaConfigType) -> Self { Self { config } } - /// Loads the lookup table required by this chip into the circuit. fn load( config: Self::SinsemillaConfigType, layouter: &mut impl Layouter, @@ -288,9 +290,6 @@ where config.generator_table.load(layouter) } - /// # Side-effects - /// - /// All columns in `advices` and will be equality-enabled. #[allow(clippy::too_many_arguments)] #[allow(non_snake_case)] fn configure( @@ -325,7 +324,7 @@ where } } -// TODO: add doc +/// A function to generate the common part of SinsemillaConfig 'SinsemillaConfigCommon' pub fn create_common_config( meta: &mut ConstraintSystem, advices: [Column; 5], @@ -354,7 +353,7 @@ where } } -// TODO: remove duplicate? +// TODO: remove duplicated code? impl Chip for SinsemillaChip where Hash: HashDomains, @@ -373,7 +372,7 @@ where } } -// TODO: remove duplicate? +// TODO: remove duplicated code? // Implement `SinsemillaInstructions` for `SinsemillaChip` impl SinsemillaInstructions @@ -419,7 +418,6 @@ where Ok(MessagePiece::new(cell, num_words)) } - // TODO: in the opt version: hash_message_vanilla -> hash_message #[allow(non_snake_case)] #[allow(clippy::type_complexity)] fn hash_to_point( @@ -430,6 +428,7 @@ where ) -> Result<(Self::NonIdentityPoint, Vec), Error> { layouter.assign_region( || "hash_to_point", + // TODO: in the opt version: hash_message_vanilla -> hash_message |mut region| self.hash_message_vanilla(&mut region, Q, &message), ) } diff --git a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs index 9ca6f448a0..6e16717150 100644 --- a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs @@ -58,40 +58,6 @@ where )) } - /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). - #[allow(non_snake_case)] - #[allow(clippy::type_complexity)] - fn hash_message( - &self, - region: &mut Region<'_, pallas::Base>, - Q: pallas::Affine, - message: &>::Message, - ) -> Result< - ( - NonIdentityEccPoint, - Vec>>, - ), - Error, - > { - let (offset, x_a, y_a) = self.public_initialization(region, Q)?; - - let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?; - - // todo: add test - - 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, - )) - } - /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] #[allow(clippy::type_complexity)] @@ -171,7 +137,7 @@ where 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(); @@ -179,7 +145,7 @@ where // Constrain the initial x_a, lambda_1, lambda_2, x_p using the q_sinsemilla4 // selector. - let mut y_a: Y = { + let y_a: Y = { // Enable `q_sinsemilla4` on the first row. config.base.q_sinsemilla4.enable(region, offset)?; region.assign_fixed( @@ -192,55 +158,6 @@ where Value::known(y_q.into()).into() }; - // Constrain the initial x_q to equal the x-coordinate of the domain's `Q`. - let mut x_a: X = { - let x_a = region.assign_advice_from_constant( - || "fixed x_q", - config.base.double_and_add.x_a, - offset, - x_q.into(), - )?; - - x_a.into() - }; - Ok((offset, x_a, y_a)) - } - #[allow(non_snake_case)] - /// Assign the coordinates of the initial public point `Q` - /// - /// | offset | x_A | x_P | q_sinsemilla4 | - /// -------------------------------------- - /// | 0 | | y_Q | | - /// | 1 | x_Q | | 1 | - fn public_initialization( - &self, - region: &mut Region<'_, pallas::Base>, - Q: pallas::Affine, - ) -> Result<(usize, X, Y), Error> { - let config = self.config().clone(); - let mut offset = 0; - - // Get the `x`- and `y`-coordinates of the starting `Q` base. - let x_q = *Q.coordinates().unwrap().x(); - let y_q = *Q.coordinates().unwrap().y(); - - // 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.base.q_sinsemilla4.enable(region, offset + 1)?; - let y_a: AssignedCell, pallas::Base> = region - .assign_advice_from_constant( - || "fixed y_q", - config.base.double_and_add.x_p, - offset, - y_q.into(), - )?; - - y_a.value_field().into() - }; - offset += 1; - // Constrain the initial x_q to equal the x-coordinate of the domain's `Q`. let x_a: X = { let x_a = region.assign_advice_from_constant( @@ -252,7 +169,6 @@ where x_a.into() }; - Ok((offset, x_a, y_a)) } diff --git a/halo2_gadgets/src/sinsemilla_opt/chip.rs b/halo2_gadgets/src/sinsemilla_opt/chip.rs index 73b29ba77a..0cb8148390 100644 --- a/halo2_gadgets/src/sinsemilla_opt/chip.rs +++ b/halo2_gadgets/src/sinsemilla_opt/chip.rs @@ -1,30 +1,21 @@ mod generator_table; mod hash_to_point; +use crate::ecc::{chip::NonIdentityEccPoint, FixedPoints}; use crate::sinsemilla::message::{Message, MessagePiece}; use crate::sinsemilla::primitives as sinsemilla; -use crate::{ - ecc::{ - chip::{DoubleAndAdd, NonIdentityEccPoint}, - FixedPoints, - }, - utilities::lookup_range_check::LookupRangeCheckConfig, -}; use halo2_proofs::{ circuit::{AssignedCell, Chip, Layouter, Value}, plonk::{ - Advice, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector, - TableColumn, VirtualCells, + Advice, Column, ConstraintSystem, Error, Expression, Fixed, TableColumn, VirtualCells, }, poly::Rotation, }; use pasta_curves::pallas; use pasta_curves::pallas::Base; -use std::marker::PhantomData; use crate::sinsemilla::chip::{ - create_common_config, SinsemillaChip, SinsemillaChipProps, SinsemillaConfigCommon, - SinsemillaConfigProps, + create_common_config, SinsemillaChipProps, SinsemillaConfigCommon, SinsemillaConfigProps, }; use crate::sinsemilla::{CommitDomains, HashDomains, SinsemillaInstructions}; use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; @@ -62,7 +53,7 @@ where self.lookup_config } - // todo: add doc + /// Query an advice value 'y_q' from a specific advice column `x_p` at the previous rotation. fn get_y_q(&self, meta: &mut VirtualCells) -> Expression { meta.query_advice(self.base.double_and_add.x_p, Rotation::prev()) } @@ -114,9 +105,6 @@ where config.generator_table.load(layouter) } - /// # Side-effects - /// - /// All columns in `advices` and will be equality-enabled. #[allow(clippy::too_many_arguments)] #[allow(non_snake_case)] fn configure( @@ -152,7 +140,7 @@ where } } -// TODO: remove duplicate? +// TODO: remove duplicated code? impl Chip for SinsemillaChipOptimized where Hash: HashDomains, @@ -171,7 +159,7 @@ where } } -// TODO: remove duplicate? +// TODO: remove duplicated code? // Implement `SinsemillaInstructions` for `SinsemillaChip` impl SinsemillaInstructions diff --git a/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs index e4f1558e6e..21418f8026 100644 --- a/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs @@ -1,4 +1,4 @@ -use super::{NonIdentityEccPoint, SinsemillaChip, SinsemillaChipOptimized}; +use super::{NonIdentityEccPoint, SinsemillaChipOptimized}; use crate::{ ecc::FixedPoints, sinsemilla::primitives::{self as sinsemilla, lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S}, @@ -24,39 +24,6 @@ where Commit: CommitDomains, { // TODO: simplify three hash_message functions - /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). - #[allow(non_snake_case)] - #[allow(clippy::type_complexity)] - pub(crate) fn hash_message_vanilla( - &self, - region: &mut Region<'_, pallas::Base>, - Q: pallas::Affine, - message: &>::Message, - ) -> Result< - ( - NonIdentityEccPoint, - Vec>>, - ), - Error, - > { - 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)?; - - // todo: add test - - 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, - )) - } /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] @@ -164,47 +131,6 @@ where )) } - #[allow(non_snake_case)] - 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; - - // Get the `x`- and `y`-coordinates of the starting `Q` base. - let x_q = *Q.coordinates().unwrap().x(); - let y_q = *Q.coordinates().unwrap().y(); - - // Constrain the initial x_a, lambda_1, lambda_2, x_p using the q_sinsemilla4 - // selector. - let mut y_a: Y = { - // Enable `q_sinsemilla4` on the first row. - config.base.q_sinsemilla4.enable(region, offset)?; - region.assign_fixed( - || "fixed y_q", - config.base.fixed_y_q, - offset, - || Value::known(y_q), - )?; - - Value::known(y_q.into()).into() - }; - - // Constrain the initial x_q to equal the x-coordinate of the domain's `Q`. - let mut x_a: X = { - let x_a = region.assign_advice_from_constant( - || "fixed x_q", - config.base.double_and_add.x_a, - offset, - x_q.into(), - )?; - - x_a.into() - }; - Ok((offset, x_a, y_a)) - } #[allow(non_snake_case)] /// Assign the coordinates of the initial public point `Q` /// diff --git a/halo2_gadgets/src/sinsemilla_opt/merkle.rs b/halo2_gadgets/src/sinsemilla_opt/merkle.rs index 8dd8967ed6..ccc869c194 100644 --- a/halo2_gadgets/src/sinsemilla_opt/merkle.rs +++ b/halo2_gadgets/src/sinsemilla_opt/merkle.rs @@ -183,7 +183,7 @@ pub mod tests { tests::{TestCommitDomain, TestHashDomain}, HashDomains, }, - utilities::{i2lebsp, lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, + utilities::{i2lebsp, UtilitiesInstructions}, }; use group::ff::{Field, PrimeField, PrimeFieldBits}; diff --git a/halo2_gadgets/src/utilities/lookup_range_check.rs b/halo2_gadgets/src/utilities/lookup_range_check.rs index de9ddaa1df..896259a7a0 100644 --- a/halo2_gadgets/src/utilities/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities/lookup_range_check.rs @@ -1,6 +1,7 @@ //! Make use of a K-bit lookup table to decompose a field element into K-bit //! words. +use ff::PrimeFieldBits; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region}, plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector, TableColumn}, @@ -8,10 +9,6 @@ use halo2_proofs::{ }; use std::{convert::TryInto, marker::PhantomData}; -use crate::sinsemilla::chip::{SinsemillaConfig, SinsemillaConfigProps}; -use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; -use ff::PrimeFieldBits; - use super::*; /// The running sum $[z_0, ..., z_W]$. If created in strict mode, $z_W = 0$. @@ -25,8 +22,8 @@ impl std::ops::Deref for RunningSum { } } -// Create a Generic Function. The function will use the LookupRangeCheck trait, -// allowing it to work with any configuration type that implements LookupRangeCheck: +/// Create a Generic Function. The function will use the LookupRangeCheck trait, +/// allowing it to work with any configuration type that implements LookupRangeCheck: pub fn witness_short_generic( lookup_config: &C, layouter: impl Layouter, @@ -64,12 +61,22 @@ pub struct LookupRangeCheckConfig { pub(crate) _marker: PhantomData, } -/// FIXME: add doc +/// Trait that provides common methods for a lookup range check. pub trait LookupRangeCheck { - /// FIXME: add doc + /// Returns a reference to the `LookupRangeCheckConfig` instance. fn base(&self) -> &LookupRangeCheckConfig; - /// FIXME: add doc + /// The `running_sum` advice column breaks the field element into `K`-bit + /// words. It is used to construct the input expression to the lookup + /// argument. + /// + /// The `table_idx` fixed column contains values from [0..2^K). Looking up + /// a value in `table_idx` constrains it to be within this range. The table + /// can be loaded outside this helper. + /// + /// # Side-effects + /// + /// Both the `running_sum` and `constants` columns will be equality-enabled. fn configure( meta: &mut ConstraintSystem, running_sum: Column, @@ -79,9 +86,15 @@ pub trait LookupRangeCheck { Self: Sized; #[cfg(test)] + // Fill `table_idx` and `table_range_check_tag`. + // This is only used in testing for now, since the Sinsemilla chip provides a pre-loaded table + // in the Orchard context. fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error>; - /// FIXME: add doc + /// 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. fn short_range_check( &self, region: &mut Region<'_, F>, @@ -282,7 +295,6 @@ impl LookupRangeCheck for LookupRangeCh let q_running = meta.complex_selector(); let q_bitshift = meta.selector(); - // FIXME: q_range_check_4 and q_range_check_5 need to be created here // if the order of the creation makes a difference let config = LookupRangeCheckConfig { q_lookup, @@ -297,7 +309,6 @@ impl LookupRangeCheck for LookupRangeCh meta.lookup(|meta| { let q_lookup = meta.query_selector(config.q_lookup); let q_running = meta.query_selector(config.q_running); - // FIXME: q_range_check_4 and q_range_check_5 need to be created here // if the order of the creation makes a difference let z_cur = meta.query_advice(config.running_sum, Rotation::cur()); let one = Expression::Constant(F::ONE); diff --git a/halo2_gadgets/src/utilities_opt.rs b/halo2_gadgets/src/utilities_opt.rs index ddd75ce739..f3fc961d8d 100644 --- a/halo2_gadgets/src/utilities_opt.rs +++ b/halo2_gadgets/src/utilities_opt.rs @@ -1,4 +1,4 @@ //! Utility gadgets. - -pub mod cond_swap; +// TODO: after ecc_opt +//pub mod cond_swap; pub mod lookup_range_check; diff --git a/halo2_gadgets/src/utilities_opt/cond_swap.rs b/halo2_gadgets/src/utilities_opt/cond_swap.rs index 8b13789179..f219ab0296 100644 --- a/halo2_gadgets/src/utilities_opt/cond_swap.rs +++ b/halo2_gadgets/src/utilities_opt/cond_swap.rs @@ -1 +1,259 @@ +//! Gadget and chip for a conditional swap utility. + + +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}, + poly::Rotation, +}; +use pasta_curves::pallas; +use std::marker::PhantomData; +use crate::utilities::UtilitiesInstructions; + +/// Instructions for a conditional swap gadget. + + + +#[cfg(test)] +mod tests { + use group::ff::{Field, PrimeField}; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + plonk::{Circuit, ConstraintSystem, Error}, + }; + use pasta_curves::pallas::Base; + use rand::rngs::OsRng; + use crate::utilities::cond_swap::{CondSwapChip, CondSwapConfig}; + use crate::utilities::lookup_range_check::LookupRangeCheck; + use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; + + + #[test] + fn test_mux() { + use crate::{ + ecc::{ + chip::{EccChip, EccConfig}, + tests::TestFixedBases, + NonIdentityPoint, Point, + }, + }; + + 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 = LookupRangeCheckConfigOptimized::configure( + meta, + advices[9], + table_idx, + ); + + 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(())); + } + } +} \ No newline at end of file diff --git a/halo2_gadgets/src/utilities_opt/lookup_range_check.rs b/halo2_gadgets/src/utilities_opt/lookup_range_check.rs index d3e17320f7..823bf07e2a 100644 --- a/halo2_gadgets/src/utilities_opt/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities_opt/lookup_range_check.rs @@ -1,9 +1,6 @@ //! Make use of a K-bit lookup table to decompose a field element into K-bit //! words. -use std::marker::PhantomData; -use std::ops::Range; - use halo2_proofs::{ circuit::{AssignedCell, Region}, plonk::{ @@ -11,6 +8,7 @@ use halo2_proofs::{ }, poly::Rotation, }; +use std::marker::PhantomData; #[cfg(test)] use halo2_proofs::circuit::{Layouter, Value}; @@ -18,7 +16,6 @@ use halo2_proofs::circuit::{Layouter, Value}; use ff::PrimeFieldBits; use crate::utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig}; -use crate::utilities::{bitrange_subset, RangeConstrained}; /// Configuration that provides methods for a lookup range check. #[derive(Eq, PartialEq, Debug, Clone, Copy)] @@ -158,10 +155,6 @@ impl LookupRangeCheckConfigOptimized { config } - - pub(crate) fn table_range_check_tag(&self) -> TableColumn { - self.table_range_check_tag - } } impl LookupRangeCheck From 330a70a57f5460417c2ed920900716f74a8f74c0 Mon Sep 17 00:00:00 2001 From: YaoGalteland Date: Mon, 15 Apr 2024 19:00:35 +0200 Subject: [PATCH 4/7] simplify verify_sinsemilla_hash --- .../src/sinsemilla/chip/hash_to_point.rs | 133 ++++++++++-------- .../src/sinsemilla_opt/chip/hash_to_point.rs | 3 +- 2 files changed, 77 insertions(+), 59 deletions(-) diff --git a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs index 6e16717150..0120b7bb75 100644 --- a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs @@ -1,5 +1,5 @@ use super::super::{CommitDomains, HashDomains, SinsemillaInstructions}; -use super::{NonIdentityEccPoint, SinsemillaChip}; +use super::{NonIdentityEccPoint, SinsemillaChip, SinsemillaChipProps, SinsemillaConfigProps}; use crate::{ ecc::FixedPoints, sinsemilla::primitives::{self as sinsemilla, lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S}, @@ -14,15 +14,88 @@ use halo2_proofs::{ use group::ff::{PrimeField, PrimeFieldBits}; use pasta_curves::{arithmetic::CurveAffine, pallas}; +use crate::sinsemilla::primitives::{K, S_PERSONALIZATION}; +use crate::utilities::lookup_range_check::LookupRangeCheck; +use group::prime::PrimeCurveAffine; +use group::Curve; +use pasta_curves::arithmetic::CurveExt; use std::ops::Deref; // TODO: SinsemillaChip to SinsemillaChipTraits +// 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, { + #[cfg(test)] + #[allow(non_snake_case)] + pub(crate) fn verify_sinsemilla_hash( + &self, + message: &>::Message, + Q: EccPointQ, + x_a: X, + y_a: AssignedCell, pallas::Base>, + zs_sum: Vec>>, + ) -> Result< + ( + NonIdentityEccPoint, + Vec>>, + ), + Error, + > { + let field_elems: Value> = message + .iter() + .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(value_Q) + .assert_if_known(|((field_elems, (x_a, y_a)), value_Q)| { + // 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(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 + }); + + 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, + )) + } + // TODO: simplify three hash_message functions /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] @@ -47,15 +120,7 @@ where let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?; - // todo: add test - - 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.verify_sinsemilla_hash(message, EccPointQ::PublicPoint(Q), x_a, y_a, zs_sum) } /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). @@ -81,53 +146,7 @@ where 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())) - .zip(Q.point()) - .assert_if_known(|((field_elems, (x_a, y_a)), Q)| { - // 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.verify_sinsemilla_hash(message, EccPointQ::PrivatePoint(Q), x_a, y_a, zs_sum) } #[allow(non_snake_case)] diff --git a/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs index 21418f8026..e9d387a655 100644 --- a/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs @@ -48,8 +48,7 @@ where let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?; - // todo: add test - + // TODO: how to call verify_sinsemilla_hash 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())?; From f78bd8b11f77f4d003e0178835920872b8beca82 Mon Sep 17 00:00:00 2001 From: YaoGalteland Date: Mon, 15 Apr 2024 19:54:10 +0200 Subject: [PATCH 5/7] reduce errors --- halo2_gadgets/src/ecc_opt.rs | 2 ++ halo2_gadgets/src/sinsemilla.rs | 2 -- halo2_gadgets/src/sinsemilla/chip.rs | 16 +++++++++++++++- .../src/sinsemilla/chip/hash_to_point.rs | 12 ++++++++++-- halo2_gadgets/src/sinsemilla_opt.rs | 2 ++ halo2_gadgets/src/utilities/cond_swap.rs | 2 ++ .../src/utilities/lookup_range_check.rs | 4 ++-- halo2_gadgets/src/utilities_opt.rs | 5 +++-- 8 files changed, 36 insertions(+), 9 deletions(-) diff --git a/halo2_gadgets/src/ecc_opt.rs b/halo2_gadgets/src/ecc_opt.rs index 3ef3167b1d..1a72206df9 100644 --- a/halo2_gadgets/src/ecc_opt.rs +++ b/halo2_gadgets/src/ecc_opt.rs @@ -1 +1,3 @@ +//! Optimized ECC gadgets. + pub mod chip; diff --git a/halo2_gadgets/src/sinsemilla.rs b/halo2_gadgets/src/sinsemilla.rs index a7028052bf..25205e4c09 100644 --- a/halo2_gadgets/src/sinsemilla.rs +++ b/halo2_gadgets/src/sinsemilla.rs @@ -647,8 +647,6 @@ pub(crate) mod tests { meta.enable_constant(constants); let table_idx = meta.lookup_table_column(); - #[cfg(feature = "zsa")] - let table_range_check_tag = meta.lookup_table_column(); let lagrange_coeffs = [ meta.fixed_column(), meta.fixed_column(), diff --git a/halo2_gadgets/src/sinsemilla/chip.rs b/halo2_gadgets/src/sinsemilla/chip.rs index 9b74ef1979..b305a3f7ba 100644 --- a/halo2_gadgets/src/sinsemilla/chip.rs +++ b/halo2_gadgets/src/sinsemilla/chip.rs @@ -79,6 +79,8 @@ where F: FixedPoints, Commit: CommitDomains, { + /// The `LookupConfigType` associated type defines the configuration type used by + /// the implementing structure for lookup operations. type LookupConfigType; /// Returns a reference to the `SinsemillaConfigCommon` instance. @@ -111,6 +113,7 @@ where /// Configures constraints in the constraint system `meta` using the value 'y_q' /// This function sets up various gates within the circuit to enforce the correct relationships /// between variables according to elliptic curve arithmetic and the Sinsemilla hash function. + #[allow(non_snake_case)] fn configure_from_y_q(&self, meta: &mut ConstraintSystem) { let two = pallas::Base::from(2); @@ -230,12 +233,23 @@ where F: FixedPoints, Commit: CommitDomains, { + /// A type that holds any general chip state that needs to be loaded at the start of + /// [`Circuit::synthesize`]. This might simply be `()` for some chips. type Loaded; - + /// The `RangeCheckConfigType` associated type defines the configuration type used by + /// the implementing structure for lookup range check operations. type RangeCheckConfigType; + + /// The `SinsemillaConfigType` associated type defines the configuration type used by + /// the implementing structure for Sinsemilla hash operations. type SinsemillaConfigType: SinsemillaConfigProps; + + /// The `LookupType` defines the number of column used in implementation. + /// It is (TableColumn, TableColumn, TableColumn) in the Vanilla version + /// It is (TableColumn, TableColumn, TableColumn, TableColumn) in the Optimized version type LookupType; + /// Returns a reference to the `SinsemillaConfigCommon` instance. fn base(&self) -> &SinsemillaConfigCommon; /// Reconstructs this chip from the given config. diff --git a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs index 0120b7bb75..536320964b 100644 --- a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs @@ -1,5 +1,5 @@ use super::super::{CommitDomains, HashDomains, SinsemillaInstructions}; -use super::{NonIdentityEccPoint, SinsemillaChip, SinsemillaChipProps, SinsemillaConfigProps}; +use super::{NonIdentityEccPoint, SinsemillaChip}; use crate::{ ecc::FixedPoints, sinsemilla::primitives::{self as sinsemilla, lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S}, @@ -15,7 +15,6 @@ use group::ff::{PrimeField, PrimeFieldBits}; use pasta_curves::{arithmetic::CurveAffine, pallas}; use crate::sinsemilla::primitives::{K, S_PERSONALIZATION}; -use crate::utilities::lookup_range_check::LookupRangeCheck; use group::prime::PrimeCurveAffine; use group::Curve; use pasta_curves::arithmetic::CurveExt; @@ -126,6 +125,9 @@ where /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] #[allow(clippy::type_complexity)] + /// TODO: remove this function, the first step is to remove hash_to_point_with_private_init in SinsemillaInstructions + /// How can we enable hash_to_point_with_private_init in SinsemillaInstructions for the optimized version, + /// while disabling hash_to_point_with_private_init in SinsemillaInstructions for the orchard version? pub(crate) fn hash_message_with_private_init( &self, region: &mut Region<'_, pallas::Base>, @@ -150,6 +152,11 @@ where } #[allow(non_snake_case)] + /// Assign the coordinates of the initial public point `Q` + /// + /// | offset | x_A | q_sinsemilla4 | fixed_y_q | + /// -------------------------------------- + /// | 0 | x_Q | 1 | y_Q | fn public_initialization_vanilla( &self, region: &mut Region<'_, pallas::Base>, @@ -198,6 +205,7 @@ where /// -------------------------------------- /// | 0 | | y_Q | | /// | 1 | x_Q | | 1 | + /// TODO: remove this function, the first step is to remove hash_to_point_with_private_init in SinsemillaInstructions fn private_initialization( &self, region: &mut Region<'_, pallas::Base>, diff --git a/halo2_gadgets/src/sinsemilla_opt.rs b/halo2_gadgets/src/sinsemilla_opt.rs index bf4dc43e4e..60a33834d8 100644 --- a/halo2_gadgets/src/sinsemilla_opt.rs +++ b/halo2_gadgets/src/sinsemilla_opt.rs @@ -1,2 +1,4 @@ +//! Optimized Sinsemilla gadgets. + pub mod chip; pub mod merkle; diff --git a/halo2_gadgets/src/utilities/cond_swap.rs b/halo2_gadgets/src/utilities/cond_swap.rs index 504685800e..5f46967bc8 100644 --- a/halo2_gadgets/src/utilities/cond_swap.rs +++ b/halo2_gadgets/src/utilities/cond_swap.rs @@ -28,6 +28,8 @@ pub trait CondSwapInstructions: UtilitiesInstructions { 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, diff --git a/halo2_gadgets/src/utilities/lookup_range_check.rs b/halo2_gadgets/src/utilities/lookup_range_check.rs index 896259a7a0..79aee26655 100644 --- a/halo2_gadgets/src/utilities/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities/lookup_range_check.rs @@ -328,8 +328,8 @@ impl LookupRangeCheck for LookupRangeCh // 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; + let short_word = z_cur; + let q_short = one - q_running; q_short * short_word }; diff --git a/halo2_gadgets/src/utilities_opt.rs b/halo2_gadgets/src/utilities_opt.rs index f3fc961d8d..d23aaa6f88 100644 --- a/halo2_gadgets/src/utilities_opt.rs +++ b/halo2_gadgets/src/utilities_opt.rs @@ -1,4 +1,5 @@ -//! Utility gadgets. -// TODO: after ecc_opt +//! Optimized Utility gadgets. +//! +// TODO: cond_swap after ecc_opt //pub mod cond_swap; pub mod lookup_range_check; From bfe5fba6c6449a4427aecbade23987aa4c98020c Mon Sep 17 00:00:00 2001 From: YaoGalteland Date: Tue, 16 Apr 2024 13:40:32 +0200 Subject: [PATCH 6/7] update lookuprangecheck configure --- halo2_gadgets/src/sinsemilla/chip.rs | 4 +- halo2_gadgets/src/sinsemilla/merkle.rs | 1 + halo2_gadgets/src/sinsemilla_opt/merkle.rs | 5 +- .../src/utilities/lookup_range_check.rs | 167 +++++++++--------- .../src/utilities_opt/lookup_range_check.rs | 11 +- 5 files changed, 88 insertions(+), 100 deletions(-) diff --git a/halo2_gadgets/src/sinsemilla/chip.rs b/halo2_gadgets/src/sinsemilla/chip.rs index b305a3f7ba..6822faf146 100644 --- a/halo2_gadgets/src/sinsemilla/chip.rs +++ b/halo2_gadgets/src/sinsemilla/chip.rs @@ -233,8 +233,8 @@ where F: FixedPoints, Commit: CommitDomains, { - /// A type that holds any general chip state that needs to be loaded at the start of - /// [`Circuit::synthesize`]. This might simply be `()` for some chips. + /// A type that holds any general chip state that needs to be loaded initially. + /// This might simply be `()` for some chips. type Loaded; /// The `RangeCheckConfigType` associated type defines the configuration type used by /// the implementing structure for lookup range check operations. diff --git a/halo2_gadgets/src/sinsemilla/merkle.rs b/halo2_gadgets/src/sinsemilla/merkle.rs index 58f7851f45..3128676b66 100644 --- a/halo2_gadgets/src/sinsemilla/merkle.rs +++ b/halo2_gadgets/src/sinsemilla/merkle.rs @@ -197,6 +197,7 @@ pub mod tests { use crate::sinsemilla::chip::SinsemillaChipProps; use crate::utilities::lookup_range_check::LookupRangeCheck; + use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; use rand::{rngs::OsRng, RngCore}; use std::{convert::TryInto, iter}; diff --git a/halo2_gadgets/src/sinsemilla_opt/merkle.rs b/halo2_gadgets/src/sinsemilla_opt/merkle.rs index ccc869c194..997c8bedb8 100644 --- a/halo2_gadgets/src/sinsemilla_opt/merkle.rs +++ b/halo2_gadgets/src/sinsemilla_opt/merkle.rs @@ -251,8 +251,9 @@ pub mod tests { meta.lookup_table_column(), ); - let range_check = - LookupRangeCheckConfigOptimized::configure(meta, advices[9], lookup.0); + let range_check = LookupRangeCheckConfigOptimized::configure_with_tag( + meta, advices[9], lookup.0, lookup.3, + ); let sinsemilla_config_1 = SinsemillaChipOptimized::configure( meta, diff --git a/halo2_gadgets/src/utilities/lookup_range_check.rs b/halo2_gadgets/src/utilities/lookup_range_check.rs index 79aee26655..1b6d19045f 100644 --- a/halo2_gadgets/src/utilities/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities/lookup_range_check.rs @@ -1,6 +1,7 @@ //! Make use of a K-bit lookup table to decompose a field element into K-bit //! words. +use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; use ff::PrimeFieldBits; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region}, @@ -61,11 +62,7 @@ pub struct LookupRangeCheckConfig { pub(crate) _marker: PhantomData, } -/// Trait that provides common methods for a lookup range check. -pub trait LookupRangeCheck { - /// Returns a reference to the `LookupRangeCheckConfig` instance. - fn base(&self) -> &LookupRangeCheckConfig; - +impl 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. @@ -77,13 +74,88 @@ pub trait LookupRangeCheck { /// # Side-effects /// /// Both the `running_sum` and `constants` columns will be equality-enabled. - fn configure( + pub(crate) fn configure( meta: &mut ConstraintSystem, running_sum: Column, table_idx: TableColumn, - ) -> Self - where - Self: Sized; + ) -> Self { + meta.enable_equality(running_sum); + + let q_lookup = meta.complex_selector(); + let q_running = meta.complex_selector(); + let q_bitshift = meta.selector(); + + // if the order of the creation makes a difference + let config = LookupRangeCheckConfig { + q_lookup, + q_running, + q_bitshift, + running_sum, + table_idx, + _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); + // 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); + + // 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; + let q_short = one - q_running; + + q_short * short_word + }; + + vec![( + q_lookup * (running_sum_lookup + short_lookup), + config.table_idx, + )] + }); + + // 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 + } +} + +/// Trait that provides common methods for a lookup range check. +pub trait LookupRangeCheck { + /// Returns a reference to the `LookupRangeCheckConfig` instance. + fn base(&self) -> &LookupRangeCheckConfig; #[cfg(test)] // Fill `table_idx` and `table_range_check_tag`. @@ -284,83 +356,6 @@ impl LookupRangeCheck for LookupRangeCh self } - fn configure( - meta: &mut ConstraintSystem, - running_sum: Column, - table_idx: TableColumn, - ) -> Self { - meta.enable_equality(running_sum); - - let q_lookup = meta.complex_selector(); - let q_running = meta.complex_selector(); - let q_bitshift = meta.selector(); - - // if the order of the creation makes a difference - let config = LookupRangeCheckConfig { - q_lookup, - q_running, - q_bitshift, - running_sum, - table_idx, - _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); - // 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); - - // 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; - let q_short = one - q_running; - - q_short * short_word - }; - - vec![( - q_lookup * (running_sum_lookup + short_lookup), - config.table_idx, - )] - }); - - // 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 - } - #[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 diff --git a/halo2_gadgets/src/utilities_opt/lookup_range_check.rs b/halo2_gadgets/src/utilities_opt/lookup_range_check.rs index 823bf07e2a..e6c79d47a6 100644 --- a/halo2_gadgets/src/utilities_opt/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities_opt/lookup_range_check.rs @@ -41,7 +41,7 @@ impl LookupRangeCheckConfigOptimized { /// # Side-effects /// /// Both the `running_sum` and `constants` columns will be equality-enabled. - fn configure_with_tag( + pub(crate) fn configure_with_tag( meta: &mut ConstraintSystem, running_sum: Column, table_idx: TableColumn, @@ -164,15 +164,6 @@ impl LookupRangeCheck &self.base } - fn configure( - meta: &mut ConstraintSystem, - running_sum: Column, - table_idx: TableColumn, - ) -> Self { - let table_range_check_tag = meta.lookup_table_column(); - Self::configure_with_tag(meta, running_sum, table_idx, table_range_check_tag) - } - #[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 From bf669556f5bb8e7735a10135417b26f682e3ef8f Mon Sep 17 00:00:00 2001 From: YaoGalteland Date: Mon, 22 Apr 2024 14:45:59 +0200 Subject: [PATCH 7/7] hash_to_point simplification --- halo2_gadgets/Cargo.toml | 3 +- halo2_gadgets/src/ecc/chip.rs | 57 +- halo2_gadgets/src/ecc/chip/mul_fixed/short.rs | 1 - halo2_gadgets/src/ecc_opt.rs | 356 +++++++ halo2_gadgets/src/ecc_opt/chip.rs | 46 + halo2_gadgets/src/lib.rs | 2 +- halo2_gadgets/src/sinsemilla.rs | 51 - halo2_gadgets/src/sinsemilla/chip.rs | 181 ++-- .../src/sinsemilla/chip/hash_to_point.rs | 751 ++++++------- halo2_gadgets/src/sinsemilla/merkle.rs | 12 +- halo2_gadgets/src/sinsemilla/merkle/chip.rs | 984 +++++++++--------- halo2_gadgets/src/sinsemilla_opt.rs | 25 + halo2_gadgets/src/sinsemilla_opt/chip.rs | 105 +- .../src/sinsemilla_opt/chip/hash_to_point.rs | 404 +------ halo2_gadgets/src/sinsemilla_opt/merkle.rs | 160 +-- .../src/sinsemilla_opt/merkle/chip.rs | 528 +--------- .../src/utilities/lookup_range_check.rs | 59 +- halo2_gadgets/src/utilities_opt/cond_swap.rs | 1 - halo2_proofs/src/circuit.rs | 24 + halo2_proofs/src/dev.rs | 3 +- halo2_proofs/src/dev/graph.rs | 2 +- rust-toolchain.toml | 2 +- 22 files changed, 1491 insertions(+), 2266 deletions(-) diff --git a/halo2_gadgets/Cargo.toml b/halo2_gadgets/Cargo.toml index 09ab4efc84..6066297dda 100644 --- a/halo2_gadgets/Cargo.toml +++ b/halo2_gadgets/Cargo.toml @@ -49,6 +49,7 @@ pprof = { version = "0.8", features = ["criterion", "flamegraph"] } # MSRV 1.56 bench = false [features] +# default = ["test-dev-graph"] test-dev-graph = [ "halo2_proofs/dev-graph", "plotters", @@ -82,4 +83,4 @@ harness = false [[bench]] name = "sha256" harness = false -required-features = ["unstable-sha256-gadget"] +required-features = ["unstable-sha256-gadget"] \ No newline at end of file diff --git a/halo2_gadgets/src/ecc/chip.rs b/halo2_gadgets/src/ecc/chip.rs index 402020384b..d24d9e1899 100644 --- a/halo2_gadgets/src/ecc/chip.rs +++ b/halo2_gadgets/src/ecc/chip.rs @@ -7,7 +7,7 @@ use crate::{ }; use arrayvec::ArrayVec; -use ff::PrimeField; +use ff::{PrimeField, PrimeFieldBits}; use group::prime::PrimeCurveAffine; use halo2_proofs::{ circuit::{AssignedCell, Chip, Layouter, Value}, @@ -17,17 +17,18 @@ 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::*; // Exposed for Sinsemilla. pub(crate) use mul::incomplete::DoubleAndAdd; +use crate::utilities::lookup_range_check::LookupRangeCheck; /// A curve point represented in affine (x, y) coordinates, or the /// identity represented as (0, 0). @@ -230,25 +231,39 @@ pub trait FixedPoint: std::fmt::Debug + Eq + Clone { pub struct EccChip> { config: EccConfig, } +#[macro_export] +/// Implement `Chip` for `chip_type` and `config_type` +macro_rules! impl_trait_Chip_fixedpoints_for { + ($chip_type:ty, $config_type:ty) => { + impl> Chip for $chip_type { + type Config = $config_type; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } -impl> Chip for EccChip { - type Config = EccConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } + fn loaded(&self) -> &Self::Loaded { + &() + } + } + }; } -impl> UtilitiesInstructions - for EccChip -{ - type Var = AssignedCell; +impl_trait_Chip_fixedpoints_for!(EccChip,EccConfig); +#[macro_export] +/// Implement `UtilitiesInstructions` for `chip_type` +macro_rules! impl_trait_UtilitiesInstructions_FixedPoints_for { + ($chip_type:ty) => { + impl> UtilitiesInstructions + for $chip_type + { + type Var = AssignedCell; + } + }; } +impl_trait_UtilitiesInstructions_FixedPoints_for!(EccChip); + impl> EccChip { /// Reconstructs this chip from the given config. diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs index 056e716e1e..51e6371c14 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs @@ -316,7 +316,6 @@ pub mod tests { }; use pasta_curves::pallas; - use crate::utilities::lookup_range_check::LookupRangeCheck; use crate::{ ecc::{ chip::{EccChip, FixedPoint, MagnitudeSign}, diff --git a/halo2_gadgets/src/ecc_opt.rs b/halo2_gadgets/src/ecc_opt.rs index 1a72206df9..7391361c11 100644 --- a/halo2_gadgets/src/ecc_opt.rs +++ b/halo2_gadgets/src/ecc_opt.rs @@ -1,3 +1,359 @@ //! Optimized ECC gadgets. pub mod chip; +#[cfg(test)] +pub(crate) mod tests { + use ff::PrimeField; + use group::{prime::PrimeCurveAffine, Curve, Group}; + + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + plonk::{Circuit, ConstraintSystem, Error}, + }; + use lazy_static::lazy_static; + use pasta_curves::pallas; + + use crate::ecc::{ + chip::{ + find_zs_and_us, BaseFieldElem, EccChip, EccConfig, FixedPoint, FullScalar, ShortScalar, + H, NUM_WINDOWS, NUM_WINDOWS_SHORT, + }, + FixedPoints, + }; + use crate::utilities::lookup_range_check::LookupRangeCheckConfig; + use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; + + #[derive(Debug, Eq, PartialEq, Clone)] + pub(crate) struct TestFixedBases; + #[derive(Debug, Eq, PartialEq, Clone)] + pub(crate) struct FullWidth(pallas::Affine, &'static [(u64, [pallas::Base; H])]); + #[derive(Debug, Eq, PartialEq, Clone)] + pub(crate) struct BaseField; + #[derive(Debug, Eq, PartialEq, Clone)] + pub(crate) struct Short; + + lazy_static! { + static ref BASE: pallas::Affine = pallas::Point::generator().to_affine(); + static ref ZS_AND_US: Vec<(u64, [pallas::Base; H])> = + find_zs_and_us(*BASE, NUM_WINDOWS).unwrap(); + static ref ZS_AND_US_SHORT: Vec<(u64, [pallas::Base; H])> = + find_zs_and_us(*BASE, NUM_WINDOWS_SHORT).unwrap(); + } + + impl FullWidth { + pub(crate) fn from_pallas_generator() -> Self { + FullWidth(*BASE, &ZS_AND_US) + } + + pub(crate) fn from_parts( + base: pallas::Affine, + zs_and_us: &'static [(u64, [pallas::Base; H])], + ) -> Self { + FullWidth(base, zs_and_us) + } + } + + impl FixedPoint for FullWidth { + type FixedScalarKind = FullScalar; + + fn generator(&self) -> pallas::Affine { + self.0 + } + + fn u(&self) -> Vec<[[u8; 32]; H]> { + self.1 + .iter() + .map(|(_, us)| { + [ + us[0].to_repr(), + us[1].to_repr(), + us[2].to_repr(), + us[3].to_repr(), + us[4].to_repr(), + us[5].to_repr(), + us[6].to_repr(), + us[7].to_repr(), + ] + }) + .collect() + } + + fn z(&self) -> Vec { + self.1.iter().map(|(z, _)| *z).collect() + } + } + + impl FixedPoint for BaseField { + type FixedScalarKind = BaseFieldElem; + + fn generator(&self) -> pallas::Affine { + *BASE + } + + fn u(&self) -> Vec<[[u8; 32]; H]> { + ZS_AND_US + .iter() + .map(|(_, us)| { + [ + us[0].to_repr(), + us[1].to_repr(), + us[2].to_repr(), + us[3].to_repr(), + us[4].to_repr(), + us[5].to_repr(), + us[6].to_repr(), + us[7].to_repr(), + ] + }) + .collect() + } + + fn z(&self) -> Vec { + ZS_AND_US.iter().map(|(z, _)| *z).collect() + } + } + + impl FixedPoint for Short { + type FixedScalarKind = ShortScalar; + + fn generator(&self) -> pallas::Affine { + *BASE + } + + fn u(&self) -> Vec<[[u8; 32]; H]> { + ZS_AND_US_SHORT + .iter() + .map(|(_, us)| { + [ + us[0].to_repr(), + us[1].to_repr(), + us[2].to_repr(), + us[3].to_repr(), + us[4].to_repr(), + us[5].to_repr(), + us[6].to_repr(), + us[7].to_repr(), + ] + }) + .collect() + } + + fn z(&self) -> Vec { + ZS_AND_US_SHORT.iter().map(|(z, _)| *z).collect() + } + } + + impl FixedPoints for TestFixedBases { + type FullScalar = FullWidth; + type ShortScalar = Short; + type Base = BaseField; + } + + struct MyCircuit { + test_errors: bool, + } + + #[allow(non_snake_case)] + impl Circuit for MyCircuit { + type Config = EccConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + MyCircuit { test_errors: false } + } + + 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 = LookupRangeCheckConfigOptimized::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()); + + // Load 10-bit lookup table. In the Action circuit, this will be + // provided by the Sinsemilla chip. + config.lookup_config.load(&mut layouter)?; + + // Generate a random non-identity point P + let p_val = pallas::Point::random(rand::rngs::OsRng).to_affine(); // P + let p = super::NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "P"), + Value::known(p_val), + )?; + let p_neg = -p_val; + let p_neg = super::NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "-P"), + Value::known(p_neg), + )?; + + // Generate a random non-identity point Q + let q_val = pallas::Point::random(rand::rngs::OsRng).to_affine(); // Q + let q = super::NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "Q"), + Value::known(q_val), + )?; + + // Make sure P and Q are not the same point. + assert_ne!(p_val, q_val); + + // Test that we can witness the identity as a point, but not as a non-identity point. + { + let _ = super::Point::new( + chip.clone(), + layouter.namespace(|| "identity"), + Value::known(pallas::Affine::identity()), + )?; + + super::NonIdentityPoint::new( + chip.clone(), + layouter.namespace(|| "identity"), + Value::known(pallas::Affine::identity()), + ) + .expect_err("Trying to witness the identity should return an error"); + } + + // Test witness non-identity point + { + super::chip::witness_point::tests::test_witness_non_id( + chip.clone(), + layouter.namespace(|| "witness non-identity point"), + ) + } + + // Test complete addition + { + super::chip::add::tests::test_add( + chip.clone(), + layouter.namespace(|| "complete addition"), + p_val, + &p, + q_val, + &q, + &p_neg, + )?; + } + + // Test incomplete addition + { + super::chip::add_incomplete::tests::test_add_incomplete( + chip.clone(), + layouter.namespace(|| "incomplete addition"), + p_val, + &p, + q_val, + &q, + &p_neg, + self.test_errors, + )?; + } + + // Test variable-base scalar multiplication + { + super::chip::mul::tests::test_mul( + chip.clone(), + layouter.namespace(|| "variable-base scalar mul"), + &p, + p_val, + )?; + } + + // 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( + chip.clone(), + layouter.namespace(|| "full-width fixed-base scalar mul"), + )?; + } + + // Test signed short fixed-base scalar multiplication + { + super::chip::mul_fixed::short::tests::test_mul_fixed_short( + chip.clone(), + layouter.namespace(|| "signed short fixed-base scalar mul"), + )?; + } + + // Test fixed-base scalar multiplication with a base field element + { + super::chip::mul_fixed::base_field_elem::tests::test_mul_fixed_base_field( + chip, + layouter.namespace(|| "fixed-base scalar mul with base field element"), + )?; + } + + Ok(()) + } + } + + #[test] + fn ecc_chip() { + let k = 13; + let circuit = MyCircuit { test_errors: true }; + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } + + #[cfg(feature = "test-dev-graph")] + #[test] + fn print_ecc_chip() { + use plotters::prelude::*; + + let root = BitMapBackend::new("ecc-chip-layout.png", (1024, 7680)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root.titled("Ecc Chip Layout", ("sans-serif", 60)).unwrap(); + + let circuit = MyCircuit { test_errors: false }; + halo2_proofs::dev::CircuitLayout::default() + .render(13, &circuit, &root) + .unwrap(); + } +} \ No newline at end of file diff --git a/halo2_gadgets/src/ecc_opt/chip.rs b/halo2_gadgets/src/ecc_opt/chip.rs index 225a3ef493..8de4973dab 100644 --- a/halo2_gadgets/src/ecc_opt/chip.rs +++ b/halo2_gadgets/src/ecc_opt/chip.rs @@ -1 +1,47 @@ +use pasta_curves::pallas; +use halo2_proofs::plonk::{Advice, Column}; +use crate::ecc::chip::{EccChip, EccConfig}; +use crate::ecc::EccInstructions; +use crate::{impl_trait_Chip_fixedpoints_for, impl_trait_UtilitiesInstructions_for}; +use crate::utilities::lookup_range_check::LookupRangeCheckConfig; +use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; + // TODO: EccConfig has LookupRangeCheckConfig +/// Configuration for [`EccChip`]. +#[derive(Clone, Debug, Eq, PartialEq)] +#[allow(non_snake_case)] +pub struct EccConfigOptimized> { + /// Advice columns needed by instructions in the ECC chip. + pub advices: [Column; 10], + + /// Incomplete addition + add_incomplete: crate::ecc::chip::add_incomplete::Config, + + /// Complete addition + add: crate::ecc::chip::add::Config, + + /// Variable-base scalar multiplication + mul: crate::ecc::chip::mul::Config, + + /// Fixed-base full-width scalar multiplication + mul_fixed_full: crate::ecc::chip::mul_fixed::full_width::Config, + /// Fixed-base signed short scalar multiplication + mul_fixed_short: crate::ecc::chip::mul_fixed::short::Config, + /// Fixed-base mul using a base field element as a scalar + mul_fixed_base_field: crate::ecc::chip::mul_fixed::base_field_elem::Config, + + /// Witness point + witness_point: crate::ecc::chip::witness_point::Config, + + /// Lookup range check using 10-bit lookup table + pub lookup_config: LookupRangeCheckConfigOptimized, +} + +/// An [`EccInstructions`] chip that uses 10 advice columns. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EccChipOptimized> { + config: EccConfigOptimized, +} + +impl_trait_Chip_fixedpoints_for!(EccChipOptimized,EccConfigOptimized); +impl_trait_UtilitiesInstructions_for!(EccChipOptimized); diff --git a/halo2_gadgets/src/lib.rs b/halo2_gadgets/src/lib.rs index e404097d81..e98bbe5975 100644 --- a/halo2_gadgets/src/lib.rs +++ b/halo2_gadgets/src/lib.rs @@ -22,7 +22,7 @@ #![deny(unsafe_code)] pub mod ecc; -mod ecc_opt; +//mod ecc_opt; pub mod poseidon; #[cfg(feature = "unstable-sha256-gadget")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-sha256-gadget")))] diff --git a/halo2_gadgets/src/sinsemilla.rs b/halo2_gadgets/src/sinsemilla.rs index 25205e4c09..0095ba283c 100644 --- a/halo2_gadgets/src/sinsemilla.rs +++ b/halo2_gadgets/src/sinsemilla.rs @@ -88,20 +88,6 @@ 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; } @@ -345,20 +331,6 @@ HashDomain } - #[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)) - } @@ -464,28 +436,6 @@ CommitDomain 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 { @@ -577,7 +527,6 @@ pub(crate) mod tests { use pasta_curves::pallas; use crate::sinsemilla::chip::SinsemillaChipProps; - use crate::utilities::lookup_range_check::LookupRangeCheck; use std::convert::TryInto; pub(crate) const PERSONALIZATION: &str = "MerkleCRH"; diff --git a/halo2_gadgets/src/sinsemilla/chip.rs b/halo2_gadgets/src/sinsemilla/chip.rs index 6822faf146..e230ba82b8 100644 --- a/halo2_gadgets/src/sinsemilla/chip.rs +++ b/halo2_gadgets/src/sinsemilla/chip.rs @@ -11,22 +11,18 @@ use crate::{ }, utilities::lookup_range_check::LookupRangeCheckConfig, }; -use std::marker::PhantomData; - -use halo2_proofs::{ - circuit::{AssignedCell, Chip, Layouter, Value}, - plonk::{ - Advice, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector, - TableColumn, VirtualCells, - }, - poly::Rotation, -}; +use halo2_proofs::{circuit::{AssignedCell, Chip, Layouter, Value}, impl_trait_Chip_for, plonk::{ + Advice, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector, + TableColumn, VirtualCells, +}, poly::Rotation}; use pasta_curves::pallas; use pasta_curves::pallas::Base; +use std::marker::PhantomData; mod generator_table; use generator_table::GeneratorTableConfig; -mod hash_to_point; + +pub(crate) mod hash_to_point; /// Configuration for the Sinsemilla hash chip common parts #[derive(Eq, PartialEq, Clone, Debug)] @@ -244,14 +240,17 @@ where /// the implementing structure for Sinsemilla hash operations. type SinsemillaConfigType: SinsemillaConfigProps; - /// The `LookupType` defines the number of column used in implementation. + /// The `LookupTableColumnType` defines the number of column used in implementation. /// It is (TableColumn, TableColumn, TableColumn) in the Vanilla version /// It is (TableColumn, TableColumn, TableColumn, TableColumn) in the Optimized version - type LookupType; + type LookupTableColumnType; /// Returns a reference to the `SinsemillaConfigCommon` instance. fn base(&self) -> &SinsemillaConfigCommon; + /// Returns the `SinsemillaConfigType'. + fn config(&self) -> Self::SinsemillaConfigType; + /// Reconstructs this chip from the given config. fn construct(config: Self::SinsemillaConfigType) -> Self; @@ -269,7 +268,7 @@ where advices: [Column; 5], witness_pieces: Column, fixed_y_q: Column, - lookup: Self::LookupType, + lookup: Self::LookupTableColumnType, range_check: Self::RangeCheckConfigType, ) -> Self::SinsemillaConfigType; } @@ -286,12 +285,16 @@ where type SinsemillaConfigType = SinsemillaConfig; - type LookupType = (TableColumn, TableColumn, TableColumn); + type LookupTableColumnType = (TableColumn, TableColumn, TableColumn); fn base(&self) -> &SinsemillaConfigCommon { &self.config.base } + fn config(&self) -> Self::SinsemillaConfigType { + self.config.clone() + } + fn construct(config: Self::SinsemillaConfigType) -> Self { Self { config } } @@ -311,7 +314,7 @@ where advices: [Column; 5], witness_pieces: Column, fixed_y_q: Column, - lookup: Self::LookupType, + lookup: Self::LookupTableColumnType, range_check: Self::RangeCheckConfigType, ) -> Self::SinsemillaConfigType { // Enable equality on all advice columns @@ -367,100 +370,76 @@ where } } -// TODO: remove duplicated code? -impl Chip for SinsemillaChip -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, -{ - type Config = SinsemillaConfig; - type Loaded = (); - fn config(&self) -> &Self::Config { - &self.config - } +impl_trait_Chip_for!(SinsemillaChip, SinsemillaConfig); - fn loaded(&self) -> &Self::Loaded { - &() - } -} +/// Implement `SinsemillaInstructions` for `chip_type` +#[macro_export] +macro_rules! impl_trait_SinsemillaInstructions_for_SinsemillaChip { + ($chip_type:ty) => { + impl SinsemillaInstructions + for $chip_type + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, + { + type CellValue = AssignedCell; -// TODO: remove duplicated code? + type Message = Message; + type MessagePiece = MessagePiece; -// Implement `SinsemillaInstructions` for `SinsemillaChip` -impl SinsemillaInstructions - for SinsemillaChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, -{ - type CellValue = AssignedCell; - - type Message = Message; - type MessagePiece = MessagePiece; + type RunningSum = Vec; - type RunningSum = Vec; + type X = AssignedCell; + type NonIdentityPoint = NonIdentityEccPoint; + type FixedPoints = F; - type X = AssignedCell; - type NonIdentityPoint = NonIdentityEccPoint; - type FixedPoints = F; + type HashDomains = Hash; + type CommitDomains = Commit; - type HashDomains = Hash; - type CommitDomains = Commit; + fn witness_message_piece( + &self, + mut layouter: impl Layouter, + field_elem: Value, + num_words: usize, + ) -> Result { + let config = self.config.clone(); - fn witness_message_piece( - &self, - mut layouter: impl Layouter, - field_elem: Value, - num_words: usize, - ) -> Result { - let config = self.config().clone(); - - let cell = layouter.assign_region( - || "witness message piece", - |mut region| { - region.assign_advice( + let cell = layouter.assign_region( || "witness message piece", - config.base.witness_pieces, - 0, - || field_elem, + |mut region| { + region.assign_advice( + || "witness message piece", + config.base.witness_pieces, + 0, + || field_elem, + ) + }, + )?; + Ok(MessagePiece::new(cell, num_words)) + } + + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + fn hash_to_point( + &self, + mut layouter: impl Layouter, + Q: pallas::Affine, + message: Self::Message, + ) -> Result<(Self::NonIdentityPoint, Vec), Error> { + layouter.assign_region( + || "hash_to_point", + |mut region| self.hash_message(&mut region, Q, &message), ) - }, - )?; - Ok(MessagePiece::new(cell, num_words)) - } + } - #[allow(non_snake_case)] - #[allow(clippy::type_complexity)] - fn hash_to_point( - &self, - mut layouter: impl Layouter, - Q: pallas::Affine, - message: Self::Message, - ) -> Result<(Self::NonIdentityPoint, Vec), Error> { - layouter.assign_region( - || "hash_to_point", - // TODO: in the opt version: hash_message_vanilla -> hash_message - |mut region| self.hash_message_vanilla(&mut region, Q, &message), - ) - } - - #[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() - } + fn extract(point: &Self::NonIdentityPoint) -> Self::X { + point.x() + } + } + }; } + +impl_trait_SinsemillaInstructions_for_SinsemillaChip!(SinsemillaChip); + diff --git a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs index 536320964b..0d9114a2f7 100644 --- a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs @@ -1,32 +1,24 @@ use super::super::{CommitDomains, HashDomains, SinsemillaInstructions}; -use super::{NonIdentityEccPoint, SinsemillaChip}; +use super::{NonIdentityEccPoint, SinsemillaChip, SinsemillaChipProps, SinsemillaConfigProps}; use crate::{ ecc::FixedPoints, sinsemilla::primitives::{self as sinsemilla, lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S}, }; +use crate::sinsemilla::message::{Message, MessagePiece}; +use crate::sinsemilla::primitives::{K, S_PERSONALIZATION}; use ff::Field; -use halo2_proofs::{ - circuit::{AssignedCell, Chip, Region, Value}, - plonk::{Assigned, Error}, -}; - use group::ff::{PrimeField, PrimeFieldBits}; -use pasta_curves::{arithmetic::CurveAffine, pallas}; - -use crate::sinsemilla::primitives::{K, S_PERSONALIZATION}; use group::prime::PrimeCurveAffine; use group::Curve; +use halo2_proofs::{ + circuit::{AssignedCell, Region, Value}, + plonk::{Assigned, Error}, +}; use pasta_curves::arithmetic::CurveExt; +use pasta_curves::pallas::Base; +use pasta_curves::{arithmetic::CurveAffine, pallas}; use std::ops::Deref; -// TODO: SinsemillaChip to SinsemillaChipTraits - -// Define an enum that can hold either type -#[derive(Debug, Clone)] -pub enum EccPointQ<'a> { - PublicPoint(pallas::Affine), - PrivatePoint(&'a NonIdentityEccPoint), -} impl SinsemillaChip where @@ -34,72 +26,10 @@ where Fixed: FixedPoints, Commit: CommitDomains, { - #[cfg(test)] - #[allow(non_snake_case)] - pub(crate) fn verify_sinsemilla_hash( - &self, - message: &>::Message, - Q: EccPointQ, - x_a: X, - y_a: AssignedCell, pallas::Base>, - zs_sum: Vec>>, - ) -> Result< - ( - NonIdentityEccPoint, - Vec>>, - ), - Error, - > { - let field_elems: Value> = message - .iter() - .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(value_Q) - .assert_if_known(|((field_elems, (x_a, y_a)), value_Q)| { - // 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(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 - }); - - 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, - )) - } - - // TODO: simplify three hash_message functions /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] #[allow(clippy::type_complexity)] - pub(crate) fn hash_message_vanilla( + pub(crate) fn hash_message( &self, region: &mut Region<'_, pallas::Base>, Q: pallas::Affine, @@ -117,38 +47,9 @@ where > { 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)?; - - self.verify_sinsemilla_hash(message, EccPointQ::PublicPoint(Q), 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)] - /// TODO: remove this function, the first step is to remove hash_to_point_with_private_init in SinsemillaInstructions - /// How can we enable hash_to_point_with_private_init in SinsemillaInstructions for the optimized version, - /// while disabling hash_to_point_with_private_init in SinsemillaInstructions for the orchard version? - pub(crate) fn hash_message_with_private_init( - &self, - region: &mut Region<'_, pallas::Base>, - Q: &NonIdentityEccPoint, - message: &>::Message, - ) -> Result< - ( - NonIdentityEccPoint, - Vec>>, - ), - 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)?; + let (x_a, y_a, zs_sum) = hash_all_pieces(self, region, offset, message, x_a, y_a)?; - self.verify_sinsemilla_hash(message, EccPointQ::PrivatePoint(Q), x_a, y_a, zs_sum) + verify_sinsemilla_hash(message, EccPointQ::PublicPoint(Q), x_a, y_a, zs_sum) } #[allow(non_snake_case)] @@ -162,7 +63,7 @@ where region: &mut Region<'_, pallas::Base>, Q: pallas::Affine, ) -> Result<(usize, X, Y), Error> { - let config = self.config().clone(); + let config = self.config.clone(); let offset = 0; // Get the `x`- and `y`-coordinates of the starting `Q` base. @@ -197,341 +98,367 @@ 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 | - /// TODO: remove this function, the first step is to remove hash_to_point_with_private_init in SinsemillaInstructions - 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.base.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.base.double_and_add.x_p, - offset, - )?; - - y_a.value_field().into() - }; - offset += 1; +/// Define an enum that can hold either type +#[derive(Debug, Clone)] +pub enum EccPointQ<'a> { + PublicPoint(pallas::Affine), + PrivatePoint(&'a NonIdentityEccPoint), +} - 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.base.double_and_add.x_a, - offset, - )?; +/// verify sinsemilla hash +#[allow(non_snake_case)] +#[allow(clippy::type_complexity)] +pub(crate) fn verify_sinsemilla_hash( + message: &Message, + Q: EccPointQ, + x_a: X, + y_a: AssignedCell, pallas::Base>, + zs_sum: Vec>>, +) -> Result< + ( + NonIdentityEccPoint, + Vec>>, + ), + Error, +> { + let field_elems: Value> = message + .iter() + .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(value_Q) + .assert_if_known(|((field_elems, (x_a, y_a)), value_Q)| { + // 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(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 + }); - x_a.into() - }; + 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, + )) +} - Ok((offset, x_a, y_a)) +#[allow(clippy::type_complexity)] +/// Hash `message` from the initial point `Q`. +pub(crate) fn hash_all_pieces( + sinsemilla_chip: &C, + region: &mut Region<'_, pallas::Base>, + mut offset: usize, + message: &Message, + mut x_a: X, + mut y_a: Y, +) -> Result< + ( + X, + AssignedCell, pallas::Base>, + Vec>>, + ), + Error, +> +where + C: SinsemillaChipProps, + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + let config = sinsemilla_chip.config(); + + let mut zs_sum: Vec>> = Vec::new(); + + // Hash each piece in the message. + for (idx, piece) in message.iter().enumerate() { + let final_piece = idx == message.len() - 1; + + // The value of the accumulator after this piece is processed. + let (x, y, zs) = hash_piece( + sinsemilla_chip, + region, + offset, + piece, + x_a, + y_a, + final_piece, + )?; + + // Since each message word takes one row to process, we increase + // the offset by `piece.num_words` on each iteration. + offset += piece.num_words(); + + // Update the accumulator to the latest value. + x_a = x; + y_a = y; + zs_sum.push(zs); } - #[allow(clippy::type_complexity)] - /// Hash `message` from the initial point `Q`. - fn hash_all_pieces( - &self, - region: &mut Region<'_, pallas::Base>, - mut offset: usize, - message: &>::Message, - mut x_a: X, - mut y_a: Y, - ) -> Result< - ( - X, - AssignedCell, pallas::Base>, - Vec>>, - ), - Error, - > { - let config = self.config().clone(); - - let mut zs_sum: Vec>> = Vec::new(); + // Assign the final y_a. + let y_a = { + // Assign the final y_a. + let y_a_cell = region.assign_advice( + || "y_a", + config.base().double_and_add.lambda_1, + offset, + || y_a.0, + )?; + + // Assign lambda_2 and x_p zero values since they are queried + // in the gate. (The actual values do not matter since they are + // multiplied by zero.) + { + region.assign_advice( + || "dummy lambda2", + config.base().double_and_add.lambda_2, + offset, + || Value::known(pallas::Base::zero()), + )?; + region.assign_advice( + || "dummy x_p", + config.base().double_and_add.x_p, + offset, + || Value::known(pallas::Base::zero()), + )?; + } - // Hash each piece in the message. - for (idx, piece) in message.iter().enumerate() { - let final_piece = idx == message.len() - 1; + y_a_cell + }; - // The value of the accumulator after this piece is processed. - let (x, y, zs) = self.hash_piece(region, offset, piece, x_a, y_a, final_piece)?; + Ok((x_a, y_a, zs_sum)) +} - // Since each message word takes one row to process, we increase - // the offset by `piece.num_words` on each iteration. - offset += piece.num_words(); +#[allow(clippy::type_complexity)] +/// Hashes a message piece containing `piece.length` number of `K`-bit words. +/// +/// To avoid a duplicate assignment, the accumulator x-coordinate provided +/// by the caller is not copied. This only works because `hash_piece()` is +/// an internal API. Before this call to `hash_piece()`, x_a MUST have been +/// already assigned within this region at the correct offset. +pub(crate) fn hash_piece( + sinsemilla_chip: &C, + region: &mut Region<'_, pallas::Base>, + offset: usize, + piece: &MessagePiece, + mut x_a: X, + mut y_a: Y, + final_piece: bool, +) -> Result< + ( + X, + Y, + Vec>, + ), + Error, +> +where + C: SinsemillaChipProps, + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, +{ + let config = sinsemilla_chip.config(); - // Update the accumulator to the latest value. - x_a = x; - y_a = y; - zs_sum.push(zs); + // Selector assignments + { + // Enable `q_sinsemilla1` selector on every row. + for row in 0..piece.num_words() { + config.base().q_sinsemilla1.enable(region, offset + row)?; } - // Assign the final y_a. - let y_a = { - // Assign the final y_a. - let y_a_cell = region.assign_advice( - || "y_a", - config.base.double_and_add.lambda_1, - offset, - || y_a.0, + // Set `q_sinsemilla2` fixed column to 1 on every row but the last. + for row in 0..(piece.num_words() - 1) { + region.assign_fixed( + || "q_s2 = 1", + config.base().q_sinsemilla2, + offset + row, + || Value::known(pallas::Base::one()), )?; + } - // Assign lambda_2 and x_p zero values since they are queried - // in the gate. (The actual values do not matter since they are - // multiplied by zero.) - { - region.assign_advice( - || "dummy lambda2", - config.base.double_and_add.lambda_2, - offset, - || Value::known(pallas::Base::zero()), - )?; - region.assign_advice( - || "dummy x_p", - config.base.double_and_add.x_p, - offset, - || Value::known(pallas::Base::zero()), - )?; - } - - y_a_cell - }; - - Ok((x_a, y_a, zs_sum)) + // Set `q_sinsemilla2` fixed column to 0 on the last row if this is + // not the final piece, or to 2 on the last row of the final piece. + region.assign_fixed( + || { + if final_piece { + "q_s2 for final piece" + } else { + "q_s2 between pieces" + } + }, + config.base().q_sinsemilla2, + offset + piece.num_words() - 1, + || { + Value::known(if final_piece { + pallas::Base::from(2) + } else { + pallas::Base::zero() + }) + }, + )?; } - #[allow(clippy::type_complexity)] - /// Hashes a message piece containing `piece.length` number of `K`-bit words. - /// - /// To avoid a duplicate assignment, the accumulator x-coordinate provided - /// by the caller is not copied. This only works because `hash_piece()` is - /// an internal API. Before this call to `hash_piece()`, x_a MUST have been - /// already assigned within this region at the correct offset. - fn hash_piece( - &self, - region: &mut Region<'_, pallas::Base>, - offset: usize, - piece: &>::MessagePiece, - mut x_a: X, - mut y_a: Y, - final_piece: bool, - ) -> Result< - ( - X, - Y, - Vec>, - ), - Error, - > { - let config = self.config().clone(); - - // Selector assignments - { - // Enable `q_sinsemilla1` selector on every row. - for row in 0..piece.num_words() { - config.base.q_sinsemilla1.enable(region, offset + row)?; - } - - // Set `q_sinsemilla2` fixed column to 1 on every row but the last. - for row in 0..(piece.num_words() - 1) { - region.assign_fixed( - || "q_s2 = 1", - config.base.q_sinsemilla2, - offset + row, - || Value::known(pallas::Base::one()), - )?; - } - - // Set `q_sinsemilla2` fixed column to 0 on the last row if this is - // not the final piece, or to 2 on the last row of the final piece. - region.assign_fixed( - || { - if final_piece { - "q_s2 for final piece" - } else { - "q_s2 between pieces" - } - }, - config.base.q_sinsemilla2, - offset + piece.num_words() - 1, - || { - Value::known(if final_piece { - pallas::Base::from(2) - } else { - pallas::Base::zero() - }) - }, + // Message piece as K * piece.length bitstring + let bitstring: Value> = piece.field_elem().map(|value| { + value + .to_le_bits() + .into_iter() + .take(sinsemilla::K * piece.num_words()) + .collect() + }); + + let words: Value> = bitstring.map(|bitstring| { + bitstring + .chunks_exact(sinsemilla::K) + .map(lebs2ip_k) + .collect() + }); + + // Get (x_p, y_p) for each word. + let generators: Value> = words.clone().map(|words| { + words + .iter() + .map(|word| SINSEMILLA_S[*word as usize]) + .collect() + }); + + // Convert `words` from `Value>` to `Vec>` + let words = words.transpose_vec(piece.num_words()); + + // Decompose message piece into `K`-bit pieces with a running sum `z`. + let zs = { + let mut zs = Vec::with_capacity(piece.num_words() + 1); + + // Copy message and initialize running sum `z` to decompose message in-circuit + let initial_z = piece.cell_value().copy_advice( + || "z_0 (copy of message piece)", + region, + config.base().bits, + offset, + )?; + zs.push(initial_z); + + // Assign cumulative sum such that for 0 <= i < n, + // z_i = 2^K * z_{i + 1} + m_{i + 1} + // => z_{i + 1} = (z_i - m_{i + 1}) / 2^K + // + // For a message piece m = m_1 + 2^K m_2 + ... + 2^{K(n-1)} m_n}, initialize z_0 = m. + // We end up with z_n = 0. (z_n is not directly encoded as a cell value; + // it is implicitly taken as 0 by adjusting the definition of m_{i+1}.) + let mut z = piece.field_elem(); + let inv_2_k = Value::known(pallas::Base::from_repr(INV_TWO_POW_K).unwrap()); + + // We do not assign the final z_n as it is constrained to be zero. + for (idx, word) in words[0..(words.len() - 1)].iter().enumerate() { + let word = word.map(|word| pallas::Base::from(word as u64)); + // z_{i + 1} = (z_i - m_{i + 1}) / 2^K + z = (z - word) * inv_2_k; + let cell = region.assign_advice( + || format!("z_{:?}", idx + 1), + config.base().bits, + offset + idx + 1, + || z, )?; + zs.push(cell) } - // Message piece as K * piece.length bitstring - let bitstring: Value> = piece.field_elem().map(|value| { - value - .to_le_bits() - .into_iter() - .take(sinsemilla::K * piece.num_words()) - .collect() - }); + zs + }; - let words: Value> = bitstring.map(|bitstring| { - bitstring - .chunks_exact(sinsemilla::K) - .map(lebs2ip_k) - .collect() - }); + // The accumulator x-coordinate provided by the caller MUST have been assigned + // within this region. - // Get (x_p, y_p) for each word. - let generators: Value> = words.clone().map(|words| { - words - .iter() - .map(|word| SINSEMILLA_S[*word as usize]) - .collect() - }); + let generators = generators.transpose_vec(piece.num_words()); - // Convert `words` from `Value>` to `Vec>` - let words = words.transpose_vec(piece.num_words()); + for (row, gen) in generators.iter().enumerate() { + let x_p = gen.map(|gen| gen.0); + let y_p = gen.map(|gen| gen.1); - // Decompose message piece into `K`-bit pieces with a running sum `z`. - let zs = { - let mut zs = Vec::with_capacity(piece.num_words() + 1); + // Assign `x_p` + region.assign_advice( + || "x_p", + config.base().double_and_add.x_p, + offset + row, + || x_p, + )?; - // Copy message and initialize running sum `z` to decompose message in-circuit - let initial_z = piece.cell_value().copy_advice( - || "z_0 (copy of message piece)", - region, - config.base.bits, - offset, + // Compute and assign `lambda_1` + let lambda_1 = { + let lambda_1 = (y_a.0 - y_p) * (x_a.value() - x_p).invert(); + + // Assign lambda_1 + region.assign_advice( + || "lambda_1", + config.base().double_and_add.lambda_1, + offset + row, + || lambda_1, )?; - zs.push(initial_z); - - // Assign cumulative sum such that for 0 <= i < n, - // z_i = 2^K * z_{i + 1} + m_{i + 1} - // => z_{i + 1} = (z_i - m_{i + 1}) / 2^K - // - // For a message piece m = m_1 + 2^K m_2 + ... + 2^{K(n-1)} m_n}, initialize z_0 = m. - // We end up with z_n = 0. (z_n is not directly encoded as a cell value; - // it is implicitly taken as 0 by adjusting the definition of m_{i+1}.) - let mut z = piece.field_elem(); - let inv_2_k = Value::known(pallas::Base::from_repr(INV_TWO_POW_K).unwrap()); - - // We do not assign the final z_n as it is constrained to be zero. - for (idx, word) in words[0..(words.len() - 1)].iter().enumerate() { - let word = word.map(|word| pallas::Base::from(word as u64)); - // z_{i + 1} = (z_i - m_{i + 1}) / 2^K - z = (z - word) * inv_2_k; - let cell = region.assign_advice( - || format!("z_{:?}", idx + 1), - config.base.bits, - offset + idx + 1, - || z, - )?; - zs.push(cell) - } - - zs - }; - // The accumulator x-coordinate provided by the caller MUST have been assigned - // within this region. + lambda_1 + }; - let generators = generators.transpose_vec(piece.num_words()); + // Compute `x_r` + let x_r = lambda_1.square() - x_a.value() - x_p; - for (row, gen) in generators.iter().enumerate() { - let x_p = gen.map(|gen| gen.0); - let y_p = gen.map(|gen| gen.1); + // Compute and assign `lambda_2` + let lambda_2 = { + let lambda_2 = y_a.0 * pallas::Base::from(2) * (x_a.value() - x_r).invert() - lambda_1; - // Assign `x_p` region.assign_advice( - || "x_p", - config.base.double_and_add.x_p, + || "lambda_2", + config.base().double_and_add.lambda_2, offset + row, - || x_p, + || lambda_2, )?; - // Compute and assign `lambda_1` - let lambda_1 = { - let lambda_1 = (y_a.0 - y_p) * (x_a.value() - x_p).invert(); - - // Assign lambda_1 - region.assign_advice( - || "lambda_1", - config.base.double_and_add.lambda_1, - offset + row, - || lambda_1, - )?; - - lambda_1 - }; - - // Compute `x_r` - let x_r = lambda_1.square() - x_a.value() - x_p; - - // Compute and assign `lambda_2` - let lambda_2 = { - let lambda_2 = - y_a.0 * pallas::Base::from(2) * (x_a.value() - x_r).invert() - lambda_1; - - region.assign_advice( - || "lambda_2", - config.base.double_and_add.lambda_2, - offset + row, - || lambda_2, - )?; - - lambda_2 - }; - - // Compute and assign `x_a` for the next row. - let x_a_new: X = { - let x_a_new = lambda_2.square() - x_a.value() - x_r; - - let x_a_cell = region.assign_advice( - || "x_a", - config.base.double_and_add.x_a, - offset + row + 1, - || x_a_new, - )?; - - x_a_cell.into() - }; - - // Compute y_a for the next row. - let y_a_new: Y = - (lambda_2 * (x_a.value() - x_a_new.value()) - y_a.0).into(); - - // Update the mutable `x_a`, `y_a` variables. - x_a = x_a_new; - y_a = y_a_new; - } + lambda_2 + }; + + // Compute and assign `x_a` for the next row. + let x_a_new: X = { + let x_a_new = lambda_2.square() - x_a.value() - x_r; + + let x_a_cell = region.assign_advice( + || "x_a", + config.base().double_and_add.x_a, + offset + row + 1, + || x_a_new, + )?; + + x_a_cell.into() + }; - Ok((x_a, y_a, zs)) + // Compute y_a for the next row. + let y_a_new: Y = (lambda_2 * (x_a.value() - x_a_new.value()) - y_a.0).into(); + + // Update the mutable `x_a`, `y_a` variables. + x_a = x_a_new; + y_a = y_a_new; } + + Ok((x_a, y_a, zs)) } /// The x-coordinate of the accumulator in a Sinsemilla hash instance. diff --git a/halo2_gadgets/src/sinsemilla/merkle.rs b/halo2_gadgets/src/sinsemilla/merkle.rs index 3128676b66..28d0efa907 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< @@ -196,8 +196,6 @@ pub mod tests { }; use crate::sinsemilla::chip::SinsemillaChipProps; - use crate::utilities::lookup_range_check::LookupRangeCheck; - use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; use rand::{rngs::OsRng, RngCore}; use std::{convert::TryInto, iter}; @@ -394,7 +392,7 @@ pub mod tests { let circuit = MyCircuit::default(); halo2_proofs::dev::CircuitLayout::default() - .show_labels(false) + .show_labels(true) .render(11, &circuit, &root) .unwrap(); } diff --git a/halo2_gadgets/src/sinsemilla/merkle/chip.rs b/halo2_gadgets/src/sinsemilla/merkle/chip.rs index 0d57fd3249..a2f85c8e69 100644 --- a/halo2_gadgets/src/sinsemilla/merkle/chip.rs +++ b/halo2_gadgets/src/sinsemilla/merkle/chip.rs @@ -1,32 +1,24 @@ //! Chip implementing a Merkle hash using Sinsemilla as the hash function. -use halo2_proofs::{ - circuit::{AssignedCell, Chip, Layouter, Value}, - plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, - poly::Rotation, -}; +use halo2_proofs::{circuit::{AssignedCell, Chip, Layouter, Value}, impl_trait_Chip_for, plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, poly::Rotation}; use pasta_curves::pallas; use super::MerkleInstructions; use crate::sinsemilla::chip::{SinsemillaChipProps, SinsemillaConfigProps}; -use crate::utilities::lookup_range_check::witness_short_generic; -use crate::{ - sinsemilla::{primitives as sinsemilla, MessagePiece}, - utilities::RangeConstrained, - { - ecc::FixedPoints, - sinsemilla::{ - chip::{SinsemillaChip, SinsemillaConfig}, - CommitDomains, HashDomains, SinsemillaInstructions, - }, - utilities::{ - cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions}, - UtilitiesInstructions, - }, +use crate::{sinsemilla::{primitives as sinsemilla, MessagePiece}, utilities::RangeConstrained, { + ecc::FixedPoints, + sinsemilla::{ + chip::{SinsemillaChip, SinsemillaConfig}, + CommitDomains, HashDomains, SinsemillaInstructions, }, -}; + utilities::{ + cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions}, + UtilitiesInstructions, + }, +}}; use group::ff::PrimeField; +use crate::sinsemilla_opt::chip::SinsemillaConfigOptimized; /// Configuration for the `MerkleChip` implementation. #[derive(Clone, Debug, PartialEq, Eq)] @@ -62,495 +54,499 @@ where config: MerkleConfig, } -impl Chip for MerkleChip -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, -{ - type Config = MerkleConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } +impl_trait_Chip_for!(MerkleChip, MerkleConfig); - fn loaded(&self) -> &Self::Loaded { - &() - } -} +#[macro_export] +macro_rules! impl_trait_MerkleChipProps_for_MerkleChip { + ($merkle_config_name: ident, $merkle_chip_type:ty, $merkle_config_type:ty, $sinsemilla_config_type:ty) => { + impl $merkle_chip_type + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, + { + /// Configures the [`MerkleChip`]. + pub fn configure( + meta: &mut ConstraintSystem, + sinsemilla_config: $sinsemilla_config_type, + ) -> $merkle_config_type { + // All five advice columns are equality-enabled by SinsemillaConfig. + let advices = sinsemilla_config.advices(); + let cond_swap_config = CondSwapChip::configure(meta, advices); + + // This selector enables the decomposition gate. + let q_decompose = meta.selector(); + + // Check that pieces have been decomposed correctly for Sinsemilla hash. + // + // + // a = a_0||a_1 = l || (bits 0..=239 of left) + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // c = bits 5..=254 of right + // + // The message pieces `a`, `b`, `c` are constrained by Sinsemilla to be + // 250 bits, 20 bits, and 250 bits respectively. + // + // The pieces and subpieces are arranged in the following configuration: + // | A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose | + // ------------------------------------------------------- + // | a | b | c | left | right | 1 | + // | z1_a | z1_b | b_1 | b_2 | l | 0 | + meta.create_gate("Decomposition check", |meta| { + let q_decompose = meta.query_selector(q_decompose); + let l_whole = meta.query_advice(advices[4], Rotation::next()); + + let two_pow_5 = pallas::Base::from(1 << 5); + let two_pow_10 = two_pow_5.square(); + + // a_whole is constrained by Sinsemilla to be 250 bits. + let a_whole = meta.query_advice(advices[0], Rotation::cur()); + // b_whole is constrained by Sinsemilla to be 20 bits. + let b_whole = meta.query_advice(advices[1], Rotation::cur()); + // c_whole is constrained by Sinsemilla to be 250 bits. + let c_whole = meta.query_advice(advices[2], Rotation::cur()); + let left_node = meta.query_advice(advices[3], Rotation::cur()); + let right_node = meta.query_advice(advices[4], Rotation::cur()); + + // a = a_0||a_1 = l || (bits 0..=239 of left) + // + // z_1 of SinsemillaHash(a) = a_1 + // => a_0 = a - (a_1 * 2^10) + let z1_a = meta.query_advice(advices[0], Rotation::next()); + let a_1 = z1_a; + // Derive a_0 (constrained by SinsemillaHash to be 10 bits) + let a_0 = a_whole - a_1.clone() * two_pow_10; + + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // The Orchard specification allows this representation to be non-canonical. + // + // + // z_1 of SinsemillaHash(b) = b_1 + 2^5 b_2 + // => b_0 = b - (z1_b * 2^10) + let z1_b = meta.query_advice(advices[1], Rotation::next()); + // b_1 has been constrained to be 5 bits outside this gate. + let b_1 = meta.query_advice(advices[2], Rotation::next()); + // b_2 has been constrained to be 5 bits outside this gate. + let b_2 = meta.query_advice(advices[3], Rotation::next()); + // Constrain b_1 + 2^5 b_2 = z1_b + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-bit-lengths?partial + let b1_b2_check = z1_b.clone() - (b_1.clone() + b_2.clone() * two_pow_5); + // Derive b_0 (constrained by SinsemillaHash to be 10 bits) + let b_0 = b_whole - (z1_b * two_pow_10); + + // Check that left = a_1 (240 bits) || b_0 (10 bits) || b_1 (5 bits) + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-decomposition?partial + let left_check = { + let reconstructed = { + let two_pow_240 = pallas::Base::from_u128(1 << 120).square(); + a_1 + (b_0 + b_1 * two_pow_10) * two_pow_240 + }; + reconstructed - left_node + }; + + // Check that right = b_2 (5 bits) || c (250 bits) + // The Orchard specification allows this representation to be non-canonical. + // + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-decomposition?partial + let right_check = b_2 + c_whole * two_pow_5 - right_node; + + Constraints::with_selector( + q_decompose, + [ + ("l_check", a_0 - l_whole), + ("left_check", left_check), + ("right_check", right_check), + ("b1_b2_check", b1_b2_check), + ], + ) + }); -impl MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, -{ - /// Configures the [`MerkleChip`]. - pub fn configure( - meta: &mut ConstraintSystem, - 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); - - // This selector enables the decomposition gate. - let q_decompose = meta.selector(); - - // Check that pieces have been decomposed correctly for Sinsemilla hash. - // - // - // a = a_0||a_1 = l || (bits 0..=239 of left) - // b = b_0||b_1||b_2 - // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) - // c = bits 5..=254 of right - // - // The message pieces `a`, `b`, `c` are constrained by Sinsemilla to be - // 250 bits, 20 bits, and 250 bits respectively. - // - // The pieces and subpieces are arranged in the following configuration: - // | A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose | - // ------------------------------------------------------- - // | a | b | c | left | right | 1 | - // | z1_a | z1_b | b_1 | b_2 | l | 0 | - meta.create_gate("Decomposition check", |meta| { - let q_decompose = meta.query_selector(q_decompose); - let l_whole = meta.query_advice(advices[4], Rotation::next()); - - let two_pow_5 = pallas::Base::from(1 << 5); - let two_pow_10 = two_pow_5.square(); - - // a_whole is constrained by Sinsemilla to be 250 bits. - let a_whole = meta.query_advice(advices[0], Rotation::cur()); - // b_whole is constrained by Sinsemilla to be 20 bits. - let b_whole = meta.query_advice(advices[1], Rotation::cur()); - // c_whole is constrained by Sinsemilla to be 250 bits. - let c_whole = meta.query_advice(advices[2], Rotation::cur()); - let left_node = meta.query_advice(advices[3], Rotation::cur()); - let right_node = meta.query_advice(advices[4], Rotation::cur()); - - // a = a_0||a_1 = l || (bits 0..=239 of left) - // - // z_1 of SinsemillaHash(a) = a_1 - // => a_0 = a - (a_1 * 2^10) - let z1_a = meta.query_advice(advices[0], Rotation::next()); - let a_1 = z1_a; - // Derive a_0 (constrained by SinsemillaHash to be 10 bits) - let a_0 = a_whole - a_1.clone() * two_pow_10; - - // b = b_0||b_1||b_2 - // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) - // The Orchard specification allows this representation to be non-canonical. - // - // - // z_1 of SinsemillaHash(b) = b_1 + 2^5 b_2 - // => b_0 = b - (z1_b * 2^10) - let z1_b = meta.query_advice(advices[1], Rotation::next()); - // b_1 has been constrained to be 5 bits outside this gate. - let b_1 = meta.query_advice(advices[2], Rotation::next()); - // b_2 has been constrained to be 5 bits outside this gate. - let b_2 = meta.query_advice(advices[3], Rotation::next()); - // Constrain b_1 + 2^5 b_2 = z1_b - // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-bit-lengths?partial - let b1_b2_check = z1_b.clone() - (b_1.clone() + b_2.clone() * two_pow_5); - // Derive b_0 (constrained by SinsemillaHash to be 10 bits) - let b_0 = b_whole - (z1_b * two_pow_10); - - // Check that left = a_1 (240 bits) || b_0 (10 bits) || b_1 (5 bits) - // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-decomposition?partial - let left_check = { - let reconstructed = { - let two_pow_240 = pallas::Base::from_u128(1 << 120).square(); - a_1 + (b_0 + b_1 * two_pow_10) * two_pow_240 - }; - reconstructed - left_node - }; - - // Check that right = b_2 (5 bits) || c (250 bits) - // The Orchard specification allows this representation to be non-canonical. - // - // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-decomposition?partial - let right_check = b_2 + c_whole * two_pow_5 - right_node; - - Constraints::with_selector( - q_decompose, - [ - ("l_check", a_0 - l_whole), - ("left_check", left_check), - ("right_check", right_check), - ("b1_b2_check", b1_b2_check), - ], - ) - }); - - MerkleConfig { - advices, - q_decompose, - cond_swap_config, - sinsemilla_config, + $merkle_config_name { + advices, + q_decompose, + cond_swap_config, + sinsemilla_config, + } + } + /// Constructs a [`MerkleChip`] given a [`MerkleConfig`]. + pub fn construct(config: $merkle_config_type) -> Self { + Self { config } + } } - } - - /// Constructs a [`MerkleChip`] given a [`MerkleConfig`]. - pub fn construct(config: MerkleConfig) -> Self { - MerkleChip { config } - } + }; } -impl - MerkleInstructions - for MerkleChip -where - Hash: HashDomains + Eq, - F: FixedPoints, - Commit: CommitDomains + Eq, -{ - #[allow(non_snake_case)] - fn hash_layer( - &self, - mut layouter: impl Layouter, - Q: pallas::Affine, - // l = MERKLE_DEPTH - layer - 1 - l: usize, - left: Self::Var, - right: Self::Var, - ) -> Result { - let config = self.config().clone(); - - // We need to hash `l || left || right`, where `l` is a 10-bit value. - // We allow `left` and `right` to be non-canonical 255-bit encodings. - // - // a = a_0||a_1 = l || (bits 0..=239 of left) - // b = b_0||b_1||b_2 - // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) - // c = bits 5..=254 of right - // - // We start by witnessing all of the individual pieces, and range-constraining the - // short pieces b_1 and b_2. - // - // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-bit-lengths?partial - - // `a = a_0||a_1` = `l` || (bits 0..=239 of `left`) - let a = MessagePiece::from_subpieces( - self.clone(), - layouter.namespace(|| "Witness a = a_0 || a_1"), - [ - RangeConstrained::bitrange_of(Value::known(&pallas::Base::from(l as u64)), 0..10), - RangeConstrained::bitrange_of(left.value(), 0..240), - ], - )?; - - // b = b_0 || b_1 || b_2 - // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) - let (b_1, b_2, b) = { - // b_0 = (bits 240..=249 of `left`) - let b_0 = RangeConstrained::bitrange_of(left.value(), 240..250); - - // b_1 = (bits 250..=254 of `left`) - // Constrain b_1 to 5 bits. - let b_1 = witness_short_generic( - &config.sinsemilla_config.lookup_config(), - layouter.namespace(|| "b_1"), - left.value(), - 250..(pallas::Base::NUM_BITS as usize), - )?; - - // b_2 = (bits 0..=4 of `right`) - // Constrain b_2 to 5 bits. - let b_2 = witness_short_generic( - &config.sinsemilla_config.lookup_config(), - layouter.namespace(|| "b_2"), - right.value(), - 0..5, - )?; - - let b = MessagePiece::from_subpieces( - self.clone(), - layouter.namespace(|| "Witness b = b_0 || b_1 || b_2"), - [b_0, b_1.value(), b_2.value()], - )?; - - (b_1, b_2, b) - }; - - // c = bits 5..=254 of `right` - let c = MessagePiece::from_subpieces( - self.clone(), - layouter.namespace(|| "Witness c"), - [RangeConstrained::bitrange_of( - right.value(), - 5..(pallas::Base::NUM_BITS as usize), - )], - )?; - - // hash = SinsemillaHash(Q, 𝑙⋆ || left⋆ || right⋆) - // - // `hash = ⊥` is handled internally to `SinsemillaChip::hash_to_point`: incomplete - // addition constraints allows ⊥ to occur, and then during synthesis it detects - // these edge cases and raises an error (aborting proof creation). - // - // Note that MerkleCRH as-defined maps ⊥ to 0. This is for completeness outside - // the circuit (so that the ⊥ does not propagate into the type system). The chip - // explicitly doesn't map ⊥ to 0; in fact it cannot, as doing so would require - // constraints that amount to using complete addition. The rationale for excluding - // this map is the same as why Sinsemilla uses incomplete addition: this situation - // yields a nontrivial discrete log relation, and by assumption it is hard to find - // these. - // - // https://p.z.cash/proto:merkle-crh-orchard - let (point, zs) = self.hash_to_point( - layouter.namespace(|| format!("hash at l = {}", l)), - Q, - vec![a.inner(), b.inner(), c.inner()].into(), - )?; - let hash = Self::extract(&point); - - // `SinsemillaChip::hash_to_point` returns the running sum for each `MessagePiece`. - // Grab the outputs we need for the decomposition constraints. - let z1_a = zs[0][1].clone(); - let z1_b = zs[1][1].clone(); - - // Check that the pieces have been decomposed properly. - // - // The pieces and subpieces are arranged in the following configuration: - // | A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose | - // ------------------------------------------------------- - // | a | b | c | left | right | 1 | - // | z1_a | z1_b | b_1 | b_2 | l | 0 | +impl_trait_MerkleChipProps_for_MerkleChip!( + MerkleConfig, + MerkleChip, + MerkleConfig, + SinsemillaConfig +); +#[macro_export] +macro_rules! impl_trait_MerkleInstructions_for_MerkleChip { + ($chip_type:ty) => { + impl + MerkleInstructions + for $chip_type + where + Hash: HashDomains + Eq, + F: FixedPoints, + Commit: CommitDomains + Eq, { - layouter.assign_region( - || "Check piece decomposition", - |mut region| { - // Set the fixed column `l` to the current l. - // Recall that l = MERKLE_DEPTH - layer - 1. - // The layer with 2^n nodes is called "layer n". - config.q_decompose.enable(&mut region, 0)?; - region.assign_advice_from_constant( - || format!("l {}", l), - config.advices[4], - 1, - pallas::Base::from(l as u64), + #[allow(non_snake_case)] + fn hash_layer( + &self, + mut layouter: impl Layouter, + Q: pallas::Affine, + // l = MERKLE_DEPTH - layer - 1 + l: usize, + left: Self::Var, + right: Self::Var, + ) -> Result { + let config = self.config().clone(); + + // We need to hash `l || left || right`, where `l` is a 10-bit value. + // We allow `left` and `right` to be non-canonical 255-bit encodings. + // + // a = a_0||a_1 = l || (bits 0..=239 of left) + // b = b_0||b_1||b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + // c = bits 5..=254 of right + // + // We start by witnessing all of the individual pieces, and range-constraining the + // short pieces b_1 and b_2. + // + // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-bit-lengths?partial + + // `a = a_0||a_1` = `l` || (bits 0..=239 of `left`) + let a = MessagePiece::from_subpieces( + self.clone(), + layouter.namespace(|| "Witness a = a_0 || a_1"), + [ + RangeConstrained::bitrange_of(Value::known(&pallas::Base::from(l as u64)), 0..10), + RangeConstrained::bitrange_of(left.value(), 0..240), + ], + )?; + + // b = b_0 || b_1 || b_2 + // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) + let (b_1, b_2, b) = { + // b_0 = (bits 240..=249 of `left`) + let b_0 = RangeConstrained::bitrange_of(left.value(), 240..250); + + // b_1 = (bits 250..=254 of `left`) + // Constrain b_1 to 5 bits. + let b_1 = RangeConstrained::witness_short( + &config.sinsemilla_config.lookup_config(), + layouter.namespace(|| "b_1"), + left.value(), + 250..(pallas::Base::NUM_BITS as usize), )?; - // Offset 0 - // Copy and assign `a` at the correct position. - a.inner().cell_value().copy_advice( - || "copy a", - &mut region, - config.advices[0], - 0, + // b_2 = (bits 0..=4 of `right`) + // Constrain b_2 to 5 bits. + let b_2 = RangeConstrained::witness_short( + &config.sinsemilla_config.lookup_config(), + layouter.namespace(|| "b_2"), + right.value(), + 0..5, )?; - // Copy and assign `b` at the correct position. - b.inner().cell_value().copy_advice( - || "copy b", - &mut region, - config.advices[1], - 0, + let b = MessagePiece::from_subpieces( + self.clone(), + layouter.namespace(|| "Witness b = b_0 || b_1 || b_2"), + [b_0, b_1.value(), b_2.value()], )?; - // Copy and assign `c` at the correct position. - c.inner().cell_value().copy_advice( - || "copy c", - &mut region, - config.advices[2], - 0, + + (b_1, b_2, b) + }; + + // c = bits 5..=254 of `right` + let c = MessagePiece::from_subpieces( + self.clone(), + layouter.namespace(|| "Witness c"), + [RangeConstrained::bitrange_of( + right.value(), + 5..(pallas::Base::NUM_BITS as usize), + )], + )?; + + // hash = SinsemillaHash(Q, 𝑙⋆ || left⋆ || right⋆) + // + // `hash = ⊥` is handled internally to `SinsemillaChip::hash_to_point`: incomplete + // addition constraints allows ⊥ to occur, and then during synthesis it detects + // these edge cases and raises an error (aborting proof creation). + // + // Note that MerkleCRH as-defined maps ⊥ to 0. This is for completeness outside + // the circuit (so that the ⊥ does not propagate into the type system). The chip + // explicitly doesn't map ⊥ to 0; in fact it cannot, as doing so would require + // constraints that amount to using complete addition. The rationale for excluding + // this map is the same as why Sinsemilla uses incomplete addition: this situation + // yields a nontrivial discrete log relation, and by assumption it is hard to find + // these. + // + // https://p.z.cash/proto:merkle-crh-orchard + let (point, zs) = self.hash_to_point( + layouter.namespace(|| format!("hash at l = {}", l)), + Q, + vec![a.inner(), b.inner(), c.inner()].into(), + )?; + let hash = Self::extract(&point); + + // `SinsemillaChip::hash_to_point` returns the running sum for each `MessagePiece`. + // Grab the outputs we need for the decomposition constraints. + let z1_a = zs[0][1].clone(); + let z1_b = zs[1][1].clone(); + + // Check that the pieces have been decomposed properly. + // + // The pieces and subpieces are arranged in the following configuration: + // | A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose | + // ------------------------------------------------------- + // | a | b | c | left | right | 1 | + // | z1_a | z1_b | b_1 | b_2 | l | 0 | + { + layouter.assign_region( + || "Check piece decomposition", + |mut region| { + // Set the fixed column `l` to the current l. + // Recall that l = MERKLE_DEPTH - layer - 1. + // The layer with 2^n nodes is called "layer n". + config.q_decompose.enable(&mut region, 0)?; + region.assign_advice_from_constant( + || format!("l {}", l), + config.advices[4], + 1, + pallas::Base::from(l as u64), + )?; + + // Offset 0 + // Copy and assign `a` at the correct position. + a.inner().cell_value().copy_advice( + || "copy a", + &mut region, + config.advices[0], + 0, + )?; + // Copy and assign `b` at the correct position. + b.inner().cell_value().copy_advice( + || "copy b", + &mut region, + config.advices[1], + 0, + )?; + // Copy and assign `c` at the correct position. + c.inner().cell_value().copy_advice( + || "copy c", + &mut region, + config.advices[2], + 0, + )?; + // Copy and assign the left node at the correct position. + left.copy_advice(|| "left", &mut region, config.advices[3], 0)?; + // Copy and assign the right node at the correct position. + right.copy_advice(|| "right", &mut region, config.advices[4], 0)?; + + // Offset 1 + // Copy and assign z_1 of SinsemillaHash(a) = a_1 + z1_a.copy_advice(|| "z1_a", &mut region, config.advices[0], 1)?; + // Copy and assign z_1 of SinsemillaHash(b) = b_1 + z1_b.copy_advice(|| "z1_b", &mut region, config.advices[1], 1)?; + // Copy `b_1`, which has been constrained to be a 5-bit value + b_1.inner() + .copy_advice(|| "b_1", &mut region, config.advices[2], 1)?; + // Copy `b_2`, which has been constrained to be a 5-bit value + b_2.inner() + .copy_advice(|| "b_2", &mut region, config.advices[3], 1)?; + + Ok(()) + }, )?; - // Copy and assign the left node at the correct position. - left.copy_advice(|| "left", &mut region, config.advices[3], 0)?; - // Copy and assign the right node at the correct position. - right.copy_advice(|| "right", &mut region, config.advices[4], 0)?; - - // Offset 1 - // Copy and assign z_1 of SinsemillaHash(a) = a_1 - z1_a.copy_advice(|| "z1_a", &mut region, config.advices[0], 1)?; - // Copy and assign z_1 of SinsemillaHash(b) = b_1 - z1_b.copy_advice(|| "z1_b", &mut region, config.advices[1], 1)?; - // Copy `b_1`, which has been constrained to be a 5-bit value - b_1.inner() - .copy_advice(|| "b_1", &mut region, config.advices[2], 1)?; - // Copy `b_2`, which has been constrained to be a 5-bit value - b_2.inner() - .copy_advice(|| "b_2", &mut region, config.advices[3], 1)?; - - Ok(()) - }, - )?; + } + + // Check layer hash output against Sinsemilla primitives hash + #[cfg(test)] + { + use crate::{sinsemilla::primitives::HashDomain, utilities::i2lebsp}; + + use group::ff::PrimeFieldBits; + + left.value() + .zip(right.value()) + .zip(hash.value()) + .assert_if_known(|((left, right), hash)| { + let l = i2lebsp::<10>(l as u64); + let left: Vec<_> = left + .to_le_bits() + .iter() + .by_vals() + .take(pallas::Base::NUM_BITS as usize) + .collect(); + let right: Vec<_> = right + .to_le_bits() + .iter() + .by_vals() + .take(pallas::Base::NUM_BITS as usize) + .collect(); + let merkle_crh = HashDomain::from_Q(Q.into()); + + let mut message = l.to_vec(); + message.extend_from_slice(&left); + message.extend_from_slice(&right); + + let expected = merkle_crh.hash(message.into_iter()).unwrap(); + + expected.to_repr() == hash.to_repr() + }); + } + + Ok(hash) + } } - - // Check layer hash output against Sinsemilla primitives hash - #[cfg(test)] + }; +} +impl_trait_MerkleInstructions_for_MerkleChip!(MerkleChip); + +#[macro_export] +macro_rules! impl_trait_UtilitiesInstructions_for { + ($chip_type:ty) => { + impl UtilitiesInstructions for $chip_type + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, { - use crate::{sinsemilla::primitives::HashDomain, utilities::i2lebsp}; - - use group::ff::PrimeFieldBits; - - left.value() - .zip(right.value()) - .zip(hash.value()) - .assert_if_known(|((left, right), hash)| { - let l = i2lebsp::<10>(l as u64); - let left: Vec<_> = left - .to_le_bits() - .iter() - .by_vals() - .take(pallas::Base::NUM_BITS as usize) - .collect(); - let right: Vec<_> = right - .to_le_bits() - .iter() - .by_vals() - .take(pallas::Base::NUM_BITS as usize) - .collect(); - let merkle_crh = HashDomain::from_Q(Q.into()); - - let mut message = l.to_vec(); - message.extend_from_slice(&left); - message.extend_from_slice(&right); - - let expected = merkle_crh.hash(message.into_iter()).unwrap(); - - expected.to_repr() == hash.to_repr() - }); + type Var = AssignedCell; } - - Ok(hash) - } + }; } - -impl UtilitiesInstructions for MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, -{ - type Var = AssignedCell; +impl_trait_UtilitiesInstructions_for!(MerkleChip); +#[macro_export] +macro_rules! impl_trait_CondSwapInstructions_for { + ($chip_type:ty) => { + impl CondSwapInstructions for $chip_type + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, + { + #[allow(clippy::type_complexity)] + fn swap( + &self, + layouter: impl Layouter, + pair: (Self::Var, Value), + swap: Value, + ) -> Result<(Self::Var, Self::Var), Error> { + let config = self.config.cond_swap_config.clone(); + 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 CondSwapInstructions for MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, -{ - #[allow(clippy::type_complexity)] - fn swap( - &self, - layouter: impl Layouter, - pair: (Self::Var, Value), - swap: Value, - ) -> Result<(Self::Var, Self::Var), Error> { - let config = self.config().cond_swap_config.clone(); - 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_trait_CondSwapInstructions_for!(MerkleChip); + +#[macro_export] +macro_rules! impl_trait_SinsemillaInstructions_for_MerkleChip { + ($merkle_chip_type:ty, $sinsemilla_chip_type:ty,$sinsemilla_chip_expr:expr) => { + impl SinsemillaInstructions + for $merkle_chip_type + where + Hash: HashDomains, + F: FixedPoints, + Commit: CommitDomains, + { + type CellValue = <$sinsemilla_chip_type as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::CellValue; + + type Message = <$sinsemilla_chip_type as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::Message; + type MessagePiece = <$sinsemilla_chip_type as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::MessagePiece; + type RunningSum = <$sinsemilla_chip_type as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::RunningSum; + + type X = <$sinsemilla_chip_type as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::X; + type NonIdentityPoint = <$sinsemilla_chip_type as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::NonIdentityPoint; + type FixedPoints = <$sinsemilla_chip_type as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::FixedPoints; + + type HashDomains = <$sinsemilla_chip_type as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::HashDomains; + type CommitDomains = <$sinsemilla_chip_type as SinsemillaInstructions< + pallas::Affine, + { sinsemilla::K }, + { sinsemilla::C }, + >>::CommitDomains; + + fn witness_message_piece( + &self, + layouter: impl Layouter, + value: Value, + num_words: usize, + ) -> Result { + let config = self.config.sinsemilla_config.clone(); + let chip = $sinsemilla_chip_expr(config); + chip.witness_message_piece(layouter, value, num_words) + } + + #[allow(non_snake_case)] + #[allow(clippy::type_complexity)] + fn hash_to_point( + &self, + layouter: impl Layouter, + Q: pallas::Affine, + message: Self::Message, + ) -> Result<(Self::NonIdentityPoint, Vec>), Error> { + let config = self.config().sinsemilla_config.clone(); + let chip = $sinsemilla_chip_expr(config); + chip.hash_to_point(layouter, Q, message) + } + + fn extract(point: &Self::NonIdentityPoint) -> Self::X { + point.x() + } + } + }; } -impl SinsemillaInstructions - for MerkleChip -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, -{ - type CellValue = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::CellValue; - - type Message = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::Message; - type MessagePiece = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::MessagePiece; - type RunningSum = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::RunningSum; - - type X = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::X; - type NonIdentityPoint = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::NonIdentityPoint; - type FixedPoints = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::FixedPoints; - - type HashDomains = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::HashDomains; - type CommitDomains = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::CommitDomains; - - fn witness_message_piece( - &self, - layouter: impl Layouter, - value: Value, - num_words: usize, - ) -> Result { - let config = self.config().sinsemilla_config.clone(); - let chip = SinsemillaChip::::construct(config); - chip.witness_message_piece(layouter, value, num_words) - } - - #[allow(non_snake_case)] - #[allow(clippy::type_complexity)] - fn hash_to_point( - &self, - layouter: impl Layouter, - Q: pallas::Affine, - message: Self::Message, - ) -> Result<(Self::NonIdentityPoint, Vec>), Error> { - let config = self.config().sinsemilla_config.clone(); - 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) - } -} +impl_trait_SinsemillaInstructions_for_MerkleChip!(MerkleChip, SinsemillaChip,SinsemillaChip::::construct); diff --git a/halo2_gadgets/src/sinsemilla_opt.rs b/halo2_gadgets/src/sinsemilla_opt.rs index 60a33834d8..917775292a 100644 --- a/halo2_gadgets/src/sinsemilla_opt.rs +++ b/halo2_gadgets/src/sinsemilla_opt.rs @@ -1,4 +1,29 @@ //! Optimized Sinsemilla gadgets. +use crate::sinsemilla::SinsemillaInstructions; +use halo2_proofs::circuit::Layouter; +use halo2_proofs::plonk::Error; +use pasta_curves::arithmetic::CurveAffine; + pub mod chip; pub mod merkle; + +/// The `ExtendedSinsemillaInstructions` trait extends the functionality of +/// the `SinsemillaInstructions` trait, providing additional function hash_to_point_with_private_init. +pub trait ExtendedSinsemillaInstructions: + SinsemillaInstructions +{ + /// Hashes a message to an ECC curve point. + /// This returns both the resulting point, as well as the message + /// decomposition in the form of intermediate values in a cumulative + /// 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>; +} diff --git a/halo2_gadgets/src/sinsemilla_opt/chip.rs b/halo2_gadgets/src/sinsemilla_opt/chip.rs index 0cb8148390..ae7eb5a64e 100644 --- a/halo2_gadgets/src/sinsemilla_opt/chip.rs +++ b/halo2_gadgets/src/sinsemilla_opt/chip.rs @@ -1,25 +1,26 @@ +//! Chip implementations for the Sinsemilla_optimized gadgets. + mod generator_table; mod hash_to_point; use crate::ecc::{chip::NonIdentityEccPoint, FixedPoints}; use crate::sinsemilla::message::{Message, MessagePiece}; use crate::sinsemilla::primitives as sinsemilla; -use halo2_proofs::{ - circuit::{AssignedCell, Chip, Layouter, Value}, - plonk::{ - Advice, Column, ConstraintSystem, Error, Expression, Fixed, TableColumn, VirtualCells, - }, - poly::Rotation, -}; +use halo2_proofs::{circuit::{AssignedCell, Chip, Layouter, Value}, impl_trait_Chip_for, plonk::{ + Advice, Column, ConstraintSystem, Error, Expression, Fixed, TableColumn, VirtualCells, +}, poly::Rotation}; use pasta_curves::pallas; -use pasta_curves::pallas::Base; +use pasta_curves::pallas::{Affine, Base}; use crate::sinsemilla::chip::{ create_common_config, SinsemillaChipProps, SinsemillaConfigCommon, SinsemillaConfigProps, }; +use crate::sinsemilla::primitives::{C, K}; use crate::sinsemilla::{CommitDomains, HashDomains, SinsemillaInstructions}; +use crate::sinsemilla_opt::ExtendedSinsemillaInstructions; use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; use generator_table::GeneratorTableConfigOptimized; +use crate::{impl_trait_SinsemillaInstructions_for_SinsemillaChip}; /// Configuration for the SinsemillaConfigOptimized hash chip #[derive(Eq, PartialEq, Clone, Debug)] @@ -85,12 +86,16 @@ where type SinsemillaConfigType = SinsemillaConfigOptimized; - type LookupType = (TableColumn, TableColumn, TableColumn, TableColumn); + type LookupTableColumnType = (TableColumn, TableColumn, TableColumn, TableColumn); fn base(&self) -> &SinsemillaConfigCommon { &self.config.base } + fn config(&self) -> Self::SinsemillaConfigType { + self.config.clone() + } + /// Reconstructs this chip from the given config. fn construct(config: Self::SinsemillaConfigType) -> Self { Self { config } @@ -112,7 +117,7 @@ where advices: [Column; 5], witness_pieces: Column, fixed_y_q: Column, - lookup: Self::LookupType, + lookup: Self::LookupTableColumnType, range_check: Self::RangeCheckConfigType, ) -> Self::SinsemillaConfigType { // Enable equality on all advice columns @@ -140,85 +145,18 @@ where } } -// TODO: remove duplicated code? -impl Chip for SinsemillaChipOptimized -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, -{ - type Config = SinsemillaConfigOptimized; - type Loaded = (); +impl_trait_Chip_for!(SinsemillaChipOptimized, SinsemillaConfigOptimized); +impl_trait_SinsemillaInstructions_for_SinsemillaChip!(SinsemillaChipOptimized); - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} - -// TODO: remove duplicated code? - -// Implement `SinsemillaInstructions` for `SinsemillaChip` -impl SinsemillaInstructions +// Implement `ExtendedSinsemillaInstructions` for `SinsemillaChip` +impl + ExtendedSinsemillaInstructions for SinsemillaChipOptimized where Hash: HashDomains, F: FixedPoints, Commit: CommitDomains, { - type CellValue = AssignedCell; - - type Message = Message; - type MessagePiece = MessagePiece; - - type RunningSum = Vec; - - type X = AssignedCell; - type NonIdentityPoint = NonIdentityEccPoint; - type FixedPoints = F; - - type HashDomains = Hash; - type CommitDomains = Commit; - - fn witness_message_piece( - &self, - mut layouter: impl Layouter, - field_elem: Value, - num_words: usize, - ) -> Result { - let config = self.config().clone(); - - let cell = layouter.assign_region( - || "witness message piece", - |mut region| { - region.assign_advice( - || "witness message piece", - config.base.witness_pieces, - 0, - || field_elem, - ) - }, - )?; - Ok(MessagePiece::new(cell, num_words)) - } - - #[allow(non_snake_case)] - #[allow(clippy::type_complexity)] - fn hash_to_point( - &self, - mut layouter: impl Layouter, - Q: pallas::Affine, - message: Self::Message, - ) -> Result<(Self::NonIdentityPoint, Vec), Error> { - layouter.assign_region( - || "hash_to_point", - |mut region| self.hash_message(&mut region, Q, &message), - ) - } - #[allow(non_snake_case)] #[allow(clippy::type_complexity)] fn hash_to_point_with_private_init( @@ -232,7 +170,4 @@ where |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_opt/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs index e9d387a655..2cf1d95e64 100644 --- a/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla_opt/chip/hash_to_point.rs @@ -1,30 +1,24 @@ use super::{NonIdentityEccPoint, SinsemillaChipOptimized}; +use crate::sinsemilla::chip::hash_to_point::{ + hash_all_pieces, verify_sinsemilla_hash, EccPointQ, X, Y, +}; +use crate::sinsemilla::{CommitDomains, HashDomains, SinsemillaInstructions}; use crate::{ ecc::FixedPoints, - sinsemilla::primitives::{self as sinsemilla, lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S}, + sinsemilla::primitives::{self as sinsemilla}, }; - -use ff::Field; use halo2_proofs::{ - circuit::{AssignedCell, Chip, Region, Value}, + circuit::{AssignedCell, Chip, Region}, plonk::{Assigned, Error}, }; - -use group::ff::{PrimeField, PrimeFieldBits}; use pasta_curves::{arithmetic::CurveAffine, pallas}; -use crate::sinsemilla::{CommitDomains, HashDomains, SinsemillaInstructions}; -use std::ops::Deref; -// TODO: SinsemillaChip to SinsemillaChipTraits - impl SinsemillaChipOptimized where Hash: HashDomains, Fixed: FixedPoints, Commit: CommitDomains, { - // TODO: simplify three hash_message functions - /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). #[allow(non_snake_case)] #[allow(clippy::type_complexity)] @@ -46,16 +40,9 @@ where > { let (offset, x_a, y_a) = self.public_initialization(region, Q)?; - let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?; + let (x_a, y_a, zs_sum) = hash_all_pieces(self, region, offset, message, x_a, y_a)?; - // TODO: how to call verify_sinsemilla_hash - 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, - )) + verify_sinsemilla_hash(message, EccPointQ::PublicPoint(Q), x_a, y_a, zs_sum) } /// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial). @@ -79,55 +66,9 @@ where > { 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}; + let (x_a, y_a, zs_sum) = hash_all_pieces(self, region, offset, message, x_a, y_a)?; - 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())) - .zip(Q.point()) - .assert_if_known(|((field_elems, (x_a, y_a)), Q)| { - // 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, - )) + verify_sinsemilla_hash(message, EccPointQ::PrivatePoint(Q), x_a, y_a, zs_sum) } #[allow(non_snake_case)] @@ -227,329 +168,4 @@ where Ok((offset, x_a, y_a)) } - - #[allow(clippy::type_complexity)] - /// Hash `message` from the initial point `Q`. - fn hash_all_pieces( - &self, - region: &mut Region<'_, pallas::Base>, - mut offset: usize, - message: &>::Message, - mut x_a: X, - mut y_a: Y, - ) -> Result< - ( - X, - AssignedCell, pallas::Base>, - Vec>>, - ), - Error, - > { - let config = self.config().clone(); - - let mut zs_sum: Vec>> = Vec::new(); - - // Hash each piece in the message. - for (idx, piece) in message.iter().enumerate() { - let final_piece = idx == message.len() - 1; - - // The value of the accumulator after this piece is processed. - let (x, y, zs) = self.hash_piece(region, offset, piece, x_a, y_a, final_piece)?; - - // Since each message word takes one row to process, we increase - // the offset by `piece.num_words` on each iteration. - offset += piece.num_words(); - - // Update the accumulator to the latest value. - x_a = x; - y_a = y; - zs_sum.push(zs); - } - - // Assign the final y_a. - let y_a = { - // Assign the final y_a. - let y_a_cell = region.assign_advice( - || "y_a", - config.base.double_and_add.lambda_1, - offset, - || y_a.0, - )?; - - // Assign lambda_2 and x_p zero values since they are queried - // in the gate. (The actual values do not matter since they are - // multiplied by zero.) - { - region.assign_advice( - || "dummy lambda2", - config.base.double_and_add.lambda_2, - offset, - || Value::known(pallas::Base::zero()), - )?; - region.assign_advice( - || "dummy x_p", - config.base.double_and_add.x_p, - offset, - || Value::known(pallas::Base::zero()), - )?; - } - - y_a_cell - }; - - Ok((x_a, y_a, zs_sum)) - } - - #[allow(clippy::type_complexity)] - /// Hashes a message piece containing `piece.length` number of `K`-bit words. - /// - /// To avoid a duplicate assignment, the accumulator x-coordinate provided - /// by the caller is not copied. This only works because `hash_piece()` is - /// an internal API. Before this call to `hash_piece()`, x_a MUST have been - /// already assigned within this region at the correct offset. - fn hash_piece( - &self, - region: &mut Region<'_, pallas::Base>, - offset: usize, - piece: &>::MessagePiece, - mut x_a: X, - mut y_a: Y, - final_piece: bool, - ) -> Result< - ( - X, - Y, - Vec>, - ), - Error, - > { - let config = self.config().clone(); - - // Selector assignments - { - // Enable `q_sinsemilla1` selector on every row. - for row in 0..piece.num_words() { - config.base.q_sinsemilla1.enable(region, offset + row)?; - } - - // Set `q_sinsemilla2` fixed column to 1 on every row but the last. - for row in 0..(piece.num_words() - 1) { - region.assign_fixed( - || "q_s2 = 1", - config.base.q_sinsemilla2, - offset + row, - || Value::known(pallas::Base::one()), - )?; - } - - // Set `q_sinsemilla2` fixed column to 0 on the last row if this is - // not the final piece, or to 2 on the last row of the final piece. - region.assign_fixed( - || { - if final_piece { - "q_s2 for final piece" - } else { - "q_s2 between pieces" - } - }, - config.base.q_sinsemilla2, - offset + piece.num_words() - 1, - || { - Value::known(if final_piece { - pallas::Base::from(2) - } else { - pallas::Base::zero() - }) - }, - )?; - } - - // Message piece as K * piece.length bitstring - let bitstring: Value> = piece.field_elem().map(|value| { - value - .to_le_bits() - .into_iter() - .take(sinsemilla::K * piece.num_words()) - .collect() - }); - - let words: Value> = bitstring.map(|bitstring| { - bitstring - .chunks_exact(sinsemilla::K) - .map(lebs2ip_k) - .collect() - }); - - // Get (x_p, y_p) for each word. - let generators: Value> = words.clone().map(|words| { - words - .iter() - .map(|word| SINSEMILLA_S[*word as usize]) - .collect() - }); - - // Convert `words` from `Value>` to `Vec>` - let words = words.transpose_vec(piece.num_words()); - - // Decompose message piece into `K`-bit pieces with a running sum `z`. - let zs = { - let mut zs = Vec::with_capacity(piece.num_words() + 1); - - // Copy message and initialize running sum `z` to decompose message in-circuit - let initial_z = piece.cell_value().copy_advice( - || "z_0 (copy of message piece)", - region, - config.base.bits, - offset, - )?; - zs.push(initial_z); - - // Assign cumulative sum such that for 0 <= i < n, - // z_i = 2^K * z_{i + 1} + m_{i + 1} - // => z_{i + 1} = (z_i - m_{i + 1}) / 2^K - // - // For a message piece m = m_1 + 2^K m_2 + ... + 2^{K(n-1)} m_n}, initialize z_0 = m. - // We end up with z_n = 0. (z_n is not directly encoded as a cell value; - // it is implicitly taken as 0 by adjusting the definition of m_{i+1}.) - let mut z = piece.field_elem(); - let inv_2_k = Value::known(pallas::Base::from_repr(INV_TWO_POW_K).unwrap()); - - // We do not assign the final z_n as it is constrained to be zero. - for (idx, word) in words[0..(words.len() - 1)].iter().enumerate() { - let word = word.map(|word| pallas::Base::from(word as u64)); - // z_{i + 1} = (z_i - m_{i + 1}) / 2^K - z = (z - word) * inv_2_k; - let cell = region.assign_advice( - || format!("z_{:?}", idx + 1), - config.base.bits, - offset + idx + 1, - || z, - )?; - zs.push(cell) - } - - zs - }; - - // The accumulator x-coordinate provided by the caller MUST have been assigned - // within this region. - - let generators = generators.transpose_vec(piece.num_words()); - - for (row, gen) in generators.iter().enumerate() { - let x_p = gen.map(|gen| gen.0); - let y_p = gen.map(|gen| gen.1); - - // Assign `x_p` - region.assign_advice( - || "x_p", - config.base.double_and_add.x_p, - offset + row, - || x_p, - )?; - - // Compute and assign `lambda_1` - let lambda_1 = { - let lambda_1 = (y_a.0 - y_p) * (x_a.value() - x_p).invert(); - - // Assign lambda_1 - region.assign_advice( - || "lambda_1", - config.base.double_and_add.lambda_1, - offset + row, - || lambda_1, - )?; - - lambda_1 - }; - - // Compute `x_r` - let x_r = lambda_1.square() - x_a.value() - x_p; - - // Compute and assign `lambda_2` - let lambda_2 = { - let lambda_2 = - y_a.0 * pallas::Base::from(2) * (x_a.value() - x_r).invert() - lambda_1; - - region.assign_advice( - || "lambda_2", - config.base.double_and_add.lambda_2, - offset + row, - || lambda_2, - )?; - - lambda_2 - }; - - // Compute and assign `x_a` for the next row. - let x_a_new: X = { - let x_a_new = lambda_2.square() - x_a.value() - x_r; - - let x_a_cell = region.assign_advice( - || "x_a", - config.base.double_and_add.x_a, - offset + row + 1, - || x_a_new, - )?; - - x_a_cell.into() - }; - - // Compute y_a for the next row. - let y_a_new: Y = - (lambda_2 * (x_a.value() - x_a_new.value()) - y_a.0).into(); - - // Update the mutable `x_a`, `y_a` variables. - x_a = x_a_new; - y_a = y_a_new; - } - - Ok((x_a, y_a, zs)) - } -} - -/// The x-coordinate of the accumulator in a Sinsemilla hash instance. -pub(crate) struct X(pub(crate) AssignedCell, F>); - -impl From, F>> for X { - fn from(cell_value: AssignedCell, F>) -> Self { - X(cell_value) - } -} - -impl Deref for X { - type Target = AssignedCell, F>; - - fn deref(&self) -> &AssignedCell, F> { - &self.0 - } -} - -/// The y-coordinate of the accumulator in a Sinsemilla hash instance. -/// -/// 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`. -pub(crate) struct Y(pub(crate) Value>); - -impl From>> for Y { - fn from(value: Value>) -> Self { - Y(value) - } -} - -impl Deref for Y { - type Target = Value>; - - fn deref(&self) -> &Value> { - &self.0 - } } diff --git a/halo2_gadgets/src/sinsemilla_opt/merkle.rs b/halo2_gadgets/src/sinsemilla_opt/merkle.rs index 997c8bedb8..7e6174c5a3 100644 --- a/halo2_gadgets/src/sinsemilla_opt/merkle.rs +++ b/halo2_gadgets/src/sinsemilla_opt/merkle.rs @@ -7,6 +7,7 @@ use halo2_proofs::{ use pasta_curves::arithmetic::CurveAffine; use crate::sinsemilla::{HashDomains, SinsemillaInstructions}; +use crate::sinsemilla::merkle::{MerkleInstructions, MerklePath}; use crate::utilities::{cond_swap::CondSwapInstructions, i2lebsp, UtilitiesInstructions}; pub mod chip; @@ -14,160 +15,7 @@ pub mod chip; /// SWU hash-to-curve personalization for the Merkle CRH generator pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH"; -/// Instructions to check the validity of a Merkle path of a given `PATH_LENGTH`. -/// The hash function used is a Sinsemilla instance with `K`-bit words. -/// The hash function can process `MAX_WORDS` words. -pub trait MerkleInstructions< - C: CurveAffine, - const PATH_LENGTH: usize, - const K: usize, - const MAX_WORDS: usize, ->: - SinsemillaInstructions - + CondSwapInstructions - + UtilitiesInstructions - + Chip -{ - /// Compute MerkleCRH for a given `layer`. The hash that computes the root - /// is at layer 0, and the hashes that are applied to two leaves are at - /// layer `MERKLE_DEPTH - 1` = layer 31. - #[allow(non_snake_case)] - fn hash_layer( - &self, - layouter: impl Layouter, - Q: C, - l: usize, - left: Self::Var, - right: Self::Var, - ) -> Result; -} - -/// Gadget representing a Merkle path that proves a leaf exists in a Merkle tree at a -/// specific position. -#[derive(Clone, Debug)] -pub struct MerklePath< - C: CurveAffine, - MerkleChipOptimized, - const PATH_LENGTH: usize, - const K: usize, - const MAX_WORDS: usize, - const PAR: usize, -> where - MerkleChipOptimized: MerkleInstructions + Clone, -{ - chips: [MerkleChipOptimized; PAR], - domain: MerkleChipOptimized::HashDomains, - leaf_pos: Value, - // The Merkle path is ordered from leaves to root. - path: Value<[C::Base; PATH_LENGTH]>, -} - -impl< - C: CurveAffine, - MerkleChipOptimized, - const PATH_LENGTH: usize, - const K: usize, - const MAX_WORDS: usize, - const PAR: usize, - > MerklePath -where - MerkleChipOptimized: MerkleInstructions + Clone, -{ - /// Constructs a [`MerklePath`]. - /// - /// A circuit may have many more columns available than are required by a single - /// `MerkleChipOptimized`. To make better use of the available circuit area, the `MerklePath` - /// gadget will distribute its path hashing across each `MerkleChipOptimized` in `chips`, such - /// that each chip processes `ceil(PATH_LENGTH / PAR)` layers (with the last chip - /// processing fewer layers if the division is inexact). - pub fn construct( - chips: [MerkleChipOptimized; PAR], - domain: MerkleChipOptimized::HashDomains, - leaf_pos: Value, - path: Value<[C::Base; PATH_LENGTH]>, - ) -> Self { - assert_ne!(PAR, 0); - Self { - chips, - domain, - leaf_pos, - path, - } - } -} - -#[allow(non_snake_case)] -impl< - C: CurveAffine, - MerkleChipOptimized, - const PATH_LENGTH: usize, - const K: usize, - const MAX_WORDS: usize, - const PAR: usize, - > MerklePath -where - MerkleChipOptimized: MerkleInstructions + Clone, -{ - /// Calculates the root of the tree containing the given leaf at this Merkle path. - /// - /// Implements [Zcash Protocol Specification Section 4.9: Merkle Path Validity][merklepath]. - /// - /// [merklepath]: https://zips.z.cash/protocol/protocol.pdf#merklepath - pub fn calculate_root( - &self, - mut layouter: impl Layouter, - leaf: MerkleChipOptimized::Var, - ) -> Result { - // Each chip processes `ceil(PATH_LENGTH / PAR)` layers. - let layers_per_chip = (PATH_LENGTH + PAR - 1) / PAR; - - // Assign each layer to a chip. - let chips = (0..PATH_LENGTH).map(|i| self.chips[i / layers_per_chip].clone()); - - // The Merkle path is ordered from leaves to root, which is consistent with the - // little-endian representation of `pos` below. - let path = self.path.transpose_array(); - - // Get position as a PATH_LENGTH-bit bitstring (little-endian bit order). - let pos: [Value; PATH_LENGTH] = { - let pos: Value<[bool; PATH_LENGTH]> = self.leaf_pos.map(|pos| i2lebsp(pos as u64)); - pos.transpose_array() - }; - let Q = self.domain.Q(); - - let mut node = leaf; - for (l, ((sibling, pos), chip)) in path.iter().zip(pos.iter()).zip(chips).enumerate() { - // `l` = MERKLE_DEPTH - layer - 1, which is the index obtained from - // enumerating this Merkle path (going from leaf to root). - // For example, when `layer = 31` (the first sibling on the Merkle path), - // we have `l` = 32 - 31 - 1 = 0. - // On the other hand, when `layer = 0` (the final sibling on the Merkle path), - // we have `l` = 32 - 0 - 1 = 31. - - // Constrain which of (node, sibling) is (left, right) with a conditional swap - // tied to the current bit of the position. - let pair = { - let pair = (node, *sibling); - - // Swap node and sibling if needed - chip.swap(layouter.namespace(|| "node position"), pair, *pos)? - }; - - // Compute the node in layer l from its children: - // M^l_i = MerkleCRH(l, M^{l+1}_{2i}, M^{l+1}_{2i+1}) - node = chip.hash_layer( - layouter.namespace(|| format!("MerkleCRH({}, left, right)", l)), - Q, - l, - pair.0, - pair.1, - )?; - } - - Ok(node) - } -} #[cfg(test)] pub mod tests { @@ -195,7 +43,6 @@ pub mod tests { }; use crate::sinsemilla::chip::SinsemillaChipProps; - use crate::utilities::lookup_range_check::LookupRangeCheck; use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; use rand::{rngs::OsRng, RngCore}; use std::{convert::TryInto, iter}; @@ -390,13 +237,14 @@ pub mod tests { fn print_merkle_chip() { use plotters::prelude::*; - let root = BitMapBackend::new("merkle-path-layout.png", (1024, 7680)).into_drawing_area(); + let root = BitMapBackend::new("merkle-path-optimized-layout.png", (1024, 7680)) + .into_drawing_area(); root.fill(&WHITE).unwrap(); let root = root.titled("MerkleCRH Path", ("sans-serif", 60)).unwrap(); let circuit = MyCircuit::default(); halo2_proofs::dev::CircuitLayout::default() - .show_labels(false) + .show_labels(true) .render(11, &circuit, &root) .unwrap(); } diff --git a/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs b/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs index 474de3044d..407cebd5bf 100644 --- a/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs +++ b/halo2_gadgets/src/sinsemilla_opt/merkle/chip.rs @@ -1,29 +1,20 @@ //! Chip implementing a Merkle hash using Sinsemilla as the hash function. -use halo2_proofs::{ - circuit::{AssignedCell, Chip, Layouter, Value}, - plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, - poly::Rotation, -}; +use halo2_proofs::{circuit::{AssignedCell, Chip, Layouter, Value}, impl_trait_Chip_for, plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, poly::Rotation}; use pasta_curves::pallas; use super::MerkleInstructions; use crate::sinsemilla::chip::{SinsemillaChipProps, SinsemillaConfigProps}; use crate::sinsemilla_opt::chip::{SinsemillaChipOptimized, SinsemillaConfigOptimized}; -use crate::utilities::lookup_range_check::witness_short_generic; -use crate::{ - sinsemilla::{primitives as sinsemilla, MessagePiece}, - utilities::RangeConstrained, - { - ecc::FixedPoints, - sinsemilla::{CommitDomains, HashDomains, SinsemillaInstructions}, - utilities::{ - cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions}, - UtilitiesInstructions, - }, +use crate::{sinsemilla::{primitives as sinsemilla, MessagePiece}, utilities::RangeConstrained, { + ecc::FixedPoints, + sinsemilla::{CommitDomains, HashDomains, SinsemillaInstructions}, + utilities::{ + cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions}, + UtilitiesInstructions, }, -}; +}, impl_trait_CondSwapInstructions_for, impl_trait_SinsemillaInstructions_for_MerkleChip, impl_trait_UtilitiesInstructions_for, impl_trait_MerkleInstructions_for_MerkleChip, impl_trait_MerkleChipProps_for_MerkleChip}; use group::ff::PrimeField; /// Configuration for the `MerkleChipOptimized` implementation. @@ -60,495 +51,14 @@ where config: MerkleConfigOptimized, } -impl Chip for MerkleChipOptimized -where - Hash: HashDomains, - Fixed: FixedPoints, - Commit: CommitDomains, -{ - type Config = MerkleConfigOptimized; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} - -impl MerkleChipOptimized -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, -{ - /// Configures the [`MerkleChipOptimized`]. - pub fn configure( - meta: &mut ConstraintSystem, - sinsemilla_config: SinsemillaConfigOptimized, - ) -> MerkleConfigOptimized { - // All five advice columns are equality-enabled by SinsemillaConfigOptimized. - let advices = sinsemilla_config.advices(); - let cond_swap_config = CondSwapChip::configure(meta, advices); - - // This selector enables the decomposition gate. - let q_decompose = meta.selector(); - - // Check that pieces have been decomposed correctly for Sinsemilla hash. - // - // - // a = a_0||a_1 = l || (bits 0..=239 of left) - // b = b_0||b_1||b_2 - // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) - // c = bits 5..=254 of right - // - // The message pieces `a`, `b`, `c` are constrained by Sinsemilla to be - // 250 bits, 20 bits, and 250 bits respectively. - // - // The pieces and subpieces are arranged in the following configuration: - // | A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose | - // ------------------------------------------------------- - // | a | b | c | left | right | 1 | - // | z1_a | z1_b | b_1 | b_2 | l | 0 | - meta.create_gate("Decomposition check", |meta| { - let q_decompose = meta.query_selector(q_decompose); - let l_whole = meta.query_advice(advices[4], Rotation::next()); - - let two_pow_5 = pallas::Base::from(1 << 5); - let two_pow_10 = two_pow_5.square(); - - // a_whole is constrained by Sinsemilla to be 250 bits. - let a_whole = meta.query_advice(advices[0], Rotation::cur()); - // b_whole is constrained by Sinsemilla to be 20 bits. - let b_whole = meta.query_advice(advices[1], Rotation::cur()); - // c_whole is constrained by Sinsemilla to be 250 bits. - let c_whole = meta.query_advice(advices[2], Rotation::cur()); - let left_node = meta.query_advice(advices[3], Rotation::cur()); - let right_node = meta.query_advice(advices[4], Rotation::cur()); - - // a = a_0||a_1 = l || (bits 0..=239 of left) - // - // z_1 of SinsemillaHash(a) = a_1 - // => a_0 = a - (a_1 * 2^10) - let z1_a = meta.query_advice(advices[0], Rotation::next()); - let a_1 = z1_a; - // Derive a_0 (constrained by SinsemillaHash to be 10 bits) - let a_0 = a_whole - a_1.clone() * two_pow_10; - - // b = b_0||b_1||b_2 - // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) - // The Orchard specification allows this representation to be non-canonical. - // - // - // z_1 of SinsemillaHash(b) = b_1 + 2^5 b_2 - // => b_0 = b - (z1_b * 2^10) - let z1_b = meta.query_advice(advices[1], Rotation::next()); - // b_1 has been constrained to be 5 bits outside this gate. - let b_1 = meta.query_advice(advices[2], Rotation::next()); - // b_2 has been constrained to be 5 bits outside this gate. - let b_2 = meta.query_advice(advices[3], Rotation::next()); - // Constrain b_1 + 2^5 b_2 = z1_b - // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-bit-lengths?partial - let b1_b2_check = z1_b.clone() - (b_1.clone() + b_2.clone() * two_pow_5); - // Derive b_0 (constrained by SinsemillaHash to be 10 bits) - let b_0 = b_whole - (z1_b * two_pow_10); - - // Check that left = a_1 (240 bits) || b_0 (10 bits) || b_1 (5 bits) - // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-decomposition?partial - let left_check = { - let reconstructed = { - let two_pow_240 = pallas::Base::from_u128(1 << 120).square(); - a_1 + (b_0 + b_1 * two_pow_10) * two_pow_240 - }; - reconstructed - left_node - }; - - // Check that right = b_2 (5 bits) || c (250 bits) - // The Orchard specification allows this representation to be non-canonical. - // - // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-decomposition?partial - let right_check = b_2 + c_whole * two_pow_5 - right_node; - - Constraints::with_selector( - q_decompose, - [ - ("l_check", a_0 - l_whole), - ("left_check", left_check), - ("right_check", right_check), - ("b1_b2_check", b1_b2_check), - ], - ) - }); - - MerkleConfigOptimized { - advices, - q_decompose, - cond_swap_config, - sinsemilla_config, - } - } - - /// Constructs a [`MerkleChipOptimized`] given a [`MerkleConfigOptimized`]. - pub fn construct(config: MerkleConfigOptimized) -> Self { - MerkleChipOptimized { config } - } -} - -impl - MerkleInstructions - for MerkleChipOptimized -where - Hash: HashDomains + Eq, - F: FixedPoints, - Commit: CommitDomains + Eq, -{ - #[allow(non_snake_case)] - fn hash_layer( - &self, - mut layouter: impl Layouter, - Q: pallas::Affine, - // l = MERKLE_DEPTH - layer - 1 - l: usize, - left: Self::Var, - right: Self::Var, - ) -> Result { - let config = self.config().clone(); - - // We need to hash `l || left || right`, where `l` is a 10-bit value. - // We allow `left` and `right` to be non-canonical 255-bit encodings. - // - // a = a_0||a_1 = l || (bits 0..=239 of left) - // b = b_0||b_1||b_2 - // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) - // c = bits 5..=254 of right - // - // We start by witnessing all of the individual pieces, and range-constraining the - // short pieces b_1 and b_2. - // - // https://p.z.cash/halo2-0.1:sinsemilla-merkle-crh-bit-lengths?partial - - // `a = a_0||a_1` = `l` || (bits 0..=239 of `left`) - let a = MessagePiece::from_subpieces( - self.clone(), - layouter.namespace(|| "Witness a = a_0 || a_1"), - [ - RangeConstrained::bitrange_of(Value::known(&pallas::Base::from(l as u64)), 0..10), - RangeConstrained::bitrange_of(left.value(), 0..240), - ], - )?; - - // b = b_0 || b_1 || b_2 - // = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right) - let (b_1, b_2, b) = { - // b_0 = (bits 240..=249 of `left`) - let b_0 = RangeConstrained::bitrange_of(left.value(), 240..250); - - // b_1 = (bits 250..=254 of `left`) - // Constrain b_1 to 5 bits. - let b_1 = witness_short_generic( - &config.sinsemilla_config.lookup_config(), - layouter.namespace(|| "b_1"), - left.value(), - 250..(pallas::Base::NUM_BITS as usize), - )?; - - // b_2 = (bits 0..=4 of `right`) - // Constrain b_2 to 5 bits. - let b_2 = witness_short_generic( - &config.sinsemilla_config.lookup_config(), - layouter.namespace(|| "b_2"), - right.value(), - 0..5, - )?; - - let b = MessagePiece::from_subpieces( - self.clone(), - layouter.namespace(|| "Witness b = b_0 || b_1 || b_2"), - [b_0, b_1.value(), b_2.value()], - )?; - - (b_1, b_2, b) - }; - - // c = bits 5..=254 of `right` - let c = MessagePiece::from_subpieces( - self.clone(), - layouter.namespace(|| "Witness c"), - [RangeConstrained::bitrange_of( - right.value(), - 5..(pallas::Base::NUM_BITS as usize), - )], - )?; - - // hash = SinsemillaHash(Q, 𝑙⋆ || left⋆ || right⋆) - // - // `hash = ⊥` is handled internally to `SinsemillaChipOptimized::hash_to_point`: incomplete - // addition constraints allows ⊥ to occur, and then during synthesis it detects - // these edge cases and raises an error (aborting proof creation). - // - // Note that MerkleCRH as-defined maps ⊥ to 0. This is for completeness outside - // the circuit (so that the ⊥ does not propagate into the type system). The chip - // explicitly doesn't map ⊥ to 0; in fact it cannot, as doing so would require - // constraints that amount to using complete addition. The rationale for excluding - // this map is the same as why Sinsemilla uses incomplete addition: this situation - // yields a nontrivial discrete log relation, and by assumption it is hard to find - // these. - // - // https://p.z.cash/proto:merkle-crh-orchard - let (point, zs) = self.hash_to_point( - layouter.namespace(|| format!("hash at l = {}", l)), - Q, - vec![a.inner(), b.inner(), c.inner()].into(), - )?; - let hash = Self::extract(&point); - - // `SinsemillaChipOptimized::hash_to_point` returns the running sum for each `MessagePiece`. - // Grab the outputs we need for the decomposition constraints. - let z1_a = zs[0][1].clone(); - let z1_b = zs[1][1].clone(); - - // Check that the pieces have been decomposed properly. - // - // The pieces and subpieces are arranged in the following configuration: - // | A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose | - // ------------------------------------------------------- - // | a | b | c | left | right | 1 | - // | z1_a | z1_b | b_1 | b_2 | l | 0 | - { - layouter.assign_region( - || "Check piece decomposition", - |mut region| { - // Set the fixed column `l` to the current l. - // Recall that l = MERKLE_DEPTH - layer - 1. - // The layer with 2^n nodes is called "layer n". - config.q_decompose.enable(&mut region, 0)?; - region.assign_advice_from_constant( - || format!("l {}", l), - config.advices[4], - 1, - pallas::Base::from(l as u64), - )?; - - // Offset 0 - // Copy and assign `a` at the correct position. - a.inner().cell_value().copy_advice( - || "copy a", - &mut region, - config.advices[0], - 0, - )?; - // Copy and assign `b` at the correct position. - b.inner().cell_value().copy_advice( - || "copy b", - &mut region, - config.advices[1], - 0, - )?; - // Copy and assign `c` at the correct position. - c.inner().cell_value().copy_advice( - || "copy c", - &mut region, - config.advices[2], - 0, - )?; - // Copy and assign the left node at the correct position. - left.copy_advice(|| "left", &mut region, config.advices[3], 0)?; - // Copy and assign the right node at the correct position. - right.copy_advice(|| "right", &mut region, config.advices[4], 0)?; - - // Offset 1 - // Copy and assign z_1 of SinsemillaHash(a) = a_1 - z1_a.copy_advice(|| "z1_a", &mut region, config.advices[0], 1)?; - // Copy and assign z_1 of SinsemillaHash(b) = b_1 - z1_b.copy_advice(|| "z1_b", &mut region, config.advices[1], 1)?; - // Copy `b_1`, which has been constrained to be a 5-bit value - b_1.inner() - .copy_advice(|| "b_1", &mut region, config.advices[2], 1)?; - // Copy `b_2`, which has been constrained to be a 5-bit value - b_2.inner() - .copy_advice(|| "b_2", &mut region, config.advices[3], 1)?; - - Ok(()) - }, - )?; - } - - // Check layer hash output against Sinsemilla primitives hash - #[cfg(test)] - { - use crate::{sinsemilla::primitives::HashDomain, utilities::i2lebsp}; - - use group::ff::PrimeFieldBits; - - left.value() - .zip(right.value()) - .zip(hash.value()) - .assert_if_known(|((left, right), hash)| { - let l = i2lebsp::<10>(l as u64); - let left: Vec<_> = left - .to_le_bits() - .iter() - .by_vals() - .take(pallas::Base::NUM_BITS as usize) - .collect(); - let right: Vec<_> = right - .to_le_bits() - .iter() - .by_vals() - .take(pallas::Base::NUM_BITS as usize) - .collect(); - let merkle_crh = HashDomain::from_Q(Q.into()); - - let mut message = l.to_vec(); - message.extend_from_slice(&left); - message.extend_from_slice(&right); - - let expected = merkle_crh.hash(message.into_iter()).unwrap(); - - expected.to_repr() == hash.to_repr() - }); - } - - Ok(hash) - } -} - -impl UtilitiesInstructions for MerkleChipOptimized -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, -{ - type Var = AssignedCell; -} - -impl CondSwapInstructions for MerkleChipOptimized -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, -{ - #[allow(clippy::type_complexity)] - fn swap( - &self, - layouter: impl Layouter, - pair: (Self::Var, Value), - swap: Value, - ) -> Result<(Self::Var, Self::Var), Error> { - let config = self.config().cond_swap_config.clone(); - 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 MerkleChipOptimized -where - Hash: HashDomains, - F: FixedPoints, - Commit: CommitDomains, -{ - type CellValue = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::CellValue; - - type Message = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::Message; - type MessagePiece = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::MessagePiece; - type RunningSum = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::RunningSum; - - type X = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::X; - type NonIdentityPoint = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::NonIdentityPoint; - type FixedPoints = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::FixedPoints; - - type HashDomains = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::HashDomains; - type CommitDomains = as SinsemillaInstructions< - pallas::Affine, - { sinsemilla::K }, - { sinsemilla::C }, - >>::CommitDomains; - - fn witness_message_piece( - &self, - layouter: impl Layouter, - value: Value, - num_words: usize, - ) -> Result { - let config = self.config().sinsemilla_config.clone(); - let chip = SinsemillaChipOptimized::::construct(config); - chip.witness_message_piece(layouter, value, num_words) - } - - #[allow(non_snake_case)] - #[allow(clippy::type_complexity)] - fn hash_to_point( - &self, - layouter: impl Layouter, - Q: pallas::Affine, - message: Self::Message, - ) -> Result<(Self::NonIdentityPoint, Vec>), Error> { - let config = self.config().sinsemilla_config.clone(); - let chip = SinsemillaChipOptimized::::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 = SinsemillaChipOptimized::::construct(config); - chip.hash_to_point_with_private_init(layouter, Q, message) - } - - fn extract(point: &Self::NonIdentityPoint) -> Self::X { - SinsemillaChipOptimized::::extract(point) - } -} +impl_trait_Chip_for!(MerkleChipOptimized, MerkleConfigOptimized); +impl_trait_MerkleChipProps_for_MerkleChip!( + MerkleConfigOptimized, + MerkleChipOptimized, + MerkleConfigOptimized, + SinsemillaConfigOptimized +); +impl_trait_MerkleInstructions_for_MerkleChip!(MerkleChipOptimized); +impl_trait_UtilitiesInstructions_for!(MerkleChipOptimized); +impl_trait_CondSwapInstructions_for!(MerkleChipOptimized); +impl_trait_SinsemillaInstructions_for_MerkleChip!(MerkleChipOptimized, SinsemillaChipOptimized,SinsemillaChipOptimized::::construct); diff --git a/halo2_gadgets/src/utilities/lookup_range_check.rs b/halo2_gadgets/src/utilities/lookup_range_check.rs index 1b6d19045f..d544a4bf9d 100644 --- a/halo2_gadgets/src/utilities/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities/lookup_range_check.rs @@ -1,7 +1,6 @@ //! Make use of a K-bit lookup table to decompose a field element into K-bit //! words. -use crate::utilities_opt::lookup_range_check::LookupRangeCheckConfigOptimized; use ff::PrimeFieldBits; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region}, @@ -23,34 +22,36 @@ impl std::ops::Deref for RunningSum { } } -/// Create a Generic Function. The function will use the LookupRangeCheck trait, -/// allowing it to work with any configuration type that implements LookupRangeCheck: -pub fn witness_short_generic( - lookup_config: &C, - layouter: impl Layouter, - value: Value<&F>, - bitrange: Range, -) -> Result>, Error> -where - F: PrimeFieldBits, - C: LookupRangeCheck, -{ - let num_bits = bitrange.len(); - assert!(num_bits < K); - - lookup_config - .witness_short_check( - layouter, - value.map(|value| bitrange_subset(value, bitrange)), - num_bits, - ) - .map(|inner| RangeConstrained { - inner, - num_bits, - _phantom: PhantomData::default(), - }) -} +impl RangeConstrained> { + /// Witnesses a subset of the bits in `value` and constrains them to be the correct + /// number of bits. + /// + /// # Panics + /// + /// Panics if `bitrange.len() >= K`. + pub fn witness_short>( + lookup_config: &L, + layouter: impl Layouter, + value: Value<&F>, + bitrange: Range, + ) -> Result { + let num_bits = bitrange.len(); + assert!(num_bits < K); + // Witness the subset and constrain it to be the correct number of bits. + lookup_config + .witness_short_check( + layouter, + value.map(|value| bitrange_subset(value, bitrange)), + num_bits, + ) + .map(|inner| Self { + inner, + num_bits, + _phantom: PhantomData, + }) + } +} /// Configuration that provides methods for a lookup range check. #[derive(Eq, PartialEq, Debug, Clone, Copy)] pub struct LookupRangeCheckConfig { @@ -153,7 +154,7 @@ impl LookupRangeCheckConfig { } /// Trait that provides common methods for a lookup range check. -pub trait LookupRangeCheck { +pub trait LookupRangeCheck: Clone { /// Returns a reference to the `LookupRangeCheckConfig` instance. fn base(&self) -> &LookupRangeCheckConfig; diff --git a/halo2_gadgets/src/utilities_opt/cond_swap.rs b/halo2_gadgets/src/utilities_opt/cond_swap.rs index f219ab0296..7e31ddb1f6 100644 --- a/halo2_gadgets/src/utilities_opt/cond_swap.rs +++ b/halo2_gadgets/src/utilities_opt/cond_swap.rs @@ -13,7 +13,6 @@ use pasta_curves::pallas; use std::marker::PhantomData; use crate::utilities::UtilitiesInstructions; -/// Instructions for a conditional swap gadget. diff --git a/halo2_proofs/src/circuit.rs b/halo2_proofs/src/circuit.rs index 9a792942cc..67fddef1f2 100644 --- a/halo2_proofs/src/circuit.rs +++ b/halo2_proofs/src/circuit.rs @@ -48,6 +48,30 @@ pub trait Chip: Sized { fn loaded(&self) -> &Self::Loaded; } +#[macro_export] +/// Implement `Chip` for `chip_type` and `config_type` +macro_rules! impl_trait_Chip_for { + ($chip_type:ty, $config_type:ty) => { + impl Chip for $chip_type + where + Hash: HashDomains, + Fixed: FixedPoints, + Commit: CommitDomains, + { + type Config = $config_type; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } + } + }; +} + /// Index of a region in a layouter #[derive(Clone, Copy, Debug)] pub struct RegionIndex(usize); diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index a292b0ab4e..9c634fd667 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -31,8 +31,9 @@ pub use gates::CircuitGates; mod tfp; pub use tfp::TracingFloorPlanner; +/// fixme: rm pub later? #[cfg(feature = "dev-graph")] -mod graph; +pub mod graph; #[cfg(feature = "dev-graph")] #[cfg_attr(docsrs, doc(cfg(feature = "dev-graph")))] diff --git a/halo2_proofs/src/dev/graph.rs b/halo2_proofs/src/dev/graph.rs index 18ef8154df..d7b25bfbf3 100644 --- a/halo2_proofs/src/dev/graph.rs +++ b/halo2_proofs/src/dev/graph.rs @@ -8,7 +8,7 @@ use crate::{ FloorPlanner, Instance, Selector, }, }; - +/// fixme: rm doc later? pub mod layout; /// Builds a dot graph string representing the given circuit. diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 948d27daa4..f8c2abbb3d 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.60.0" +channel = "1.65.0"