From 426f954b1dec7da68c0976502b31644b6cacbaf8 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 10 Jul 2021 13:19:42 +0800 Subject: [PATCH] gadget::ecc.rs: Inline witness_scalar_* APIs. Witness a scalar in the region where it is used for multiplication, instead of witnessing it separately and then copying it in. --- src/circuit/gadget/ecc.rs | 162 +++++----------- src/circuit/gadget/ecc/chip.rs | 99 ++-------- src/circuit/gadget/ecc/chip/mul.rs | 45 +++-- src/circuit/gadget/ecc/chip/mul/overflow.rs | 6 +- src/circuit/gadget/ecc/chip/mul_fixed.rs | 82 ++++---- .../ecc/chip/mul_fixed/base_field_elem.rs | 1 + .../gadget/ecc/chip/mul_fixed/full_width.rs | 124 +++++++----- .../gadget/ecc/chip/mul_fixed/short.rs | 178 +++++++++++------- .../gadget/ecc/chip/witness_scalar_fixed.rs | 96 ---------- .../chip/witness_scalar_fixed/full_width.rs | 27 --- .../ecc/chip/witness_scalar_fixed/short.rs | 98 ---------- 11 files changed, 329 insertions(+), 589 deletions(-) delete mode 100644 src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs delete mode 100644 src/circuit/gadget/ecc/chip/witness_scalar_fixed/full_width.rs delete mode 100644 src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 55293a9b..9441a5e2 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -1,10 +1,9 @@ //! Gadgets for elliptic curve operations. -use ff::Field; use std::fmt::Debug; use halo2::{ - arithmetic::{CurveAffine, FieldExt}, + arithmetic::CurveAffine, circuit::{Chip, Layouter}, plonk::Error, }; @@ -53,30 +52,6 @@ pub trait EccInstructions: Chip + UtilitiesInstructions b: &Self::Point, ) -> Result<(), Error>; - /// Witnesses the given base field element as a private input to the circuit - /// for variable-base scalar mul. - fn witness_scalar_var( - &self, - layouter: &mut impl Layouter, - value: Option, - ) -> Result; - - /// Witnesses the given full-width scalar as a private input to the circuit - /// for fixed-base scalar mul. - fn witness_scalar_fixed( - &self, - layouter: &mut impl Layouter, - value: Option, - ) -> Result; - - /// Witnesses the given signed short scalar as a private input to the circuit - /// for fixed-base scalar mul. - fn witness_scalar_fixed_short( - &self, - layouter: &mut impl Layouter, - value: Option, - ) -> Result; - /// Witnesses the given point as a private input to the circuit. /// This maps the identity to (0, 0) in affine coordinates. fn witness_point( @@ -111,25 +86,25 @@ pub trait EccInstructions: Chip + UtilitiesInstructions fn mul( &self, layouter: &mut impl Layouter, - scalar: &Self::ScalarVar, + scalar: &Self::Var, base: &Self::Point, - ) -> Result; + ) -> Result<(Self::Point, Self::ScalarVar), Error>; /// Performs fixed-base scalar multiplication using a full-width scalar, returning `[scalar] base`. fn mul_fixed( &self, layouter: &mut impl Layouter, - scalar: &Self::ScalarFixed, + scalar: Option, base: &Self::FixedPoints, - ) -> Result; + ) -> Result<(Self::Point, Self::ScalarFixed), Error>; /// Performs fixed-base scalar multiplication using a short signed scalar, returning `[scalar] base`. fn mul_fixed_short( &self, layouter: &mut impl Layouter, - scalar: &Self::ScalarFixedShort, + scalar: Option, base: &Self::FixedPointsShort, - ) -> Result; + ) -> Result<(Self::Point, Self::ScalarFixedShort), Error>; /// Performs fixed-base scalar multiplication using a base field element as the scalar. /// In the current implementation, this base field element must be output from another @@ -159,18 +134,6 @@ pub struct ScalarVar + Clone + Debug inner: EccChip::ScalarVar, } -impl + Clone + Debug + Eq> ScalarVar { - /// Constructs a new ScalarVar with the given value. - pub fn new( - chip: EccChip, - mut layouter: impl Layouter, - value: Option, - ) -> Result { - chip.witness_scalar_var(&mut layouter, value) - .map(|inner| ScalarVar { chip, inner }) - } -} - /// A full-width element of the given elliptic curve's scalar field, to be used for fixed-base scalar mul. #[derive(Debug)] pub struct ScalarFixed @@ -181,21 +144,6 @@ where inner: EccChip::ScalarFixed, } -impl ScalarFixed -where - EccChip: EccInstructions + Clone + Debug + Eq, -{ - /// Constructs a new ScalarFixed with the given value. - pub fn new( - chip: EccChip, - mut layouter: impl Layouter, - value: Option, - ) -> Result { - chip.witness_scalar_fixed(&mut layouter, value) - .map(|inner| ScalarFixed { chip, inner }) - } -} - /// A signed short element of the given elliptic curve's scalar field, to be used for fixed-base scalar mul. #[derive(Debug)] pub struct ScalarFixedShort @@ -206,39 +154,6 @@ where inner: EccChip::ScalarFixedShort, } -impl ScalarFixedShort -where - EccChip: EccInstructions + Clone + Debug + Eq, -{ - /// Constructs a new ScalarFixedShort with the given value. - /// - /// # Panics - /// - /// The short scalar must be in the range [-(2^64 - 1), (2^64 - 1)]. - pub fn new( - chip: EccChip, - mut layouter: impl Layouter, - value: Option, - ) -> Result { - // Check that the scalar is in the range [-(2^64 - 1), (2^64 - 1)] - if let Some(value) = value { - let mut sign = C::Scalar::one(); - - // T = (p-1) / 2 - let t = (C::Scalar::zero() - C::Scalar::one()) * C::Scalar::TWO_INV; - - if value > t { - sign = -sign; - } - let magnitude = value * sign; - assert!(magnitude < C::Scalar::from_u128(1 << 64)); - } - - chip.witness_scalar_fixed_short(&mut layouter, value) - .map(|inner| ScalarFixedShort { chip, inner }) - } -} - /// An elliptic curve point over the given curve. #[derive(Copy, Clone, Debug)] pub struct Point + Clone + Debug + Eq> { @@ -307,14 +222,21 @@ impl + Clone + Debug + Eq> Point, - by: &ScalarVar, - ) -> Result { - assert_eq!(self.chip, by.chip); + by: &EccChip::Var, + ) -> Result<(Self, ScalarVar), Error> { self.chip - .mul(&mut layouter, &by.inner, &self.inner) - .map(|inner| Point { - chip: self.chip.clone(), - inner, + .mul(&mut layouter, by, &self.inner) + .map(|(point, scalar)| { + ( + Point { + chip: self.chip.clone(), + inner: point, + }, + ScalarVar { + chip: self.chip.clone(), + inner: scalar, + }, + ) }) } } @@ -353,14 +275,21 @@ where pub fn mul( &self, mut layouter: impl Layouter, - by: &ScalarFixed, - ) -> Result, Error> { - assert_eq!(self.chip, by.chip); + by: Option, + ) -> Result<(Point, ScalarFixed), Error> { self.chip - .mul_fixed(&mut layouter, &by.inner, &self.inner) - .map(|inner| Point { - chip: self.chip.clone(), - inner, + .mul_fixed(&mut layouter, by, &self.inner) + .map(|(point, scalar)| { + ( + Point { + chip: self.chip.clone(), + inner: point, + }, + ScalarFixed { + chip: self.chip.clone(), + inner: scalar, + }, + ) }) } @@ -404,14 +333,21 @@ where pub fn mul( &self, mut layouter: impl Layouter, - by: &ScalarFixedShort, - ) -> Result, Error> { - assert_eq!(self.chip, by.chip); + by: Option, + ) -> Result<(Point, ScalarFixedShort), Error> { self.chip - .mul_fixed_short(&mut layouter, &by.inner, &self.inner) - .map(|inner| Point { - chip: self.chip.clone(), - inner, + .mul_fixed_short(&mut layouter, by, &self.inner) + .map(|(point, scalar)| { + ( + Point { + chip: self.chip.clone(), + inner: point, + }, + ScalarFixedShort { + chip: self.chip.clone(), + inner: scalar, + }, + ) }) } diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 5dde03d6..c37d3301 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -21,7 +21,6 @@ pub(super) mod add_incomplete; pub(super) mod mul; pub(super) mod mul_fixed; pub(super) mod witness_point; -pub(super) mod witness_scalar_fixed; /// A curve point represented in affine (x, y) coordinates. Each coordinate is /// assigned to a cell. @@ -248,30 +247,15 @@ impl EccChip { mul_config.create_gate(meta); } - // Create witness scalar_fixed gate that applies to both full-width and - // short scalars + // Create gate that is only used in full-width fixed-base scalar mul. { - let config: witness_scalar_fixed::Config = (&config).into(); - config.create_gate(meta); - } - - // Create witness scalar_fixed gate that only applies to short scalars - { - let config: witness_scalar_fixed::short::Config = (&config).into(); - config.create_gate(meta); - } - - // Create fixed-base scalar mul gate that is used in both full-width - // and short multiplication. - { - let mul_fixed_config: mul_fixed::Config<{ constants::NUM_WINDOWS }> = (&config).into(); - mul_fixed_config.create_gate_scalar(meta); + let mul_fixed_full_config: mul_fixed::full_width::Config = (&config).into(); + mul_fixed_full_config.create_gate(meta); } // Create gate that is only used in short fixed-base scalar mul. { - let short_config: mul_fixed::short::Config<{ constants::NUM_WINDOWS_SHORT }> = - (&config).into(); + let short_config: mul_fixed::short::Config = (&config).into(); short_config.create_gate(meta); } @@ -285,17 +269,6 @@ impl EccChip { } } -/// A base-field element used as the scalar in variable-base scalar multiplication. -#[derive(Copy, Clone, Debug)] -pub struct EccScalarVar(CellValue); -impl std::ops::Deref for EccScalarVar { - type Target = CellValue; - - fn deref(&self) -> &CellValue { - &self.0 - } -} - /// A full-width scalar used for fixed-base scalar multiplication. /// This is decomposed into 85 3-bit windows in little-endian order, /// i.e. `windows` = [k_0, k_1, ..., k_84] (for a 255-bit scalar) @@ -350,7 +323,7 @@ impl EccBaseFieldElemFixed { impl EccInstructions for EccChip { type ScalarFixed = EccScalarFixed; type ScalarFixedShort = EccScalarFixedShort; - type ScalarVar = EccScalarVar; + type ScalarVar = CellValue; type Point = EccPoint; type X = CellValue; type FixedPoints = OrchardFixedBasesFull; @@ -374,50 +347,6 @@ impl EccInstructions for EccChip { ) } - fn witness_scalar_var( - &self, - layouter: &mut impl Layouter, - value: Option, - ) -> Result { - let config = self.config().clone(); - layouter.assign_region( - || "Witness scalar for variable-base mul", - |mut region| { - let cell = region.assign_advice( - || "witness scalar var", - config.advices[0], - 0, - || value.ok_or(Error::SynthesisError), - )?; - Ok(EccScalarVar(CellValue::new(cell, value))) - }, - ) - } - - fn witness_scalar_fixed( - &self, - layouter: &mut impl Layouter, - value: Option, - ) -> Result { - let config: witness_scalar_fixed::full_width::Config = self.config().into(); - layouter.assign_region( - || "witness scalar for fixed-base mul", - |mut region| config.assign_region(value, 0, &mut region), - ) - } - - fn witness_scalar_fixed_short( - &self, - layouter: &mut impl Layouter, - value: Option, - ) -> Result { - let config: witness_scalar_fixed::short::Config = self.config().into(); - layouter.assign_region( - || "witness short scalar for fixed-base mul", - |mut region| config.assign_region(value, 0, &mut region), - ) - } - fn witness_point( &self, layouter: &mut impl Layouter, @@ -463,9 +392,9 @@ impl EccInstructions for EccChip { fn mul( &self, layouter: &mut impl Layouter, - scalar: &Self::ScalarVar, + scalar: &Self::Var, base: &Self::Point, - ) -> Result { + ) -> Result<(Self::Point, Self::ScalarVar), Error> { let config: mul::Config = self.config().into(); config.assign( layouter.namespace(|| "variable-base scalar mul"), @@ -477,11 +406,10 @@ impl EccInstructions for EccChip { fn mul_fixed( &self, layouter: &mut impl Layouter, - scalar: &Self::ScalarFixed, + scalar: Option, base: &Self::FixedPoints, - ) -> Result { - let config: mul_fixed::full_width::Config<{ constants::NUM_WINDOWS }> = - self.config().into(); + ) -> Result<(Self::Point, Self::ScalarFixed), Error> { + let config: mul_fixed::full_width::Config = self.config().into(); config.assign( layouter.namespace(|| format!("fixed-base mul of {:?}", base)), scalar, @@ -492,11 +420,10 @@ impl EccInstructions for EccChip { fn mul_fixed_short( &self, layouter: &mut impl Layouter, - scalar: &Self::ScalarFixedShort, + scalar: Option, base: &Self::FixedPointsShort, - ) -> Result { - let config: mul_fixed::short::Config<{ constants::NUM_WINDOWS_SHORT }> = - self.config().into(); + ) -> Result<(Self::Point, Self::ScalarFixedShort), Error> { + let config: mul_fixed::short::Config = self.config().into(); config.assign( layouter.namespace(|| format!("short fixed-base mul of {:?}", base)), scalar, diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 2672bca0..c6e81d41 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -1,4 +1,4 @@ -use super::{add, CellValue, EccConfig, EccPoint, EccScalarVar, Var}; +use super::{add, CellValue, EccConfig, EccPoint, Var}; use crate::{circuit::gadget::utilities::copy, constants::T_Q}; use std::ops::{Deref, Range}; @@ -137,9 +137,9 @@ impl Config { pub(super) fn assign( &self, mut layouter: impl Layouter, - alpha: EccScalarVar, + alpha: CellValue, base: &EccPoint, - ) -> Result { + ) -> Result<(EccPoint, CellValue), Error> { let (result, zs): (EccPoint, Vec>) = layouter.assign_region( || "variable-base scalar mul", |mut region| { @@ -264,7 +264,7 @@ impl Config { self.overflow_config .overflow_check(layouter.namespace(|| "overflow check"), alpha, &zs)?; - Ok(result) + Ok((result, alpha)) } /// Processes the final scalar bit `k_0`. @@ -448,18 +448,26 @@ fn decompose_for_scalar_mul(scalar: Option) -> Vec> { #[cfg(test)] pub mod tests { use group::Curve; - use halo2::{circuit::Layouter, plonk::Error}; + use halo2::{ + circuit::{Chip, Layouter}, + plonk::Error, + }; use pasta_curves::{arithmetic::FieldExt, pallas}; - use crate::circuit::gadget::ecc::{EccInstructions, Point, ScalarVar}; + use crate::circuit::gadget::{ + ecc::{chip::EccChip, EccInstructions, Point}, + utilities::UtilitiesInstructions, + }; - pub fn test_mul + Clone + Eq + std::fmt::Debug>( + pub fn test_mul( chip: EccChip, mut layouter: impl Layouter, zero: &Point, p: &Point, p_val: pallas::Affine, ) -> Result<(), Error> { + let column = chip.config().advices[0]; + fn constrain_equal< EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, >( @@ -483,10 +491,10 @@ pub mod tests { // [a]B { let scalar_val = pallas::Base::rand(); - let result = { - let scalar = ScalarVar::new( - chip.clone(), + let (result, _) = { + let scalar = chip.load_private( layouter.namespace(|| "random scalar"), + column, Some(scalar_val), )?; p.mul(layouter.namespace(|| "random [a]B"), &scalar)? @@ -504,9 +512,9 @@ pub mod tests { // uses incomplete addition at the beginning of its double-and-add. { let scalar_val = pallas::Base::rand(); - let scalar = ScalarVar::new( - chip.clone(), + let scalar = chip.load_private( layouter.namespace(|| "random scalar"), + column, Some(scalar_val), )?; zero.mul(layouter.namespace(|| "[a]𝒪"), &scalar) @@ -517,12 +525,9 @@ pub mod tests { // uses complete addition for the final bits of the scalar. { let scalar_val = pallas::Base::zero(); - let result = { - let scalar = ScalarVar::new( - chip.clone(), - layouter.namespace(|| "zero"), - Some(scalar_val), - )?; + let (result, _) = { + let scalar = + chip.load_private(layouter.namespace(|| "zero"), column, Some(scalar_val))?; p.mul(layouter.namespace(|| "[0]B"), &scalar)? }; constrain_equal( @@ -537,9 +542,9 @@ pub mod tests { // [-1]B (the largest possible base field element) { let scalar_val = -pallas::Base::one(); - let result = { + let (result, _) = { let scalar = - ScalarVar::new(chip.clone(), layouter.namespace(|| "-1"), Some(scalar_val))?; + chip.load_private(layouter.namespace(|| "-1"), column, Some(scalar_val))?; p.mul(layouter.namespace(|| "[-1]B"), &scalar)? }; constrain_equal( diff --git a/src/circuit/gadget/ecc/chip/mul/overflow.rs b/src/circuit/gadget/ecc/chip/mul/overflow.rs index 089bf563..361b8020 100644 --- a/src/circuit/gadget/ecc/chip/mul/overflow.rs +++ b/src/circuit/gadget/ecc/chip/mul/overflow.rs @@ -1,4 +1,4 @@ -use super::super::{copy, CellValue, EccConfig, EccScalarVar, Var}; +use super::super::{copy, CellValue, EccConfig, Var}; use super::Z; use crate::{ circuit::gadget::utilities::lookup_range_check::LookupRangeCheckConfig, constants::T_Q, @@ -95,7 +95,7 @@ impl Config { pub(super) fn overflow_check( &self, mut layouter: impl Layouter, - alpha: EccScalarVar, + alpha: CellValue, zs: &[Z], // [z_0, z_1, ..., z_{254}, z_{255}] ) -> Result<(), Error> { // s = alpha + k_254 ⋅ 2^130 is witnessed here, and then copied into @@ -189,7 +189,7 @@ impl Config { || "copy original alpha", self.advices[1], offset + 1, - &*alpha, + &alpha, &self.perm, )?; diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index 03aa26a8..0e4f4d38 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -1,19 +1,18 @@ use super::{ - add, add_incomplete, copy, CellValue, EccBaseFieldElemFixed, EccConfig, EccPoint, - EccScalarFixed, EccScalarFixedShort, Var, + add, add_incomplete, CellValue, EccBaseFieldElemFixed, EccConfig, EccPoint, EccScalarFixed, + EccScalarFixedShort, Var, }; use crate::constants::{ self, load::{OrchardFixedBase, OrchardFixedBasesFull, ValueCommitV, WindowUs}, + util, }; +use arrayvec::ArrayVec; use group::Curve; use halo2::{ circuit::Region, - plonk::{ - Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, Selector, - VirtualCells, - }, + plonk::{Advice, Column, Error, Expression, Fixed, Permutation, Selector, VirtualCells}, poly::Rotation, }; use lazy_static::lazy_static; @@ -72,6 +71,7 @@ impl OrchardFixedBases { #[derive(Clone, Debug)] pub struct Config { q_mul_fixed: Selector, + q_scalar_fixed: Selector, // The fixed Lagrange interpolation coefficients for `x_p`. lagrange_coeffs: [Column; constants::H], // The fixed `z` for each window such that `y + z = u^2`. @@ -97,6 +97,7 @@ impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { let config = Self { q_mul_fixed: ecc_config.q_mul_fixed, + q_scalar_fixed: ecc_config.q_scalar_fixed, lagrange_coeffs: ecc_config.lagrange_coeffs, fixed_z: ecc_config.fixed_z, x_p: ecc_config.advices[0], @@ -143,17 +144,6 @@ impl From<&EccConfig> for Config { } impl Config { - pub(super) fn create_gate_scalar(&self, meta: &mut ConstraintSystem) { - meta.create_gate( - "x_p, y_p checks for ScalarFixed, ScalarFixedShort", - |meta| { - let mul_fixed = meta.query_selector(self.q_mul_fixed); - let window = meta.query_advice(self.window, Rotation::cur()); - self.coords_check(meta, mul_fixed, window) - }, - ) - } - #[allow(clippy::op_ref)] fn coords_check( &self, @@ -288,25 +278,53 @@ impl Config { Ok(()) } - fn copy_scalar( + /// Witnesses the given scalar as `NUM_WINDOWS` 3-bit windows. + /// + /// The scalar is allowed to be non-canonical. + fn decompose_scalar_fixed( &self, - region: &mut Region<'_, pallas::Base>, + scalar: Option, offset: usize, - scalar: &ScalarFixed, - ) -> Result<(), Error> { - // Copy the scalar decomposition (`k`-bit windows) - for (window_idx, window) in scalar.windows().iter().enumerate() { - copy( - region, - || format!("k[{:?}]", window), - self.window, - window_idx + offset, - window, - &self.perm, - )?; + region: &mut Region<'_, pallas::Base>, + ) -> Result, NUM_WINDOWS>, Error> { + // Enable `q_scalar_fixed` selector + for idx in 0..NUM_WINDOWS { + self.q_scalar_fixed.enable(region, offset + idx)?; } - Ok(()) + // Decompose scalar into `k-bit` windows + let scalar_windows: Option> = scalar.map(|scalar| { + util::decompose_word::( + scalar, + SCALAR_NUM_BITS, + constants::FIXED_BASE_WINDOW_SIZE, + ) + }); + + // Store the scalar decomposition + let mut windows: ArrayVec, NUM_WINDOWS> = ArrayVec::new(); + + let scalar_windows: Vec> = if let Some(windows) = scalar_windows { + assert_eq!(windows.len(), NUM_WINDOWS); + windows + .into_iter() + .map(|window| Some(pallas::Base::from_u64(window as u64))) + .collect() + } else { + vec![None; NUM_WINDOWS] + }; + + for (idx, window) in scalar_windows.into_iter().enumerate() { + let window_cell = region.assign_advice( + || format!("k[{:?}]", offset + idx), + self.window, + offset + idx, + || window.ok_or(Error::SynthesisError), + )?; + windows.push(CellValue::new(window_cell, window)); + } + + Ok(windows) } fn process_window( diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/base_field_elem.rs b/src/circuit/gadget/ecc/chip/mul_fixed/base_field_elem.rs index 55846070..dd93c18d 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/base_field_elem.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/base_field_elem.rs @@ -490,6 +490,7 @@ pub mod tests { base_val: pallas::Affine, ) -> Result<(), Error> { let column = chip.config().advices[0]; + fn constrain_equal( chip: EccChip, mut layouter: impl Layouter, diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs index d7caa2fa..5e01d920 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -1,38 +1,90 @@ use super::super::{EccConfig, EccPoint, EccScalarFixed, OrchardFixedBasesFull}; -use halo2::{circuit::Layouter, plonk::Error}; +use crate::{ + circuit::gadget::utilities::range_check, + constants::{self, L_ORCHARD_SCALAR, NUM_WINDOWS}, +}; +use halo2::{ + circuit::{Layouter, Region}, + plonk::{ConstraintSystem, Error, Selector}, + poly::Rotation, +}; use pasta_curves::pallas; -pub struct Config(super::Config); +pub struct Config { + q_scalar_fixed: Selector, + super_config: super::Config, +} -impl From<&EccConfig> for Config { +impl From<&EccConfig> for Config { fn from(config: &EccConfig) -> Self { - Self(config.into()) + Self { + q_scalar_fixed: config.q_scalar_fixed, + super_config: config.into(), + } } } -impl Config { +impl Config { + pub fn create_gate(&self, meta: &mut ConstraintSystem) { + // Check that each window `k` is within 3 bits + meta.create_gate("Full-width fixed-base scalar mul", |meta| { + let q_scalar_fixed = meta.query_selector(self.q_scalar_fixed); + let window = meta.query_advice(self.super_config.window, Rotation::cur()); + + self.super_config + .coords_check(meta, q_scalar_fixed.clone(), window.clone()) + .into_iter() + // Constrain each window to a 3-bit value: + // 1 * (window - 0) * (window - 1) * ... * (window - 7) + .chain(Some(( + "window range check", + q_scalar_fixed * range_check(window, constants::H), + ))) + }); + } + + /// Witnesses the given scalar as `NUM_WINDOWS` 3-bit windows. + /// + /// The scalar is allowed to be non-canonical. + fn witness( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + scalar: Option, + ) -> Result { + let windows = self + .super_config + .decompose_scalar_fixed::(scalar, offset, region)?; + + Ok(EccScalarFixed { + value: scalar, + windows, + }) + } + pub fn assign( &self, mut layouter: impl Layouter, - scalar: &EccScalarFixed, + scalar: Option, base: OrchardFixedBasesFull, - ) -> Result { - let (acc, mul_b) = layouter.assign_region( + ) -> Result<(EccPoint, EccScalarFixed), Error> { + let (scalar, acc, mul_b) = layouter.assign_region( || "Full-width fixed-base mul (incomplete addition)", |mut region| { let offset = 0; - // Copy the scalar decomposition - self.0.copy_scalar(&mut region, offset, &scalar.into())?; + let scalar = self.witness(&mut region, offset, scalar)?; - self.0.assign_region_inner( + let (acc, mul_b) = self.super_config.assign_region_inner( &mut region, offset, - &scalar.into(), + &(&scalar).into(), base.into(), - self.0.q_mul_fixed, - ) + self.super_config.q_mul_fixed, + )?; + + Ok((scalar, acc, mul_b)) }, )?; @@ -40,7 +92,7 @@ impl Config { let result = layouter.assign_region( || "Full-width fixed-base mul (last window, complete addition)", |mut region| { - self.0 + self.super_config .add_config .assign_region(&mul_b, &acc, 0, &mut region) }, @@ -60,7 +112,7 @@ impl Config { } } - Ok(result) + Ok((result, scalar)) } } @@ -72,7 +124,7 @@ pub mod tests { use crate::circuit::gadget::ecc::{ chip::{EccChip, OrchardFixedBasesFull}, - FixedPoint, Point, ScalarFixed, + FixedPoint, Point, }; use crate::constants; @@ -154,15 +206,7 @@ pub mod tests { { let scalar_fixed = pallas::Scalar::rand(); - let result = { - let scalar_fixed = ScalarFixed::new( - chip.clone(), - layouter.namespace(|| "random scalar"), - Some(scalar_fixed), - )?; - base.mul(layouter.namespace(|| "random [a]B"), &scalar_fixed)? - }; - + let (result, _) = base.mul(layouter.namespace(|| "random [a]B"), Some(scalar_fixed))?; constrain_equal( chip.clone(), layouter.namespace(|| "random [a]B"), @@ -183,14 +227,8 @@ pub mod tests { .fold(pallas::Scalar::zero(), |acc, c| { acc * &h + &pallas::Scalar::from_u64(c.to_digit(8).unwrap().into()) }); - let result = { - let scalar_fixed = ScalarFixed::new( - chip.clone(), - layouter.namespace(|| "mul with double"), - Some(scalar_fixed), - )?; - base.mul(layouter.namespace(|| "mul with double"), &scalar_fixed)? - }; + let (result, _) = + base.mul(layouter.namespace(|| "mul with double"), Some(scalar_fixed))?; constrain_equal( chip.clone(), @@ -205,14 +243,7 @@ pub mod tests { // on the last step. { let scalar_fixed = pallas::Scalar::zero(); - let result = { - let scalar_fixed = ScalarFixed::new( - chip.clone(), - layouter.namespace(|| "zero"), - Some(scalar_fixed), - )?; - base.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)? - }; + let (result, _) = base.mul(layouter.namespace(|| "mul by zero"), Some(scalar_fixed))?; constrain_equal( chip.clone(), layouter.namespace(|| "mul by zero"), @@ -225,14 +256,7 @@ pub mod tests { // [-1]B is the largest scalar field element. { let scalar_fixed = -pallas::Scalar::one(); - let result = { - let scalar_fixed = ScalarFixed::new( - chip.clone(), - layouter.namespace(|| "-1"), - Some(scalar_fixed), - )?; - base.mul(layouter.namespace(|| "mul by -1"), &scalar_fixed)? - }; + let (result, _) = base.mul(layouter.namespace(|| "mul by -1"), Some(scalar_fixed))?; constrain_equal( chip, layouter.namespace(|| "mul by -1"), diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index d5fe53bf..15a8624f 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -1,34 +1,54 @@ use std::array; use super::super::{copy, CellValue, EccConfig, EccPoint, EccScalarFixedShort, Var}; -use crate::constants::ValueCommitV; +use crate::constants::{ValueCommitV, L_VALUE, NUM_WINDOWS_SHORT}; use halo2::{ - circuit::Layouter, - plonk::{ConstraintSystem, Error, Selector}, + circuit::{Layouter, Region}, + plonk::{ConstraintSystem, Error, Expression, Selector}, poly::Rotation, }; -use pasta_curves::pallas; +use pasta_curves::{arithmetic::FieldExt, pallas}; -pub struct Config { +pub struct Config { // Selector used for fixed-base scalar mul with short signed exponent. q_mul_fixed_short: Selector, - super_config: super::Config, + q_scalar_fixed_short: Selector, + super_config: super::Config, } -impl From<&EccConfig> for Config { +impl From<&EccConfig> for Config { fn from(config: &EccConfig) -> Self { Self { q_mul_fixed_short: config.q_mul_fixed_short, + q_scalar_fixed_short: config.q_scalar_fixed_short, super_config: config.into(), } } } -impl Config { +impl Config { // We reuse the constraints in the `mul_fixed` gate so exclude them here. // Here, we add some new constraints specific to the short signed case. pub(crate) fn create_gate(&self, meta: &mut ConstraintSystem) { + // Check that sign is either 1 or -1. + // Check that last window is either 0 or 1. + meta.create_gate("Check sign and last window", |meta| { + let q_scalar_fixed_short = meta.query_selector(self.q_scalar_fixed_short); + let last_window = meta.query_advice(self.super_config.window, Rotation::prev()); + let sign = meta.query_advice(self.super_config.window, Rotation::cur()); + + let one = Expression::Constant(pallas::Base::one()); + + let last_window_check = last_window.clone() * (one.clone() - last_window); + let sign_check = sign.clone() * sign - one; + + vec![ + q_scalar_fixed_short.clone() * last_window_check, + q_scalar_fixed_short * sign_check, + ] + }); + meta.create_gate("Short fixed-base mul gate", |meta| { let q_mul_fixed_short = meta.query_selector(self.q_mul_fixed_short); let y_p = meta.query_advice(self.super_config.y_p, Rotation::cur()); @@ -51,28 +71,80 @@ impl Config { }); } + fn witness( + &self, + region: &mut Region<'_, pallas::Base>, + offset: usize, + value: Option, + ) -> Result { + // Enable `q_scalar_fixed_short` + self.q_scalar_fixed_short + .enable(region, offset + NUM_WINDOWS_SHORT)?; + + // Compute the scalar's sign and magnitude + let sign = value.map(|value| { + // t = (p - 1)/2 + let t = (pallas::Scalar::zero() - &pallas::Scalar::one()) * &pallas::Scalar::TWO_INV; + if value > t { + -pallas::Scalar::one() + } else { + pallas::Scalar::one() + } + }); + + let magnitude = sign.zip(value).map(|(sign, value)| sign * &value); + + // Decompose magnitude into `k`-bit windows + let windows = self + .super_config + .decompose_scalar_fixed::(magnitude, offset, region)?; + + // Assign the sign and enable `q_scalar_fixed_short` + let sign = sign.map(|sign| { + assert!(sign == pallas::Scalar::one() || sign == -pallas::Scalar::one()); + if sign == pallas::Scalar::one() { + pallas::Base::one() + } else { + -pallas::Base::one() + } + }); + let sign_cell = region.assign_advice( + || "sign", + self.super_config.window, + offset + NUM_WINDOWS_SHORT, + || sign.ok_or(Error::SynthesisError), + )?; + + Ok(EccScalarFixedShort { + magnitude, + sign: CellValue::::new(sign_cell, sign), + windows, + }) + } + pub fn assign( &self, mut layouter: impl Layouter, - scalar: &EccScalarFixedShort, + scalar: Option, base: &ValueCommitV, - ) -> Result { - let (acc, mul_b) = layouter.assign_region( + ) -> Result<(EccPoint, EccScalarFixedShort), Error> { + let (scalar, acc, mul_b) = layouter.assign_region( || "Short fixed-base mul (incomplete addition)", |mut region| { let offset = 0; // Copy the scalar decomposition - self.super_config - .copy_scalar(&mut region, offset, &scalar.into())?; + let scalar = self.witness(&mut region, offset, scalar)?; - self.super_config.assign_region_inner( + let (acc, mul_b) = self.super_config.assign_region_inner( &mut region, offset, - &scalar.into(), + &(&scalar).into(), base.clone().into(), self.super_config.q_mul_fixed, - ) + )?; + + Ok((scalar, acc, mul_b)) }, )?; @@ -158,7 +230,7 @@ impl Config { } } - Ok(result) + Ok((result, scalar)) } } @@ -168,7 +240,7 @@ pub mod tests { use halo2::{circuit::Layouter, plonk::Error}; use pasta_curves::{arithmetic::FieldExt, pallas}; - use crate::circuit::gadget::ecc::{chip::EccChip, FixedPointShort, Point, ScalarFixedShort}; + use crate::circuit::gadget::ecc::{chip::EccChip, FixedPointShort, Point}; use crate::constants::load::ValueCommitV; #[allow(clippy::op_ref)] @@ -199,20 +271,17 @@ pub mod tests { // [0]B should return (0,0) since it uses complete addition // on the last step. { - let scalar_fixed = pallas::Scalar::zero(); - let result = { - let scalar_fixed = ScalarFixedShort::new( - chip.clone(), - layouter.namespace(|| "zero"), - Some(scalar_fixed), - )?; - value_commit_v.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)? - }; + let scalar_fixed_short = pallas::Scalar::zero(); + let (result, _) = value_commit_v.mul( + layouter.namespace(|| "mul by zero"), + Some(scalar_fixed_short), + )?; + constrain_equal( chip.clone(), layouter.namespace(|| "mul by zero"), base_val, - scalar_fixed, + scalar_fixed_short, result, )?; } @@ -225,16 +294,11 @@ pub mod tests { sign = -sign; } let scalar_fixed_short = sign * &scalar_fixed_short; + let (result, _) = value_commit_v.mul( + layouter.namespace(|| "random short scalar"), + Some(scalar_fixed_short), + )?; - let result = { - let scalar_fixed_short = ScalarFixedShort::new( - chip.clone(), - layouter.namespace(|| "random short scalar"), - Some(scalar_fixed_short), - )?; - - value_commit_v.mul(layouter.namespace(|| "random [a]B"), &scalar_fixed_short)? - }; constrain_equal( chip.clone(), layouter.namespace(|| "random [a]B"), @@ -247,15 +311,11 @@ pub mod tests { // [2^64 - 1]B { let scalar_fixed_short = pallas::Scalar::from_u64(0xFFFF_FFFF_FFFF_FFFFu64); + let (result, _) = value_commit_v.mul( + layouter.namespace(|| "[2^64 - 1]B"), + Some(scalar_fixed_short), + )?; - let result = { - let scalar_fixed_short = ScalarFixedShort::new( - chip.clone(), - layouter.namespace(|| "2^64 - 1"), - Some(scalar_fixed_short), - )?; - value_commit_v.mul(layouter.namespace(|| "[2^64 - 1]B"), &scalar_fixed_short)? - }; constrain_equal( chip.clone(), layouter.namespace(|| "[2^64 - 1]B"), @@ -268,15 +328,11 @@ pub mod tests { // [-(2^64 - 1)]B { let scalar_fixed_short = -pallas::Scalar::from_u64(0xFFFF_FFFF_FFFF_FFFFu64); + let (result, _) = value_commit_v.mul( + layouter.namespace(|| "-[2^64 - 1]B"), + Some(scalar_fixed_short), + )?; - let result = { - let scalar_fixed_short = ScalarFixedShort::new( - chip.clone(), - layouter.namespace(|| "-(2^64 - 1)"), - Some(scalar_fixed_short), - )?; - value_commit_v.mul(layouter.namespace(|| "[-(2^64 - 1)]B"), &scalar_fixed_short)? - }; constrain_equal( chip.clone(), layouter.namespace(|| "[-2^64 - 1]B"), @@ -292,17 +348,11 @@ pub mod tests { { let scalar_fixed_short = pallas::Scalar::from_u64(0xB6DB_6DB6_DB6D_B6DCu64); - let result = { - let scalar_fixed_short = ScalarFixedShort::new( - chip.clone(), - layouter.namespace(|| "mul with double"), - Some(scalar_fixed_short), - )?; - value_commit_v.mul( - layouter.namespace(|| "mul with double"), - &scalar_fixed_short, - )? - }; + let (result, _) = value_commit_v.mul( + layouter.namespace(|| "mul with double"), + Some(scalar_fixed_short), + )?; + constrain_equal( chip, layouter.namespace(|| "mul with double"), diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs deleted file mode 100644 index ac1ddc12..00000000 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs +++ /dev/null @@ -1,96 +0,0 @@ -use super::{CellValue, EccConfig, Var}; -use crate::{ - circuit::gadget::utilities::range_check, - constants::{self, util}, -}; -use arrayvec::ArrayVec; -use halo2::{ - circuit::Region, - plonk::{Advice, Column, ConstraintSystem, Error, Permutation, Selector}, - poly::Rotation, -}; -use pasta_curves::{arithmetic::FieldExt, pallas}; - -pub mod full_width; -pub mod short; - -pub struct Config { - q_scalar_fixed: Selector, - // Decomposition of scalar into `k`-bit windows. - window: Column, - perm: Permutation, -} - -impl From<&EccConfig> for Config { - fn from(ecc_config: &EccConfig) -> Self { - Self { - q_scalar_fixed: ecc_config.q_scalar_fixed, - window: ecc_config.advices[9], - perm: ecc_config.perm.clone(), - } - } -} - -impl Config { - pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { - // Range check gate applies to both full-width and short scalars. - // Check that `k` is within the allowed window size - meta.create_gate("witness scalar fixed", |meta| { - let q_scalar_fixed = meta.query_selector(self.q_scalar_fixed); - let window = meta.query_advice(self.window, Rotation::cur()); - - // Constrain each window to a 3-bit value: - // 1 * (window - 0) * (window - 1) * ... * (window - 7) - vec![q_scalar_fixed * range_check(window, constants::H)] - }); - } - - /// Witnesses the given scalar as `NUM_WINDOWS` 3-bit windows. - /// - /// The scalar is allowed to be non-canonical. - fn decompose_scalar_fixed( - &self, - scalar: Option, - offset: usize, - region: &mut Region<'_, pallas::Base>, - ) -> Result, NUM_WINDOWS>, Error> { - // Enable `q_scalar_fixed` selector - for idx in 0..NUM_WINDOWS { - self.q_scalar_fixed.enable(region, offset + idx)?; - } - - // Decompose scalar into `k-bit` windows - let scalar_windows: Option> = scalar.map(|scalar| { - util::decompose_word::( - scalar, - SCALAR_NUM_BITS, - constants::FIXED_BASE_WINDOW_SIZE, - ) - }); - - // Store the scalar decomposition - let mut windows: ArrayVec, NUM_WINDOWS> = ArrayVec::new(); - - let scalar_windows: Vec> = if let Some(windows) = scalar_windows { - assert_eq!(windows.len(), NUM_WINDOWS); - windows - .into_iter() - .map(|window| Some(pallas::Base::from_u64(window as u64))) - .collect() - } else { - vec![None; NUM_WINDOWS] - }; - - for (idx, window) in scalar_windows.into_iter().enumerate() { - let window_cell = region.assign_advice( - || format!("k[{:?}]", offset + idx), - self.window, - offset + idx, - || window.ok_or(Error::SynthesisError), - )?; - windows.push(CellValue::new(window_cell, window)); - } - - Ok(windows) - } -} diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/full_width.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/full_width.rs deleted file mode 100644 index c6b0f4ed..00000000 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/full_width.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::super::{EccConfig, EccScalarFixed}; -use crate::constants::{L_ORCHARD_SCALAR, NUM_WINDOWS}; -use halo2::{circuit::Region, plonk::Error}; -use pasta_curves::pallas; - -pub struct Config(super::Config); - -impl From<&EccConfig> for Config { - fn from(ecc_config: &EccConfig) -> Self { - Self(ecc_config.into()) - } -} - -impl Config { - pub fn assign_region( - &self, - value: Option, - offset: usize, - region: &mut Region<'_, pallas::Base>, - ) -> Result { - let windows = self - .0 - .decompose_scalar_fixed::(value, offset, region)?; - - Ok(EccScalarFixed { value, windows }) - } -} diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs deleted file mode 100644 index 467c7aea..00000000 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs +++ /dev/null @@ -1,98 +0,0 @@ -use super::super::{CellValue, EccConfig, EccScalarFixedShort, Var}; -use crate::constants::{L_VALUE, NUM_WINDOWS_SHORT}; -use halo2::{ - circuit::Region, - plonk::{ConstraintSystem, Error, Expression, Selector}, - poly::Rotation, -}; -use pasta_curves::{arithmetic::FieldExt, pallas}; - -pub struct Config { - q_scalar_fixed_short: Selector, - super_config: super::Config, -} - -impl From<&EccConfig> for Config { - fn from(ecc_config: &EccConfig) -> Self { - Self { - q_scalar_fixed_short: ecc_config.q_scalar_fixed_short, - super_config: ecc_config.into(), - } - } -} - -impl Config { - pub fn create_gate(&self, meta: &mut ConstraintSystem) { - // Check that sign is either 1 or -1. - // Check that last window is either 0 or 1. - meta.create_gate("Check sign and last window", |meta| { - let q_scalar_fixed_short = meta.query_selector(self.q_scalar_fixed_short); - let last_window = meta.query_advice(self.super_config.window, Rotation::prev()); - let sign = meta.query_advice(self.super_config.window, Rotation::cur()); - - let one = Expression::Constant(pallas::Base::one()); - - let last_window_check = last_window.clone() * (one.clone() - last_window); - let sign_check = sign.clone() * sign - one; - - vec![ - q_scalar_fixed_short.clone() * last_window_check, - q_scalar_fixed_short * sign_check, - ] - }); - } -} - -impl Config { - #[allow(clippy::op_ref)] - pub fn assign_region( - &self, - value: Option, - offset: usize, - region: &mut Region<'_, pallas::Base>, - ) -> Result { - // Enable `q_scalar_fixed_short` - self.q_scalar_fixed_short - .enable(region, offset + NUM_WINDOWS_SHORT)?; - - // Compute the scalar's sign and magnitude - let sign = value.map(|value| { - // t = (p - 1)/2 - let t = (pallas::Scalar::zero() - &pallas::Scalar::one()) * &pallas::Scalar::TWO_INV; - if value > t { - -pallas::Scalar::one() - } else { - pallas::Scalar::one() - } - }); - - let magnitude = sign.zip(value).map(|(sign, value)| sign * &value); - - // Decompose magnitude into `k`-bit windows - let windows = self - .super_config - .decompose_scalar_fixed::(magnitude, offset, region)?; - - // Assign the sign and enable `q_scalar_fixed_short` - let sign = sign.map(|sign| { - assert!(sign == pallas::Scalar::one() || sign == -pallas::Scalar::one()); - if sign == pallas::Scalar::one() { - pallas::Base::one() - } else { - -pallas::Base::one() - } - }); - let sign_cell = region.assign_advice( - || "sign", - self.super_config.window, - offset + NUM_WINDOWS_SHORT, - || sign.ok_or(Error::SynthesisError), - )?; - - Ok(EccScalarFixedShort { - magnitude, - sign: CellValue::::new(sign_cell, sign), - windows, - }) - } -}