diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 046ede72..b297df14 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -15,7 +15,7 @@ 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; +pub(super) mod witness_scalar_fixed; /// A curve point represented in affine (x, y) coordinates. Each coordinate is /// assigned to a cell. @@ -186,6 +186,19 @@ impl EccChip { mul_config.create_gate(meta); } + // Create witness scalar_fixed gate that applies to both full-width and + // short scalars + { + let config: witness_scalar_fixed::Config = (&config).into(); + config.create_gate(meta); + } + + // Create witness scalar_fixed gate that only apploes to short scalars + { + let config: witness_scalar_fixed::short::Config = (&config).into(); + config.create_gate(meta); + } + config } } @@ -262,18 +275,26 @@ impl EccInstructions for EccChip { fn witness_scalar_fixed( &self, - _layouter: &mut impl Layouter, - _value: Option, + layouter: &mut impl Layouter, + value: Option, ) -> Result { - todo!() + 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, + layouter: &mut impl Layouter, + value: Option, ) -> Result { - todo!() + 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( diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs new file mode 100644 index 00000000..6d9dc210 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs @@ -0,0 +1,94 @@ +use super::{CellValue, EccConfig, Var}; +use crate::constants::{self, util}; +use arrayvec::ArrayVec; +use ff::PrimeFieldBits; +use halo2::{ + circuit::Region, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, 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, +} + +impl From<&EccConfig> for Config { + fn from(ecc_config: &EccConfig) -> Self { + Self { + q_scalar_fixed: ecc_config.q_scalar_fixed, + window: ecc_config.advices[0], + } + } +} + +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()); + + let range_check = + (0..constants::H).fold(Expression::Constant(pallas::Base::one()), |acc, i| { + acc * (window.clone() - Expression::Constant(pallas::Base::from_u64(i as u64))) + }); + vec![q_scalar_fixed * range_check] + }); + } + + fn decompose_scalar_fixed( + &self, + scalar: Option, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result, NUM_WINDOWS>, Error> + where + pallas::Scalar: PrimeFieldBits, + { + // 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_scalar_fixed::( + 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 new file mode 100644 index 00000000..c6b0f4ed --- /dev/null +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/full_width.rs @@ -0,0 +1,27 @@ +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 new file mode 100644 index 00000000..7c6e2efe --- /dev/null +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs @@ -0,0 +1,96 @@ +use super::super::{CellValue, EccConfig, EccScalarFixedShort, Var}; +use crate::constants::{L_VALUE, NUM_WINDOWS_SHORT}; +use ff::PrimeFieldBits; +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. + meta.create_gate("check sign", |meta| { + let q_scalar_fixed_short = meta.query_selector(self.q_scalar_fixed_short); + let sign = meta.query_advice(self.super_config.window, Rotation::cur()); + + vec![ + q_scalar_fixed_short + * (sign.clone() + Expression::Constant(pallas::Base::one())) + * (sign - Expression::Constant(pallas::Base::one())), + ] + }); + } +} + +impl Config { + #[allow(clippy::op_ref)] + pub fn assign_region( + &self, + value: Option, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result + where + pallas::Scalar: PrimeFieldBits, + { + // 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, + NUM_WINDOWS_SHORT, + || sign.ok_or(Error::SynthesisError), + )?; + + Ok(EccScalarFixedShort { + magnitude, + sign: CellValue::::new(sign_cell, sign), + windows, + }) + } +}