diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 2cf189dc..3ae1798a 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -9,6 +9,8 @@ use halo2::{ plonk::Error, }; +use crate::circuit::gadget::utilities::CellValue; + pub mod chip; /// The set of circuit instructions required to use the ECC gadgets. @@ -128,6 +130,16 @@ pub trait EccInstructions: Chip { scalar: &Self::ScalarFixedShort, base: &Self::FixedPointsShort, ) -> Result; + + /// 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 + /// instruction. + fn mul_fixed_base_field_elem( + &self, + layouter: &mut impl Layouter, + base_field_elem: CellValue, + base: &Self::FixedPoints, + ) -> Result; } /// An element of the given elliptic curve's base field, that is used as a scalar @@ -352,6 +364,21 @@ where }) } + /// Multiplies `self` using a value encoded in a base field element + /// as the scalar. + pub fn mul_base_field_elem( + &self, + mut layouter: impl Layouter, + by: CellValue, + ) -> Result, Error> { + self.chip + .mul_fixed_base_field_elem(&mut layouter, by, &self.inner) + .map(|inner| Point { + chip: self.chip.clone(), + inner, + }) + } + /// Wraps the given fixed base (obtained directly from an instruction) in a gadget. pub fn from_inner(chip: EccChip, inner: EccChip::FixedPoints) -> Self { FixedPoint { chip, inner } @@ -521,18 +548,26 @@ mod tests { // Test signed short fixed-base scalar multiplication { super::chip::mul_fixed::short::tests::test_mul_fixed_short( - chip, + 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() { - let k = 12; + let k = 13; let circuit = MyCircuit {}; let prover = MockProver::run(k, &circuit, vec![]).unwrap(); assert_eq!(prover.verify(), Ok(())) diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 26acdc6a..6c046213 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -101,10 +101,11 @@ pub struct EccConfig { pub q_mul_overflow: Selector, /// Fixed-base full-width scalar multiplication - pub q_mul_fixed: Selector, - + pub mul_fixed: Column, /// Fixed-base signed short scalar multiplication pub q_mul_fixed_short: Selector, + /// Fixed-base multiplication using a base field element as the scalar + pub base_field_fixed: Column, /// Witness point pub q_point: Selector, @@ -174,8 +175,9 @@ impl EccChip { q_init_z: meta.selector(), q_mul_z: meta.selector(), q_mul_overflow: meta.selector(), - q_mul_fixed: meta.selector(), + mul_fixed: meta.fixed_column(), q_mul_fixed_short: meta.selector(), + base_field_fixed: meta.fixed_column(), q_point: meta.selector(), q_scalar_fixed: meta.selector(), q_scalar_fixed_short: meta.selector(), @@ -224,7 +226,7 @@ impl EccChip { // and short multiplication. { let mul_fixed_config: mul_fixed::Config<{ constants::NUM_WINDOWS }> = (&config).into(); - mul_fixed_config.create_gate(meta); + mul_fixed_config.create_gate_scalar(meta); } // Create gate that is only used in short fixed-base scalar mul. @@ -234,6 +236,12 @@ impl EccChip { short_config.create_gate(meta); } + // Create gate ths is only used in fixed-base mul using a base field element. + { + let base_field_config: mul_fixed::base_field_elem::Config = (&config).into(); + base_field_config.create_gate(meta); + } + config } } @@ -250,10 +258,10 @@ impl std::ops::Deref for EccScalarVar { } /// A full-width scalar used for fixed-base scalar multiplication. -/// This is decomposed in chunks of `window_width` bits in little-endian order. -/// For example, if `window_width` = 3, we will have [k_0, k_1, ..., k_n] -/// where `scalar = k_0 + k_1 * (2^3) + ... + k_n * (2^3)^n` and each `k_i` is -/// in the range [0..2^3). +/// 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) +/// where `scalar = k_0 + k_1 * (2^3) + ... + k_84 * (2^3)^84` and +/// each `k_i` is in the range [0..2^3). #[derive(Clone, Debug)] pub struct EccScalarFixed { value: Option, @@ -261,10 +269,13 @@ pub struct EccScalarFixed { } /// A signed short scalar used for fixed-base scalar multiplication. -/// This is decomposed in chunks of `window_width` bits in little-endian order. -/// For example, if `window_width` = 3, we will have [k_0, k_1, ..., k_n] -/// where `scalar = k_0 + k_1 * (2^3) + ... + k_n * (2^3)^n` and each `k_i` is -/// in the range [0..2^3). +/// A short scalar must have magnitude in the range [0..2^64), with +/// a sign of either 1 or -1. +/// This is decomposed into 22 3-bit windows in little-endian order, +/// i.e. `windows` = [k_0, k_1, ..., k_21] (for a 64-bit magnitude) +/// where `scalar = k_0 + k_1 * (2^3) + ... + k_84 * (2^3)^84` and +/// each `k_i` is in the range [0..2^3). +/// k_21 must be a single bit, i.e. 0 or 1. #[derive(Clone, Debug)] pub struct EccScalarFixedShort { magnitude: Option, @@ -272,6 +283,26 @@ pub struct EccScalarFixedShort { windows: ArrayVec, { constants::NUM_WINDOWS_SHORT }>, } +/// A base field element used for fixed-base scalar multiplication. +/// This is decomposed into 3-bit windows in little-endian order +/// using a running sum `z`, where z_{i+1} = (z_i - a_i) / (2^3) +/// for element α = a_0 + (2^3) a_1 + ... + (2^{3(n-1)}) a_{n-1}. +/// Each `a_i` is in the range [0..2^3). +/// +/// `windows` = [z_1, ..., z_85], where we expect z_85 = 0. +/// Since z_0 is initialized as α, we store it as `base_field_elem`. +#[derive(Clone, Debug)] +struct EccBaseFieldElemFixed { + base_field_elem: CellValue, + running_sum: ArrayVec, { constants::NUM_WINDOWS }>, +} + +impl EccBaseFieldElemFixed { + fn base_field_elem(&self) -> CellValue { + self.base_field_elem + } +} + impl EccInstructions for EccChip { type ScalarFixed = EccScalarFixed; type ScalarFixedShort = EccScalarFixedShort; @@ -426,4 +457,17 @@ impl EccInstructions for EccChip { |mut region| config.assign_region(scalar, base, 0, &mut region), ) } + + fn mul_fixed_base_field_elem( + &self, + layouter: &mut impl Layouter, + base_field_elem: CellValue, + base: &Self::FixedPoints, + ) -> Result { + let config: mul_fixed::base_field_elem::Config = self.config().into(); + layouter.assign_region( + || format!("base field elem fixed-base mul of {:?}", base), + |mut region| config.assign_region(base_field_elem, *base, 0, &mut region), + ) + } } diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index b2c5b577..13c50187 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -1,8 +1,6 @@ -use std::array; - use super::{ - add, add_incomplete, copy, CellValue, EccConfig, EccPoint, EccScalarFixed, EccScalarFixedShort, - Var, + add, add_incomplete, copy, CellValue, EccBaseFieldElemFixed, EccConfig, EccPoint, + EccScalarFixed, EccScalarFixedShort, Var, }; use crate::constants::{ self, @@ -12,7 +10,9 @@ use crate::constants::{ use group::Curve; use halo2::{ circuit::Region, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, Selector}, + plonk::{ + Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, VirtualCells, + }, poly::Rotation, }; use pasta_curves::{ @@ -20,6 +20,7 @@ use pasta_curves::{ pallas, }; +pub mod base_field_elem; pub mod full_width; pub mod short; @@ -67,9 +68,7 @@ impl OrchardFixedBases { #[derive(Clone, Debug)] pub struct Config { - // Selector used in both short and full-width fixed-base scalar mul. - q_mul_fixed: Selector, - + mul_fixed: Column, // 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`. @@ -94,7 +93,7 @@ pub struct Config { impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { let config = Self { - q_mul_fixed: ecc_config.q_mul_fixed, + mul_fixed: ecc_config.mul_fixed, lagrange_coeffs: ecc_config.lagrange_coeffs, fixed_z: ecc_config.fixed_z, x_p: ecc_config.advices[0], @@ -141,40 +140,50 @@ 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_fixed(self.mul_fixed, Rotation::cur()); + let window = meta.query_advice(self.window, Rotation::cur()); + self.coords_check(meta, mul_fixed, window) + }, + ) + } + #[allow(clippy::op_ref)] - pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { - meta.create_gate("Fixed-base scalar mul gate", |meta| { - let q_mul_fixed = meta.query_selector(self.q_mul_fixed); - let y_p = meta.query_advice(self.y_p, Rotation::cur()); + fn coords_check( + &self, + meta: &mut VirtualCells<'_, pallas::Base>, + toggle: Expression, + window: Expression, + ) -> Vec> { + let y_p = meta.query_advice(self.y_p, Rotation::cur()); + let x_p = meta.query_advice(self.x_p, Rotation::cur()); + let z = meta.query_fixed(self.fixed_z, Rotation::cur()); + let u = meta.query_advice(self.u, Rotation::cur()); - let window = meta.query_advice(self.window, Rotation::cur()); - let x_p = meta.query_advice(self.x_p, Rotation::cur()); - - let z = meta.query_fixed(self.fixed_z, Rotation::cur()); - let u = meta.query_advice(self.u, Rotation::cur()); - - let window_pow: Vec> = (0..constants::H) - .map(|pow| { - (0..pow).fold(Expression::Constant(pallas::Base::one()), |acc, _| { - acc * window.clone() - }) + let window_pow: Vec> = (0..constants::H) + .map(|pow| { + (0..pow).fold(Expression::Constant(pallas::Base::one()), |acc, _| { + acc * window.clone() }) - .collect(); + }) + .collect(); - let interpolated_x = window_pow.iter().zip(self.lagrange_coeffs.iter()).fold( - Expression::Constant(pallas::Base::zero()), - |acc, (window_pow, coeff)| { - acc + (window_pow.clone() * meta.query_fixed(*coeff, Rotation::cur())) - }, - ); + let interpolated_x = window_pow.iter().zip(self.lagrange_coeffs.iter()).fold( + Expression::Constant(pallas::Base::zero()), + |acc, (window_pow, coeff)| { + acc + (window_pow.clone() * meta.query_fixed(*coeff, Rotation::cur())) + }, + ); - // Check interpolation of x-coordinate - let x_check = interpolated_x - x_p; - // Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed - let y_check = u.clone() * u - y_p - z; + // Check interpolation of x-coordinate + let x_check = interpolated_x - x_p; + // Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed + let y_check = u.clone() * u - y_p - z; - array::IntoIter::new([x_check, y_check]).map(move |poly| q_mul_fixed.clone() * poly) - }); + vec![toggle.clone() * x_check, toggle * y_check] } #[allow(clippy::type_complexity)] @@ -184,12 +193,10 @@ impl Config { offset: usize, scalar: &ScalarFixed, base: OrchardFixedBases, + coords_check_toggle: Column, ) -> Result<(EccPoint, EccPoint), Error> { // Assign fixed columns for given fixed base - self.assign_fixed_constants(region, offset, base)?; - - // Copy the scalar decomposition - self.copy_scalar(region, offset, scalar)?; + self.assign_fixed_constants(region, offset, base, coords_check_toggle)?; // Initialize accumulator let acc = self.initialize_accumulator(region, offset, base, scalar)?; @@ -208,6 +215,7 @@ impl Config { region: &mut Region<'_, pallas::Base>, offset: usize, base: OrchardFixedBases, + fixed_column: Column, ) -> Result<(), Error> { let (lagrange_coeffs, z) = match base { OrchardFixedBases::ValueCommitV => { @@ -230,8 +238,12 @@ impl Config { // Assign fixed columns for given fixed base for window in 0..NUM_WINDOWS { - // Enable `q_mul_fixed` selector - self.q_mul_fixed.enable(region, window + offset)?; + region.assign_fixed( + || "Enable coords check", + fixed_column, + window + offset, + || Ok(pallas::Base::one()), + )?; // Assign x-coordinate Lagrange interpolation coefficients for k in 0..(constants::H) { @@ -483,6 +495,7 @@ impl Config { enum ScalarFixed { FullWidth(EccScalarFixed), Short(EccScalarFixedShort), + BaseFieldElem(EccBaseFieldElemFixed), } impl From<&EccScalarFixed> for ScalarFixed { @@ -497,33 +510,66 @@ impl From<&EccScalarFixedShort> for ScalarFixed { } } +impl From<&EccBaseFieldElemFixed> for ScalarFixed { + fn from(base_field_elem: &EccBaseFieldElemFixed) -> Self { + Self::BaseFieldElem(base_field_elem.clone()) + } +} + impl ScalarFixed { fn windows(&self) -> &[CellValue] { match self { ScalarFixed::FullWidth(scalar) => &scalar.windows, ScalarFixed::Short(scalar) => &scalar.windows, + _ => unreachable!("The base field element is not witnessed as windows."), } } // The scalar decomposition was done in the base field. For computation // outside the circuit, we now convert them back into the scalar field. fn windows_field(&self) -> Vec> { - self.windows() - .iter() - .map(|bits| { - bits.value() - .map(|value| pallas::Scalar::from_bytes(&value.to_bytes()).unwrap()) - }) - .collect::>() + match self { + Self::BaseFieldElem(scalar) => { + let mut zs = vec![scalar.base_field_elem]; + zs.extend_from_slice(&scalar.running_sum); + + (0..(zs.len() - 1)) + .map(|idx| { + let z_cur = zs[idx].value(); + let z_next = zs[idx + 1].value(); + let word = z_cur.zip(z_next).map(|(z_cur, z_next)| { + z_cur - z_next * pallas::Base::from_u64(constants::H as u64) + }); + word.map(|word| pallas::Scalar::from_bytes(&word.to_bytes()).unwrap()) + }) + .collect::>() + } + _ => self + .windows() + .iter() + .map(|bits| { + bits.value() + .map(|value| pallas::Scalar::from_bytes(&value.to_bytes()).unwrap()) + }) + .collect::>(), + } } // The scalar decomposition is guaranteed to be in three-bit windows, // so we also cast the least significant byte in their serialisation // into usize for convenient indexing into `u`-values fn windows_usize(&self) -> Vec> { - self.windows() + self.windows_field() .iter() - .map(|bits| bits.value().map(|value| value.to_bytes()[0] as usize)) + .map(|window| { + if let Some(window) = window { + let window = window.to_bytes()[0] as usize; + assert!(window < constants::H); + Some(window) + } else { + None + } + }) .collect::>() } } 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 new file mode 100644 index 00000000..8260425d --- /dev/null +++ b/src/circuit/gadget/ecc/chip/mul_fixed/base_field_elem.rs @@ -0,0 +1,375 @@ +use super::super::{EccBaseFieldElemFixed, EccConfig, EccPoint, OrchardFixedBasesFull}; + +use crate::{ + circuit::gadget::utilities::{copy, CellValue, Var}, + constants::{self, util::decompose_scalar_fixed, NUM_WINDOWS}, +}; +use halo2::{ + circuit::Region, + plonk::{Column, ConstraintSystem, Error, Expression, Fixed}, + poly::Rotation, +}; +use pasta_curves::{arithmetic::FieldExt, pallas}; + +use arrayvec::ArrayVec; + +pub struct Config { + base_field_fixed: Column, + super_config: super::Config<{ constants::NUM_WINDOWS }>, +} + +impl From<&EccConfig> for Config { + fn from(config: &EccConfig) -> Self { + Self { + base_field_fixed: config.base_field_fixed, + super_config: config.into(), + } + } +} + +impl Config { + pub fn create_gate(&self, meta: &mut ConstraintSystem) { + // Decompose the base field element α into three-bit windows + // using a running sum `z`, where z_{i+1} = (z_i - a_i) / (2^3) + // for α = a_0 + (2^3) a_1 + ... + (2^{3(n-1)}) a_{n-1}. + // For a Pallas base field element (255 bits), n = 255 / 3 = 85. + // + // We set z_0 = α, which implies: + // z_1 = (α - a_0) / 2^3, (subtract the lowest 3 bits) + // = a_1 + (2^3) a_2 + ... + 2^{3(n-1)} a_{n-1}, + // z_2 = (z_1 - a_1) / 2^3 + // = a_2 + (2^3) a_3 + ... + 2^{3(n-2)} a_{n-1}, + // ..., + // z_{n-1} = a_{n-1} + // z_n = (z_{n-1} - a_{n-1}) / 2^3 + // = 0. + // + // This gate checks that each a_i = z_i - z_{i+1} * (2^3) is within + // 3 bits. + meta.create_gate("Decompose base field element", |meta| { + // This gate is activated when base_field_fixed = 1 + let fixed_is_one = { + let base_field_fixed = meta.query_fixed(self.base_field_fixed, Rotation::cur()); + let two = Expression::Constant(pallas::Base::from_u64(2)); + base_field_fixed.clone() * (two - base_field_fixed) + }; + + let z_cur = meta.query_advice(self.super_config.window, Rotation::cur()); + let z_next = meta.query_advice(self.super_config.window, Rotation::next()); + + // z_{i+1} = (z_i - a_i) / (2^3) + // => a_i = z_i - (z_{i+1} * (2^3)) + let word = z_cur - z_next * pallas::Base::from_u64(constants::H as u64); + + // (word - 7) * (word - 6) * ... * (word - 1) * word = 0 + let range_check = + (0..constants::H).fold(Expression::Constant(pallas::Base::one()), |acc, i| { + acc * (word.clone() - Expression::Constant(pallas::Base::from_u64(i as u64))) + }); + + vec![fixed_is_one * range_check] + }); + + meta.create_gate("x_p, y_p checks for BaseFieldElemFixed", |meta| { + // This gate is activated when base_field_fixed = 1 + let fixed_is_one = { + let base_field_fixed = meta.query_fixed(self.base_field_fixed, Rotation::cur()); + let two = Expression::Constant(pallas::Base::from_u64(2)); + base_field_fixed.clone() * (base_field_fixed - two) + }; + + let z_cur = meta.query_advice(self.super_config.window, Rotation::cur()); + let z_next = meta.query_advice(self.super_config.window, Rotation::next()); + let window = z_cur - z_next * pallas::Base::from_u64(constants::H as u64); + self.super_config.coords_check(meta, fixed_is_one, window) + }); + + // Check that we get z_85 = 0 as the final output of the running sum. + meta.create_gate("z_85 = 0", |meta| { + // This gate is activated when base_field_fixed = 2 + let fixed_is_two = { + let base_field_fixed = meta.query_fixed(self.base_field_fixed, Rotation::cur()); + let one = Expression::Constant(pallas::Base::one()); + base_field_fixed.clone() * (base_field_fixed - one) + }; + + let z_85 = meta.query_advice(self.super_config.window, Rotation::cur()); + + vec![fixed_is_two * z_85] + }); + } + + pub fn assign_region( + &self, + scalar: CellValue, + base: OrchardFixedBasesFull, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result { + // Decompose scalar + let scalar = self.decompose_base_field_elem(scalar, offset, region)?; + + let (acc, mul_b) = self.super_config.assign_region_inner( + region, + offset, + &(&scalar).into(), + base.into(), + self.base_field_fixed, + )?; + + // Increase offset by 1 because the running sum decomposition takes + // up 86 rows (1 more than the number of windows.) + let offset = offset + 1; + + // Add to the accumulator and return the final result as `[scalar]B`. + let result = self.super_config.add_config.assign_region( + &mul_b, + &acc, + offset + NUM_WINDOWS, + region, + )?; + + #[cfg(test)] + // Check that the correct multiple is obtained. + { + use group::Curve; + + let base: super::OrchardFixedBases = base.into(); + let scalar = &scalar + .base_field_elem() + .value() + .map(|scalar| pallas::Scalar::from_bytes(&scalar.to_bytes()).unwrap()); + let real_mul = scalar.map(|scalar| base.generator() * scalar); + let result = result.point(); + + if let (Some(real_mul), Some(result)) = (real_mul, result) { + assert_eq!(real_mul.to_affine(), result); + } + } + + Ok(result) + } + + fn decompose_base_field_elem( + &self, + base_field_elem: CellValue, + offset: usize, + region: &mut Region<'_, pallas::Base>, + ) -> Result { + // Decompose base field element into 3-bit words. + let words: Vec> = { + let words = base_field_elem.value().map(|base_field_elem| { + decompose_scalar_fixed::( + base_field_elem, + constants::L_ORCHARD_BASE, + constants::FIXED_BASE_WINDOW_SIZE, + ) + }); + + if let Some(words) = words { + words.into_iter().map(Some).collect() + } else { + vec![None; constants::NUM_WINDOWS] + } + }; + + // Initialize empty ArrayVec to store running sum values [z_1, ..., z_85]. + let mut running_sum: ArrayVec, { constants::NUM_WINDOWS }> = + ArrayVec::new(); + + // Assign running sum `z_i`, i = 0..=n, where z_{i+1} = (z_i - a_i) / (2^3) + // and `z_0` is initialized as `base_field_elem`. + let mut z = copy( + region, + || "z_0 = base_field_elem", + self.super_config.window, + offset, + &base_field_elem, + &self.super_config.perm, + )?; + + for idx in 0..words.len() { + region.assign_fixed( + || "Decomposition check", + self.base_field_fixed, + offset + idx, + || Ok(pallas::Base::from_u64(1)), + )?; + } + + let offset = offset + 1; + + let eight_inv = pallas::Base::TWO_INV.square() * pallas::Base::TWO_INV; + for (idx, word) in words.iter().enumerate() { + // z_next = (z_cur - word) / (2^3) + let z_next = { + let word = word.map(|word| pallas::Base::from_u64(word as u64)); + let z_next_val = z + .value() + .zip(word) + .map(|(z_cur_val, word)| (z_cur_val - word) * eight_inv); + let cell = region.assign_advice( + || format!("word {:?}", idx), + self.super_config.window, + offset + idx, + || z_next_val.ok_or(Error::SynthesisError), + )?; + CellValue::new(cell, z_next_val) + }; + + // Update `z`. + z = z_next; + running_sum.push(z); + } + + let offset = offset + words.len() - 1; + region.assign_fixed( + || "Check z_85 = 0", + self.base_field_fixed, + offset, + || Ok(pallas::Base::from_u64(2)), + )?; + + Ok(EccBaseFieldElemFixed { + base_field_elem, + running_sum, + }) + } +} + +#[cfg(test)] +pub mod tests { + use halo2::{ + circuit::{Chip, Layouter}, + plonk::Error, + }; + use pasta_curves::{arithmetic::FieldExt, pallas}; + + use crate::circuit::gadget::{ + ecc::{ + chip::{EccChip, OrchardFixedBasesFull}, + FixedPoint, + }, + utilities::{CellValue, UtilitiesInstructions}, + }; + use crate::constants; + + pub fn test_mul_fixed_base_field( + chip: EccChip, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + impl UtilitiesInstructions for EccChip { + type Var = CellValue; + } + + // commit_ivk_r + let commit_ivk_r = OrchardFixedBasesFull::CommitIvkR; + let commit_ivk_r = FixedPoint::from_inner(chip.clone(), commit_ivk_r); + test_single_base( + chip.clone(), + layouter.namespace(|| "commit_ivk_r"), + commit_ivk_r, + )?; + + // note_commit_r + let note_commit_r = OrchardFixedBasesFull::NoteCommitR; + let note_commit_r = FixedPoint::from_inner(chip.clone(), note_commit_r); + test_single_base( + chip.clone(), + layouter.namespace(|| "note_commit_r"), + note_commit_r, + )?; + + // nullifier_k + let nullifier_k = OrchardFixedBasesFull::NullifierK; + let nullifier_k = FixedPoint::from_inner(chip.clone(), nullifier_k); + test_single_base( + chip.clone(), + layouter.namespace(|| "nullifier_k"), + nullifier_k, + )?; + + // value_commit_r + let value_commit_r = OrchardFixedBasesFull::ValueCommitR; + let value_commit_r = FixedPoint::from_inner(chip.clone(), value_commit_r); + test_single_base( + chip.clone(), + layouter.namespace(|| "value_commit_r"), + value_commit_r, + )?; + + // spend_auth_g + let spend_auth_g = OrchardFixedBasesFull::SpendAuthG; + let spend_auth_g = FixedPoint::from_inner(chip.clone(), spend_auth_g); + test_single_base(chip, layouter.namespace(|| "spend_auth_g"), spend_auth_g)?; + + Ok(()) + } + + #[allow(clippy::op_ref)] + fn test_single_base( + chip: EccChip, + mut layouter: impl Layouter, + base: FixedPoint, + ) -> Result<(), Error> { + let column = chip.config().advices[0]; + + // [a]B + { + let scalar_fixed = pallas::Base::rand(); + let scalar_fixed = chip.load_private( + layouter.namespace(|| "witness random base field element"), + column, + Some(scalar_fixed), + )?; + + base.mul_base_field_elem(layouter.namespace(|| "mul"), scalar_fixed)?; + } + + // There is a single canonical sequence of window values for which a doubling occurs on the last step: + // 1333333333333333333333333333333333333333333333333333333333333333333333333333333333334 in octal. + // (There is another *non-canonical* sequence + // 5333333333333333333333333333333333333333332711161673731021062440252244051273333333333 in octal.) + { + let h = pallas::Base::from_u64(constants::H as u64); + let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334" + .chars() + .fold(pallas::Base::zero(), |acc, c| { + acc * &h + &pallas::Base::from_u64(c.to_digit(8).unwrap().into()) + }); + + let scalar_fixed = chip.load_private( + layouter.namespace(|| "mul with double"), + column, + Some(scalar_fixed), + )?; + + base.mul_base_field_elem(layouter.namespace(|| "mul with double"), scalar_fixed)?; + } + + // [0]B should return (0,0) since it uses complete addition + // on the last step. + { + let scalar_fixed = pallas::Base::zero(); + let scalar_fixed = chip.load_private( + layouter.namespace(|| "mul by zero"), + column, + Some(scalar_fixed), + )?; + base.mul_base_field_elem(layouter.namespace(|| "mul by zero"), scalar_fixed)?; + } + + // [-1]B is the largest base field element + { + let scalar_fixed = -pallas::Base::one(); + let scalar_fixed = chip.load_private( + layouter.namespace(|| "mul by -1"), + column, + Some(scalar_fixed), + )?; + base.mul_base_field_elem(layouter.namespace(|| "mul by -1"), scalar_fixed)?; + } + + Ok(()) + } +} 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 92cf5495..1b9b4110 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -19,9 +19,16 @@ impl Config { offset: usize, region: &mut Region<'_, pallas::Base>, ) -> Result { - let (acc, mul_b) = - self.0 - .assign_region_inner(region, offset, &scalar.into(), base.into())?; + // Copy the scalar decomposition + self.0.copy_scalar(region, offset, &scalar.into())?; + + let (acc, mul_b) = self.0.assign_region_inner( + region, + offset, + &scalar.into(), + base.into(), + self.0.mul_fixed, + )?; // Add to the accumulator and return the final result as `[scalar]B`. let result = self @@ -33,13 +40,9 @@ impl Config { // Check that the correct multiple is obtained. { use group::Curve; - use halo2::arithmetic::FieldExt; let base: super::OrchardFixedBases = base.into(); - let scalar = scalar - .value - .map(|scalar| pallas::Scalar::from_bytes(&scalar.to_bytes()).unwrap()); - let real_mul = scalar.map(|scalar| base.generator() * scalar); + let real_mul = scalar.value.map(|scalar| base.generator() * scalar); let result = result.point(); if let (Some(real_mul), Some(result)) = (real_mul, result) { @@ -159,13 +162,24 @@ pub mod tests { { let scalar_fixed = pallas::Scalar::zero(); let scalar_fixed = ScalarFixed::new( - chip, + chip.clone(), layouter.namespace(|| "ScalarFixed"), Some(scalar_fixed), )?; base.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)?; } + // [-1]B is the largest scalar field element. + { + let scalar_fixed = -pallas::Scalar::one(); + let scalar_fixed = ScalarFixed::new( + chip, + layouter.namespace(|| "ScalarFixed"), + Some(scalar_fixed), + )?; + base.mul(layouter.namespace(|| "mul by -1"), &scalar_fixed)?; + } + Ok(()) } } diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index b003a569..6de6c6a4 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -58,11 +58,16 @@ impl Config { offset: usize, region: &mut Region<'_, pallas::Base>, ) -> Result { + // Copy the scalar decomposition + self.super_config + .copy_scalar(region, offset, &scalar.into())?; + let (acc, mul_b) = self.super_config.assign_region_inner( region, offset, &scalar.into(), base.clone().into(), + self.super_config.mul_fixed, )?; // Add to the cumulative sum to get `[magnitude]B`. diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs index 6d9dc210..19162ed9 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs @@ -1,10 +1,9 @@ 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}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector}, poly::Rotation, }; use pasta_curves::{arithmetic::FieldExt, pallas}; @@ -16,13 +15,15 @@ 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[0], + window: ecc_config.advices[9], + perm: ecc_config.perm.clone(), } } } @@ -48,10 +49,7 @@ impl Config { scalar: Option, offset: usize, region: &mut Region<'_, pallas::Base>, - ) -> Result, NUM_WINDOWS>, Error> - where - pallas::Scalar: PrimeFieldBits, - { + ) -> Result, NUM_WINDOWS>, Error> { // Enable `q_scalar_fixed` selector for idx in 0..NUM_WINDOWS { self.q_scalar_fixed.enable(region, offset + idx)?;