From c9331b123de82e6862054223b697789e9038762d Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 26 Apr 2021 23:22:20 +0800 Subject: [PATCH 01/49] Add ECC chip --- src/circuit/gadget/ecc.rs | 2 + src/circuit/gadget/ecc/chip.rs | 391 +++++++++++++++++++++++++++++++++ 2 files changed, 393 insertions(+) create mode 100644 src/circuit/gadget/ecc/chip.rs diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 31d252e32..b3b79dfb2 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -9,6 +9,8 @@ use halo2::{ plonk::Error, }; +pub mod chip; + /// The set of circuit instructions required to use the ECC gadgets. pub trait EccInstructions: Chip { /// Variable representing an element of the elliptic curve's base field, that diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs new file mode 100644 index 000000000..c4a2ec5c1 --- /dev/null +++ b/src/circuit/gadget/ecc/chip.rs @@ -0,0 +1,391 @@ +use super::EccInstructions; +use crate::constants; +use ff::PrimeField; +use halo2::{ + arithmetic::{CurveAffine, FieldExt}, + circuit::{Cell, Chip, Layouter, Region}, + plonk::{Advice, Any, Column, ConstraintSystem, Error, Fixed, Permutation, Selector}, + poly::Rotation, +}; + +// mod add; +// mod add_incomplete; +// mod double; +// mod load; +// mod mul; +// mod mul_fixed; +// mod mul_fixed_short; +// mod util; +// mod witness_point; +// mod witness_scalar_fixed; +// mod witness_scalar_fixed_short; + +/// A structure containing a cell and its assigned value. +#[derive(Clone, Debug)] +pub struct CellValue { + /// The cell of this `CellValue` + pub cell: Cell, + /// The value assigned to this `CellValue` + pub value: Option, +} + +impl CellValue { + /// Construct a `CellValue`. + pub fn new(cell: Cell, value: Option) -> Self { + CellValue { cell, value } + } +} + +/// A curve point represented in affine (x, y) coordinates. Each coordinate is +/// assigned to a cell. +#[derive(Clone, Debug)] +pub struct EccPoint { + /// x-coordinate + pub x: CellValue, + /// y-coordinate + pub y: CellValue, +} + +/// Configuration for the ECC chip +#[derive(Clone, Debug)] +#[allow(non_snake_case)] +pub struct EccConfig { + /// Advice column for scalar decomposition into bits + pub bits: Column, + /// Holds a point (x_p, y_p) + pub P: (Column, Column), + /// A pair (lambda1, lambda2) representing gradients + pub lambda: (Column, Column), + /// Advice columns needed by instructions in the ECC chip. + pub extras: [Column; 5], + /// Coefficients of interpolation polynomials for x-coordinates (used in fixed-base scalar multiplication) + pub lagrange_coeffs: [Column; constants::H], + /// Fixed z such that y + z = u^2 some square, and -y + z is a non-square. (Used in fixed-base scalar multiplication) + pub fixed_z: Column, + /// Fixed column used in scalar decomposition for variable-base scalar mul + pub mul_decompose: Column, + + /// Point doubling + pub q_double: Selector, + /// Incomplete addition + pub q_add_incomplete: Selector, + /// Complete addition + pub q_add: Selector, + /// Variable-base scalar multiplication + pub q_mul: Selector, + /// Fixed-base full-width scalar multiplication + pub q_mul_fixed: Selector, + /// Fixed-base signed short scalar multiplication + pub q_mul_fixed_short: Selector, + /// Witness point + pub q_point: Selector, + /// Witness full-width scalar for fixed-base scalar mul + pub q_scalar_fixed: Selector, + /// Witness signed short scalar for full-width fixed-base scalar mul + pub q_scalar_fixed_short: Selector, + /// Permutation + pub perm: Permutation, +} + +/// TODO +#[derive(Clone, Debug)] +pub struct EccLoaded(C); + +/// TODO +#[derive(Clone, Debug)] +pub struct OrchardFixedBase(C); +/// TODO +#[derive(Clone, Debug)] +pub struct OrchardFixedBaseShort(C); +/// TODO +#[derive(Clone, Debug)] +pub struct OrchardFixedBases(C); +/// TODO +#[derive(Clone, Debug)] +pub struct OrchardFixedBasesShort(C); + +/// A chip implementing EccInstructions +#[derive(Clone, Debug)] +pub struct EccChip { + id: u64, + config: EccConfig, + loaded: EccLoaded, +} + +impl PartialEq for EccChip { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for EccChip {} + +impl Chip for EccChip { + type Config = EccConfig; + type Loaded = EccLoaded; + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &self.loaded + } +} + +impl EccChip { + pub fn construct( + config: >::Config, + loaded: >::Loaded, + ) -> Self { + Self { + id: rand::random::(), + config, + loaded, + } + } + + #[allow(non_snake_case)] + #[allow(clippy::many_single_char_names)] + #[allow(clippy::too_many_arguments)] + pub fn configure( + meta: &mut ConstraintSystem, + bits: Column, + P: (Column, Column), + lambda: (Column, Column), + extras: [Column; 5], + ) -> >::Config { + let q_double = meta.selector(); + let q_add_incomplete = meta.selector(); + let q_add = meta.selector(); + let q_mul = meta.selector(); + let q_mul_fixed = meta.selector(); + let q_mul_fixed_short = meta.selector(); + let q_point = meta.selector(); + let q_scalar_fixed = meta.selector(); + let q_scalar_fixed_short = meta.selector(); + + 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(), + ]; + let fixed_z = meta.fixed_column(); + let mul_decompose = meta.fixed_column(); + + // Set up permutations + let perm = Permutation::new( + meta, + &[ + P.0.into(), + P.1.into(), + bits.into(), + extras[0].into(), + extras[1].into(), + extras[2].into(), + ], + ); + + // TODO: Create witness point gate + + // TODO: Create witness scalar_fixed gate + + // TODO: Create witness scalar_fixed_short gate + + // TODO: Create point doubling gate + + // TODO: Create point addition gate + + // TODO: Create complete point addition gate + + // TODO: Create fixed-base full-width scalar mul gate + + // TODO: Create fixed-base short signed scalar mul gate + + // TODO: Create variable-base scalar mul gate + + EccConfig { + bits, + P, + lambda, + extras, + lagrange_coeffs, + fixed_z, + mul_decompose, + q_double, + q_add_incomplete, + q_add, + q_mul, + q_mul_fixed, + q_mul_fixed_short, + q_point, + q_scalar_fixed, + q_scalar_fixed_short, + perm, + } + } + + #[allow(clippy::type_complexity)] + pub fn load( + config: >::Config, + layouter: &mut impl Layouter, + ) -> Result<>::Loaded, Error> { + todo!() + } +} + +/// A full-width scalar used for variable-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`. +#[derive(Clone, Debug)] +pub struct EccScalarFixed { + value: Option, + k_bits: Vec>, +} + +/// A signed short scalar used for variable-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`. +#[derive(Clone, Debug)] +pub struct EccScalarFixedShort { + magnitude: Option, + sign: CellValue, + k_bits: Vec>, +} + +impl EccInstructions for EccChip { + type ScalarFixed = EccScalarFixed; + type ScalarFixedShort = EccScalarFixedShort; + type ScalarVar = CellValue; + type Point = EccPoint; + type X = CellValue; + type FixedPoint = OrchardFixedBase; + type FixedPointShort = OrchardFixedBaseShort; + type FixedPoints = OrchardFixedBases; + type FixedPointsShort = OrchardFixedBasesShort; + + fn witness_scalar_var( + &self, + layouter: &mut impl Layouter, + value: Option, + ) -> Result { + todo!() + } + + fn witness_scalar_fixed( + &self, + layouter: &mut impl Layouter, + value: Option, + ) -> Result { + let config = self.config(); + + todo!() + } + + fn witness_scalar_fixed_short( + &self, + layouter: &mut impl Layouter, + value: Option, + ) -> Result { + let config = self.config(); + + todo!() + } + + fn witness_point( + &self, + layouter: &mut impl Layouter, + value: Option, + ) -> Result { + let config = self.config(); + + todo!() + } + + fn extract_p(point: &Self::Point) -> &Self::X { + &point.x + } + + fn get_fixed(&self, fixed_point: Self::FixedPoints) -> Result { + todo!() + } + + fn get_fixed_short( + &self, + fixed_point: Self::FixedPointsShort, + ) -> Result { + todo!() + } + + fn add_incomplete( + &self, + layouter: &mut impl Layouter, + a: &Self::Point, + b: &Self::Point, + ) -> Result { + let config = self.config(); + + todo!() + } + + fn add( + &self, + layouter: &mut impl Layouter, + a: &Self::Point, + b: &Self::Point, + ) -> Result { + let config = self.config(); + + todo!() + } + + fn double( + &self, + layouter: &mut impl Layouter, + a: &Self::Point, + ) -> Result { + let config = self.config(); + + todo!() + } + + fn mul( + &self, + layouter: &mut impl Layouter, + scalar: &Self::ScalarVar, + base: &Self::Point, + ) -> Result { + let config = self.config(); + + todo!() + } + + fn mul_fixed( + &self, + layouter: &mut impl Layouter, + scalar: &Self::ScalarFixed, + base: &Self::FixedPoint, + ) -> Result { + let config = self.config(); + + todo!() + } + + fn mul_fixed_short( + &self, + layouter: &mut impl Layouter, + scalar: &Self::ScalarFixedShort, + base: &Self::FixedPointShort, + ) -> Result { + let config = self.config(); + + todo!() + } +} From b752b92f081e8a034ec0f651c7393d3aa95320d5 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 18 May 2021 12:47:15 +0800 Subject: [PATCH 02/49] Load ECC chip --- src/circuit/gadget/ecc/chip.rs | 75 ++++++---- src/circuit/gadget/ecc/chip/load.rs | 223 ++++++++++++++++++++++++++++ 2 files changed, 271 insertions(+), 27 deletions(-) create mode 100644 src/circuit/gadget/ecc/chip/load.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index c4a2ec5c1..af634cae8 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -3,15 +3,15 @@ use crate::constants; use ff::PrimeField; use halo2::{ arithmetic::{CurveAffine, FieldExt}, - circuit::{Cell, Chip, Layouter, Region}, - plonk::{Advice, Any, Column, ConstraintSystem, Error, Fixed, Permutation, Selector}, + circuit::{Cell, Chip, Layouter}, + plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Permutation, Selector}, poly::Rotation, }; // mod add; // mod add_incomplete; // mod double; -// mod load; +mod load; // mod mul; // mod mul_fixed; // mod mul_fixed_short; @@ -20,6 +20,8 @@ use halo2::{ // mod witness_scalar_fixed; // mod witness_scalar_fixed_short; +pub use load::*; + /// A structure containing a cell and its assigned value. #[derive(Clone, Debug)] pub struct CellValue { @@ -46,6 +48,18 @@ pub struct EccPoint { pub y: CellValue, } +#[derive(Clone, Debug)] +/// For each Orchard fixed base, we precompute: +/// * coefficients for x-coordinate interpolation polynomials, and +/// * z-values such that y + z = u^2 some square while -y + z is non-square. +pub struct EccLoaded { + commit_ivk_r: OrchardFixedBase, + note_commit_r: OrchardFixedBase, + nullifier_k: OrchardFixedBase, + value_commit_r: OrchardFixedBase, + value_commit_v: OrchardFixedBaseShort, +} + /// Configuration for the ECC chip #[derive(Clone, Debug)] #[allow(non_snake_case)] @@ -87,23 +101,6 @@ pub struct EccConfig { pub perm: Permutation, } -/// TODO -#[derive(Clone, Debug)] -pub struct EccLoaded(C); - -/// TODO -#[derive(Clone, Debug)] -pub struct OrchardFixedBase(C); -/// TODO -#[derive(Clone, Debug)] -pub struct OrchardFixedBaseShort(C); -/// TODO -#[derive(Clone, Debug)] -pub struct OrchardFixedBases(C); -/// TODO -#[derive(Clone, Debug)] -pub struct OrchardFixedBasesShort(C); - /// A chip implementing EccInstructions #[derive(Clone, Debug)] pub struct EccChip { @@ -120,6 +117,21 @@ impl PartialEq for EccChip { impl Eq for EccChip {} +impl EccLoaded { + fn get(&self, point: OrchardFixedBases) -> OrchardFixedBase { + match point { + OrchardFixedBases::CommitIvkR(_) => self.commit_ivk_r.clone(), + OrchardFixedBases::NoteCommitR(_) => self.note_commit_r.clone(), + OrchardFixedBases::NullifierK(_) => self.nullifier_k.clone(), + OrchardFixedBases::ValueCommitR(_) => self.value_commit_r.clone(), + } + } + + fn get_short(&self, _point: OrchardFixedBasesShort) -> OrchardFixedBaseShort { + self.value_commit_v.clone() + } +} + impl Chip for EccChip { type Config = EccConfig; type Loaded = EccLoaded; @@ -231,11 +243,20 @@ impl EccChip { } #[allow(clippy::type_complexity)] - pub fn load( - config: >::Config, - layouter: &mut impl Layouter, - ) -> Result<>::Loaded, Error> { - todo!() + pub fn load() -> >::Loaded { + let commit_ivk_r = load::commit_ivk_r(); + let note_commit_r = load::note_commit_r(); + let nullifier_k = load::nullifier_k(); + let value_commit_r = load::value_commit_r(); + let value_commit_v = load::value_commit_v(); + + EccLoaded { + commit_ivk_r, + note_commit_r, + nullifier_k, + value_commit_r, + value_commit_v, + } } } @@ -314,14 +335,14 @@ impl EccInstructions for EccChip { } fn get_fixed(&self, fixed_point: Self::FixedPoints) -> Result { - todo!() + Ok(self.loaded().get(fixed_point)) } fn get_fixed_short( &self, fixed_point: Self::FixedPointsShort, ) -> Result { - todo!() + Ok(self.loaded().get_short(fixed_point)) } fn add_incomplete( diff --git a/src/circuit/gadget/ecc/chip/load.rs b/src/circuit/gadget/ecc/chip/load.rs new file mode 100644 index 000000000..1d5d3fa39 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/load.rs @@ -0,0 +1,223 @@ +use std::convert::TryInto; + +use crate::constants::{self, FixedBase, H, NUM_WINDOWS, NUM_WINDOWS_SHORT}; +use halo2::arithmetic::{CurveAffine, FieldExt}; + +#[derive(Copy, Clone, Debug)] +pub enum OrchardFixedBases { + CommitIvkR(constants::CommitIvkR), + NoteCommitR(constants::NoteCommitR), + NullifierK(constants::NullifierK), + ValueCommitR(constants::ValueCommitR), +} + +#[derive(Copy, Clone, Debug)] +pub struct OrchardFixedBasesShort(pub constants::ValueCommitV); + +/// A fixed base to be used in scalar multiplication with a full-width scalar. +#[derive(Clone, Debug)] +pub struct OrchardFixedBase { + pub base: OrchardFixedBases, + pub lagrange_coeffs: LagrangeCoeffs, + pub z: Z, + pub u: U, +} + +/// A fixed base to be used in scalar multiplication with a short signed exponent. +#[derive(Clone, Debug)] +pub struct OrchardFixedBaseShort { + pub base: OrchardFixedBasesShort, + pub lagrange_coeffs_short: LagrangeCoeffsShort, + pub z_short: ZShort, + pub u_short: UShort, +} + +#[derive(Clone, Debug)] +// 8 coefficients per window +pub struct WindowLagrangeCoeffs(pub Box<[F; H]>); + +#[derive(Clone, Debug)] +// 85 windows per base (with the exception of ValueCommitV) +pub struct LagrangeCoeffs(pub Box<[WindowLagrangeCoeffs; constants::NUM_WINDOWS]>); + +#[derive(Clone, Debug)] +// 22 windows for ValueCommitV +pub struct LagrangeCoeffsShort(pub Box<[WindowLagrangeCoeffs; NUM_WINDOWS_SHORT]>); + +#[derive(Clone, Debug)] +// 85 Z's per base (with the exception of ValueCommitV) +pub struct Z(pub Box<[F; NUM_WINDOWS]>); + +#[derive(Clone, Debug)] +// 22 Z's for ValueCommitV +pub struct ZShort(pub Box<[F; NUM_WINDOWS_SHORT]>); + +#[derive(Clone, Debug)] +// 8 u's per window +pub struct WindowUs(pub Box<[F; H]>); + +#[derive(Clone, Debug)] +// 85 windows per base (with the exception of ValueCommitV) +pub struct U(pub Box<[WindowUs; NUM_WINDOWS]>); + +#[derive(Clone, Debug)] +// 22 windows for ValueCommitV +pub struct UShort(pub Box<[WindowUs; NUM_WINDOWS_SHORT]>); + +pub(super) fn commit_ivk_r() -> OrchardFixedBase { + let commit_ivk_r = constants::commit_ivk_r::generator(); + OrchardFixedBase { + base: OrchardFixedBases::CommitIvkR(commit_ivk_r), + lagrange_coeffs: load_lagrange_coeffs(commit_ivk_r.0.compute_lagrange_coeffs(NUM_WINDOWS)), + z: load_z(&constants::commit_ivk_r::Z), + u: process_u(&constants::commit_ivk_r::U), + } +} + +pub(super) fn note_commit_r() -> OrchardFixedBase { + let note_commit_r = constants::note_commit_r::generator(); + OrchardFixedBase { + base: OrchardFixedBases::NoteCommitR(note_commit_r), + lagrange_coeffs: load_lagrange_coeffs(note_commit_r.0.compute_lagrange_coeffs(NUM_WINDOWS)), + z: load_z(&constants::note_commit_r::Z), + u: process_u(&constants::note_commit_r::U), + } +} + +pub(super) fn nullifier_k() -> OrchardFixedBase { + let nullifier_k = constants::nullifier_k::generator(); + OrchardFixedBase { + base: OrchardFixedBases::NullifierK(nullifier_k), + lagrange_coeffs: load_lagrange_coeffs(nullifier_k.0.compute_lagrange_coeffs(NUM_WINDOWS)), + z: load_z(&constants::nullifier_k::Z), + u: process_u(&constants::nullifier_k::U), + } +} + +pub(super) fn value_commit_r() -> OrchardFixedBase { + let value_commit_r = constants::value_commit_r::generator(); + OrchardFixedBase { + base: OrchardFixedBases::ValueCommitR(value_commit_r), + lagrange_coeffs: load_lagrange_coeffs( + value_commit_r.0.compute_lagrange_coeffs(NUM_WINDOWS), + ), + z: load_z(&constants::value_commit_r::Z), + u: process_u(&constants::value_commit_r::U), + } +} + +pub(super) fn value_commit_v() -> OrchardFixedBaseShort { + let value_commit_v = constants::value_commit_v::generator(); + OrchardFixedBaseShort { + base: OrchardFixedBasesShort(value_commit_v), + lagrange_coeffs_short: load_lagrange_coeffs_short( + value_commit_v.0.compute_lagrange_coeffs(NUM_WINDOWS_SHORT), + ), + z_short: load_z_short(&constants::value_commit_v::Z_SHORT), + u_short: process_u_short(&constants::value_commit_v::U_SHORT), + } +} + +fn load_lagrange_coeffs(coeffs: Vec<[F; H]>) -> LagrangeCoeffs { + LagrangeCoeffs( + coeffs + .iter() + .map(|window| { + WindowLagrangeCoeffs( + window + .iter() + .map(|&coeff| coeff) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap(), + ) + }) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap(), + ) +} + +fn load_lagrange_coeffs_short(coeffs: Vec<[F; H]>) -> LagrangeCoeffsShort { + LagrangeCoeffsShort( + coeffs + .iter() + .map(|window| { + WindowLagrangeCoeffs( + window + .iter() + .map(|&coeff| coeff) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap(), + ) + }) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap(), + ) +} + +fn load_z(zs: &[u64]) -> Z { + Z(zs.iter() + .map(|z| F::from_u64(*z)) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap()) +} + +fn load_z_short(zs: &[u64]) -> ZShort { + ZShort( + zs.iter() + .map(|z| F::from_u64(*z)) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap(), + ) +} + +fn process_u(us: &[[[u8; 32]; H]]) -> U { + U(us.iter() + .map(|window_us| { + WindowUs( + window_us + .iter() + .map(|u| F::from_bytes(&u).unwrap()) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap(), + ) + }) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap()) +} + +fn process_u_short(us: &[[[u8; 32]; H]]) -> UShort { + UShort( + us.iter() + .map(|window_us| { + WindowUs( + window_us + .iter() + .map(|u| F::from_bytes(&u).unwrap()) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap(), + ) + }) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap(), + ) +} From 46af16b5aaf33e95342c75a05e54f99ed8251a9e Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 26 Apr 2021 23:40:52 +0800 Subject: [PATCH 03/49] Add chip::util mod with assign_and_constrain() helper --- src/circuit/gadget/ecc/chip.rs | 2 +- src/circuit/gadget/ecc/chip/util.rs | 37 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/circuit/gadget/ecc/chip/util.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index af634cae8..017829bbd 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -15,7 +15,7 @@ mod load; // mod mul; // mod mul_fixed; // mod mul_fixed_short; -// mod util; +mod util; // mod witness_point; // mod witness_scalar_fixed; // mod witness_scalar_fixed_short; diff --git a/src/circuit/gadget/ecc/chip/util.rs b/src/circuit/gadget/ecc/chip/util.rs new file mode 100644 index 000000000..6a24975dd --- /dev/null +++ b/src/circuit/gadget/ecc/chip/util.rs @@ -0,0 +1,37 @@ +use super::CellValue; +use halo2::{ + arithmetic::FieldExt, + circuit::Region, + plonk::{Advice, Any, Column, Error, Fixed, Permutation}, +}; +use std::convert::TryFrom; + +/// Assign a cell the same value as another cell and set up a copy constraint between them. +pub(super) fn assign_and_constrain( + region: &mut Region<'_, F>, + annotation: A, + column: Column, + row: usize, + copy: &CellValue, + perm: &Permutation, +) -> Result, Error> +where + A: Fn() -> AR, + AR: Into, +{ + let cell = if let Ok(column) = Column::::try_from(column) { + region.assign_advice(annotation, column, row, || { + copy.value.ok_or(Error::SynthesisError) + })? + } else if let Ok(column) = Column::::try_from(column) { + region.assign_fixed(annotation, column, row, || { + copy.value.ok_or(Error::SynthesisError) + })? + } else { + return Err(Error::SynthesisError); + }; + + region.constrain_equal(perm, cell, copy.cell)?; + + Ok(CellValue::new(cell, copy.value)) +} From ae288a2e39ecfe5942a21fdf99edde6bebb43543 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 26 Apr 2021 23:43:27 +0800 Subject: [PATCH 04/49] Witness point --- src/circuit/gadget/ecc/chip.rs | 17 ++++-- src/circuit/gadget/ecc/chip/witness_point.rs | 55 ++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 src/circuit/gadget/ecc/chip/witness_point.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 017829bbd..80c186e44 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -16,7 +16,7 @@ mod load; // mod mul_fixed; // mod mul_fixed_short; mod util; -// mod witness_point; +mod witness_point; // mod witness_scalar_fixed; // mod witness_scalar_fixed_short; @@ -203,7 +203,15 @@ impl EccChip { ], ); - // TODO: Create witness point gate + // Create witness point gate + { + let q_point = meta.query_selector(q_point, Rotation::cur()); + let P = ( + meta.query_advice(P.0, Rotation::cur()), + meta.query_advice(P.1, Rotation::cur()), + ); + witness_point::create_gate::(meta, q_point, P.0, P.1); + } // TODO: Create witness scalar_fixed gate @@ -327,7 +335,10 @@ impl EccInstructions for EccChip { ) -> Result { let config = self.config(); - todo!() + layouter.assign_region( + || "witness point", + |mut region| witness_point::assign_region(value, 0, &mut region, config.clone()), + ) } fn extract_p(point: &Self::Point) -> &Self::X { diff --git a/src/circuit/gadget/ecc/chip/witness_point.rs b/src/circuit/gadget/ecc/chip/witness_point.rs new file mode 100644 index 000000000..43c7a0f27 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/witness_point.rs @@ -0,0 +1,55 @@ +use super::{CellValue, EccConfig, EccPoint}; + +use halo2::{ + arithmetic::CurveAffine, + circuit::Region, + plonk::{ConstraintSystem, Error, Expression}, +}; + +pub(super) fn create_gate( + meta: &mut ConstraintSystem, + q_point: Expression, + x_p: Expression, + y_p: Expression, +) { + meta.create_gate("witness point", |_| { + // Check that y^2 = x^3 + b, where b = 5 in the Pallas equation + q_point + * (y_p.clone() * y_p - (x_p.clone() * x_p.clone() * x_p) - Expression::Constant(C::b())) + }); +} + +pub(super) fn assign_region( + value: Option, + offset: usize, + region: &mut Region<'_, C::Base>, + config: EccConfig, +) -> Result, Error> { + // Enable `q_point` selector + config.q_point.enable(region, offset)?; + + let value = value.map(|value| value.coordinates().unwrap()); + + // Assign `x_p` value + let x_p_val = value.map(|value| *value.x()); + let x_p_var = region.assign_advice( + || "x_p", + config.P.0, + offset, + || x_p_val.ok_or(Error::SynthesisError), + )?; + + // Assign `y_p` value + let y_p_val = value.map(|value| *value.y()); + let y_p_var = region.assign_advice( + || "y_p", + config.P.1, + offset, + || y_p_val.ok_or(Error::SynthesisError), + )?; + + Ok(EccPoint { + x: CellValue::::new(x_p_var, x_p_val), + y: CellValue::::new(y_p_var, y_p_val), + }) +} From d6116d6f8aa26f24bd77cbb1318620ddb1d70a97 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 27 Apr 2021 22:47:59 +0800 Subject: [PATCH 05/49] Point doubling --- src/circuit/gadget/ecc/chip.rs | 18 +++++- src/circuit/gadget/ecc/chip/double.rs | 91 +++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 src/circuit/gadget/ecc/chip/double.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 80c186e44..350f123f6 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -10,7 +10,7 @@ use halo2::{ // mod add; // mod add_incomplete; -// mod double; +mod double; mod load; // mod mul; // mod mul_fixed; @@ -217,7 +217,16 @@ impl EccChip { // TODO: Create witness scalar_fixed_short gate - // TODO: Create point doubling gate + // Create point doubling gate + { + let q_double = meta.query_selector(q_double, Rotation::cur()); + let x_a = meta.query_advice(extras[0], Rotation::cur()); + let y_a = meta.query_advice(extras[1], Rotation::cur()); + let x_p = meta.query_advice(P.0, Rotation::cur()); + let y_p = meta.query_advice(P.1, Rotation::cur()); + + double::create_gate(meta, q_double, x_a, y_a, x_p, y_p); + } // TODO: Create point addition gate @@ -385,7 +394,10 @@ impl EccInstructions for EccChip { ) -> Result { let config = self.config(); - todo!() + layouter.assign_region( + || "point doubling", + |mut region| double::assign_region(a, 0, &mut region, config.clone()), + ) } fn mul( diff --git a/src/circuit/gadget/ecc/chip/double.rs b/src/circuit/gadget/ecc/chip/double.rs new file mode 100644 index 000000000..e393f9662 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/double.rs @@ -0,0 +1,91 @@ +use super::{util, CellValue, EccConfig, EccPoint}; + +use halo2::{ + arithmetic::FieldExt, + circuit::Region, + plonk::{ConstraintSystem, Error, Expression}, +}; + +pub(super) fn create_gate( + meta: &mut ConstraintSystem, + q_double: Expression, + x_a: Expression, + y_a: Expression, + x_p: Expression, + y_p: Expression, +) { + let x_p_2 = x_p.clone() * x_p.clone(); + let x_p_4 = x_p_2.clone() * x_p_2.clone(); + + // 4⋅(y_p)^2⋅(x_a + 2⋅x_p) − 9⋅(x_p)^4 = 0 + meta.create_gate("point doubling expr1", |_| { + let expr1 = y_p.clone() + * y_p.clone() + * (x_a.clone() + x_p.clone() * F::from_u64(2)) + * F::from_u64(4) + - x_p_4 * F::from_u64(9); + q_double.clone() * expr1 + }); + + // 2⋅y_p⋅(y_a + y_p) − 3⋅(x_p)^2⋅(x_p − x_a) = 0 + meta.create_gate("point doubling expr2", |_| { + let expr2 = + y_p.clone() * (y_a + y_p) * F::from_u64(2) - x_p_2 * (x_p - x_a) * F::from_u64(3); + + q_double * expr2 + }); +} + +#[allow(non_snake_case)] +pub(super) fn assign_region( + a: &EccPoint, + offset: usize, + region: &mut Region<'_, F>, + config: EccConfig, +) -> Result, Error> { + // Rename columns + let A = (config.extras[0], config.extras[1]); + + // Enable `q_double` selector + config.q_double.enable(region, offset)?; + + // Copy the point into `x_p`, `y_p` columns + util::assign_and_constrain( + region, + || "x_p", + config.P.0.into(), + offset, + &a.x, + &config.perm, + )?; + util::assign_and_constrain( + region, + || "y_p", + config.P.1.into(), + offset, + &a.y, + &config.perm, + )?; + + // Compute the doubled point + let (x_p, y_p) = (a.x.value, a.y.value); + let r = x_p.zip(y_p).map(|(x_p, y_p)| { + let lambda = F::from_u64(3) * x_p * x_p * F::TWO_INV * y_p.invert().unwrap(); + let x_r = lambda * lambda - x_p - x_p; + let y_r = lambda * (x_p - x_r) - y_p; + (x_r, y_r) + }); + let x_r = r.map(|r| r.0); + let y_r = r.map(|r| r.1); + + // Assign the doubled point to `x_a`, `y_a` columns + let x_r_var = + region.assign_advice(|| "x_r", A.0, offset, || x_r.ok_or(Error::SynthesisError))?; + let y_r_var = + region.assign_advice(|| "y_r", A.1, offset, || y_r.ok_or(Error::SynthesisError))?; + + Ok(EccPoint { + x: CellValue::::new(x_r_var, x_r), + y: CellValue::::new(y_r_var, y_r), + }) +} From b67d1f722fff5838b334765c5c942d7a74679252 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 26 Apr 2021 23:53:24 +0800 Subject: [PATCH 06/49] Incomplete point addition --- src/circuit/gadget/ecc/chip.rs | 20 ++- src/circuit/gadget/ecc/chip/add_incomplete.rs | 126 ++++++++++++++++++ 2 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 src/circuit/gadget/ecc/chip/add_incomplete.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 350f123f6..466af6325 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -9,7 +9,7 @@ use halo2::{ }; // mod add; -// mod add_incomplete; +mod add_incomplete; mod double; mod load; // mod mul; @@ -228,7 +228,18 @@ impl EccChip { double::create_gate(meta, q_double, x_a, y_a, x_p, y_p); } - // TODO: Create point addition gate + // Create incomplete point addition gate + { + let q_add = meta.query_selector(q_add_incomplete, Rotation::cur()); + let x_p = meta.query_advice(P.0, Rotation::cur()); + let y_p = meta.query_advice(P.1, Rotation::cur()); + let x_q = meta.query_advice(extras[0], Rotation::cur()); + let y_q = meta.query_advice(extras[1], Rotation::cur()); + let x_a = meta.query_advice(extras[0], Rotation::next()); + let y_a = meta.query_advice(extras[1], Rotation::next()); + + add_incomplete::create_gate(meta, q_add, x_p, y_p, x_q, y_q, x_a, y_a); + } // TODO: Create complete point addition gate @@ -373,7 +384,10 @@ impl EccInstructions for EccChip { ) -> Result { let config = self.config(); - todo!() + layouter.assign_region( + || "point addition", + |mut region| add_incomplete::assign_region(a, b, 0, &mut region, config.clone()), + ) } fn add( diff --git a/src/circuit/gadget/ecc/chip/add_incomplete.rs b/src/circuit/gadget/ecc/chip/add_incomplete.rs new file mode 100644 index 000000000..f574b9248 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/add_incomplete.rs @@ -0,0 +1,126 @@ +use super::{util, CellValue, EccConfig, EccPoint}; +use halo2::{ + arithmetic::FieldExt, + circuit::Region, + plonk::{ConstraintSystem, Error, Expression}, +}; + +#[allow(clippy::too_many_arguments)] +pub(super) fn create_gate( + meta: &mut ConstraintSystem, + q_add_incomplete: Expression, + x_p: Expression, + y_p: Expression, + x_q: Expression, + y_q: Expression, + x_a: Expression, + y_a: Expression, +) { + // (x_a + x_q + x_p)⋅(x_p − x_q)^2 − (y_p − y_q)^2 = 0 + meta.create_gate("point addition expr1", |_| { + let expr1 = (x_a.clone() + x_q.clone() + x_p.clone()) + * (x_p.clone() - x_q.clone()) + * (x_p.clone() - x_q.clone()) + - (y_p.clone() - y_q.clone()) * (y_p.clone() - y_q.clone()); + + q_add_incomplete.clone() * expr1 + }); + + // (y_a + y_q)(x_p − x_q) − (y_p − y_q)(x_q − x_a) = 0 + meta.create_gate("point addition expr2", |_| { + let expr2 = (y_a + y_q.clone()) * (x_p - x_q.clone()) - (y_p - y_q) * (x_q - x_a); + + q_add_incomplete * expr2 + }); +} + +#[allow(non_snake_case)] +pub(super) fn assign_region( + a: &EccPoint, + b: &EccPoint, + offset: usize, + region: &mut Region<'_, F>, + config: EccConfig, +) -> Result, Error> { + // Compute the sum `a + b` + let (x_p, y_p) = (a.x.value, a.y.value); + let (x_q, y_q) = (b.x.value, b.y.value); + + // Handle exceptional cases + x_p.zip(y_p) + .zip(x_q) + .zip(y_q) + .map(|(((x_p, y_p), x_q), y_q)| { + // P is point at infinity + if (x_p == F::zero() && y_p == F::zero()) + // Q is point at infinity + || (x_q == F::zero() && y_q == F::zero()) + // x_p = x_q + || (x_p == x_q) + { + return Err(Error::SynthesisError); + } + Ok(()) + }); + + // Rename columns for `add` context + let A = (config.extras[0], config.extras[1]); + + // Enable `q_add_incomplete` selector + config.q_add_incomplete.enable(region, offset)?; + + // Copy point `a` into `x_p`, `y_p` columns + util::assign_and_constrain( + region, + || "x_p", + config.P.0.into(), + offset, + &a.x, + &config.perm, + )?; + util::assign_and_constrain( + region, + || "y_p", + config.P.1.into(), + offset, + &a.y, + &config.perm, + )?; + + // Copy point `b` into `x_a`, `y_a` columns + util::assign_and_constrain(region, || "x_q", A.0.into(), offset, &b.x, &config.perm)?; + util::assign_and_constrain(region, || "y_q", A.1.into(), offset, &b.y, &config.perm)?; + + let r = x_p + .zip(y_p) + .zip(x_q) + .zip(y_q) + .map(|(((x_p, y_p), x_q), y_q)| { + let lambda = (y_q - y_p) * (x_q - x_p).invert().unwrap(); + let x_r = lambda * lambda - x_p - x_q; + let y_r = lambda * (x_p - x_r) - y_p; + (x_r, y_r) + }); + + // Assign the sum to `x_a`, `y_a` columns in the next row + let x_r = r.map(|r| r.0); + let x_r_var = region.assign_advice( + || "x_r", + A.0, + offset + 1, + || x_r.ok_or(Error::SynthesisError), + )?; + + let y_r = r.map(|r| r.1); + let y_r_var = region.assign_advice( + || "y_r", + A.1, + offset + 1, + || y_r.ok_or(Error::SynthesisError), + )?; + + Ok(EccPoint { + x: CellValue::::new(x_r_var, x_r), + y: CellValue::::new(y_r_var, y_r), + }) +} From 94d0cd3f953010621b46f450316dd0203ac05ae5 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 26 Apr 2021 23:54:39 +0800 Subject: [PATCH 07/49] Witness scalar for fixed-base mul --- src/circuit/gadget/ecc/chip.rs | 22 +++++++- .../gadget/ecc/chip/witness_scalar_fixed.rs | 55 +++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 466af6325..ceecb3b3c 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -17,7 +17,7 @@ mod load; // mod mul_fixed_short; mod util; mod witness_point; -// mod witness_scalar_fixed; +mod witness_scalar_fixed; // mod witness_scalar_fixed_short; pub use load::*; @@ -213,7 +213,12 @@ impl EccChip { witness_point::create_gate::(meta, q_point, P.0, P.1); } - // TODO: Create witness scalar_fixed gate + // Create witness scalar_fixed gate + { + let q_scalar_fixed = meta.query_selector(q_scalar_fixed, Rotation::cur()); + let k = meta.query_advice(bits, Rotation::cur()); + witness_scalar_fixed::create_gate(meta, q_scalar_fixed, k); + } // TODO: Create witness scalar_fixed_short gate @@ -335,7 +340,18 @@ impl EccInstructions for EccChip { ) -> Result { let config = self.config(); - todo!() + layouter.assign_region( + || "witness scalar for fixed-base mul", + |mut region| { + witness_scalar_fixed::assign_region( + value, + C::Scalar::NUM_BITS as usize, + 0, + &mut region, + config.clone(), + ) + }, + ) } fn witness_scalar_fixed_short( 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 000000000..9ea33c9b5 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs @@ -0,0 +1,55 @@ +use super::{CellValue, EccConfig, EccScalarFixed}; +use crate::constants::{self, util}; +use halo2::{ + arithmetic::{CurveAffine, FieldExt}, + circuit::Region, + plonk::{ConstraintSystem, Error, Expression}, +}; + +pub(super) fn create_gate( + meta: &mut ConstraintSystem, + q_scalar_fixed: Expression, + k: Expression, +) { + meta.create_gate("witness scalar fixed", |_| { + // Check that `k` is within the allowed window size + let range_check = (0..constants::H).fold(Expression::Constant(F::one()), |acc, i| { + acc * (k.clone() - Expression::Constant(F::from_u64(i as u64))) + }); + q_scalar_fixed * range_check + }); +} + +pub(super) fn assign_region( + value: Option, + scalar_num_bits: usize, + offset: usize, + region: &mut Region<'_, C::Base>, + config: EccConfig, +) -> Result, Error> { + // Decompose scalar into windows + let bits: Option> = value.map(|value| { + util::decompose_scalar_fixed::(value, scalar_num_bits, constants::FIXED_BASE_WINDOW_SIZE) + }); + + // Store the scalar decomposition + let mut k_bits: Vec> = Vec::new(); + + if let Some(bits) = bits { + for (idx, window) in bits.iter().enumerate() { + // Enable `q_scalar_fixed` selector + config.q_scalar_fixed.enable(region, offset + idx)?; + + let window = C::Base::from_u64(*window as u64); + let k_var = region.assign_advice( + || format!("k[{:?}]", offset + idx), + config.bits, + offset + idx, + || Ok(window), + )?; + k_bits.push(CellValue::new(k_var, Some(window))); + } + } + + Ok(EccScalarFixed { value, k_bits }) +} From d8f02af8cc3e310dacf32f1572f3aecd7ae25b5e Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 26 Apr 2021 23:57:14 +0800 Subject: [PATCH 08/49] Fixed-base scalar mul --- src/circuit/gadget/ecc/chip.rs | 21 +- src/circuit/gadget/ecc/chip/mul_fixed.rs | 237 +++++++++++++++++++++++ 2 files changed, 255 insertions(+), 3 deletions(-) create mode 100644 src/circuit/gadget/ecc/chip/mul_fixed.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index ceecb3b3c..81e03ca96 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -13,7 +13,7 @@ mod add_incomplete; mod double; mod load; // mod mul; -// mod mul_fixed; +mod mul_fixed; // mod mul_fixed_short; mod util; mod witness_point; @@ -248,7 +248,17 @@ impl EccChip { // TODO: Create complete point addition gate - // TODO: Create fixed-base full-width scalar mul gate + // Create fixed-base full-width scalar mul gate + { + let q_mul_fixed = meta.query_selector(q_mul_fixed, Rotation::cur()); + let x_p = meta.query_advice(P.0, Rotation::cur()); + let y_p = meta.query_advice(P.1, Rotation::cur()); + let k = meta.query_advice(bits, Rotation::cur()); + let u = meta.query_advice(extras[2], Rotation::cur()); + let z = meta.query_fixed(fixed_z, Rotation::cur()); + + mul_fixed::create_gate(meta, lagrange_coeffs, q_mul_fixed, x_p, y_p, k, u, z); + } // TODO: Create fixed-base short signed scalar mul gate @@ -449,7 +459,12 @@ impl EccInstructions for EccChip { ) -> Result { let config = self.config(); - todo!() + layouter.assign_region( + || format!("Multiply {:?}", base.base), + |mut region| { + mul_fixed::assign_region::(scalar, base, 0, &mut region, config.clone()) + }, + ) } fn mul_fixed_short( diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs new file mode 100644 index 000000000..0708f983d --- /dev/null +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -0,0 +1,237 @@ +use super::{ + add_incomplete, util, witness_point, EccConfig, EccPoint, EccScalarFixed, OrchardFixedBase, + OrchardFixedBases, +}; +use crate::constants; + +use group::Curve; +use halo2::{ + arithmetic::{CurveAffine, Field, FieldExt}, + circuit::Region, + plonk::{Column, ConstraintSystem, Error, Expression, Fixed}, + poly::Rotation, +}; + +#[allow(clippy::too_many_arguments)] +pub(super) fn create_gate( + meta: &mut ConstraintSystem, + lagrange_coeffs: [Column; constants::H], + q_mul_fixed: Expression, + x_p: Expression, + y_p: Expression, + k: Expression, + u: Expression, + z: Expression, +) { + // Check interpolation of x-coordinate + meta.create_gate("fixed-base scalar mul (x)", |meta| { + let k_pow: Vec> = (0..constants::H) + .map(|pow| (0..pow).fold(Expression::Constant(F::one()), |acc, _| acc * k.clone())) + .collect(); + + let interpolated_x = k_pow + .iter() + .zip(lagrange_coeffs.iter()) + .fold(Expression::Constant(F::zero()), |acc, (k_pow, coeff)| { + acc + (k_pow.clone() * meta.query_fixed(*coeff, Rotation::cur())) + }); + + q_mul_fixed.clone() * (interpolated_x - x_p) + }); + + // Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed + meta.create_gate("fixed-base scalar mul (y)", |_| { + q_mul_fixed * (u.clone() * u - y_p - z) + }); +} + +#[allow(non_snake_case)] +pub(super) fn assign_region( + scalar: &EccScalarFixed, + base: &OrchardFixedBase, + offset: usize, + region: &mut Region<'_, C::Base>, + config: EccConfig, +) -> Result, Error> { + // Rename columns for `mul_fixed` context + let A = (config.extras[0], config.extras[1]); + let mul_fixed_u = config.extras[2]; + + // Assign fixed columns for given fixed base + for w in 0..constants::NUM_WINDOWS { + // Enable `q_mul_fixed` selector + config.q_mul_fixed.enable(region, w + offset)?; + + // Assign x-coordinate Lagrange interpolation coefficients + for k in 0..(constants::H) { + region.assign_fixed( + || { + format!( + "Lagrange interpolation coeff for window: {:?}, k: {:?}", + w, k + ) + }, + config.lagrange_coeffs[k], + w + offset, + || Ok(base.lagrange_coeffs.0[w].0[k]), + )?; + } + + // Assign z-values for each window + region.assign_fixed( + || format!("z-value for window: {:?}", w), + config.fixed_z.into(), + w + offset, + || Ok(base.z.0[w]), + )?; + } + + // Copy the scalar decomposition + for (w, k) in scalar.k_bits.iter().enumerate() { + util::assign_and_constrain( + region, + || format!("k[{:?}]", w), + config.bits.into(), + w + offset, + k, + &config.perm, + )?; + } + + // Get the value of the fixed base. `ValueCommitV` is excluded here + // since it is only used in multiplication with a short signed exponent. + let b = match base.base { + OrchardFixedBases::CommitIvkR(inner) => inner.0.value(), + OrchardFixedBases::NoteCommitR(inner) => inner.0.value(), + OrchardFixedBases::NullifierK(inner) => inner.0.value(), + OrchardFixedBases::ValueCommitR(inner) => inner.0.value(), + }; + + // The scalar decomposition was done in the base field. For computation + // outside the circuit, we now convert them back into the scalar field. + let k = scalar + .k_bits + .iter() + .map(|bits| { + bits.value + .map(|value| C::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 + let k_usize = scalar + .k_bits + .iter() + .map(|bits| bits.value.map(|value| value.to_bytes()[0] as usize)) + .collect::>(); + + // This is 2^w, where w is the window width + let h = C::Scalar::from_u64(constants::H as u64); + + // Process the least significant window outside the for loop + let mul_b = k[0].map(|k_0| b * (k_0 + C::Scalar::one())); + let mul_b = witness_point::assign_region( + mul_b.map(|point| point.to_affine()), + 0, + region, + config.clone(), + )?; + + // Assign u = (y_p + z_w).sqrt() for the least significant window + { + let u_val = k_usize[0].map(|k_0| base.u.0[0].0[k_0]); + region.assign_advice( + || "u", + mul_fixed_u, + offset, + || u_val.ok_or(Error::SynthesisError), + )?; + } + + // Initialise the point which will cumulatively sum to [scalar]B. + // Copy and assign `mul_b` to x_a, y_a columns on the next row + let x_sum = util::assign_and_constrain( + region, + || "initialize sum x", + A.0.into(), + offset + 1, + &mul_b.x, + &config.perm, + )?; + let y_sum = util::assign_and_constrain( + region, + || "initialize sum y", + A.1.into(), + offset + 1, + &mul_b.y, + &config.perm, + )?; + + let mut sum = EccPoint { x: x_sum, y: y_sum }; + + // Process all windows excluding least and most significant windows + for (w, k) in k[1..(k.len() - 1)].iter().enumerate() { + // Offset index by 1 since we already assigned row 0 outside this loop + let w = w + 1; + + // Compute [(k_w + 1) ⋅ 8^w]B + let mul_b = k.map(|k| b * (k + C::Scalar::one()) * h.pow(&[w as u64, 0, 0, 0])); + let mul_b = witness_point::assign_region( + mul_b.map(|point| point.to_affine()), + offset + w, + region, + config.clone(), + )?; + + // Assign u = (y_p + z_w).sqrt() + let u_val = k_usize[w].map(|k| base.u.0[w].0[k]); + region.assign_advice( + || "u", + mul_fixed_u, + w, + || u_val.ok_or(Error::SynthesisError), + )?; + + // Add to the cumulative sum + sum = add_incomplete::assign_region(&mul_b, &sum, offset + w, region, config.clone())?; + } + + // Process most significant window outside the for loop + let offset_sum = (0..(constants::NUM_WINDOWS - 1)).fold(C::ScalarExt::zero(), |acc, w| { + acc + h.pow(&[w as u64, 0, 0, 0]) + }); + + // `scalar = [k * 8^84 - offset_sum]`, where `offset_sum = \sum_{j = 0}^{83} 8^j`. + let scalar = k[k.len() - 1] + .map(|k| k * h.pow(&[(constants::NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_sum); + let mul_b = scalar.map(|scalar| b * scalar); + let mul_b = witness_point::assign_region( + mul_b.map(|point| point.to_affine()), + offset + constants::NUM_WINDOWS - 1, + region, + config.clone(), + )?; + + // Assign u = (y_p + z_w).sqrt() for the most significant window + { + let u_val = + k_usize[constants::NUM_WINDOWS - 1].map(|k| base.u.0[constants::NUM_WINDOWS - 1].0[k]); + region.assign_advice( + || "u", + mul_fixed_u, + offset + constants::NUM_WINDOWS - 1, + || u_val.ok_or(Error::SynthesisError), + )?; + } + + // Add to the cumulative sum and return the final result as `[scalar]B`. + add_incomplete::assign_region( + &mul_b, + &sum, + offset + constants::NUM_WINDOWS - 1, + region, + config, + ) +} From 13d9024516cbbb4b3dc28de5820b304478e827a7 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 27 Apr 2021 00:01:44 +0800 Subject: [PATCH 09/49] Witness short signed scalar for fixed-base mul --- src/circuit/gadget/ecc/chip.rs | 16 +++- .../ecc/chip/witness_scalar_fixed_short.rs | 92 +++++++++++++++++++ 2 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 src/circuit/gadget/ecc/chip/witness_scalar_fixed_short.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 81e03ca96..f7cc09056 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -18,7 +18,7 @@ mod mul_fixed; mod util; mod witness_point; mod witness_scalar_fixed; -// mod witness_scalar_fixed_short; +mod witness_scalar_fixed_short; pub use load::*; @@ -220,7 +220,12 @@ impl EccChip { witness_scalar_fixed::create_gate(meta, q_scalar_fixed, k); } - // TODO: Create witness scalar_fixed_short gate + // Create witness scalar_fixed_short gate + { + let q_scalar_fixed_short = meta.query_selector(q_scalar_fixed_short, Rotation::cur()); + let k = meta.query_advice(bits, Rotation::cur()); + witness_scalar_fixed_short::create_gate(meta, q_scalar_fixed_short, k); + } // Create point doubling gate { @@ -371,7 +376,12 @@ impl EccInstructions for EccChip { ) -> Result { let config = self.config(); - todo!() + layouter.assign_region( + || "witness scalar for fixed-base mul", + |mut region| { + witness_scalar_fixed_short::assign_region(value, 0, &mut region, config.clone()) + }, + ) } fn witness_point( 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 000000000..e0026b2c7 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed_short.rs @@ -0,0 +1,92 @@ +use super::{CellValue, EccConfig, EccScalarFixedShort}; +use crate::constants::{self, util}; +use halo2::{ + arithmetic::{CurveAffine, Field, FieldExt}, + circuit::Region, + plonk::{ConstraintSystem, Error, Expression}, +}; + +pub(super) fn create_gate( + meta: &mut ConstraintSystem, + q_scalar_fixed_short: Expression, + s: Expression, +) { + // Check that s \in {1, -1} + meta.create_gate("check sign", |_| { + q_scalar_fixed_short + * (s.clone() + Expression::Constant(F::one())) + * (s - Expression::Constant(F::one())) + }); +} + +pub(super) fn assign_region( + value: Option, + offset: usize, + region: &mut Region<'_, C::Base>, + config: EccConfig, +) -> Result, Error> { + // Compute the scalar's sign and magnitude + let sign = value.map(|value| { + // t = (p - 1)/2 + let t = (C::Scalar::zero() - C::Scalar::one()) * C::Scalar::TWO_INV; + if value > t { + -C::Scalar::one() + } else { + C::Scalar::one() + } + }); + let magnitude = sign.zip(value).map(|(sign, value)| sign * value); + + // Decompose magnitude into windows + let bits: Option> = magnitude.map(|magnitude| { + util::decompose_scalar_fixed::( + magnitude, + constants::L_VALUE, + constants::FIXED_BASE_WINDOW_SIZE, + ) + }); + + // Assign and store the magnitude decomposition + let mut k_bits: Vec> = Vec::new(); + + if let Some(bits) = bits { + for (idx, window) in bits.iter().enumerate() { + // Enable `q_scalar_fixed` selector + config.q_scalar_fixed.enable(region, offset + idx)?; + + let window = C::Base::from_u64(*window as u64); + let k_var = region.assign_advice( + || format!("k[{:?}]", offset + idx), + config.bits, + offset + idx, + || Ok(window), + )?; + k_bits.push(CellValue::new(k_var, Some(window))); + } + } + + // Assign the sign and enable `q_scalar_fixed_short` + let sign = sign.map(|sign| { + assert!(sign == C::Scalar::one() || sign == -C::Scalar::one()); + if sign == C::Scalar::one() { + C::Base::one() + } else { + -C::Base::one() + } + }); + let sign_cell = region.assign_advice( + || "sign", + config.bits, + offset + k_bits.len(), + || sign.ok_or(Error::SynthesisError), + )?; + config + .q_scalar_fixed_short + .enable(region, offset + k_bits.len())?; + + Ok(EccScalarFixedShort { + magnitude, + sign: CellValue::::new(sign_cell, sign), + k_bits, + }) +} From 5c4e307767028e85b0161352cb0b3be8d0f61150 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 27 Apr 2021 00:06:34 +0800 Subject: [PATCH 10/49] Signed short fixed-base scalar mul --- src/circuit/gadget/ecc/chip.rs | 19 +- .../gadget/ecc/chip/mul_fixed_short.rs | 259 ++++++++++++++++++ 2 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 src/circuit/gadget/ecc/chip/mul_fixed_short.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index f7cc09056..11cb235fd 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -14,7 +14,7 @@ mod double; mod load; // mod mul; mod mul_fixed; -// mod mul_fixed_short; +mod mul_fixed_short; mod util; mod witness_point; mod witness_scalar_fixed; @@ -265,7 +265,15 @@ impl EccChip { mul_fixed::create_gate(meta, lagrange_coeffs, q_mul_fixed, x_p, y_p, k, u, z); } - // TODO: Create fixed-base short signed scalar mul gate + // Create fixed-base short signed scalar mul gate + { + let q_mul_fixed_short = meta.query_selector(q_mul_fixed_short, Rotation::cur()); + let s = meta.query_advice(bits, Rotation::cur()); + let y_a = meta.query_advice(extras[1], Rotation::cur()); + let y_p = meta.query_advice(P.1, Rotation::cur()); + + mul_fixed_short::create_gate(meta, q_mul_fixed_short, s, y_a, y_p); + } // TODO: Create variable-base scalar mul gate @@ -485,6 +493,11 @@ impl EccInstructions for EccChip { ) -> Result { let config = self.config(); - todo!() + layouter.assign_region( + || format!("Multiply {:?}", base.base), + |mut region| { + mul_fixed_short::assign_region::(scalar, base, 0, &mut region, config.clone()) + }, + ) } } diff --git a/src/circuit/gadget/ecc/chip/mul_fixed_short.rs b/src/circuit/gadget/ecc/chip/mul_fixed_short.rs new file mode 100644 index 000000000..d2d4cb7ae --- /dev/null +++ b/src/circuit/gadget/ecc/chip/mul_fixed_short.rs @@ -0,0 +1,259 @@ +use super::{ + add_incomplete, util, witness_point, EccConfig, EccPoint, EccScalarFixedShort, + OrchardFixedBaseShort, +}; +use crate::constants; + +use group::Curve; +use halo2::{ + arithmetic::{CurveAffine, Field, FieldExt}, + circuit::Region, + plonk::{ConstraintSystem, Error, Expression}, +}; + +// 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(super) fn create_gate( + meta: &mut ConstraintSystem, + q_mul_fixed_short: Expression, + s: Expression, + y_a: Expression, + y_p: Expression, +) { + // `(x_a, y_a)` is the result of `[m]B`, where `m` is the magnitude. + // We conditionally negate this result using `y_p = y_a * s`, where `s` is the sign. + + // Check that the final `y_p = y_a` or `y_p = -y_a` + meta.create_gate("check y", |_| { + q_mul_fixed_short.clone() * (y_p.clone() - y_a.clone()) * (y_p.clone() + y_a.clone()) + }); + + // Check that s * y_p = y_a + meta.create_gate("check negation", |_| q_mul_fixed_short * (s * y_p - y_a)); +} + +#[allow(non_snake_case)] +pub(super) fn assign_region( + scalar: &EccScalarFixedShort, + base: &OrchardFixedBaseShort, + offset: usize, + region: &mut Region<'_, C::Base>, + config: EccConfig, +) -> Result, Error> { + // Rename columns for `mul_fixed` context + let A = (config.extras[0], config.extras[1]); + let mul_fixed_u = config.extras[2]; + + // Assign fixed columns for given fixed base + for w in 0..constants::NUM_WINDOWS_SHORT { + // Enable `q_mul_fixed` selector + config.q_mul_fixed.enable(region, w + offset)?; + + // Assign x-coordinate Lagrange interpolation coefficients + for k in 0..(constants::H) { + region.assign_fixed( + || { + format!( + "Lagrange interpolation coeff for window: {:?}, k: {:?}", + w, k + ) + }, + config.lagrange_coeffs[k], + w + offset, + || Ok(base.lagrange_coeffs_short.0[w].0[k]), + )?; + } + + // Assign z-values for each window + region.assign_fixed( + || format!("z-value for window: {:?}", w), + config.fixed_z.into(), + w + offset, + || Ok(base.z_short.0[w]), + )?; + } + + // Copy the scalar decomposition + for (w, k) in scalar.k_bits.iter().enumerate() { + util::assign_and_constrain( + region, + || format!("k[{:?}]", w), + config.bits.into(), + w + offset, + k, + &config.perm, + )?; + } + + // Get the value of the fixed base. We only use `ValueCommitV` here. + let b = base.base.0 .0.value(); + + // The scalar decomposition was done in the base field. For computation + // outside the circuit, we now convert them back into the scalar field. + let k = scalar + .k_bits + .iter() + .map(|bits| { + bits.value + .map(|value| C::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 + let k_usize = scalar + .k_bits + .iter() + .map(|bits| bits.value.map(|value| value.to_bytes()[0] as usize)) + .collect::>(); + + // This is 2^w, where w is the window width + let h = C::Scalar::from_u64(constants::H as u64); + + // Process the least significant window outside the for loop + let mul_b = k[0].map(|k_0| b * (k_0 + C::Scalar::one())); + let mul_b = witness_point::assign_region( + mul_b.map(|point| point.to_affine()), + 0, + region, + config.clone(), + )?; + + // Assign u = (y_p + z_w).sqrt() for the least significant window + { + let u_val = k_usize[0].map(|k_0| base.u_short.0[0].0[k_0]); + region.assign_advice( + || "u", + mul_fixed_u, + offset, + || u_val.ok_or(Error::SynthesisError), + )?; + } + + // Initialise the point which will cumulatively sum to [scalar]B. + // Copy and assign `mul_b` to x_a, y_a columns on the next row + let x_sum = util::assign_and_constrain( + region, + || "initialize sum x", + A.0.into(), + offset + 1, + &mul_b.x, + &config.perm, + )?; + let y_sum = util::assign_and_constrain( + region, + || "initialize sum y", + A.1.into(), + offset + 1, + &mul_b.y, + &config.perm, + )?; + + let mut sum = EccPoint { x: x_sum, y: y_sum }; + + // Process all windows excluding least and most significant windows + for (w, k) in k[1..(k.len() - 1)].iter().enumerate() { + // Offset index by 1 since we already assigned row 0 outside this loop + let w = w + 1; + + // Compute [(k_w + 1) ⋅ 8^w]B + let mul_b = k.map(|k| b * (k + C::Scalar::one()) * h.pow(&[w as u64, 0, 0, 0])); + let mul_b = witness_point::assign_region( + mul_b.map(|point| point.to_affine()), + offset + w, + region, + config.clone(), + )?; + + // Assign u = (y_p + z_w).sqrt() + let u_val = k_usize[w].map(|k| base.u_short.0[w].0[k]); + region.assign_advice( + || "u", + mul_fixed_u, + w, + || u_val.ok_or(Error::SynthesisError), + )?; + + // Add to the cumulative sum + sum = add_incomplete::assign_region(&mul_b, &sum, offset + w, region, config.clone()) + .unwrap(); + } + + // Process most significant window outside the for loop + let offset_sum = (0..(constants::NUM_WINDOWS_SHORT - 1)) + .fold(C::ScalarExt::zero(), |acc, w| { + acc + h.pow(&[w as u64, 0, 0, 0]) + }); + + // `scalar = [k * 8^21 - offset_sum]`, where `offset_sum = \sum_{j = 0}^{20} 8^j`. + let last_scalar = k[k.len() - 1] + .map(|k| k * h.pow(&[(constants::NUM_WINDOWS_SHORT - 1) as u64, 0, 0, 0]) - offset_sum); + let mul_b = last_scalar.map(|last_scalar| b * last_scalar); + let mul_b = witness_point::assign_region( + mul_b.map(|point| point.to_affine()), + offset + constants::NUM_WINDOWS_SHORT - 1, + region, + config.clone(), + )?; + + // Assign u = (y_p + z_w).sqrt() for the most significant window + { + let u_val = k_usize[constants::NUM_WINDOWS_SHORT - 1] + .map(|k| base.u_short.0[constants::NUM_WINDOWS_SHORT - 1].0[k]); + region.assign_advice( + || "u", + mul_fixed_u, + offset + constants::NUM_WINDOWS_SHORT - 1, + || u_val.ok_or(Error::SynthesisError), + )?; + } + + // Add to the cumulative sum to get `[magnitude]B`. + let magnitude_mul = add_incomplete::assign_region( + &mul_b, + &sum, + offset + constants::NUM_WINDOWS_SHORT - 1, + region, + config.clone(), + )?; + + // Assign sign to `bits` column + let sign = util::assign_and_constrain( + region, + || "sign", + config.bits.into(), + offset + constants::NUM_WINDOWS_SHORT, + &scalar.sign, + &config.perm, + )?; + + // Conditionally negate `y`-coordinate + let y_val = match sign.value { + Some(sign) => { + if sign == -C::Base::one() { + magnitude_mul.y.value.map(|y: C::Base| -y) + } else { + magnitude_mul.y.value + } + } + None => None, + }; + + // Enable mul_fixed_short selector on final row + config + .q_mul_fixed_short + .enable(region, offset + constants::NUM_WINDOWS_SHORT)?; + + // Assign final `x, y` to `x_p, y_p` columns and return final point + let x_val = magnitude_mul.x.value; + let mul = x_val + .zip(y_val) + .map(|(x, y)| C::from_xy(x, y).unwrap().to_curve()); + witness_point::assign_region( + mul.map(|point| point.to_affine()), + offset + constants::NUM_WINDOWS_SHORT, + region, + config, + ) +} From 23bebc6c51378ed1fad643dd89055c22003d40f4 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 27 Apr 2021 00:11:27 +0800 Subject: [PATCH 11/49] Complete addition --- src/circuit/gadget/ecc/chip.rs | 38 +++- src/circuit/gadget/ecc/chip/add.rs | 272 +++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+), 3 deletions(-) create mode 100644 src/circuit/gadget/ecc/chip/add.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 11cb235fd..6d78a7e1e 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -8,7 +8,7 @@ use halo2::{ poly::Rotation, }; -// mod add; +mod add; mod add_incomplete; mod double; mod load; @@ -251,7 +251,36 @@ impl EccChip { add_incomplete::create_gate(meta, q_add, x_p, y_p, x_q, y_q, x_a, y_a); } - // TODO: Create complete point addition gate + // Create complete point addition gate + { + let q_add = meta.query_selector(q_add, Rotation::cur()); + let x_p = meta.query_advice(P.0, Rotation::cur()); + let y_p = meta.query_advice(P.1, Rotation::cur()); + let x_q = meta.query_advice(extras[0], Rotation::cur()); + let y_q = meta.query_advice(extras[1], Rotation::cur()); + let x_r = meta.query_advice(extras[0], Rotation::next()); + let y_r = meta.query_advice(extras[1], Rotation::next()); + let lambda_cur = meta.query_advice(lambda.0, Rotation::cur()); + + let a = meta.query_advice(extras[2], Rotation::cur()); + let b = meta.query_advice(extras[3], Rotation::cur()); + let c = meta.query_advice(extras[4], Rotation::cur()); + let d = meta.query_advice(lambda.1, Rotation::cur()); + + // \alpha = (x_q - x_p)^{-1} + let alpha = meta.query_advice(extras[2], Rotation::next()); + // \beta = x_p^{-1} + let beta = meta.query_advice(extras[3], Rotation::next()); + // \gamma = x_q^{-1} + let gamma = meta.query_advice(extras[4], Rotation::next()); + // \delta = (y_p + y_q)^{-1} + let delta = meta.query_advice(lambda.1, Rotation::next()); + + add::create_gate( + meta, q_add, a, b, c, d, alpha, beta, gamma, delta, lambda_cur, x_p, y_p, x_q, y_q, + x_r, y_r, + ); + } // Create fixed-base full-width scalar mul gate { @@ -442,7 +471,10 @@ impl EccInstructions for EccChip { ) -> Result { let config = self.config(); - todo!() + layouter.assign_region( + || "point addition", + |mut region| add::assign_region::(a, b, 0, &mut region, config.clone()), + ) } fn double( diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs new file mode 100644 index 000000000..3fc39c9e6 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -0,0 +1,272 @@ +use super::{util, CellValue, EccConfig, EccPoint}; + +use group::Curve; +use halo2::{ + arithmetic::{CurveAffine, CurveExt, Field, FieldExt}, + circuit::Region, + plonk::{ConstraintSystem, Error, Expression}, +}; + +#[allow(clippy::too_many_arguments)] +pub(crate) fn create_gate( + meta: &mut ConstraintSystem, + q_add: Expression, + a: Expression, + b: Expression, + c: Expression, + d: Expression, + alpha: Expression, + beta: Expression, + gamma: Expression, + delta: Expression, + lambda: Expression, + x_p: Expression, + y_p: Expression, + x_q: Expression, + y_q: Expression, + x_r: Expression, + y_r: Expression, +) { + // Boolean checks on A, B, C, D + { + meta.create_gate("Check A is boolean", |_| { + q_add.clone() * a.clone() * (Expression::Constant(F::one()) - a.clone()) + }); + meta.create_gate("Check B is boolean", |_| { + q_add.clone() * b.clone() * (Expression::Constant(F::one()) - b.clone()) + }); + meta.create_gate("Check C is boolean", |_| { + q_add.clone() * c.clone() * (Expression::Constant(F::one()) - c.clone()) + }); + meta.create_gate("Check D is boolean", |_| { + q_add.clone() * d.clone() * (Expression::Constant(F::one()) - d.clone()) + }); + } + + // Logical implications of Boolean flags + { + // x_q = x_p ⟹ A + meta.create_gate("x_q = x_p ⟹ A", |_| { + let lhs = (x_q.clone() - x_p.clone()) * alpha.clone(); + let rhs = Expression::Constant(F::one()) - a.clone(); + q_add.clone() * (lhs - rhs) + }); + + // x_p = 0 ⟹ B + meta.create_gate("x_p = 0 ⟹ B", |_| { + let lhs = x_p.clone() * beta.clone(); + let rhs = Expression::Constant(F::one()) - b.clone(); + q_add.clone() * (lhs - rhs) + }); + + // B ⟹ x_p = 0 + meta.create_gate("B ⟹ x_p = 0", |_| q_add.clone() * b.clone() * x_p.clone()); + + // x_q = 0 ⟹ C + meta.create_gate("x_q = 0 ⟹ C", |_| { + let lhs = x_q.clone() * gamma.clone(); + let rhs = Expression::Constant(F::one()) - c.clone(); + q_add.clone() * (lhs - rhs) + }); + + // C ⟹ x_q = 0 + meta.create_gate("C ⟹ x_q = 0", |_| q_add.clone() * c.clone() * x_q.clone()); + + // y_q = -y_p ⟹ D + meta.create_gate("y_q = y_p ⟹ D", |_| { + let lhs = (y_q.clone() + y_p.clone()) * delta.clone(); + let rhs = Expression::Constant(F::one()) - d.clone(); + q_add.clone() * (lhs - rhs) + }); + } + + // Handle cases in incomplete addition + { + // x_q ≠ x_p ⟹ λ = (y_q − y_p)/(x_q − x_p) + meta.create_gate("x equality", |_| { + let equal = x_q.clone() - x_p.clone(); + let unequal = equal.clone() * lambda.clone() - (y_q.clone() - y_p.clone()); + q_add.clone() * equal * unequal + }); + + // A ∧ y_p ≠ 0 ⟹ λ = (3 * x_p^2) / 2 * y_p + meta.create_gate("x equal, y nonzero", |_| { + let three_x_p_sq = Expression::Constant(F::from_u64(3)) * x_p.clone() * x_p.clone(); + let two_y_p = Expression::Constant(F::from_u64(2)) * y_p.clone(); + let gradient = two_y_p * lambda.clone() - three_x_p_sq; + q_add.clone() * a.clone() * gradient + }); + + // (¬B ∧ ¬C ⟹ x_r = λ^2 − x_p − x_q) ∧ (B ⟹ x_r = x_q) + meta.create_gate("x_r check", |_| { + let not_b = Expression::Constant(F::one()) - b.clone(); + let not_c = Expression::Constant(F::one()) - c.clone(); + let x_r_lambda = + lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); + let x_r_x_q = b.clone() * (x_r.clone() - x_q.clone()); + q_add.clone() * (not_b * not_c * x_r_lambda - x_r_x_q) + }); + + // ¬B ∧ ¬C ⟹ y_r = λ⋅(x_p − x_r) − y_p) ∧ (B ⟹ y_r = y_q) + meta.create_gate("y_r check", |_| { + let not_b = Expression::Constant(F::one()) - b.clone(); + let not_c = Expression::Constant(F::one()) - c.clone(); + let y_r_lambda = + lambda.clone() * (x_p.clone() - x_r.clone()) - y_p.clone() - y_r.clone(); + let y_r_y_q = b.clone() * (y_r.clone() - y_q.clone()); + q_add.clone() * (not_b * not_c * y_r_lambda - y_r_y_q) + }); + + // C ⟹ x_r = x_p + meta.create_gate("x_r = x_p when x_q = 0", |_| { + q_add.clone() * (c.clone() * (x_r.clone() - x_p.clone())) + }); + + // C ⟹ y_r = y_p + meta.create_gate("y_r = y_p when x_q = 0", |_| { + q_add.clone() * (c.clone() * (y_r.clone() - y_p.clone())) + }); + + // D ⟹ x_r = 0 + meta.create_gate("D ⟹ x_r = 0", |_| q_add.clone() * d.clone() * x_r.clone()); + + // D ⟹ y_r = 0 + meta.create_gate("D ⟹ y_r = 0", |_| q_add.clone() * d.clone() * y_r.clone()); + } +} + +#[allow(clippy::many_single_char_names)] +#[allow(non_snake_case)] +pub(super) fn assign_region( + a: &EccPoint, + b: &EccPoint, + offset: usize, + region: &mut Region<'_, C::Base>, + config: EccConfig, +) -> Result, Error> { + // Rename columns here to match specification + let A = (config.extras[0], config.extras[1]); + + // Enable `q_add` selector + config.q_add.enable(region, offset)?; + + // Copy point `a` into `x_p`, `y_p` columns + util::assign_and_constrain( + region, + || "x_p", + config.P.0.into(), + offset, + &a.x, + &config.perm, + )?; + util::assign_and_constrain( + region, + || "y_p", + config.P.1.into(), + offset, + &a.y, + &config.perm, + )?; + + // Copy point `b` into `x_a`, `y_a` columns + util::assign_and_constrain(region, || "x_q", A.0.into(), offset, &b.x, &config.perm)?; + util::assign_and_constrain(region, || "y_q", A.1.into(), offset, &b.y, &config.perm)?; + + let (x_p, y_p) = (a.x.value, a.y.value); + let (x_q, y_q) = (b.x.value, b.y.value); + + // Rename columns here to match specification + let a = config.extras[2]; // Used for both A and alpha + let b = config.extras[3]; // Used for both B and beta + let c = config.extras[4]; // Used for both C and gamma + let d = config.lambda.1; // Used for both D and delta + let lambda = config.lambda.0; + + // Assign A, B, C, D, α, β, γ, δ + { + x_p.zip(x_q) + .zip(y_p) + .zip(y_q) + .map(|(((x_p, x_q), y_p), y_q)| -> Result<(), Error> { + if x_q == x_p { + // x_q = x_p ⟹ A + region.assign_advice(|| "set A", a, offset, || Ok(C::Base::one()))?; + + // Doubling case, λ = (y_q − y_p) / (x_q − x_p) + if y_p != C::Base::zero() { + let lambda_val = C::Base::from_u64(3) + * x_p + * x_p + * (C::Base::from_u64(2) * y_p).invert().unwrap(); + region.assign_advice(|| "set lambda", lambda, offset, || Ok(lambda_val))?; + } + } else { + // α = 1 / (x_q - x_p) + let alpha_val = (x_q - x_p).invert().unwrap(); + region.assign_advice(|| "set alpha", a, offset + 1, || Ok(alpha_val))?; + + // Non-doubling case, λ = (y_q - y_p) / (x_q - x_p) + let lambda_val = (x_q - x_p).invert().unwrap() * (y_q - y_p); + region.assign_advice(|| "set lambda", lambda, offset, || Ok(lambda_val))?; + } + + if x_p == C::Base::zero() { + // x_p = 0 ⟹ B + region.assign_advice(|| "set B", b, offset, || Ok(C::Base::one()))?; + } else { + // β = 1 / x_p + let beta_val = x_p.invert().unwrap(); + region.assign_advice(|| "set beta", b, offset + 1, || Ok(beta_val))?; + } + if x_q == C::Base::zero() { + // x_q = 0 ⟹ C + region.assign_advice(|| "set C", c, offset, || Ok(C::Base::one()))?; + } else { + // γ = 1 / x_q + let gamma_val = x_q.invert().unwrap(); + region.assign_advice(|| "set gamma", c, offset + 1, || Ok(gamma_val))?; + } + + if y_p == -y_q { + // y_p = -y_p ⟹ D + region.assign_advice(|| "set D", d, offset, || Ok(C::Base::one()))?; + } else { + // δ = 1 / (y_q + y_p) + let delta_val = (y_q + y_p).invert().unwrap(); + region.assign_advice(|| "set delta", d, offset + 1, || Ok(delta_val))?; + } + Ok(()) + }); + } + + // Compute R = P + Q + let r = x_p + .zip(y_p) + .zip(x_q) + .zip(y_q) + .map(|(((x_p, y_p), x_q), y_q)| { + let p = C::from_xy(x_p, y_p).unwrap(); + let q = C::from_xy(x_q, y_q).unwrap(); + p + q + }); + + // `r` can be the point at infinity. + r.map_or(Err(Error::SynthesisError), |r| { + if r.is_on_curve().into() { + // Assign `x_r` + let x_r_val = *r.to_affine().coordinates().unwrap().x(); + let x_r_cell = region.assign_advice(|| "set x_r", A.0, offset + 1, || Ok(x_r_val))?; + + // Assign `y_r` + let y_r_val = *r.to_affine().coordinates().unwrap().y(); + let y_r_cell = region.assign_advice(|| "set y_r", A.1, offset + 1, || Ok(y_r_val))?; + + Ok(EccPoint { + x: CellValue::::new(x_r_cell, Some(x_r_val)), + y: CellValue::::new(y_r_cell, Some(y_r_val)), + }) + } else { + Err(Error::SynthesisError) + } + }) +} From f751555e4751bcba7b47158921633c1354a7101a Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 4 May 2021 17:47:27 +0800 Subject: [PATCH 12/49] Witness scalar var --- src/circuit/gadget/ecc/chip.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 6d78a7e1e..76de75adf 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -382,7 +382,18 @@ impl EccInstructions for EccChip { layouter: &mut impl Layouter, value: Option, ) -> Result { - todo!() + layouter.assign_region( + || "Witness scalar var", + |mut region| { + let cell = region.assign_advice( + || "Scalar var", + self.config().P.0, + 0, + || value.ok_or(Error::SynthesisError), + )?; + Ok(CellValue::new(cell, value)) + }, + ) } fn witness_scalar_fixed( From 718213047a3aabbcfa2d2b037efc657aeaf38ffd Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 27 Apr 2021 00:23:22 +0800 Subject: [PATCH 13/49] Variable-base scalar mul --- src/circuit/gadget/ecc/chip.rs | 111 +++++- src/circuit/gadget/ecc/chip/mul.rs | 520 +++++++++++++++++++++++++++++ 2 files changed, 624 insertions(+), 7 deletions(-) create mode 100644 src/circuit/gadget/ecc/chip/mul.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 76de75adf..5f5beb5b8 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -12,7 +12,7 @@ mod add; mod add_incomplete; mod double; mod load; -// mod mul; +mod mul; mod mul_fixed; mod mul_fixed_short; mod util; @@ -72,6 +72,7 @@ pub struct EccConfig { pub lambda: (Column, Column), /// Advice columns needed by instructions in the ECC chip. pub extras: [Column; 5], + /// Coefficients of interpolation polynomials for x-coordinates (used in fixed-base scalar multiplication) pub lagrange_coeffs: [Column; constants::H], /// Fixed z such that y + z = u^2 some square, and -y + z is a non-square. (Used in fixed-base scalar multiplication) @@ -85,8 +86,12 @@ pub struct EccConfig { pub q_add_incomplete: Selector, /// Complete addition pub q_add: Selector, - /// Variable-base scalar multiplication - pub q_mul: Selector, + /// Variable-base scalar multiplication (hi half) + pub q_mul_hi: Selector, + /// Variable-base scalar multiplication (lo half) + pub q_mul_lo: Selector, + /// Variable-base scalar multiplication (final scalar) + pub q_mul_decompose: Selector, /// Fixed-base full-width scalar multiplication pub q_mul_fixed: Selector, /// Fixed-base signed short scalar multiplication @@ -170,7 +175,9 @@ impl EccChip { let q_double = meta.selector(); let q_add_incomplete = meta.selector(); let q_add = meta.selector(); - let q_mul = meta.selector(); + let q_mul_hi = meta.selector(); + let q_mul_lo = meta.selector(); + let q_mul_decompose = meta.selector(); let q_mul_fixed = meta.selector(); let q_mul_fixed_short = meta.selector(); let q_point = meta.selector(); @@ -304,7 +311,92 @@ impl EccChip { mul_fixed_short::create_gate(meta, q_mul_fixed_short, s, y_a, y_p); } - // TODO: Create variable-base scalar mul gate + // Create variable-base scalar mul gate (hi half) + { + let q_mul = meta.query_selector(q_mul_hi, Rotation::cur()); + + let z_cur = meta.query_advice(bits, Rotation::cur()); + let z_prev = meta.query_advice(bits, Rotation::prev()); + let x_a_cur = meta.query_advice(extras[0], Rotation::cur()); + let x_a_next = meta.query_advice(extras[0], Rotation::next()); + let x_p_cur = meta.query_advice(P.0, Rotation::cur()); + let x_p_next = meta.query_advice(P.0, Rotation::next()); + let y_p_cur = meta.query_advice(P.1, Rotation::cur()); + let y_p_next = meta.query_advice(P.1, Rotation::next()); + let lambda1_cur = meta.query_advice(lambda.0, Rotation::cur()); + let lambda2_cur = meta.query_advice(lambda.1, Rotation::cur()); + let lambda1_next = meta.query_advice(lambda.0, Rotation::next()); + let lambda2_next = meta.query_advice(lambda.1, Rotation::next()); + + mul::create_gate( + meta, + q_mul, + z_cur, + z_prev, + x_a_cur, + x_a_next, + x_p_cur, + x_p_next, + y_p_cur, + y_p_next, + lambda1_cur, + lambda2_cur, + lambda1_next, + lambda2_next, + ) + } + + // Create variable-base scalar mul gate (lo half) + { + let q_mul = meta.query_selector(q_mul_lo, Rotation::cur()); + + let z_cur = meta.query_advice(extras[1], Rotation::cur()); + let z_prev = meta.query_advice(extras[1], Rotation::prev()); + let x_a_cur = meta.query_advice(extras[2], Rotation::cur()); + let x_a_next = meta.query_advice(extras[2], Rotation::next()); + let x_p_cur = meta.query_advice(P.0, Rotation::cur()); + let x_p_next = meta.query_advice(P.0, Rotation::next()); + let y_p_cur = meta.query_advice(P.1, Rotation::cur()); + let y_p_next = meta.query_advice(P.1, Rotation::next()); + let lambda1_cur = meta.query_advice(extras[3], Rotation::cur()); + let lambda2_cur = meta.query_advice(extras[4], Rotation::cur()); + let lambda1_next = meta.query_advice(extras[3], Rotation::next()); + let lambda2_next = meta.query_advice(extras[4], Rotation::next()); + + mul::create_gate( + meta, + q_mul, + z_cur, + z_prev, + x_a_cur, + x_a_next, + x_p_cur, + x_p_next, + y_p_cur, + y_p_next, + lambda1_cur, + lambda2_cur, + lambda1_next, + lambda2_next, + ) + } + + // Create scalar decomposition gate for complete addition part of variable-base scalar mul + { + let q_mul_decompose = meta.query_selector(q_mul_decompose, Rotation::cur()); + let z_cur = meta.query_advice(bits, Rotation::cur()); + let z_prev = meta.query_advice(bits, Rotation::prev()); + + mul::create_decompose_gate(meta, q_mul_decompose, z_cur, z_prev) + } + + // Create final scalar check gate for variable-base scalar mul + { + let mul_decompose = meta.query_fixed(mul_decompose, Rotation::cur()); + let z_cur = meta.query_advice(bits, Rotation::cur()); + + mul::create_final_scalar_gate::(meta, mul_decompose, z_cur) + } EccConfig { bits, @@ -317,7 +409,9 @@ impl EccChip { q_double, q_add_incomplete, q_add, - q_mul, + q_mul_hi, + q_mul_lo, + q_mul_decompose, q_mul_fixed, q_mul_fixed_short, q_point, @@ -509,7 +603,10 @@ impl EccInstructions for EccChip { ) -> Result { let config = self.config(); - todo!() + layouter.assign_region( + || "variable-base mul", + |mut region| mul::assign_region::(scalar, base, 0, &mut region, config.clone()), + ) } fn mul_fixed( diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs new file mode 100644 index 000000000..d0fdbdedf --- /dev/null +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -0,0 +1,520 @@ +use super::{add, double, util, CellValue, EccConfig, EccPoint}; +use crate::constants::NUM_COMPLETE_BITS; +use std::ops::Deref; + +use ff::PrimeField; +use halo2::{ + arithmetic::{CurveAffine, Field, FieldExt}, + circuit::Region, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, +}; + +#[allow(clippy::too_many_arguments)] +pub(super) fn create_gate( + meta: &mut ConstraintSystem, + q_mul: Expression, + z_cur: Expression, + z_prev: Expression, + x_a_cur: Expression, + x_a_next: Expression, + x_p_cur: Expression, + x_p_next: Expression, + y_p_cur: Expression, + y_p_next: Expression, + lambda1_cur: Expression, + lambda2_cur: Expression, + lambda1_next: Expression, + lambda2_next: Expression, +) { + // The current bit in the scalar decomposition, k_i = z_i - 2⋅z_{i+1}. + // Recall that we assigned the cumulative variable `z_i` in descending order, + // i from n down to 0. So z_{i+1} corresponds to the `z_prev` query. + let k = z_cur - Expression::Constant(F::from_u64(2)) * z_prev; + + // (k_i) ⋅ (k_i - 1) = 0 + meta.create_gate("Scalar boolean decomposition", |_| { + let bool_check = k.clone() * (k.clone() + Expression::Constant(-F::one())); + q_mul.clone() * bool_check + }); + + // The base used in double-and-add remains constant. We check that its + // x- and y- coordinates are the same throughout. + meta.create_gate("x_p equality", |_| { + q_mul.clone() * (x_p_cur.clone() - x_p_next.clone()) + }); + meta.create_gate("y_p equality", |_| { + q_mul.clone() * (y_p_cur.clone() - y_p_next.clone()) + }); + + // y_{A,i} = (λ_{1,i} + λ_{2,i}) + // * (x_{A,i} - (λ_{1,i}^2 - x_{A,i} - x_{P,i})) / 2 + let y_a_cur = (lambda1_cur.clone() + lambda2_cur.clone()) + * (x_a_cur.clone() + - (lambda1_cur.clone() * lambda1_cur.clone() - x_a_cur.clone() - x_p_cur.clone())) + * F::TWO_INV; + + // y_{A,i+1} = (λ_{1,i+1} + λ_{2,i+1}) + // * (x_{A,i+1} - (λ_{1,i+1}^2 - x_{A,i+1} - x_{P,i+1})) / 2 + let y_a_next = (lambda1_next.clone() + lambda2_next) + * (x_a_next.clone() - (lambda1_next.clone() * lambda1_next - x_a_next.clone() - x_p_next)) + * F::TWO_INV; + + // λ_{1,i}⋅(x_{A,i} − x_{P,i}) − y_{A,i} + (2k_i - 1) y_{P,i} = 0 + meta.create_gate("Double-and-add lambda1", |_| { + let expr = lambda1_cur.clone() * (x_a_cur.clone() - x_p_cur.clone()) - y_a_cur.clone() + + (k * F::from_u64(2) + Expression::Constant(-F::one())) * y_p_cur.clone(); + q_mul.clone() * expr + }); + + // (λ_{1,i} + λ_{2,i})⋅(x_{A,i} − (λ_{1,i}^2 − x_{A,i} − x_{P,i})) − 2y_{A,i}) = 0 + meta.create_gate("Double-and-add expr0", |_| { + let lambda_neg = lambda1_cur.clone() + lambda2_cur.clone(); + let lambda1_expr = + lambda1_cur.clone() * lambda1_cur.clone() - x_a_cur.clone() - x_p_cur.clone(); + let expr = lambda_neg * (x_a_cur.clone() - lambda1_expr) + - Expression::Constant(F::from_u64(2)) * y_a_cur.clone(); + q_mul.clone() * expr + }); + + // λ_{2,i}^2 − x_{A,i+1} −(λ_{1,i}^2 − x_{A,i} − x_{P,i}) − x_{A,i} = 0 + meta.create_gate("Double-and-add expr1", |_| { + let expr1 = lambda2_cur.clone() * lambda2_cur.clone() + - x_a_next.clone() + - (lambda1_cur.clone() * lambda1_cur) + + x_p_cur; + + q_mul.clone() * expr1 + }); + + // λ_{2,i}⋅(x_{A,i} − x_{A,i+1}) − y_{A,i} − y_{A,i+1} = 0 + meta.create_gate("Double-and-add expr2", |_| { + let expr2 = lambda2_cur * (x_a_cur - x_a_next) - y_a_cur - y_a_next; + + q_mul.clone() * expr2 + }); +} + +/// Gate used to check scalar decomposition is correct. +/// This is used to check the bits used in complete addition, since the incomplete +/// addition gate (controlled by `q_mul`) already checks scalar decomposition for +/// the other bits. +pub(super) fn create_decompose_gate( + meta: &mut ConstraintSystem, + q_mul_decompose: Expression, + z_cur: Expression, + z_prev: Expression, +) { + meta.create_gate("Decompose scalar ", |_| { + // k_{i} = z_{i} - 2⋅z_{i+1} + let k = z_cur.clone() - Expression::Constant(F::from_u64(2)) * z_prev; + // (k_i) ⋅ (k_i - 1) = 0 + let bool_check = k.clone() * (k + Expression::Constant(-F::one())); + + q_mul_decompose.clone() * bool_check + }); +} + +/// Gate used to check final scalar is recovered. +pub(super) fn create_final_scalar_gate( + meta: &mut ConstraintSystem, + scalar: Expression, + z_cur: Expression, +) { + meta.create_gate("Decompose scalar", |_| { + // q = 2^254 + t_q is the scalar field of Pallas + let t_q = -(C::Scalar::from_u128(1u128 << 127).square()); + let t_q = C::Base::from_bytes(&t_q.to_bytes()).unwrap(); + + // Check that `k = scalar + t_q` + scalar.clone() * (scalar + Expression::Constant(t_q) - z_cur) + }); +} + +pub(super) fn assign_region( + scalar: &CellValue, + base: &EccPoint, + offset: usize, + region: &mut Region<'_, C::Base>, + config: EccConfig, +) -> Result, Error> { + // We bisect the boolean decomposition into `hi` and `lo` halves, and + // process these halves "in parallel" (i.e. on the same rows, but on + // non-overlapping columns). + let hi_columns = IncompleteColumns { + q_mul: config.q_mul_hi, + z: config.bits, + x_a: config.extras[0], + lambda: config.lambda, + }; + let lo_columns = IncompleteColumns { + q_mul: config.q_mul_lo, + z: config.extras[1], + x_a: config.extras[2], + lambda: (config.extras[3], config.extras[4]), + }; + + // Decompose the scalar bitwise (big-endian bit order). + let k_bits = decompose_scalar::(scalar.value.unwrap()); + + // Bits used in incomplete addition. k_{254} to k_{4} inclusive + let incomplete_range = 0..(C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS); + let k_incomplete = &k_bits[incomplete_range]; + let k_incomplete_hi = &k_incomplete[..k_incomplete.len() / 2]; + let k_incomplete_lo = &k_incomplete[k_incomplete.len() / 2..]; + + // Bits used in complete addition. k_{3} to k_{1} inclusive + // The LSB k_{0} is handled separately. + let complete_range = + (C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS)..(C::Scalar::NUM_BITS as usize - 1); + let k_complete = &k_bits[complete_range.clone()]; + + // Initialize the accumulator a [2]base + let acc = double::assign_region(&base, offset, region, config.clone())?; + // Initialize the running sum for scalar decomposition to zero + let z_val = C::Base::zero(); + let z_cell = region.assign_advice(|| "initial z", hi_columns.z, offset + 1, || Ok(z_val))?; + let z = CellValue::new(z_cell, Some(z_val)); + + // Double-and-add (incomplete addition) for the `hi` half of the scalar decomposition + let (x, y_a, z) = add_incomplete::( + region, + &base, + config.clone(), + offset + 1, + hi_columns, + k_incomplete_hi, + (X(acc.x.clone()), Y(acc.y.value), ZValue(z)), + )?; + + // Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition + let (x, y_a, z) = add_incomplete::( + region, + &base, + config.clone(), + offset + 1, + lo_columns, + k_incomplete_lo, + (x, y_a, z), + )?; + + // Move from incomplete addition to complete addition + let mut acc = { + let y_a_col = config.extras[1]; + let row = k_incomplete_lo.len() + 2; + + let y_a_cell = region.assign_advice( + || "y_a", + y_a_col, + row + offset, + || y_a.ok_or(Error::SynthesisError), + )?; + util::assign_and_constrain( + region, + || "Copy z from incomplete to complete", + config.bits.into(), + row + offset, + &z, + &config.perm, + )?; + EccPoint { + x: x.0, + y: CellValue::::new(y_a_cell, *y_a), + } + }; + + let mut z_val = z.value.unwrap(); + // Complete addition + for (iter, k) in k_complete.iter().enumerate() { + // Each iteration uses 4 rows (two complete additions) + let row = k_incomplete_lo.len() + 4 * iter + 3; + + // Check scalar decomposition here + region.assign_advice(|| "z", config.bits, row + offset - 1, || Ok(z_val))?; + z_val = C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64); + region.assign_advice(|| "z", config.bits, row + offset, || Ok(z_val))?; + config.q_mul_decompose.enable(region, row + offset)?; + + let x_p = base.x.value; + let x_p_cell = region.assign_advice( + || "x_p", + config.P.0, + row + offset, + || x_p.ok_or(Error::SynthesisError), + )?; + + // If the bit is set, use `y`; if the bit is not set, use `-y` + let y_p = base.y.value; + let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); + + let y_p_cell = region.assign_advice( + || "y_p", + config.P.1, + row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; + let p = EccPoint { + x: CellValue::::new(x_p_cell, x_p), + y: CellValue::::new(y_p_cell, y_p), + }; + + // Acc + U + let tmp_acc = add::assign_region::(&p, &acc, row + offset, region, config.clone())?; + + // Copy acc from `x_a`, `y_a` over to `x_p`, `y_p` on the next row + let acc_x = util::assign_and_constrain( + region, + || "copy acc x_a", + config.P.0.into(), + row + offset + 2, + &acc.x, + &config.perm, + )?; + let acc_y = util::assign_and_constrain( + region, + || "copy acc y_a", + config.P.1.into(), + row + offset + 2, + &acc.y, + &config.perm, + )?; + + acc = EccPoint { x: acc_x, y: acc_y }; + + // Acc + U + Acc + acc = add::assign_region::(&acc, &tmp_acc, row + offset + 2, region, config.clone())?; + } + + // Process the least significant bit + let k_0_row = k_incomplete_lo.len() + complete_range.len() * 4 + 4; + let k_0 = &k_bits[C::Scalar::NUM_BITS as usize - 1]; + + // Check that we recover the original scalar. + // + // NB: We assume that the scalar fits in the curve's base field. This is not + // true in general, and in particular for the Pallas curve, whose scalar field + // `Fq` is larger than its base field `Fp`. + // + // However, the only use of variable-base scalar mul in the Orchard protocol + // is in deriving diversified addresses `[ivk] g_d`, and `ivk` is guaranteed + // to be in the base field of the curve. (See non-normative notes in + // https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents.) + + z_val = C::Base::from_u64(2) * z_val + C::Base::from_u64(*k_0 as u64); + region.assign_advice(|| "final z", config.bits, k_0_row + offset, || Ok(z_val))?; + region.assign_fixed( + || "original k", + config.mul_decompose, + k_0_row + offset, + || Ok(C::Base::from_bytes(&scalar.value.unwrap().to_bytes()).unwrap()), + )?; + + // If `k_0` is 0, return `Acc - P` + if !k_0 { + let (x_p, y_p) = (base.x.value, base.y.value.map(|y_p| -y_p)); + let x_p_cell = region.assign_advice( + || "x_p", + config.P.0, + k_0_row + offset, + || x_p.ok_or(Error::SynthesisError), + )?; + + let y_p_cell = region.assign_advice( + || "y_p", + config.P.1, + k_0_row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; + let p = EccPoint { + x: CellValue::::new(x_p_cell, x_p), + y: CellValue::::new(y_p_cell, y_p), + }; + + // Return the result of the final complete addition as `[scalar]B` + add::assign_region::(&p, &acc, k_0_row + offset, region, config) + } else { + // If `k_0` is 1, simply return `Acc` + Ok(acc) + } +} + +#[derive(Copy, Clone, Debug)] +struct IncompleteColumns { + q_mul: Selector, + z: Column, + x_a: Column, + lambda: (Column, Column), +} + +#[derive(Clone, Debug)] +struct X(CellValue); +impl Deref for X { + type Target = CellValue; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Copy, Clone, Debug)] +struct Y(Option); +impl Deref for Y { + type Target = Option; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone, Debug)] +struct ZValue(CellValue); +impl Deref for ZValue { + type Target = CellValue; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// We perform incomplete addition on all but the last three bits of the +// decomposed scalar. +// We split the bits in the incomplete addition range into "hi" and "lo" +// halves and process them side by side, using the same rows but with +// non-overlapping columns. +// Returns (x, y, z). +#[allow(clippy::type_complexity)] +fn add_incomplete( + region: &mut Region<'_, C::Base>, + base: &EccPoint, + config: EccConfig, + offset: usize, + columns: IncompleteColumns, + bits: &[bool], + acc: (X, Y, ZValue), +) -> Result<(X, Y, ZValue), Error> { + // Initialise the running `z` sum for the scalar bits. + let mut z_val = acc.2.value.unwrap(); + let mut z_cell = region.assign_advice(|| "starting z", columns.z, offset, || Ok(z_val))?; + region.constrain_equal(&config.perm, z_cell, acc.2.cell)?; + + let offset = offset + 1; + + // Initialise acc + let mut x_a = acc.0.value; + let mut x_a_cell = region.assign_advice( + || "starting x_a", + columns.x_a, + offset, + || x_a.ok_or(Error::SynthesisError), + )?; + region.constrain_equal(&config.perm, x_a_cell, acc.0.cell)?; + let mut y_a = *acc.1; + + // Enable `q_mul` on all but the last row of the incomplete range. + for row in 1..(bits.len() - 1) { + columns.q_mul.enable(region, offset + row)?; + } + + // Incomplete addition + for (row, k) in bits.iter().enumerate() { + // z_{i} = 2 * z_{i+1} + k_i + z_val = C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64); + z_cell = region.assign_advice(|| "z", columns.z, row + offset, || Ok(z_val))?; + + let x_p = base.x.value; + region.assign_advice( + || "x_p", + config.P.0, + row + offset, + || x_p.ok_or(Error::SynthesisError), + )?; + + // If the bit is set, use `y`; if the bit is not set, use `-y` + let y_p = base.y.value; + region.assign_advice( + || "y_p", + config.P.1, + row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; + let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); + + // Compute and assign `lambda1` + let lambda1 = y_a + .zip(y_p) + .zip(x_a) + .zip(x_p) + .map(|(((y_a, y_p), x_a), x_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap()); + region.assign_advice( + || "lambda1", + columns.lambda.0, + row + offset, + || lambda1.ok_or(Error::SynthesisError), + )?; + + // Compute and assign `lambda2` + let x_r = lambda1 + .zip(x_a) + .zip(x_p) + .map(|((lambda1, x_a), x_p)| lambda1 * lambda1 - x_a - x_p); + let lambda2 = lambda1 + .zip(y_a) + .zip(x_a) + .zip(x_r) + .map(|(((lambda1, y_a), x_a), x_r)| { + C::Base::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 + }); + region.assign_advice( + || "lambda2", + columns.lambda.1, + row + offset, + || lambda2.ok_or(Error::SynthesisError), + )?; + + // Compute and assign `x_a` for the next row + let x_a_new = lambda2 + .zip(x_a) + .zip(x_r) + .map(|((lambda2, x_a), x_r)| lambda2 * lambda2 - x_a - x_r); + y_a = lambda2 + .zip(x_a) + .zip(x_a_new) + .zip(y_a) + .map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a); + x_a = x_a_new; + x_a_cell = region.assign_advice( + || "x_a", + columns.x_a, + row + offset + 1, + || x_a.ok_or(Error::SynthesisError), + )?; + } + Ok(( + X(CellValue::::new(x_a_cell, x_a)), + Y(y_a), + ZValue(CellValue::::new(z_cell, Some(z_val))), + )) +} + +fn decompose_scalar(scalar: C::Base) -> Vec { + // Cast into scalar field + let scalar = C::Scalar::from_bytes(&scalar.to_bytes()).unwrap(); + + // The scalar field `F_q = 2^254 + t_q` + let t_q = -(C::Scalar::from_u128(1u128 << 127).square()); + + // We will witness `k = scalar + t_q` + // `k` is decomposed bitwise in-circuit for our double-and-add algorithm. + let k = scalar + t_q; + + // `k` is decomposed bitwise (big-endian) into `[k_n, ..., k_0]`, where + // each `k_i` is a bit and `scalar = k_n * 2^n + ... + k_1 * 2 + k_0`. + let mut bits: Vec = k + .to_le_bits() + .into_iter() + .take(C::Scalar::NUM_BITS as usize) + .collect(); + bits.reverse(); + assert_eq!(bits.len(), C::Scalar::NUM_BITS as usize); + + bits +} From 05eb13c5045f08ae5ae21c6f9f84e5095f9754d2 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 5 May 2021 20:40:04 +0800 Subject: [PATCH 14/49] Add ECC test --- src/circuit/gadget/ecc.rs | 165 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index b3b79dfb2..222157cc3 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -378,3 +378,168 @@ impl + Clone + Debug + Eq> FixedPoin }) } } + +#[cfg(test)] +mod tests { + use crate::constants; + use ff::Field; + use group::{Curve, Group}; + use halo2::{ + arithmetic::{CurveAffine, FieldExt}, + circuit::{layouter::SingleChipLayouter, Layouter}, + dev::MockProver, + pasta::pallas, + plonk::{Assignment, Circuit, ConstraintSystem, Error}, + }; + + use super::chip::{EccChip, EccConfig, OrchardFixedBases, OrchardFixedBasesShort}; + + struct MyCircuit { + _marker: std::marker::PhantomData, + } + + #[allow(non_snake_case)] + impl Circuit for MyCircuit { + type Config = EccConfig; + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let bits = meta.advice_column(); + let P = (meta.advice_column(), meta.advice_column()); + let lambda = (meta.advice_column(), meta.advice_column()); + let extras = [ + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]; + + EccChip::::configure(meta, bits, P, lambda, extras) + } + + fn synthesize( + &self, + cs: &mut impl Assignment, + config: Self::Config, + ) -> Result<(), Error> { + let mut layouter = SingleChipLayouter::new(cs)?; + let loaded = EccChip::::load(); + let chip = EccChip::construct(config, loaded); + + // Generate a random point P + let p_val = C::CurveExt::random(rand::rngs::OsRng).to_affine(); // P + let p = super::Point::new(chip.clone(), layouter.namespace(|| "point"), Some(p_val))?; + + // Generate a random point Q + let q_val = C::CurveExt::random(rand::rngs::OsRng).to_affine(); // P + let q = super::Point::new(chip.clone(), layouter.namespace(|| "point"), Some(q_val))?; + + // Check complete addition point P + Q + { + let real_added = p_val + q_val; + let added_complete = p.add(layouter.namespace(|| "P + Q"), &q)?; + if let (Some(x), Some(y)) = + (added_complete.inner.x.value, added_complete.inner.y.value) + { + if C::from_xy(x, y).is_some().into() { + assert_eq!(real_added.to_affine(), C::from_xy(x, y).unwrap()); + } + } + } + + // Check incomplete addition point P + Q + { + let real_added = p_val + q_val; + let added_incomplete = p.add_incomplete(layouter.namespace(|| "P + Q"), &q)?; + if let (Some(x), Some(y)) = ( + added_incomplete.inner.x.value, + added_incomplete.inner.y.value, + ) { + if C::from_xy(x, y).is_some().into() { + assert_eq!(real_added.to_affine(), C::from_xy(x, y).unwrap()); + } + } + } + + // Check fixed-base scalar multiplication + { + let scalar_fixed = C::Scalar::rand(); + let nullifier_k = constants::nullifier_k::generator(); + let base = nullifier_k.0.value(); + let real_mul_fixed = base * scalar_fixed; + + let scalar_fixed = super::ScalarFixed::new( + chip.clone(), + layouter.namespace(|| "ScalarFixed"), + Some(scalar_fixed), + )?; + let nullifier_k = super::FixedPoint::get( + chip.clone(), + OrchardFixedBases::NullifierK(nullifier_k), + )?; + let mul_fixed = nullifier_k.mul(layouter.namespace(|| "mul"), &scalar_fixed)?; + if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { + assert_eq!(real_mul_fixed.to_affine(), C::from_xy(x, y).unwrap()); + } + } + + // Check short signed fixed-base scalar multiplication + { + let scalar_fixed_short = C::Scalar::from_u64(rand::random::()); + let mut sign = C::Scalar::one(); + if rand::random::() { + sign = -sign; + } + let scalar_fixed_short = sign * scalar_fixed_short; + let value_commit_v = constants::value_commit_v::generator(); + let real_mul_fixed_short = value_commit_v.0.value() * scalar_fixed_short; + + let scalar_fixed_short = super::ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "ScalarFixedShort"), + Some(scalar_fixed_short), + )?; + let value_commit_v = super::FixedPointShort::get( + chip.clone(), + OrchardFixedBasesShort(value_commit_v), + )?; + let mul_fixed_short = + value_commit_v.mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; + if let (Some(x), Some(y)) = + (mul_fixed_short.inner.x.value, mul_fixed_short.inner.y.value) + { + assert_eq!(real_mul_fixed_short.to_affine(), C::from_xy(x, y).unwrap()); + } + } + + // Check variable-base scalar multiplication + { + let scalar_val = C::Scalar::rand(); + let real_mul = p_val * scalar_val; + + let scalar_val = C::Base::from_bytes(&scalar_val.to_bytes()).unwrap(); + let scalar = super::ScalarVar::new( + chip, + layouter.namespace(|| "ScalarVar"), + Some(scalar_val), + )?; + let mul = p.mul(layouter.namespace(|| "mul"), &scalar)?; + if let (Some(x), Some(y)) = (mul.inner.x.value, mul.inner.y.value) { + assert_eq!(real_mul.to_affine(), C::from_xy(x, y).unwrap()); + } + } + + Ok(()) + } + } + + #[test] + fn ecc() { + let k = 11; + let circuit = MyCircuit:: { + _marker: std::marker::PhantomData, + }; + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } +} From b58a8f530a059803c84be321fd062e756b3a462c Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 22 May 2021 16:20:43 +0800 Subject: [PATCH 15/49] Map point at infinity to (0,0) in complete addition --- src/circuit/gadget/ecc/chip/add.rs | 75 +++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 3fc39c9e6..80ea8e445 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -1,8 +1,8 @@ use super::{util, CellValue, EccConfig, EccPoint}; - -use group::Curve; +use ff::Field; +use group::{Curve, Group}; use halo2::{ - arithmetic::{CurveAffine, CurveExt, Field, FieldExt}, + arithmetic::{CurveAffine, FieldExt}, circuit::Region, plonk::{ConstraintSystem, Error, Expression}, }; @@ -188,11 +188,16 @@ pub(super) fn assign_region( .zip(y_p) .zip(y_q) .map(|(((x_p, x_q), y_p), y_q)| -> Result<(), Error> { + println!("x_p: {:?}", x_p); + println!("y_p: {:?}", y_p); + println!("x_q: {:?}", x_q); + println!("y_q: {:?}", y_q); + if x_q == x_p { // x_q = x_p ⟹ A region.assign_advice(|| "set A", a, offset, || Ok(C::Base::one()))?; - // Doubling case, λ = (y_q − y_p) / (x_q − x_p) + // Doubling case, λ = 3(x_p)^2 / (2 * y_p) if y_p != C::Base::zero() { let lambda_val = C::Base::from_u64(3) * x_p @@ -245,28 +250,54 @@ pub(super) fn assign_region( .zip(x_q) .zip(y_q) .map(|(((x_p, y_p), x_q), y_q)| { - let p = C::from_xy(x_p, y_p).unwrap(); - let q = C::from_xy(x_q, y_q).unwrap(); + // If either `p` or `q` are (0,0) represent them as C::zero() + let p = if x_p == C::Base::zero() && y_p == C::Base::zero() { + C::identity() + } else { + C::from_xy(x_p, y_p).unwrap() + }; + let q = if x_q == C::Base::zero() && y_q == C::Base::zero() { + C::identity() + } else { + C::from_xy(x_q, y_q).unwrap() + }; p + q }); - // `r` can be the point at infinity. - r.map_or(Err(Error::SynthesisError), |r| { - if r.is_on_curve().into() { - // Assign `x_r` - let x_r_val = *r.to_affine().coordinates().unwrap().x(); - let x_r_cell = region.assign_advice(|| "set x_r", A.0, offset + 1, || Ok(x_r_val))?; - - // Assign `y_r` - let y_r_val = *r.to_affine().coordinates().unwrap().y(); - let y_r_cell = region.assign_advice(|| "set y_r", A.1, offset + 1, || Ok(y_r_val))?; - - Ok(EccPoint { - x: CellValue::::new(x_r_cell, Some(x_r_val)), - y: CellValue::::new(y_r_cell, Some(y_r_val)), - }) + let x_r_val = r.map(|r| { + if r.is_identity().into() { + C::Base::zero() + } else { + *r.to_affine().coordinates().unwrap().x() + } + }); + + let y_r_val = r.map(|r| { + if r.is_identity().into() { + C::Base::zero() } else { - Err(Error::SynthesisError) + *r.to_affine().coordinates().unwrap().y() } + }); + + // Assign `x_r` + let x_r_cell = region.assign_advice( + || "set x_r", + A.0, + offset + 1, + || x_r_val.ok_or(Error::SynthesisError), + )?; + + // Assign `y_r` + let y_r_cell = region.assign_advice( + || "set y_r", + A.1, + offset + 1, + || y_r_val.ok_or(Error::SynthesisError), + )?; + + Ok(EccPoint { + x: CellValue::::new(x_r_cell, x_r_val), + y: CellValue::::new(y_r_cell, y_r_val), }) } From d9a95e94455cd76b05d123a18969793c9b780b8e Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 22 May 2021 19:33:49 +0800 Subject: [PATCH 16/49] Fix bug in complete addition gates --- src/circuit/gadget/ecc/chip/add.rs | 39 ++++++++++++++---------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 80ea8e445..3dacadd1c 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -45,34 +45,34 @@ pub(crate) fn create_gate( // Logical implications of Boolean flags { - // x_q = x_p ⟹ A + // (x_q − x_p)⋅α = 1 − A meta.create_gate("x_q = x_p ⟹ A", |_| { let lhs = (x_q.clone() - x_p.clone()) * alpha.clone(); let rhs = Expression::Constant(F::one()) - a.clone(); q_add.clone() * (lhs - rhs) }); - // x_p = 0 ⟹ B + // x_p⋅β = 1 − B meta.create_gate("x_p = 0 ⟹ B", |_| { let lhs = x_p.clone() * beta.clone(); let rhs = Expression::Constant(F::one()) - b.clone(); q_add.clone() * (lhs - rhs) }); - // B ⟹ x_p = 0 + // B⋅x_p = 0 meta.create_gate("B ⟹ x_p = 0", |_| q_add.clone() * b.clone() * x_p.clone()); - // x_q = 0 ⟹ C + // x_q⋅γ = 1 − C meta.create_gate("x_q = 0 ⟹ C", |_| { let lhs = x_q.clone() * gamma.clone(); let rhs = Expression::Constant(F::one()) - c.clone(); q_add.clone() * (lhs - rhs) }); - // C ⟹ x_q = 0 + // C⋅x_q = 0 meta.create_gate("C ⟹ x_q = 0", |_| q_add.clone() * c.clone() * x_q.clone()); - // y_q = -y_p ⟹ D + // (y_q + y_p)⋅δ = 1 − D meta.create_gate("y_q = y_p ⟹ D", |_| { let lhs = (y_q.clone() + y_p.clone()) * delta.clone(); let rhs = Expression::Constant(F::one()) - d.clone(); @@ -82,14 +82,14 @@ pub(crate) fn create_gate( // Handle cases in incomplete addition { - // x_q ≠ x_p ⟹ λ = (y_q − y_p)/(x_q − x_p) + // (x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p))=0 meta.create_gate("x equality", |_| { let equal = x_q.clone() - x_p.clone(); let unequal = equal.clone() * lambda.clone() - (y_q.clone() - y_p.clone()); q_add.clone() * equal * unequal }); - // A ∧ y_p ≠ 0 ⟹ λ = (3 * x_p^2) / 2 * y_p + // A⋅(2y_p⋅λ − 3x_p^2) = 0 meta.create_gate("x equal, y nonzero", |_| { let three_x_p_sq = Expression::Constant(F::from_u64(3)) * x_p.clone() * x_p.clone(); let two_y_p = Expression::Constant(F::from_u64(2)) * y_p.clone(); @@ -97,40 +97,42 @@ pub(crate) fn create_gate( q_add.clone() * a.clone() * gradient }); - // (¬B ∧ ¬C ⟹ x_r = λ^2 − x_p − x_q) ∧ (B ⟹ x_r = x_q) + // (1 − B)⋅(1 − C)⋅(1 − D)⋅(λ^2 − x_p − x_q − x_r) + B⋅(x_r − x_q) = 0 meta.create_gate("x_r check", |_| { let not_b = Expression::Constant(F::one()) - b.clone(); let not_c = Expression::Constant(F::one()) - c.clone(); + let not_d = Expression::Constant(F::one()) - d.clone(); let x_r_lambda = lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); let x_r_x_q = b.clone() * (x_r.clone() - x_q.clone()); - q_add.clone() * (not_b * not_c * x_r_lambda - x_r_x_q) + q_add.clone() * (not_b * not_c * not_d * x_r_lambda + x_r_x_q) }); - // ¬B ∧ ¬C ⟹ y_r = λ⋅(x_p − x_r) − y_p) ∧ (B ⟹ y_r = y_q) + // (1 − B)⋅(1 − C)⋅(1 − D)⋅(λ⋅(x_p − x_r) − y_p − y_r) + B⋅(y_r − y_q) = 0 meta.create_gate("y_r check", |_| { let not_b = Expression::Constant(F::one()) - b.clone(); let not_c = Expression::Constant(F::one()) - c.clone(); + let not_d = Expression::Constant(F::one()) - d.clone(); let y_r_lambda = lambda.clone() * (x_p.clone() - x_r.clone()) - y_p.clone() - y_r.clone(); let y_r_y_q = b.clone() * (y_r.clone() - y_q.clone()); - q_add.clone() * (not_b * not_c * y_r_lambda - y_r_y_q) + q_add.clone() * (not_b * not_c * not_d * y_r_lambda + y_r_y_q) }); - // C ⟹ x_r = x_p + // C⋅(x_r − x_p) = 0 meta.create_gate("x_r = x_p when x_q = 0", |_| { q_add.clone() * (c.clone() * (x_r.clone() - x_p.clone())) }); - // C ⟹ y_r = y_p + // C⋅(y_r − y_p) = 0 meta.create_gate("y_r = y_p when x_q = 0", |_| { q_add.clone() * (c.clone() * (y_r.clone() - y_p.clone())) }); - // D ⟹ x_r = 0 + // D⋅x_r = 0 meta.create_gate("D ⟹ x_r = 0", |_| q_add.clone() * d.clone() * x_r.clone()); - // D ⟹ y_r = 0 + // D⋅y_r = 0 meta.create_gate("D ⟹ y_r = 0", |_| q_add.clone() * d.clone() * y_r.clone()); } } @@ -188,11 +190,6 @@ pub(super) fn assign_region( .zip(y_p) .zip(y_q) .map(|(((x_p, x_q), y_p), y_q)| -> Result<(), Error> { - println!("x_p: {:?}", x_p); - println!("y_p: {:?}", y_p); - println!("x_q: {:?}", x_q); - println!("y_q: {:?}", y_q); - if x_q == x_p { // x_q = x_p ⟹ A region.assign_advice(|| "set A", a, offset, || Ok(C::Base::one()))?; From 8f630d84a1fdfa99f2f4f8ea7a3ce0d1a97e34a9 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 22 May 2021 19:34:09 +0800 Subject: [PATCH 17/49] Comprehensive tests for complete and incomplete addition --- src/circuit/gadget/ecc.rs | 86 +++++++++++++++++-- src/circuit/gadget/ecc/chip/add_incomplete.rs | 3 +- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 222157cc3..b47e9c421 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -429,26 +429,76 @@ mod tests { // Generate a random point P let p_val = C::CurveExt::random(rand::rngs::OsRng).to_affine(); // P let p = super::Point::new(chip.clone(), layouter.namespace(|| "point"), Some(p_val))?; + let p_neg = -p_val; + let p_neg = + super::Point::new(chip.clone(), layouter.namespace(|| "point"), Some(p_neg))?; // Generate a random point Q let q_val = C::CurveExt::random(rand::rngs::OsRng).to_affine(); // P let q = super::Point::new(chip.clone(), layouter.namespace(|| "point"), Some(q_val))?; - // Check complete addition point P + Q + // Make sure P and Q are not the same point. + assert_ne!(p_val, q_val); + + // Check complete addition P + (-P) + let zero = { + let zero = p.add(layouter.namespace(|| "P + (-P)"), &p_neg)?; + if let (Some(x), Some(y)) = (zero.inner.x.value, zero.inner.y.value) { + assert_eq!(C::Base::zero(), x); + assert_eq!(C::Base::zero(), y); + } + zero + }; + + // Check complete addition 𝒪 + 𝒪 { + let zero = zero.add(layouter.namespace(|| "𝒪 + 𝒪"), &zero)?; + if let (Some(x), Some(y)) = (zero.inner.x.value, zero.inner.y.value) { + assert_eq!(C::Base::zero(), x); + assert_eq!(C::Base::zero(), y); + } + } + + // Check complete addition (other cases) + { + let mut checks = Vec::<(C::CurveExt, super::Point>)>::new(); + + // P + Q let real_added = p_val + q_val; let added_complete = p.add(layouter.namespace(|| "P + Q"), &q)?; - if let (Some(x), Some(y)) = - (added_complete.inner.x.value, added_complete.inner.y.value) - { - if C::from_xy(x, y).is_some().into() { - assert_eq!(real_added.to_affine(), C::from_xy(x, y).unwrap()); + checks.push((real_added, added_complete)); + + // P + P + let real_added = p_val + p_val; + let added_complete = p.add(layouter.namespace(|| "P + P"), &p)?; + checks.push((real_added, added_complete)); + + // 𝒪 + P + let real_added = p_val.to_curve(); + let added_complete = p.add(layouter.namespace(|| "𝒪 + P"), &zero)?; + checks.push((real_added, added_complete)); + + // P + 𝒪 + let real_added = p_val.to_curve(); + let added_complete = zero.add(layouter.namespace(|| "P + 𝒪"), &p)?; + checks.push((real_added, added_complete)); + + for check in checks.into_iter() { + let real_added = check.0; + let added_complete = check.1; + if let (Some(x), Some(y)) = + (added_complete.inner.x.value, added_complete.inner.y.value) + { + if C::from_xy(x, y).is_some().into() { + assert_eq!(real_added.to_affine(), C::from_xy(x, y).unwrap()); + } } } } - // Check incomplete addition point P + Q + // Check incomplete addition { + // P + Q let real_added = p_val + q_val; let added_incomplete = p.add_incomplete(layouter.namespace(|| "P + Q"), &q)?; if let (Some(x), Some(y)) = ( @@ -459,6 +509,26 @@ mod tests { assert_eq!(real_added.to_affine(), C::from_xy(x, y).unwrap()); } } + + // P + P should return an error + p.add_incomplete(layouter.namespace(|| "P + P"), &p) + .expect_err("P + P should return an error"); + + // P + (-P) should return an error + p.add_incomplete(layouter.namespace(|| "P + (-P)"), &p_neg) + .expect_err("P + (-P) should return an error"); + + // P + 𝒪 should return an error + p.add_incomplete(layouter.namespace(|| "P + 𝒪"), &zero) + .expect_err("P + 0 should return an error"); + + // 𝒪 + P should return an error + zero.add_incomplete(layouter.namespace(|| "𝒪 + P"), &p) + .expect_err("0 + P should return an error"); + + // 𝒪 + 𝒪 should return an error + zero.add_incomplete(layouter.namespace(|| "𝒪 + 𝒪"), &zero) + .expect_err("𝒪 + 𝒪 should return an error"); } // Check fixed-base scalar multiplication @@ -535,7 +605,7 @@ mod tests { #[test] fn ecc() { - let k = 11; + let k = 9; let circuit = MyCircuit:: { _marker: std::marker::PhantomData, }; diff --git a/src/circuit/gadget/ecc/chip/add_incomplete.rs b/src/circuit/gadget/ecc/chip/add_incomplete.rs index f574b9248..06db46bba 100644 --- a/src/circuit/gadget/ecc/chip/add_incomplete.rs +++ b/src/circuit/gadget/ecc/chip/add_incomplete.rs @@ -61,7 +61,8 @@ pub(super) fn assign_region( return Err(Error::SynthesisError); } Ok(()) - }); + }) + .unwrap_or(Err(Error::SynthesisError))?; // Rename columns for `add` context let A = (config.extras[0], config.extras[1]); From eb5d8295c6d53405a9c9c7c2dae25149f7a0703e Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 22 May 2021 21:33:30 +0800 Subject: [PATCH 18/49] Handle point at infinity in point doubling --- src/circuit/gadget/ecc/chip/double.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/circuit/gadget/ecc/chip/double.rs b/src/circuit/gadget/ecc/chip/double.rs index e393f9662..91d380445 100644 --- a/src/circuit/gadget/ecc/chip/double.rs +++ b/src/circuit/gadget/ecc/chip/double.rs @@ -43,6 +43,17 @@ pub(super) fn assign_region( region: &mut Region<'_, F>, config: EccConfig, ) -> Result, Error> { + // Handle point at infinity + (a.x.value) + .zip(a.y.value) + .map(|(x, y)| { + if x == F::zero() && y == F::zero() { + return Err(Error::SynthesisError); + } + Ok(()) + }) + .unwrap_or(Err(Error::SynthesisError))?; + // Rename columns let A = (config.extras[0], config.extras[1]); @@ -70,6 +81,7 @@ pub(super) fn assign_region( // Compute the doubled point let (x_p, y_p) = (a.x.value, a.y.value); let r = x_p.zip(y_p).map(|(x_p, y_p)| { + // λ = 3(x_p)^2 / (2 * y_p) let lambda = F::from_u64(3) * x_p * x_p * F::TWO_INV * y_p.invert().unwrap(); let x_r = lambda * lambda - x_p - x_p; let y_r = lambda * (x_p - x_r) - y_p; From 62f3b3f328bc085c39c659492592eaff59338876 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 22 May 2021 21:37:51 +0800 Subject: [PATCH 19/49] Comprehensive tests for fixed- and variable-base scalar mul --- src/circuit/gadget/ecc.rs | 53 +++++++++++++++++++++++- src/circuit/gadget/ecc/chip/mul.rs | 66 ++++++++++++++++++++++-------- 2 files changed, 99 insertions(+), 20 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index b47e9c421..45cfc6fc4 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -547,10 +547,24 @@ mod tests { chip.clone(), OrchardFixedBases::NullifierK(nullifier_k), )?; + + // [a]B let mul_fixed = nullifier_k.mul(layouter.namespace(|| "mul"), &scalar_fixed)?; if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { assert_eq!(real_mul_fixed.to_affine(), C::from_xy(x, y).unwrap()); } + + // [0]B should return an error since fixed-base scalar multiplication + // uses incomplete addition internally. + let scalar_fixed = C::Scalar::zero(); + let scalar_fixed = super::ScalarFixed::new( + chip.clone(), + layouter.namespace(|| "ScalarFixed"), + Some(scalar_fixed), + )?; + nullifier_k + .mul(layouter.namespace(|| "mul"), &scalar_fixed) + .expect_err("[0]B should return an error"); } // Check short signed fixed-base scalar multiplication @@ -573,6 +587,8 @@ mod tests { chip.clone(), OrchardFixedBasesShort(value_commit_v), )?; + + // [a]B let mul_fixed_short = value_commit_v.mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; if let (Some(x), Some(y)) = @@ -580,6 +596,18 @@ mod tests { { assert_eq!(real_mul_fixed_short.to_affine(), C::from_xy(x, y).unwrap()); } + + // [0]B should return an error since fixed-base scalar multiplication + // uses incomplete addition internally. + let scalar_fixed = C::Scalar::zero(); + let scalar_fixed = super::ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "ScalarFixedShort"), + Some(scalar_fixed), + )?; + value_commit_v + .mul(layouter.namespace(|| "mul"), &scalar_fixed) + .expect_err("[0]B should return an error"); } // Check variable-base scalar multiplication @@ -589,14 +617,35 @@ mod tests { let scalar_val = C::Base::from_bytes(&scalar_val.to_bytes()).unwrap(); let scalar = super::ScalarVar::new( - chip, + chip.clone(), layouter.namespace(|| "ScalarVar"), Some(scalar_val), )?; + + // [a]B let mul = p.mul(layouter.namespace(|| "mul"), &scalar)?; if let (Some(x), Some(y)) = (mul.inner.x.value, mul.inner.y.value) { assert_eq!(real_mul.to_affine(), C::from_xy(x, y).unwrap()); } + + // [a]𝒪 should return an error since variable-base scalar multiplication + // uses incomplete addition at the beginning of its double-and-add. + zero.mul(layouter.namespace(|| "mul"), &scalar) + .expect_err("[a]𝒪 should return an error"); + + // [0]B should return (0,0) since variable-base scalar multiplication + // uses complete addition for the final bits of the scalar. + let scalar_val = C::Base::zero(); + let scalar = super::ScalarVar::new( + chip, + layouter.namespace(|| "ScalarVar"), + Some(scalar_val), + )?; + let mul = p.mul(layouter.namespace(|| "mul"), &scalar)?; + if let (Some(x), Some(y)) = (mul.inner.x.value, mul.inner.y.value) { + assert_eq!(C::Base::zero(), x); + assert_eq!(C::Base::zero(), y); + } } Ok(()) @@ -605,7 +654,7 @@ mod tests { #[test] fn ecc() { - let k = 9; + let k = 11; let circuit = MyCircuit:: { _marker: std::marker::PhantomData, }; diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index d0fdbdedf..7d459c68a 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -222,16 +222,26 @@ pub(super) fn assign_region( } }; - let mut z_val = z.value.unwrap(); + let mut z_val = z.value; // Complete addition for (iter, k) in k_complete.iter().enumerate() { // Each iteration uses 4 rows (two complete additions) let row = k_incomplete_lo.len() + 4 * iter + 3; // Check scalar decomposition here - region.assign_advice(|| "z", config.bits, row + offset - 1, || Ok(z_val))?; - z_val = C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64); - region.assign_advice(|| "z", config.bits, row + offset, || Ok(z_val))?; + region.assign_advice( + || "z", + config.bits, + row + offset - 1, + || z_val.ok_or(Error::SynthesisError), + )?; + z_val = z_val.map(|z_val| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64)); + region.assign_advice( + || "z", + config.bits, + row + offset, + || z_val.ok_or(Error::SynthesisError), + )?; config.q_mul_decompose.enable(region, row + offset)?; let x_p = base.x.value; @@ -280,7 +290,7 @@ pub(super) fn assign_region( acc = EccPoint { x: acc_x, y: acc_y }; - // Acc + U + Acc + // Acc + P + Acc acc = add::assign_region::(&acc, &tmp_acc, row + offset + 2, region, config.clone())?; } @@ -299,8 +309,13 @@ pub(super) fn assign_region( // to be in the base field of the curve. (See non-normative notes in // https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents.) - z_val = C::Base::from_u64(2) * z_val + C::Base::from_u64(*k_0 as u64); - region.assign_advice(|| "final z", config.bits, k_0_row + offset, || Ok(z_val))?; + z_val = z_val.map(|z_val| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k_0 as u64)); + region.assign_advice( + || "final z", + config.bits, + k_0_row + offset, + || z_val.ok_or(Error::SynthesisError), + )?; region.assign_fixed( || "original k", config.mul_decompose, @@ -392,10 +407,19 @@ fn add_incomplete( acc: (X, Y, ZValue), ) -> Result<(X, Y, ZValue), Error> { // Initialise the running `z` sum for the scalar bits. - let mut z_val = acc.2.value.unwrap(); - let mut z_cell = region.assign_advice(|| "starting z", columns.z, offset, || Ok(z_val))?; + let mut z_val = acc.2.value; + let mut z_cell = region.assign_advice( + || "starting z", + columns.z, + offset, + || z_val.ok_or(Error::SynthesisError), + )?; region.constrain_equal(&config.perm, z_cell, acc.2.cell)?; + // Define `x_p`, `y_p` + let x_p = base.x.value; + let y_p = base.y.value; + let offset = offset + 1; // Initialise acc @@ -417,28 +441,32 @@ fn add_incomplete( // Incomplete addition for (row, k) in bits.iter().enumerate() { // z_{i} = 2 * z_{i+1} + k_i - z_val = C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64); - z_cell = region.assign_advice(|| "z", columns.z, row + offset, || Ok(z_val))?; + z_val = z_val.map(|z_val| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64)); + z_cell = region.assign_advice( + || "z", + columns.z, + row + offset, + || z_val.ok_or(Error::SynthesisError), + )?; - let x_p = base.x.value; + // Assign `x_p`, `y_p` region.assign_advice( || "x_p", config.P.0, row + offset, || x_p.ok_or(Error::SynthesisError), )?; - - // If the bit is set, use `y`; if the bit is not set, use `-y` - let y_p = base.y.value; region.assign_advice( || "y_p", config.P.1, row + offset, || y_p.ok_or(Error::SynthesisError), )?; + + // If the bit is set, use `y`; if the bit is not set, use `-y` let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); - // Compute and assign `lambda1` + // Compute and assign λ1⋅(x_A − x_P) = y_A − y_P let lambda1 = y_a .zip(y_p) .zip(x_a) @@ -451,11 +479,13 @@ fn add_incomplete( || lambda1.ok_or(Error::SynthesisError), )?; - // Compute and assign `lambda2` + // x_R = λ1^2 - x_A - x_P let x_r = lambda1 .zip(x_a) .zip(x_p) .map(|((lambda1, x_a), x_p)| lambda1 * lambda1 - x_a - x_p); + + // λ2 = (2(y_A) / (x_A - x_R)) - λ1 let lambda2 = lambda1 .zip(y_a) .zip(x_a) @@ -491,7 +521,7 @@ fn add_incomplete( Ok(( X(CellValue::::new(x_a_cell, x_a)), Y(y_a), - ZValue(CellValue::::new(z_cell, Some(z_val))), + ZValue(CellValue::::new(z_cell, z_val)), )) } From 71c3f99023a2d4403a9dadece3fdddc9286ace40 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 22 May 2021 21:50:39 +0800 Subject: [PATCH 20/49] Check limit values for short fixed-base scalar mul --- src/circuit/gadget/ecc.rs | 109 +++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 32 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 45cfc6fc4..24267c0ea 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -569,45 +569,90 @@ mod tests { // Check short signed fixed-base scalar multiplication { - let scalar_fixed_short = C::Scalar::from_u64(rand::random::()); - let mut sign = C::Scalar::one(); - if rand::random::() { - sign = -sign; - } - let scalar_fixed_short = sign * scalar_fixed_short; - let value_commit_v = constants::value_commit_v::generator(); - let real_mul_fixed_short = value_commit_v.0.value() * scalar_fixed_short; - - let scalar_fixed_short = super::ScalarFixedShort::new( - chip.clone(), - layouter.namespace(|| "ScalarFixedShort"), - Some(scalar_fixed_short), - )?; + let value_commit_v_inner = constants::value_commit_v::generator(); let value_commit_v = super::FixedPointShort::get( chip.clone(), - OrchardFixedBasesShort(value_commit_v), + OrchardFixedBasesShort(value_commit_v_inner), )?; - // [a]B - let mul_fixed_short = - value_commit_v.mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; - if let (Some(x), Some(y)) = - (mul_fixed_short.inner.x.value, mul_fixed_short.inner.y.value) + // [0]B should return an error since fixed-base scalar multiplication + // uses incomplete addition internally. { - assert_eq!(real_mul_fixed_short.to_affine(), C::from_xy(x, y).unwrap()); + let scalar_fixed = C::Scalar::zero(); + let scalar_fixed = super::ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "ScalarFixedShort"), + Some(scalar_fixed), + )?; + value_commit_v + .mul(layouter.namespace(|| "mul"), &scalar_fixed) + .expect_err("[0]B should return an error"); } - // [0]B should return an error since fixed-base scalar multiplication - // uses incomplete addition internally. - let scalar_fixed = C::Scalar::zero(); - let scalar_fixed = super::ScalarFixedShort::new( - chip.clone(), - layouter.namespace(|| "ScalarFixedShort"), - Some(scalar_fixed), - )?; - value_commit_v - .mul(layouter.namespace(|| "mul"), &scalar_fixed) - .expect_err("[0]B should return an error"); + let mut checks = Vec::<(C::CurveExt, super::Point>)>::new(); + + // Random [a]B + { + let scalar_fixed_short = C::Scalar::from_u64(rand::random::()); + let mut sign = C::Scalar::one(); + if rand::random::() { + sign = -sign; + } + let scalar_fixed_short = sign * scalar_fixed_short; + let real_mul_fixed_short = value_commit_v_inner.0.value() * scalar_fixed_short; + + let scalar_fixed_short = super::ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "ScalarFixedShort"), + Some(scalar_fixed_short), + )?; + let mul_fixed_short = value_commit_v + .mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; + + checks.push((real_mul_fixed_short, mul_fixed_short)); + } + + // [2^64 - 1]B + { + let scalar_fixed_short = C::Scalar::from_u64(0xFFFF_FFFF_FFFF_FFFFu64); + let real_mul_fixed_short = value_commit_v_inner.0.value() * scalar_fixed_short; + + let scalar_fixed_short = super::ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "ScalarFixedShort"), + Some(scalar_fixed_short), + )?; + let mul_fixed_short = value_commit_v + .mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; + + checks.push((real_mul_fixed_short, mul_fixed_short)); + } + + // [-(2^64 - 1)]B + { + let scalar_fixed_short = -C::Scalar::from_u64(0xFFFF_FFFF_FFFF_FFFFu64); + let real_mul_fixed_short = value_commit_v_inner.0.value() * scalar_fixed_short; + + let scalar_fixed_short = super::ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "ScalarFixedShort"), + Some(scalar_fixed_short), + )?; + let mul_fixed_short = value_commit_v + .mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; + + checks.push((real_mul_fixed_short, mul_fixed_short)); + } + + for check in checks.into_iter() { + let real_mul_fixed_short = check.0; + let mul_fixed_short = check.1; + if let (Some(x), Some(y)) = + (mul_fixed_short.inner.x.value, mul_fixed_short.inner.y.value) + { + assert_eq!(real_mul_fixed_short.to_affine(), C::from_xy(x, y).unwrap()); + } + } } // Check variable-base scalar multiplication From 4909a937150e74fd7545da35b8c6f765667e9400 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sun, 23 May 2021 15:42:34 +0800 Subject: [PATCH 21/49] Define separate configs for each operation This removes the need to manually rename columns for each separate context. Instead, each operation's config now implements From, providing a derivation from the top-level configuration. --- src/circuit/gadget/ecc/chip.rs | 342 ++------ src/circuit/gadget/ecc/chip/add.rs | 604 ++++++++------- src/circuit/gadget/ecc/chip/add_incomplete.rs | 245 +++--- src/circuit/gadget/ecc/chip/double.rs | 190 +++-- src/circuit/gadget/ecc/chip/mul.rs | 729 +++++++----------- src/circuit/gadget/ecc/chip/mul/incomplete.rs | 278 +++++++ src/circuit/gadget/ecc/chip/mul_fixed.rs | 604 ++++++++++----- .../gadget/ecc/chip/mul_fixed/full_width.rs | 104 +++ .../gadget/ecc/chip/mul_fixed/short.rs | 171 ++++ .../gadget/ecc/chip/mul_fixed_short.rs | 259 ------- src/circuit/gadget/ecc/chip/witness_point.rs | 110 +-- .../gadget/ecc/chip/witness_scalar_fixed.rs | 153 +++- .../chip/witness_scalar_fixed/full_width.rs | 48 ++ .../ecc/chip/witness_scalar_fixed/short.rs | 103 +++ .../ecc/chip/witness_scalar_fixed_short.rs | 92 --- 15 files changed, 2203 insertions(+), 1829 deletions(-) create mode 100644 src/circuit/gadget/ecc/chip/mul/incomplete.rs create mode 100644 src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs create mode 100644 src/circuit/gadget/ecc/chip/mul_fixed/short.rs delete mode 100644 src/circuit/gadget/ecc/chip/mul_fixed_short.rs create mode 100644 src/circuit/gadget/ecc/chip/witness_scalar_fixed/full_width.rs create mode 100644 src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs delete mode 100644 src/circuit/gadget/ecc/chip/witness_scalar_fixed_short.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 5f5beb5b8..7e786ad60 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -1,11 +1,9 @@ use super::EccInstructions; use crate::constants; -use ff::PrimeField; use halo2::{ arithmetic::{CurveAffine, FieldExt}, circuit::{Cell, Chip, Layouter}, plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Permutation, Selector}, - poly::Rotation, }; mod add; @@ -14,11 +12,9 @@ mod double; mod load; mod mul; mod mul_fixed; -mod mul_fixed_short; mod util; mod witness_point; mod witness_scalar_fixed; -mod witness_scalar_fixed_short; pub use load::*; @@ -91,7 +87,7 @@ pub struct EccConfig { /// Variable-base scalar multiplication (lo half) pub q_mul_lo: Selector, /// Variable-base scalar multiplication (final scalar) - pub q_mul_decompose: Selector, + pub q_mul_complete: Selector, /// Fixed-base full-width scalar multiplication pub q_mul_fixed: Selector, /// Fixed-base signed short scalar multiplication @@ -172,253 +168,90 @@ impl EccChip { lambda: (Column, Column), extras: [Column; 5], ) -> >::Config { - let q_double = meta.selector(); - let q_add_incomplete = meta.selector(); - let q_add = meta.selector(); - let q_mul_hi = meta.selector(); - let q_mul_lo = meta.selector(); - let q_mul_decompose = meta.selector(); - let q_mul_fixed = meta.selector(); - let q_mul_fixed_short = meta.selector(); - let q_point = meta.selector(); - let q_scalar_fixed = meta.selector(); - let q_scalar_fixed_short = meta.selector(); - - 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(), - ]; - let fixed_z = meta.fixed_column(); - let mul_decompose = meta.fixed_column(); - - // Set up permutations - let perm = Permutation::new( - meta, - &[ - P.0.into(), - P.1.into(), - bits.into(), - extras[0].into(), - extras[1].into(), - extras[2].into(), + let config = EccConfig { + bits, + P, + lambda, + extras, + 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(), ], - ); + fixed_z: meta.fixed_column(), + mul_decompose: meta.fixed_column(), + q_double: meta.selector(), + q_add_incomplete: meta.selector(), + q_add: meta.selector(), + q_mul_hi: meta.selector(), + q_mul_lo: meta.selector(), + q_mul_complete: meta.selector(), + q_mul_fixed: meta.selector(), + q_mul_fixed_short: meta.selector(), + q_point: meta.selector(), + q_scalar_fixed: meta.selector(), + q_scalar_fixed_short: meta.selector(), + perm: Permutation::new( + meta, + &[ + P.0.into(), + P.1.into(), + bits.into(), + extras[0].into(), + extras[1].into(), + extras[2].into(), + ], + ), + }; // Create witness point gate { - let q_point = meta.query_selector(q_point, Rotation::cur()); - let P = ( - meta.query_advice(P.0, Rotation::cur()), - meta.query_advice(P.1, Rotation::cur()), - ); - witness_point::create_gate::(meta, q_point, P.0, P.1); + let config: witness_point::Config = (&config).into(); + config.create_gate::(meta); } - // Create witness scalar_fixed gate + // Create witness scalar_fixed gates (both full-width and short) { - let q_scalar_fixed = meta.query_selector(q_scalar_fixed, Rotation::cur()); - let k = meta.query_advice(bits, Rotation::cur()); - witness_scalar_fixed::create_gate(meta, q_scalar_fixed, k); - } - - // Create witness scalar_fixed_short gate - { - let q_scalar_fixed_short = meta.query_selector(q_scalar_fixed_short, Rotation::cur()); - let k = meta.query_advice(bits, Rotation::cur()); - witness_scalar_fixed_short::create_gate(meta, q_scalar_fixed_short, k); + let config: witness_scalar_fixed::Config = (&config).into(); + config.create_gate::(meta); } // Create point doubling gate { - let q_double = meta.query_selector(q_double, Rotation::cur()); - let x_a = meta.query_advice(extras[0], Rotation::cur()); - let y_a = meta.query_advice(extras[1], Rotation::cur()); - let x_p = meta.query_advice(P.0, Rotation::cur()); - let y_p = meta.query_advice(P.1, Rotation::cur()); - - double::create_gate(meta, q_double, x_a, y_a, x_p, y_p); + let config: double::Config = (&config).into(); + config.create_gate(meta); } // Create incomplete point addition gate { - let q_add = meta.query_selector(q_add_incomplete, Rotation::cur()); - let x_p = meta.query_advice(P.0, Rotation::cur()); - let y_p = meta.query_advice(P.1, Rotation::cur()); - let x_q = meta.query_advice(extras[0], Rotation::cur()); - let y_q = meta.query_advice(extras[1], Rotation::cur()); - let x_a = meta.query_advice(extras[0], Rotation::next()); - let y_a = meta.query_advice(extras[1], Rotation::next()); - - add_incomplete::create_gate(meta, q_add, x_p, y_p, x_q, y_q, x_a, y_a); + let config: add_incomplete::Config = (&config).into(); + config.create_gate(meta); } // Create complete point addition gate { - let q_add = meta.query_selector(q_add, Rotation::cur()); - let x_p = meta.query_advice(P.0, Rotation::cur()); - let y_p = meta.query_advice(P.1, Rotation::cur()); - let x_q = meta.query_advice(extras[0], Rotation::cur()); - let y_q = meta.query_advice(extras[1], Rotation::cur()); - let x_r = meta.query_advice(extras[0], Rotation::next()); - let y_r = meta.query_advice(extras[1], Rotation::next()); - let lambda_cur = meta.query_advice(lambda.0, Rotation::cur()); - - let a = meta.query_advice(extras[2], Rotation::cur()); - let b = meta.query_advice(extras[3], Rotation::cur()); - let c = meta.query_advice(extras[4], Rotation::cur()); - let d = meta.query_advice(lambda.1, Rotation::cur()); - - // \alpha = (x_q - x_p)^{-1} - let alpha = meta.query_advice(extras[2], Rotation::next()); - // \beta = x_p^{-1} - let beta = meta.query_advice(extras[3], Rotation::next()); - // \gamma = x_q^{-1} - let gamma = meta.query_advice(extras[4], Rotation::next()); - // \delta = (y_p + y_q)^{-1} - let delta = meta.query_advice(lambda.1, Rotation::next()); - - add::create_gate( - meta, q_add, a, b, c, d, alpha, beta, gamma, delta, lambda_cur, x_p, y_p, x_q, y_q, - x_r, y_r, - ); + let add_config: add::Config = (&config).into(); + add_config.create_gate(meta); } - // Create fixed-base full-width scalar mul gate + // Create fixed-base scalar mul gates { - let q_mul_fixed = meta.query_selector(q_mul_fixed, Rotation::cur()); - let x_p = meta.query_advice(P.0, Rotation::cur()); - let y_p = meta.query_advice(P.1, Rotation::cur()); - let k = meta.query_advice(bits, Rotation::cur()); - let u = meta.query_advice(extras[2], Rotation::cur()); - let z = meta.query_fixed(fixed_z, Rotation::cur()); - - mul_fixed::create_gate(meta, lagrange_coeffs, q_mul_fixed, x_p, y_p, k, u, z); + let mul_fixed_config: mul_fixed::Config = (&config).into(); + mul_fixed_config.create_gate::(meta); } - // Create fixed-base short signed scalar mul gate + // Create variable-base scalar mul gates { - let q_mul_fixed_short = meta.query_selector(q_mul_fixed_short, Rotation::cur()); - let s = meta.query_advice(bits, Rotation::cur()); - let y_a = meta.query_advice(extras[1], Rotation::cur()); - let y_p = meta.query_advice(P.1, Rotation::cur()); - - mul_fixed_short::create_gate(meta, q_mul_fixed_short, s, y_a, y_p); + let mul_config: mul::Config = (&config).into(); + mul_config.create_gate(meta); } - // Create variable-base scalar mul gate (hi half) - { - let q_mul = meta.query_selector(q_mul_hi, Rotation::cur()); - - let z_cur = meta.query_advice(bits, Rotation::cur()); - let z_prev = meta.query_advice(bits, Rotation::prev()); - let x_a_cur = meta.query_advice(extras[0], Rotation::cur()); - let x_a_next = meta.query_advice(extras[0], Rotation::next()); - let x_p_cur = meta.query_advice(P.0, Rotation::cur()); - let x_p_next = meta.query_advice(P.0, Rotation::next()); - let y_p_cur = meta.query_advice(P.1, Rotation::cur()); - let y_p_next = meta.query_advice(P.1, Rotation::next()); - let lambda1_cur = meta.query_advice(lambda.0, Rotation::cur()); - let lambda2_cur = meta.query_advice(lambda.1, Rotation::cur()); - let lambda1_next = meta.query_advice(lambda.0, Rotation::next()); - let lambda2_next = meta.query_advice(lambda.1, Rotation::next()); - - mul::create_gate( - meta, - q_mul, - z_cur, - z_prev, - x_a_cur, - x_a_next, - x_p_cur, - x_p_next, - y_p_cur, - y_p_next, - lambda1_cur, - lambda2_cur, - lambda1_next, - lambda2_next, - ) - } - - // Create variable-base scalar mul gate (lo half) - { - let q_mul = meta.query_selector(q_mul_lo, Rotation::cur()); - - let z_cur = meta.query_advice(extras[1], Rotation::cur()); - let z_prev = meta.query_advice(extras[1], Rotation::prev()); - let x_a_cur = meta.query_advice(extras[2], Rotation::cur()); - let x_a_next = meta.query_advice(extras[2], Rotation::next()); - let x_p_cur = meta.query_advice(P.0, Rotation::cur()); - let x_p_next = meta.query_advice(P.0, Rotation::next()); - let y_p_cur = meta.query_advice(P.1, Rotation::cur()); - let y_p_next = meta.query_advice(P.1, Rotation::next()); - let lambda1_cur = meta.query_advice(extras[3], Rotation::cur()); - let lambda2_cur = meta.query_advice(extras[4], Rotation::cur()); - let lambda1_next = meta.query_advice(extras[3], Rotation::next()); - let lambda2_next = meta.query_advice(extras[4], Rotation::next()); - - mul::create_gate( - meta, - q_mul, - z_cur, - z_prev, - x_a_cur, - x_a_next, - x_p_cur, - x_p_next, - y_p_cur, - y_p_next, - lambda1_cur, - lambda2_cur, - lambda1_next, - lambda2_next, - ) - } - - // Create scalar decomposition gate for complete addition part of variable-base scalar mul - { - let q_mul_decompose = meta.query_selector(q_mul_decompose, Rotation::cur()); - let z_cur = meta.query_advice(bits, Rotation::cur()); - let z_prev = meta.query_advice(bits, Rotation::prev()); - - mul::create_decompose_gate(meta, q_mul_decompose, z_cur, z_prev) - } - - // Create final scalar check gate for variable-base scalar mul - { - let mul_decompose = meta.query_fixed(mul_decompose, Rotation::cur()); - let z_cur = meta.query_advice(bits, Rotation::cur()); - - mul::create_final_scalar_gate::(meta, mul_decompose, z_cur) - } - - EccConfig { - bits, - P, - lambda, - extras, - lagrange_coeffs, - fixed_z, - mul_decompose, - q_double, - q_add_incomplete, - q_add, - q_mul_hi, - q_mul_lo, - q_mul_decompose, - q_mul_fixed, - q_mul_fixed_short, - q_point, - q_scalar_fixed, - q_scalar_fixed_short, - perm, - } + config } #[allow(clippy::type_complexity)] @@ -495,19 +328,10 @@ impl EccInstructions for EccChip { layouter: &mut impl Layouter, value: Option, ) -> Result { - let config = self.config(); - + let config: witness_scalar_fixed::Config = self.config().into(); layouter.assign_region( || "witness scalar for fixed-base mul", - |mut region| { - witness_scalar_fixed::assign_region( - value, - C::Scalar::NUM_BITS as usize, - 0, - &mut region, - config.clone(), - ) - }, + |mut region| config.assign_region_full(value, 0, &mut region), ) } @@ -516,13 +340,10 @@ impl EccInstructions for EccChip { layouter: &mut impl Layouter, value: Option, ) -> Result { - let config = self.config(); - + let config: witness_scalar_fixed::Config = self.config().into(); layouter.assign_region( || "witness scalar for fixed-base mul", - |mut region| { - witness_scalar_fixed_short::assign_region(value, 0, &mut region, config.clone()) - }, + |mut region| config.assign_region_short(value, 0, &mut region), ) } @@ -531,11 +352,10 @@ impl EccInstructions for EccChip { layouter: &mut impl Layouter, value: Option, ) -> Result { - let config = self.config(); - + let config: witness_point::Config = self.config().into(); layouter.assign_region( || "witness point", - |mut region| witness_point::assign_region(value, 0, &mut region, config.clone()), + |mut region| config.assign_region(value, 0, &mut region), ) } @@ -560,11 +380,10 @@ impl EccInstructions for EccChip { a: &Self::Point, b: &Self::Point, ) -> Result { - let config = self.config(); - + let config: add_incomplete::Config = self.config().into(); layouter.assign_region( || "point addition", - |mut region| add_incomplete::assign_region(a, b, 0, &mut region, config.clone()), + |mut region| config.assign_region(a, b, 0, &mut region), ) } @@ -574,11 +393,10 @@ impl EccInstructions for EccChip { a: &Self::Point, b: &Self::Point, ) -> Result { - let config = self.config(); - + let config: add::Config = self.config().into(); layouter.assign_region( || "point addition", - |mut region| add::assign_region::(a, b, 0, &mut region, config.clone()), + |mut region| config.assign_region::(a, b, 0, &mut region), ) } @@ -587,11 +405,10 @@ impl EccInstructions for EccChip { layouter: &mut impl Layouter, a: &Self::Point, ) -> Result { - let config = self.config(); - + let config: double::Config = self.config().into(); layouter.assign_region( || "point doubling", - |mut region| double::assign_region(a, 0, &mut region, config.clone()), + |mut region| config.assign_region(a, 0, &mut region), ) } @@ -601,11 +418,10 @@ impl EccInstructions for EccChip { scalar: &Self::ScalarVar, base: &Self::Point, ) -> Result { - let config = self.config(); - + let config: mul::Config = self.config().into(); layouter.assign_region( || "variable-base mul", - |mut region| mul::assign_region::(scalar, base, 0, &mut region, config.clone()), + |mut region| config.assign_region::(scalar, base, 0, &mut region), ) } @@ -615,13 +431,10 @@ impl EccInstructions for EccChip { scalar: &Self::ScalarFixed, base: &Self::FixedPoint, ) -> Result { - let config = self.config(); - + let config: mul_fixed::Config = self.config().into(); layouter.assign_region( || format!("Multiply {:?}", base.base), - |mut region| { - mul_fixed::assign_region::(scalar, base, 0, &mut region, config.clone()) - }, + |mut region| config.assign_region_full::(scalar, base, 0, &mut region), ) } @@ -631,13 +444,10 @@ impl EccInstructions for EccChip { scalar: &Self::ScalarFixedShort, base: &Self::FixedPointShort, ) -> Result { - let config = self.config(); - + let config: mul_fixed::Config = self.config().into(); layouter.assign_region( || format!("Multiply {:?}", base.base), - |mut region| { - mul_fixed_short::assign_region::(scalar, base, 0, &mut region, config.clone()) - }, + |mut region| config.assign_region_short::(scalar, base, 0, &mut region), ) } } diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 3dacadd1c..23c29e6bf 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -4,297 +4,373 @@ use group::{Curve, Group}; use halo2::{ arithmetic::{CurveAffine, FieldExt}, circuit::Region, - plonk::{ConstraintSystem, Error, Expression}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector}, + poly::Rotation, }; -#[allow(clippy::too_many_arguments)] -pub(crate) fn create_gate( - meta: &mut ConstraintSystem, - q_add: Expression, - a: Expression, - b: Expression, - c: Expression, - d: Expression, - alpha: Expression, - beta: Expression, - gamma: Expression, - delta: Expression, - lambda: Expression, - x_p: Expression, - y_p: Expression, - x_q: Expression, - y_q: Expression, - x_r: Expression, - y_r: Expression, -) { - // Boolean checks on A, B, C, D - { - meta.create_gate("Check A is boolean", |_| { - q_add.clone() * a.clone() * (Expression::Constant(F::one()) - a.clone()) - }); - meta.create_gate("Check B is boolean", |_| { - q_add.clone() * b.clone() * (Expression::Constant(F::one()) - b.clone()) - }); - meta.create_gate("Check C is boolean", |_| { - q_add.clone() * c.clone() * (Expression::Constant(F::one()) - c.clone()) - }); - meta.create_gate("Check D is boolean", |_| { - q_add.clone() * d.clone() * (Expression::Constant(F::one()) - d.clone()) - }); +pub struct Config { + q_add: Selector, + // lambda + lambda: Column, + // x-coordinate of P in P + Q = R + pub x_p: Column, + // y-coordinate of P in P + Q = R + pub y_p: Column, + // x-coordinate of Q or R in P + Q = R + x_qr: Column, + // y-coordinate of Q or R in P + Q = R + pub y_qr: Column, + // a or alpha + a_alpha: Column, + // b or beta + b_beta: Column, + // c or gamma + c_gamma: Column, + // d or delta + d_delta: Column, + // Permutation + perm: Permutation, +} + +impl From<&EccConfig> for Config { + fn from(ecc_config: &EccConfig) -> Self { + Self { + q_add: ecc_config.q_add, + lambda: ecc_config.lambda.0, + x_p: ecc_config.P.0, + y_p: ecc_config.P.1, + x_qr: ecc_config.extras[0], + y_qr: ecc_config.extras[1], + a_alpha: ecc_config.extras[2], + b_beta: ecc_config.extras[3], + c_gamma: ecc_config.extras[4], + d_delta: ecc_config.lambda.1, + perm: ecc_config.perm.clone(), + } } +} - // Logical implications of Boolean flags - { - // (x_q − x_p)⋅α = 1 − A - meta.create_gate("x_q = x_p ⟹ A", |_| { - let lhs = (x_q.clone() - x_p.clone()) * alpha.clone(); - let rhs = Expression::Constant(F::one()) - a.clone(); - q_add.clone() * (lhs - rhs) - }); +impl Config { + #[allow(clippy::too_many_arguments)] + pub(crate) fn create_gate(&self, meta: &mut ConstraintSystem) { + let q_add = meta.query_selector(self.q_add, Rotation::cur()); + let x_p = meta.query_advice(self.x_p, Rotation::cur()); + let y_p = meta.query_advice(self.y_p, Rotation::cur()); + let x_q = meta.query_advice(self.x_qr, Rotation::cur()); + let y_q = meta.query_advice(self.y_qr, Rotation::cur()); + let x_r = meta.query_advice(self.x_qr, Rotation::next()); + let y_r = meta.query_advice(self.y_qr, Rotation::next()); + let lambda = meta.query_advice(self.lambda, Rotation::cur()); + + let a = meta.query_advice(self.a_alpha, Rotation::cur()); + let b = meta.query_advice(self.b_beta, Rotation::cur()); + let c = meta.query_advice(self.c_gamma, Rotation::cur()); + let d = meta.query_advice(self.d_delta, Rotation::cur()); + + // \alpha = (x_q - x_p)^{-1} + let alpha = meta.query_advice(self.a_alpha, Rotation::next()); + // \beta = x_p^{-1} + let beta = meta.query_advice(self.b_beta, Rotation::next()); + // \gamma = x_q^{-1} + let gamma = meta.query_advice(self.c_gamma, Rotation::next()); + // \delta = (y_p + y_q)^{-1} + let delta = meta.query_advice(self.d_delta, Rotation::next()); + + // Boolean checks on A, B, C, D + { + meta.create_gate("Check A is boolean", |_| { + q_add.clone() * a.clone() * (Expression::Constant(F::one()) - a.clone()) + }); + meta.create_gate("Check B is boolean", |_| { + q_add.clone() * b.clone() * (Expression::Constant(F::one()) - b.clone()) + }); + meta.create_gate("Check C is boolean", |_| { + q_add.clone() * c.clone() * (Expression::Constant(F::one()) - c.clone()) + }); + meta.create_gate("Check D is boolean", |_| { + q_add.clone() * d.clone() * (Expression::Constant(F::one()) - d.clone()) + }); + } - // x_p⋅β = 1 − B - meta.create_gate("x_p = 0 ⟹ B", |_| { - let lhs = x_p.clone() * beta.clone(); - let rhs = Expression::Constant(F::one()) - b.clone(); - q_add.clone() * (lhs - rhs) - }); + // Logical implications of Boolean flags + { + // (x_q − x_p)⋅α = 1 − A + meta.create_gate("x_q = x_p ⟹ A", |_| { + let lhs = (x_q.clone() - x_p.clone()) * alpha.clone(); + let rhs = Expression::Constant(F::one()) - a.clone(); + q_add.clone() * (lhs - rhs) + }); - // B⋅x_p = 0 - meta.create_gate("B ⟹ x_p = 0", |_| q_add.clone() * b.clone() * x_p.clone()); + // x_p⋅β = 1 − B + meta.create_gate("x_p = 0 ⟹ B", |_| { + let lhs = x_p.clone() * beta.clone(); + let rhs = Expression::Constant(F::one()) - b.clone(); + q_add.clone() * (lhs - rhs) + }); - // x_q⋅γ = 1 − C - meta.create_gate("x_q = 0 ⟹ C", |_| { - let lhs = x_q.clone() * gamma.clone(); - let rhs = Expression::Constant(F::one()) - c.clone(); - q_add.clone() * (lhs - rhs) - }); + // B⋅x_p = 0 + meta.create_gate("B ⟹ x_p = 0", |_| q_add.clone() * b.clone() * x_p.clone()); - // C⋅x_q = 0 - meta.create_gate("C ⟹ x_q = 0", |_| q_add.clone() * c.clone() * x_q.clone()); + // x_q⋅γ = 1 − C + meta.create_gate("x_q = 0 ⟹ C", |_| { + let lhs = x_q.clone() * gamma.clone(); + let rhs = Expression::Constant(F::one()) - c.clone(); + q_add.clone() * (lhs - rhs) + }); - // (y_q + y_p)⋅δ = 1 − D - meta.create_gate("y_q = y_p ⟹ D", |_| { - let lhs = (y_q.clone() + y_p.clone()) * delta.clone(); - let rhs = Expression::Constant(F::one()) - d.clone(); - q_add.clone() * (lhs - rhs) - }); - } + // C⋅x_q = 0 + meta.create_gate("C ⟹ x_q = 0", |_| q_add.clone() * c.clone() * x_q.clone()); - // Handle cases in incomplete addition - { - // (x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p))=0 - meta.create_gate("x equality", |_| { - let equal = x_q.clone() - x_p.clone(); - let unequal = equal.clone() * lambda.clone() - (y_q.clone() - y_p.clone()); - q_add.clone() * equal * unequal - }); + // (y_q + y_p)⋅δ = 1 − D + meta.create_gate("y_q = y_p ⟹ D", |_| { + let lhs = (y_q.clone() + y_p.clone()) * delta.clone(); + let rhs = Expression::Constant(F::one()) - d.clone(); + q_add.clone() * (lhs - rhs) + }); + } - // A⋅(2y_p⋅λ − 3x_p^2) = 0 - meta.create_gate("x equal, y nonzero", |_| { - let three_x_p_sq = Expression::Constant(F::from_u64(3)) * x_p.clone() * x_p.clone(); - let two_y_p = Expression::Constant(F::from_u64(2)) * y_p.clone(); - let gradient = two_y_p * lambda.clone() - three_x_p_sq; - q_add.clone() * a.clone() * gradient - }); + // Handle cases in incomplete addition + { + // (x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p))=0 + meta.create_gate("x equality", |_| { + let equal = x_q.clone() - x_p.clone(); + let unequal = equal.clone() * lambda.clone() - (y_q.clone() - y_p.clone()); + q_add.clone() * equal * unequal + }); - // (1 − B)⋅(1 − C)⋅(1 − D)⋅(λ^2 − x_p − x_q − x_r) + B⋅(x_r − x_q) = 0 - meta.create_gate("x_r check", |_| { - let not_b = Expression::Constant(F::one()) - b.clone(); - let not_c = Expression::Constant(F::one()) - c.clone(); - let not_d = Expression::Constant(F::one()) - d.clone(); - let x_r_lambda = - lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); - let x_r_x_q = b.clone() * (x_r.clone() - x_q.clone()); - q_add.clone() * (not_b * not_c * not_d * x_r_lambda + x_r_x_q) - }); + // A⋅(2y_p⋅λ − 3x_p^2) = 0 + meta.create_gate("x equal, y nonzero", |_| { + let three_x_p_sq = Expression::Constant(F::from_u64(3)) * x_p.clone() * x_p.clone(); + let two_y_p = Expression::Constant(F::from_u64(2)) * y_p.clone(); + let gradient = two_y_p * lambda.clone() - three_x_p_sq; + q_add.clone() * a.clone() * gradient + }); - // (1 − B)⋅(1 − C)⋅(1 − D)⋅(λ⋅(x_p − x_r) − y_p − y_r) + B⋅(y_r − y_q) = 0 - meta.create_gate("y_r check", |_| { - let not_b = Expression::Constant(F::one()) - b.clone(); - let not_c = Expression::Constant(F::one()) - c.clone(); - let not_d = Expression::Constant(F::one()) - d.clone(); - let y_r_lambda = - lambda.clone() * (x_p.clone() - x_r.clone()) - y_p.clone() - y_r.clone(); - let y_r_y_q = b.clone() * (y_r.clone() - y_q.clone()); - q_add.clone() * (not_b * not_c * not_d * y_r_lambda + y_r_y_q) - }); + // (1 − B)⋅(1 − C)⋅(1 − D)⋅(λ^2 − x_p − x_q − x_r) + B⋅(x_r − x_q) = 0 + meta.create_gate("x_r check", |_| { + let not_b = Expression::Constant(F::one()) - b.clone(); + let not_c = Expression::Constant(F::one()) - c.clone(); + let not_d = Expression::Constant(F::one()) - d.clone(); + let x_r_lambda = + lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); + let x_r_x_q = b.clone() * (x_r.clone() - x_q.clone()); + q_add.clone() * (not_b * not_c * not_d * x_r_lambda + x_r_x_q) + }); - // C⋅(x_r − x_p) = 0 - meta.create_gate("x_r = x_p when x_q = 0", |_| { - q_add.clone() * (c.clone() * (x_r.clone() - x_p.clone())) - }); + // (1 − B)⋅(1 − C)⋅(1 − D)⋅(λ⋅(x_p − x_r) − y_p − y_r) + B⋅(y_r − y_q) = 0 + meta.create_gate("y_r check", |_| { + let not_b = Expression::Constant(F::one()) - b.clone(); + let not_c = Expression::Constant(F::one()) - c.clone(); + let not_d = Expression::Constant(F::one()) - d.clone(); + let y_r_lambda = + lambda.clone() * (x_p.clone() - x_r.clone()) - y_p.clone() - y_r.clone(); + let y_r_y_q = b.clone() * (y_r.clone() - y_q.clone()); + q_add.clone() * (not_b * not_c * not_d * y_r_lambda + y_r_y_q) + }); - // C⋅(y_r − y_p) = 0 - meta.create_gate("y_r = y_p when x_q = 0", |_| { - q_add.clone() * (c.clone() * (y_r.clone() - y_p.clone())) - }); + // C⋅(x_r − x_p) = 0 + meta.create_gate("x_r = x_p when x_q = 0", |_| { + q_add.clone() * (c.clone() * (x_r.clone() - x_p.clone())) + }); + + // C⋅(y_r − y_p) = 0 + meta.create_gate("y_r = y_p when x_q = 0", |_| { + q_add.clone() * (c.clone() * (y_r.clone() - y_p.clone())) + }); - // D⋅x_r = 0 - meta.create_gate("D ⟹ x_r = 0", |_| q_add.clone() * d.clone() * x_r.clone()); + // D⋅x_r = 0 + meta.create_gate("D ⟹ x_r = 0", |_| q_add.clone() * d.clone() * x_r.clone()); - // D⋅y_r = 0 - meta.create_gate("D ⟹ y_r = 0", |_| q_add.clone() * d.clone() * y_r.clone()); + // D⋅y_r = 0 + meta.create_gate("D ⟹ y_r = 0", |_| q_add.clone() * d.clone() * y_r.clone()); + } } -} -#[allow(clippy::many_single_char_names)] -#[allow(non_snake_case)] -pub(super) fn assign_region( - a: &EccPoint, - b: &EccPoint, - offset: usize, - region: &mut Region<'_, C::Base>, - config: EccConfig, -) -> Result, Error> { - // Rename columns here to match specification - let A = (config.extras[0], config.extras[1]); - - // Enable `q_add` selector - config.q_add.enable(region, offset)?; - - // Copy point `a` into `x_p`, `y_p` columns - util::assign_and_constrain( - region, - || "x_p", - config.P.0.into(), - offset, - &a.x, - &config.perm, - )?; - util::assign_and_constrain( - region, - || "y_p", - config.P.1.into(), - offset, - &a.y, - &config.perm, - )?; - - // Copy point `b` into `x_a`, `y_a` columns - util::assign_and_constrain(region, || "x_q", A.0.into(), offset, &b.x, &config.perm)?; - util::assign_and_constrain(region, || "y_q", A.1.into(), offset, &b.y, &config.perm)?; - - let (x_p, y_p) = (a.x.value, a.y.value); - let (x_q, y_q) = (b.x.value, b.y.value); - - // Rename columns here to match specification - let a = config.extras[2]; // Used for both A and alpha - let b = config.extras[3]; // Used for both B and beta - let c = config.extras[4]; // Used for both C and gamma - let d = config.lambda.1; // Used for both D and delta - let lambda = config.lambda.0; - - // Assign A, B, C, D, α, β, γ, δ - { - x_p.zip(x_q) + #[allow(clippy::many_single_char_names)] + #[allow(non_snake_case)] + pub(super) fn assign_region( + &self, + a: &EccPoint, + b: &EccPoint, + offset: usize, + region: &mut Region<'_, C::Base>, + ) -> Result, Error> { + // Enable `q_add` selector + self.q_add.enable(region, offset)?; + + // Copy point `a` into `x_p`, `y_p` columns + util::assign_and_constrain(region, || "x_p", self.x_p.into(), offset, &a.x, &self.perm)?; + util::assign_and_constrain(region, || "y_p", self.y_p.into(), offset, &a.y, &self.perm)?; + + // Copy point `b` into `x_qr`, `y_qr` columns + util::assign_and_constrain(region, || "x_q", self.x_qr.into(), offset, &b.x, &self.perm)?; + util::assign_and_constrain(region, || "y_q", self.y_qr.into(), offset, &b.y, &self.perm)?; + + let (x_p, y_p) = (a.x.value, a.y.value); + let (x_q, y_q) = (b.x.value, b.y.value); + + // Assign A, B, C, D, α, β, γ, δ + { + x_p.zip(x_q) + .zip(y_p) + .zip(y_q) + .map(|(((x_p, x_q), y_p), y_q)| -> Result<(), Error> { + if x_q == x_p { + // x_q = x_p ⟹ A + region.assign_advice( + || "set A", + self.a_alpha, + offset, + || Ok(C::Base::one()), + )?; + + // Doubling case, λ = 3(x_p)^2 / (2 * y_p) + if y_p != C::Base::zero() { + let lambda_val = C::Base::from_u64(3) + * x_p + * x_p + * (C::Base::from_u64(2) * y_p).invert().unwrap(); + region.assign_advice( + || "set lambda", + self.lambda, + offset, + || Ok(lambda_val), + )?; + } + } else { + // α = 1 / (x_q - x_p) + let alpha_val = (x_q - x_p).invert().unwrap(); + region.assign_advice( + || "set alpha", + self.a_alpha, + offset + 1, + || Ok(alpha_val), + )?; + + // Non-doubling case, λ = (y_q - y_p) / (x_q - x_p) + let lambda_val = (x_q - x_p).invert().unwrap() * (y_q - y_p); + region.assign_advice( + || "set lambda", + self.lambda, + offset, + || Ok(lambda_val), + )?; + } + + if x_p == C::Base::zero() { + // x_p = 0 ⟹ B + region.assign_advice( + || "set B", + self.b_beta, + offset, + || Ok(C::Base::one()), + )?; + } else { + // β = 1 / x_p + let beta_val = x_p.invert().unwrap(); + region.assign_advice( + || "set beta", + self.b_beta, + offset + 1, + || Ok(beta_val), + )?; + } + if x_q == C::Base::zero() { + // x_q = 0 ⟹ C + region.assign_advice( + || "set C", + self.c_gamma, + offset, + || Ok(C::Base::one()), + )?; + } else { + // γ = 1 / x_q + let gamma_val = x_q.invert().unwrap(); + region.assign_advice( + || "set gamma", + self.c_gamma, + offset + 1, + || Ok(gamma_val), + )?; + } + + if y_p == -y_q { + // y_p = -y_p ⟹ D + region.assign_advice( + || "set D", + self.d_delta, + offset, + || Ok(C::Base::one()), + )?; + } else { + // δ = 1 / (y_q + y_p) + let delta_val = (y_q + y_p).invert().unwrap(); + region.assign_advice( + || "set delta", + self.d_delta, + offset + 1, + || Ok(delta_val), + )?; + } + Ok(()) + }); + } + + // Compute R = P + Q + let r = x_p .zip(y_p) + .zip(x_q) .zip(y_q) - .map(|(((x_p, x_q), y_p), y_q)| -> Result<(), Error> { - if x_q == x_p { - // x_q = x_p ⟹ A - region.assign_advice(|| "set A", a, offset, || Ok(C::Base::one()))?; - - // Doubling case, λ = 3(x_p)^2 / (2 * y_p) - if y_p != C::Base::zero() { - let lambda_val = C::Base::from_u64(3) - * x_p - * x_p - * (C::Base::from_u64(2) * y_p).invert().unwrap(); - region.assign_advice(|| "set lambda", lambda, offset, || Ok(lambda_val))?; - } - } else { - // α = 1 / (x_q - x_p) - let alpha_val = (x_q - x_p).invert().unwrap(); - region.assign_advice(|| "set alpha", a, offset + 1, || Ok(alpha_val))?; - - // Non-doubling case, λ = (y_q - y_p) / (x_q - x_p) - let lambda_val = (x_q - x_p).invert().unwrap() * (y_q - y_p); - region.assign_advice(|| "set lambda", lambda, offset, || Ok(lambda_val))?; - } - - if x_p == C::Base::zero() { - // x_p = 0 ⟹ B - region.assign_advice(|| "set B", b, offset, || Ok(C::Base::one()))?; - } else { - // β = 1 / x_p - let beta_val = x_p.invert().unwrap(); - region.assign_advice(|| "set beta", b, offset + 1, || Ok(beta_val))?; - } - if x_q == C::Base::zero() { - // x_q = 0 ⟹ C - region.assign_advice(|| "set C", c, offset, || Ok(C::Base::one()))?; + .map(|(((x_p, y_p), x_q), y_q)| { + // If either `p` or `q` are (0,0) represent them as C::zero() + let p = if x_p == C::Base::zero() && y_p == C::Base::zero() { + C::identity() } else { - // γ = 1 / x_q - let gamma_val = x_q.invert().unwrap(); - region.assign_advice(|| "set gamma", c, offset + 1, || Ok(gamma_val))?; - } - - if y_p == -y_q { - // y_p = -y_p ⟹ D - region.assign_advice(|| "set D", d, offset, || Ok(C::Base::one()))?; + C::from_xy(x_p, y_p).unwrap() + }; + let q = if x_q == C::Base::zero() && y_q == C::Base::zero() { + C::identity() } else { - // δ = 1 / (y_q + y_p) - let delta_val = (y_q + y_p).invert().unwrap(); - region.assign_advice(|| "set delta", d, offset + 1, || Ok(delta_val))?; - } - Ok(()) + C::from_xy(x_q, y_q).unwrap() + }; + p + q }); - } - // Compute R = P + Q - let r = x_p - .zip(y_p) - .zip(x_q) - .zip(y_q) - .map(|(((x_p, y_p), x_q), y_q)| { - // If either `p` or `q` are (0,0) represent them as C::zero() - let p = if x_p == C::Base::zero() && y_p == C::Base::zero() { - C::identity() - } else { - C::from_xy(x_p, y_p).unwrap() - }; - let q = if x_q == C::Base::zero() && y_q == C::Base::zero() { - C::identity() + let x_r_val = r.map(|r| { + if r.is_identity().into() { + C::Base::zero() } else { - C::from_xy(x_q, y_q).unwrap() - }; - p + q + *r.to_affine().coordinates().unwrap().x() + } }); - let x_r_val = r.map(|r| { - if r.is_identity().into() { - C::Base::zero() - } else { - *r.to_affine().coordinates().unwrap().x() - } - }); + let y_r_val = r.map(|r| { + if r.is_identity().into() { + C::Base::zero() + } else { + *r.to_affine().coordinates().unwrap().y() + } + }); - let y_r_val = r.map(|r| { - if r.is_identity().into() { - C::Base::zero() - } else { - *r.to_affine().coordinates().unwrap().y() - } - }); - - // Assign `x_r` - let x_r_cell = region.assign_advice( - || "set x_r", - A.0, - offset + 1, - || x_r_val.ok_or(Error::SynthesisError), - )?; - - // Assign `y_r` - let y_r_cell = region.assign_advice( - || "set y_r", - A.1, - offset + 1, - || y_r_val.ok_or(Error::SynthesisError), - )?; - - Ok(EccPoint { - x: CellValue::::new(x_r_cell, x_r_val), - y: CellValue::::new(y_r_cell, y_r_val), - }) + // Assign `x_r` + let x_r_cell = region.assign_advice( + || "set x_r", + self.x_qr, + offset + 1, + || x_r_val.ok_or(Error::SynthesisError), + )?; + + // Assign `y_r` + let y_r_cell = region.assign_advice( + || "set y_r", + self.y_qr, + offset + 1, + || y_r_val.ok_or(Error::SynthesisError), + )?; + + Ok(EccPoint { + x: CellValue::::new(x_r_cell, x_r_val), + y: CellValue::::new(y_r_cell, y_r_val), + }) + } } diff --git a/src/circuit/gadget/ecc/chip/add_incomplete.rs b/src/circuit/gadget/ecc/chip/add_incomplete.rs index 06db46bba..b98db9879 100644 --- a/src/circuit/gadget/ecc/chip/add_incomplete.rs +++ b/src/circuit/gadget/ecc/chip/add_incomplete.rs @@ -2,126 +2,139 @@ use super::{util, CellValue, EccConfig, EccPoint}; use halo2::{ arithmetic::FieldExt, circuit::Region, - plonk::{ConstraintSystem, Error, Expression}, + plonk::{Advice, Column, ConstraintSystem, Error, Permutation, Selector}, + poly::Rotation, }; -#[allow(clippy::too_many_arguments)] -pub(super) fn create_gate( - meta: &mut ConstraintSystem, - q_add_incomplete: Expression, - x_p: Expression, - y_p: Expression, - x_q: Expression, - y_q: Expression, - x_a: Expression, - y_a: Expression, -) { - // (x_a + x_q + x_p)⋅(x_p − x_q)^2 − (y_p − y_q)^2 = 0 - meta.create_gate("point addition expr1", |_| { - let expr1 = (x_a.clone() + x_q.clone() + x_p.clone()) - * (x_p.clone() - x_q.clone()) - * (x_p.clone() - x_q.clone()) - - (y_p.clone() - y_q.clone()) * (y_p.clone() - y_q.clone()); - - q_add_incomplete.clone() * expr1 - }); - - // (y_a + y_q)(x_p − x_q) − (y_p − y_q)(x_q − x_a) = 0 - meta.create_gate("point addition expr2", |_| { - let expr2 = (y_a + y_q.clone()) * (x_p - x_q.clone()) - (y_p - y_q) * (x_q - x_a); - - q_add_incomplete * expr2 - }); +#[derive(Clone, Debug)] +pub struct Config { + q_add_incomplete: Selector, + // x-coordinate of P in P + Q = R + x_p: Column, + // y-coordinate of P in P + Q = R + y_p: Column, + // x-coordinate of Q or R in P + Q = R + pub x_qr: Column, + // y-coordinate of Q or R in P + Q = R + pub y_qr: Column, + // Permutation + perm: Permutation, } -#[allow(non_snake_case)] -pub(super) fn assign_region( - a: &EccPoint, - b: &EccPoint, - offset: usize, - region: &mut Region<'_, F>, - config: EccConfig, -) -> Result, Error> { - // Compute the sum `a + b` - let (x_p, y_p) = (a.x.value, a.y.value); - let (x_q, y_q) = (b.x.value, b.y.value); - - // Handle exceptional cases - x_p.zip(y_p) - .zip(x_q) - .zip(y_q) - .map(|(((x_p, y_p), x_q), y_q)| { - // P is point at infinity - if (x_p == F::zero() && y_p == F::zero()) - // Q is point at infinity - || (x_q == F::zero() && y_q == F::zero()) - // x_p = x_q - || (x_p == x_q) - { - return Err(Error::SynthesisError); - } - Ok(()) - }) - .unwrap_or(Err(Error::SynthesisError))?; - - // Rename columns for `add` context - let A = (config.extras[0], config.extras[1]); - - // Enable `q_add_incomplete` selector - config.q_add_incomplete.enable(region, offset)?; - - // Copy point `a` into `x_p`, `y_p` columns - util::assign_and_constrain( - region, - || "x_p", - config.P.0.into(), - offset, - &a.x, - &config.perm, - )?; - util::assign_and_constrain( - region, - || "y_p", - config.P.1.into(), - offset, - &a.y, - &config.perm, - )?; - - // Copy point `b` into `x_a`, `y_a` columns - util::assign_and_constrain(region, || "x_q", A.0.into(), offset, &b.x, &config.perm)?; - util::assign_and_constrain(region, || "y_q", A.1.into(), offset, &b.y, &config.perm)?; - - let r = x_p - .zip(y_p) - .zip(x_q) - .zip(y_q) - .map(|(((x_p, y_p), x_q), y_q)| { - let lambda = (y_q - y_p) * (x_q - x_p).invert().unwrap(); - let x_r = lambda * lambda - x_p - x_q; - let y_r = lambda * (x_p - x_r) - y_p; - (x_r, y_r) +impl From<&EccConfig> for Config { + fn from(ecc_config: &EccConfig) -> Self { + Self { + q_add_incomplete: ecc_config.q_add_incomplete, + x_p: ecc_config.P.0, + y_p: ecc_config.P.1, + x_qr: ecc_config.extras[0], + y_qr: ecc_config.extras[1], + perm: ecc_config.perm.clone(), + } + } +} + +impl Config { + #[allow(clippy::too_many_arguments)] + pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { + let q_add_incomplete = meta.query_selector(self.q_add_incomplete, Rotation::cur()); + let x_p = meta.query_advice(self.x_p, Rotation::cur()); + let y_p = meta.query_advice(self.y_p, Rotation::cur()); + let x_q = meta.query_advice(self.x_qr, Rotation::cur()); + let y_q = meta.query_advice(self.y_qr, Rotation::cur()); + let x_r = meta.query_advice(self.x_qr, Rotation::next()); + let y_r = meta.query_advice(self.y_qr, Rotation::next()); + + // (x_r + x_q + x_p)⋅(x_p − x_q)^2 − (y_p − y_q)^2 = 0 + meta.create_gate("point addition expr1", |_| { + let expr1 = (x_r.clone() + x_q.clone() + x_p.clone()) + * (x_p.clone() - x_q.clone()) + * (x_p.clone() - x_q.clone()) + - (y_p.clone() - y_q.clone()) * (y_p.clone() - y_q.clone()); + + q_add_incomplete.clone() * expr1 }); - // Assign the sum to `x_a`, `y_a` columns in the next row - let x_r = r.map(|r| r.0); - let x_r_var = region.assign_advice( - || "x_r", - A.0, - offset + 1, - || x_r.ok_or(Error::SynthesisError), - )?; - - let y_r = r.map(|r| r.1); - let y_r_var = region.assign_advice( - || "y_r", - A.1, - offset + 1, - || y_r.ok_or(Error::SynthesisError), - )?; - - Ok(EccPoint { - x: CellValue::::new(x_r_var, x_r), - y: CellValue::::new(y_r_var, y_r), - }) + // (y_r + y_q)(x_p − x_q) − (y_p − y_q)(x_q − x_r) = 0 + meta.create_gate("point addition expr2", |_| { + let expr2 = (y_r + y_q.clone()) * (x_p - x_q.clone()) - (y_p - y_q) * (x_q - x_r); + + q_add_incomplete * expr2 + }); + } + + #[allow(non_snake_case)] + pub(super) fn assign_region( + &self, + a: &EccPoint, + b: &EccPoint, + offset: usize, + region: &mut Region<'_, F>, + ) -> Result, Error> { + // Compute the sum `a + b` + let (x_p, y_p) = (a.x.value, a.y.value); + let (x_q, y_q) = (b.x.value, b.y.value); + + // Handle exceptional cases + x_p.zip(y_p) + .zip(x_q) + .zip(y_q) + .map(|(((x_p, y_p), x_q), y_q)| { + // P is point at infinity + if (x_p == F::zero() && y_p == F::zero()) + // Q is point at infinity + || (x_q == F::zero() && y_q == F::zero()) + // x_p = x_q + || (x_p == x_q) + { + return Err(Error::SynthesisError); + } + Ok(()) + }) + .unwrap_or(Err(Error::SynthesisError))?; + + // Enable `q_add_incomplete` selector + self.q_add_incomplete.enable(region, offset)?; + + // Copy point `a` into `x_p`, `y_p` columns + util::assign_and_constrain(region, || "x_p", self.x_p.into(), offset, &a.x, &self.perm)?; + util::assign_and_constrain(region, || "y_p", self.y_p.into(), offset, &a.y, &self.perm)?; + + // Copy point `b` into `x_a`, `y_a` columns + util::assign_and_constrain(region, || "x_q", self.x_qr.into(), offset, &b.x, &self.perm)?; + util::assign_and_constrain(region, || "y_q", self.y_qr.into(), offset, &b.y, &self.perm)?; + + let r = x_p + .zip(y_p) + .zip(x_q) + .zip(y_q) + .map(|(((x_p, y_p), x_q), y_q)| { + let lambda = (y_q - y_p) * (x_q - x_p).invert().unwrap(); + let x_r = lambda * lambda - x_p - x_q; + let y_r = lambda * (x_p - x_r) - y_p; + (x_r, y_r) + }); + + // Assign the sum to `x_a`, `y_a` columns in the next row + let x_r = r.map(|r| r.0); + let x_r_var = region.assign_advice( + || "x_r", + self.x_qr, + offset + 1, + || x_r.ok_or(Error::SynthesisError), + )?; + + let y_r = r.map(|r| r.1); + let y_r_var = region.assign_advice( + || "y_r", + self.y_qr, + offset + 1, + || y_r.ok_or(Error::SynthesisError), + )?; + + Ok(EccPoint { + x: CellValue::::new(x_r_var, x_r), + y: CellValue::::new(y_r_var, y_r), + }) + } } diff --git a/src/circuit/gadget/ecc/chip/double.rs b/src/circuit/gadget/ecc/chip/double.rs index 91d380445..5b74bd577 100644 --- a/src/circuit/gadget/ecc/chip/double.rs +++ b/src/circuit/gadget/ecc/chip/double.rs @@ -3,101 +3,121 @@ use super::{util, CellValue, EccConfig, EccPoint}; use halo2::{ arithmetic::FieldExt, circuit::Region, - plonk::{ConstraintSystem, Error, Expression}, + plonk::{Advice, Column, ConstraintSystem, Error, Permutation, Selector}, + poly::Rotation, }; -pub(super) fn create_gate( - meta: &mut ConstraintSystem, - q_double: Expression, - x_a: Expression, - y_a: Expression, - x_p: Expression, - y_p: Expression, -) { - let x_p_2 = x_p.clone() * x_p.clone(); - let x_p_4 = x_p_2.clone() * x_p_2.clone(); +pub struct Config { + q_double: Selector, + // x-coordinate of P in [2]P = R + x_p: Column, + // y-coordinate of P in [2]P = R + y_p: Column, + // x-coordinate of R in [2]P = R + x_r: Column, + // y-coordinate of R in [2]P = R + y_r: Column, + // Permutation + perm: Permutation, +} - // 4⋅(y_p)^2⋅(x_a + 2⋅x_p) − 9⋅(x_p)^4 = 0 - meta.create_gate("point doubling expr1", |_| { - let expr1 = y_p.clone() - * y_p.clone() - * (x_a.clone() + x_p.clone() * F::from_u64(2)) - * F::from_u64(4) - - x_p_4 * F::from_u64(9); - q_double.clone() * expr1 - }); +impl From<&EccConfig> for Config { + fn from(ecc_config: &EccConfig) -> Self { + Self { + q_double: ecc_config.q_double, + x_p: ecc_config.P.0, + y_p: ecc_config.P.1, + x_r: ecc_config.extras[0], + y_r: ecc_config.extras[1], + perm: ecc_config.perm.clone(), + } + } +} - // 2⋅y_p⋅(y_a + y_p) − 3⋅(x_p)^2⋅(x_p − x_a) = 0 - meta.create_gate("point doubling expr2", |_| { - let expr2 = - y_p.clone() * (y_a + y_p) * F::from_u64(2) - x_p_2 * (x_p - x_a) * F::from_u64(3); +impl Config { + pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { + let q_double = meta.query_selector(self.q_double, Rotation::cur()); + let x_p = meta.query_advice(self.x_p, Rotation::cur()); + let y_p = meta.query_advice(self.y_p, Rotation::cur()); + let x_r = meta.query_advice(self.x_r, Rotation::cur()); + let y_r = meta.query_advice(self.y_r, Rotation::cur()); - q_double * expr2 - }); -} + let x_p_2 = x_p.clone() * x_p.clone(); + let x_p_4 = x_p_2.clone() * x_p_2.clone(); -#[allow(non_snake_case)] -pub(super) fn assign_region( - a: &EccPoint, - offset: usize, - region: &mut Region<'_, F>, - config: EccConfig, -) -> Result, Error> { - // Handle point at infinity - (a.x.value) - .zip(a.y.value) - .map(|(x, y)| { - if x == F::zero() && y == F::zero() { - return Err(Error::SynthesisError); - } - Ok(()) - }) - .unwrap_or(Err(Error::SynthesisError))?; + // 4⋅(y_p)^2⋅(x_r + 2⋅x_p) − 9⋅(x_p)^4 = 0 + meta.create_gate("point doubling expr1", |_| { + let expr1 = y_p.clone() + * y_p.clone() + * (x_r.clone() + x_p.clone() * F::from_u64(2)) + * F::from_u64(4) + - x_p_4 * F::from_u64(9); + q_double.clone() * expr1 + }); - // Rename columns - let A = (config.extras[0], config.extras[1]); + // 2⋅y_p⋅(y_r + y_p) − 3⋅(x_p)^2⋅(x_p − x_r) = 0 + meta.create_gate("point doubling expr2", |_| { + let expr2 = + y_p.clone() * (y_r + y_p) * F::from_u64(2) - x_p_2 * (x_p - x_r) * F::from_u64(3); - // Enable `q_double` selector - config.q_double.enable(region, offset)?; + q_double * expr2 + }); + } - // Copy the point into `x_p`, `y_p` columns - util::assign_and_constrain( - region, - || "x_p", - config.P.0.into(), - offset, - &a.x, - &config.perm, - )?; - util::assign_and_constrain( - region, - || "y_p", - config.P.1.into(), - offset, - &a.y, - &config.perm, - )?; + #[allow(non_snake_case)] + pub(super) fn assign_region( + &self, + a: &EccPoint, + offset: usize, + region: &mut Region<'_, F>, + ) -> Result, Error> { + // Handle point at infinity + (a.x.value) + .zip(a.y.value) + .map(|(x, y)| { + if x == F::zero() && y == F::zero() { + return Err(Error::SynthesisError); + } + Ok(()) + }) + .unwrap_or(Err(Error::SynthesisError))?; - // Compute the doubled point - let (x_p, y_p) = (a.x.value, a.y.value); - let r = x_p.zip(y_p).map(|(x_p, y_p)| { - // λ = 3(x_p)^2 / (2 * y_p) - let lambda = F::from_u64(3) * x_p * x_p * F::TWO_INV * y_p.invert().unwrap(); - let x_r = lambda * lambda - x_p - x_p; - let y_r = lambda * (x_p - x_r) - y_p; - (x_r, y_r) - }); - let x_r = r.map(|r| r.0); - let y_r = r.map(|r| r.1); + // Enable `q_double` selector + self.q_double.enable(region, offset)?; - // Assign the doubled point to `x_a`, `y_a` columns - let x_r_var = - region.assign_advice(|| "x_r", A.0, offset, || x_r.ok_or(Error::SynthesisError))?; - let y_r_var = - region.assign_advice(|| "y_r", A.1, offset, || y_r.ok_or(Error::SynthesisError))?; + // Copy the point into `x_p`, `y_p` columns + util::assign_and_constrain(region, || "x_p", self.x_p.into(), offset, &a.x, &self.perm)?; + util::assign_and_constrain(region, || "y_p", self.y_p.into(), offset, &a.y, &self.perm)?; - Ok(EccPoint { - x: CellValue::::new(x_r_var, x_r), - y: CellValue::::new(y_r_var, y_r), - }) + // Compute the doubled point + let (x_p, y_p) = (a.x.value, a.y.value); + let r = x_p.zip(y_p).map(|(x_p, y_p)| { + // λ = 3(x_p)^2 / (2 * y_p) + let lambda = F::from_u64(3) * x_p * x_p * F::TWO_INV * y_p.invert().unwrap(); + let x_r = lambda * lambda - x_p - x_p; + let y_r = lambda * (x_p - x_r) - y_p; + (x_r, y_r) + }); + let x_r = r.map(|r| r.0); + let y_r = r.map(|r| r.1); + + // Assign the doubled point to `x_r`, `y_r` columns + let x_r_var = region.assign_advice( + || "x_r", + self.x_r, + offset, + || x_r.ok_or(Error::SynthesisError), + )?; + let y_r_var = region.assign_advice( + || "y_r", + self.y_r, + offset, + || y_r.ok_or(Error::SynthesisError), + )?; + + Ok(EccPoint { + x: CellValue::::new(x_r_var, x_r), + y: CellValue::::new(y_r_var, y_r), + }) + } } diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 7d459c68a..cb3ab401d 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -6,360 +6,298 @@ use ff::PrimeField; use halo2::{ arithmetic::{CurveAffine, Field, FieldExt}, circuit::Region, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, Selector}, + poly::Rotation, }; -#[allow(clippy::too_many_arguments)] -pub(super) fn create_gate( - meta: &mut ConstraintSystem, - q_mul: Expression, - z_cur: Expression, - z_prev: Expression, - x_a_cur: Expression, - x_a_next: Expression, - x_p_cur: Expression, - x_p_next: Expression, - y_p_cur: Expression, - y_p_next: Expression, - lambda1_cur: Expression, - lambda2_cur: Expression, - lambda1_next: Expression, - lambda2_next: Expression, -) { - // The current bit in the scalar decomposition, k_i = z_i - 2⋅z_{i+1}. - // Recall that we assigned the cumulative variable `z_i` in descending order, - // i from n down to 0. So z_{i+1} corresponds to the `z_prev` query. - let k = z_cur - Expression::Constant(F::from_u64(2)) * z_prev; - - // (k_i) ⋅ (k_i - 1) = 0 - meta.create_gate("Scalar boolean decomposition", |_| { - let bool_check = k.clone() * (k.clone() + Expression::Constant(-F::one())); - q_mul.clone() * bool_check - }); - - // The base used in double-and-add remains constant. We check that its - // x- and y- coordinates are the same throughout. - meta.create_gate("x_p equality", |_| { - q_mul.clone() * (x_p_cur.clone() - x_p_next.clone()) - }); - meta.create_gate("y_p equality", |_| { - q_mul.clone() * (y_p_cur.clone() - y_p_next.clone()) - }); - - // y_{A,i} = (λ_{1,i} + λ_{2,i}) - // * (x_{A,i} - (λ_{1,i}^2 - x_{A,i} - x_{P,i})) / 2 - let y_a_cur = (lambda1_cur.clone() + lambda2_cur.clone()) - * (x_a_cur.clone() - - (lambda1_cur.clone() * lambda1_cur.clone() - x_a_cur.clone() - x_p_cur.clone())) - * F::TWO_INV; - - // y_{A,i+1} = (λ_{1,i+1} + λ_{2,i+1}) - // * (x_{A,i+1} - (λ_{1,i+1}^2 - x_{A,i+1} - x_{P,i+1})) / 2 - let y_a_next = (lambda1_next.clone() + lambda2_next) - * (x_a_next.clone() - (lambda1_next.clone() * lambda1_next - x_a_next.clone() - x_p_next)) - * F::TWO_INV; - - // λ_{1,i}⋅(x_{A,i} − x_{P,i}) − y_{A,i} + (2k_i - 1) y_{P,i} = 0 - meta.create_gate("Double-and-add lambda1", |_| { - let expr = lambda1_cur.clone() * (x_a_cur.clone() - x_p_cur.clone()) - y_a_cur.clone() - + (k * F::from_u64(2) + Expression::Constant(-F::one())) * y_p_cur.clone(); - q_mul.clone() * expr - }); - - // (λ_{1,i} + λ_{2,i})⋅(x_{A,i} − (λ_{1,i}^2 − x_{A,i} − x_{P,i})) − 2y_{A,i}) = 0 - meta.create_gate("Double-and-add expr0", |_| { - let lambda_neg = lambda1_cur.clone() + lambda2_cur.clone(); - let lambda1_expr = - lambda1_cur.clone() * lambda1_cur.clone() - x_a_cur.clone() - x_p_cur.clone(); - let expr = lambda_neg * (x_a_cur.clone() - lambda1_expr) - - Expression::Constant(F::from_u64(2)) * y_a_cur.clone(); - q_mul.clone() * expr - }); - - // λ_{2,i}^2 − x_{A,i+1} −(λ_{1,i}^2 − x_{A,i} − x_{P,i}) − x_{A,i} = 0 - meta.create_gate("Double-and-add expr1", |_| { - let expr1 = lambda2_cur.clone() * lambda2_cur.clone() - - x_a_next.clone() - - (lambda1_cur.clone() * lambda1_cur) - + x_p_cur; - - q_mul.clone() * expr1 - }); - - // λ_{2,i}⋅(x_{A,i} − x_{A,i+1}) − y_{A,i} − y_{A,i+1} = 0 - meta.create_gate("Double-and-add expr2", |_| { - let expr2 = lambda2_cur * (x_a_cur - x_a_next) - y_a_cur - y_a_next; - - q_mul.clone() * expr2 - }); +mod incomplete; +use incomplete::IncompleteConfig; + +pub struct Config { + // Selector used to constrain the cells used in complete addition. + q_mul_complete: Selector, + // Fixed column used to check recovery of the original scalar after decomposition. + mul_decompose: Column, + // Advice column used to decompose scalar in complete addition. + z_complete: Column, + // Permutation + perm: Permutation, + // Configuration used in complete addition + add_config: add::Config, + // Configuration used in point doubling + double_config: double::Config, + // Configuration used for `hi` bits of the scalar + hi_config: IncompleteConfig, + // Configuration used for `lo` bits of the scalar + lo_config: IncompleteConfig, } -/// Gate used to check scalar decomposition is correct. -/// This is used to check the bits used in complete addition, since the incomplete -/// addition gate (controlled by `q_mul`) already checks scalar decomposition for -/// the other bits. -pub(super) fn create_decompose_gate( - meta: &mut ConstraintSystem, - q_mul_decompose: Expression, - z_cur: Expression, - z_prev: Expression, -) { - meta.create_gate("Decompose scalar ", |_| { - // k_{i} = z_{i} - 2⋅z_{i+1} - let k = z_cur.clone() - Expression::Constant(F::from_u64(2)) * z_prev; - // (k_i) ⋅ (k_i - 1) = 0 - let bool_check = k.clone() * (k + Expression::Constant(-F::one())); - - q_mul_decompose.clone() * bool_check - }); +impl From<&EccConfig> for Config { + fn from(ecc_config: &EccConfig) -> Self { + Self { + q_mul_complete: ecc_config.q_mul_complete, + mul_decompose: ecc_config.mul_decompose, + z_complete: ecc_config.bits, + perm: ecc_config.perm.clone(), + add_config: ecc_config.into(), + double_config: ecc_config.into(), + hi_config: IncompleteConfig::into_hi_config(ecc_config), + lo_config: IncompleteConfig::into_lo_config(ecc_config), + } + } } -/// Gate used to check final scalar is recovered. -pub(super) fn create_final_scalar_gate( - meta: &mut ConstraintSystem, - scalar: Expression, - z_cur: Expression, -) { - meta.create_gate("Decompose scalar", |_| { - // q = 2^254 + t_q is the scalar field of Pallas - let t_q = -(C::Scalar::from_u128(1u128 << 127).square()); - let t_q = C::Base::from_bytes(&t_q.to_bytes()).unwrap(); - - // Check that `k = scalar + t_q` - scalar.clone() * (scalar + Expression::Constant(t_q) - z_cur) - }); -} +impl Config { + pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { + self.hi_config.create_gate(meta); + self.lo_config.create_gate(meta); + self.create_decompose_gate(meta); + self.create_final_scalar_gate(meta); + } -pub(super) fn assign_region( - scalar: &CellValue, - base: &EccPoint, - offset: usize, - region: &mut Region<'_, C::Base>, - config: EccConfig, -) -> Result, Error> { - // We bisect the boolean decomposition into `hi` and `lo` halves, and - // process these halves "in parallel" (i.e. on the same rows, but on - // non-overlapping columns). - let hi_columns = IncompleteColumns { - q_mul: config.q_mul_hi, - z: config.bits, - x_a: config.extras[0], - lambda: config.lambda, - }; - let lo_columns = IncompleteColumns { - q_mul: config.q_mul_lo, - z: config.extras[1], - x_a: config.extras[2], - lambda: (config.extras[3], config.extras[4]), - }; - - // Decompose the scalar bitwise (big-endian bit order). - let k_bits = decompose_scalar::(scalar.value.unwrap()); - - // Bits used in incomplete addition. k_{254} to k_{4} inclusive - let incomplete_range = 0..(C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS); - let k_incomplete = &k_bits[incomplete_range]; - let k_incomplete_hi = &k_incomplete[..k_incomplete.len() / 2]; - let k_incomplete_lo = &k_incomplete[k_incomplete.len() / 2..]; - - // Bits used in complete addition. k_{3} to k_{1} inclusive - // The LSB k_{0} is handled separately. - let complete_range = - (C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS)..(C::Scalar::NUM_BITS as usize - 1); - let k_complete = &k_bits[complete_range.clone()]; - - // Initialize the accumulator a [2]base - let acc = double::assign_region(&base, offset, region, config.clone())?; - // Initialize the running sum for scalar decomposition to zero - let z_val = C::Base::zero(); - let z_cell = region.assign_advice(|| "initial z", hi_columns.z, offset + 1, || Ok(z_val))?; - let z = CellValue::new(z_cell, Some(z_val)); - - // Double-and-add (incomplete addition) for the `hi` half of the scalar decomposition - let (x, y_a, z) = add_incomplete::( - region, - &base, - config.clone(), - offset + 1, - hi_columns, - k_incomplete_hi, - (X(acc.x.clone()), Y(acc.y.value), ZValue(z)), - )?; - - // Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition - let (x, y_a, z) = add_incomplete::( - region, - &base, - config.clone(), - offset + 1, - lo_columns, - k_incomplete_lo, - (x, y_a, z), - )?; - - // Move from incomplete addition to complete addition - let mut acc = { - let y_a_col = config.extras[1]; - let row = k_incomplete_lo.len() + 2; - - let y_a_cell = region.assign_advice( - || "y_a", - y_a_col, - row + offset, - || y_a.ok_or(Error::SynthesisError), - )?; - util::assign_and_constrain( + pub(super) fn assign_region( + &self, + scalar: &CellValue, + base: &EccPoint, + offset: usize, + region: &mut Region<'_, C::Base>, + ) -> Result, Error> { + // Decompose the scalar bitwise (big-endian bit order). + let k_bits = decompose_scalar::(scalar.value.unwrap()); + + // Bits used in incomplete addition. k_{254} to k_{4} inclusive + let incomplete_range = 0..(C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS); + let k_incomplete = &k_bits[incomplete_range]; + let k_incomplete_hi = &k_incomplete[..k_incomplete.len() / 2]; + let k_incomplete_lo = &k_incomplete[k_incomplete.len() / 2..]; + + // Bits used in complete addition. k_{3} to k_{1} inclusive + // The LSB k_{0} is handled separately. + let complete_range = (C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS) + ..(C::Scalar::NUM_BITS as usize - 1); + let k_complete = &k_bits[complete_range.clone()]; + + // Initialize the accumulator a [2]base + let acc = self.double_config.assign_region(&base, offset, region)?; + + // Initialize the running sum for scalar decomposition to zero + let z_val = C::Base::zero(); + let z_cell = + region.assign_advice(|| "initial z", self.hi_config.z, offset + 1, || Ok(z_val))?; + let z = CellValue::new(z_cell, Some(z_val)); + + // Double-and-add (incomplete addition) for the `hi` half of the scalar decomposition + let (x, y_a, z) = self.hi_config.double_and_add( region, - || "Copy z from incomplete to complete", - config.bits.into(), - row + offset, - &z, - &config.perm, + &base, + offset + 1, + k_incomplete_hi, + (X(acc.x.clone()), Y(acc.y.value), ZValue(z)), )?; - EccPoint { - x: x.0, - y: CellValue::::new(y_a_cell, *y_a), - } - }; - - let mut z_val = z.value; - // Complete addition - for (iter, k) in k_complete.iter().enumerate() { - // Each iteration uses 4 rows (two complete additions) - let row = k_incomplete_lo.len() + 4 * iter + 3; - // Check scalar decomposition here - region.assign_advice( - || "z", - config.bits, - row + offset - 1, - || z_val.ok_or(Error::SynthesisError), - )?; - z_val = z_val.map(|z_val| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64)); - region.assign_advice( - || "z", - config.bits, - row + offset, - || z_val.ok_or(Error::SynthesisError), - )?; - config.q_mul_decompose.enable(region, row + offset)?; - - let x_p = base.x.value; - let x_p_cell = region.assign_advice( - || "x_p", - config.P.0, - row + offset, - || x_p.ok_or(Error::SynthesisError), + // Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition + let (x, y_a, z) = self.lo_config.double_and_add( + region, + &base, + offset + 1, + k_incomplete_lo, + (x, y_a, z), )?; - // If the bit is set, use `y`; if the bit is not set, use `-y` - let y_p = base.y.value; - let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); - - let y_p_cell = region.assign_advice( - || "y_p", - config.P.1, - row + offset, - || y_p.ok_or(Error::SynthesisError), - )?; - let p = EccPoint { - x: CellValue::::new(x_p_cell, x_p), - y: CellValue::::new(y_p_cell, y_p), + // Move from incomplete addition to complete addition + let mut acc = { + let y_a_col = self.add_config.y_qr; + let row = k_incomplete_lo.len() + 2; + + let y_a_cell = region.assign_advice( + || "y_a", + y_a_col, + row + offset, + || y_a.ok_or(Error::SynthesisError), + )?; + util::assign_and_constrain( + region, + || "Copy z from incomplete to complete", + self.z_complete.into(), + row + offset, + &z, + &self.perm, + )?; + EccPoint { + x: x.0, + y: CellValue::::new(y_a_cell, *y_a), + } }; - // Acc + U - let tmp_acc = add::assign_region::(&p, &acc, row + offset, region, config.clone())?; + let mut z_val = z.value; + // Complete addition + for (iter, k) in k_complete.iter().enumerate() { + // Each iteration uses 4 rows (two complete additions) + let row = k_incomplete_lo.len() + 4 * iter + 3; + + // Check scalar decomposition here + region.assign_advice( + || "z", + self.z_complete, + row + offset - 1, + || z_val.ok_or(Error::SynthesisError), + )?; + z_val = z_val.map(|z_val| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64)); + region.assign_advice( + || "z", + self.z_complete, + row + offset, + || z_val.ok_or(Error::SynthesisError), + )?; + self.q_mul_complete.enable(region, row + offset)?; + + let x_p = base.x.value; + let x_p_cell = region.assign_advice( + || "x_p", + self.add_config.x_p, + row + offset, + || x_p.ok_or(Error::SynthesisError), + )?; + + // If the bit is set, use `y`; if the bit is not set, use `-y` + let y_p = base.y.value; + let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); + + let y_p_cell = region.assign_advice( + || "y_p", + self.add_config.y_p, + row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; + let p = EccPoint { + x: CellValue::::new(x_p_cell, x_p), + y: CellValue::::new(y_p_cell, y_p), + }; + + // Acc + U + let tmp_acc = self + .add_config + .assign_region::(&p, &acc, row + offset, region)?; + + // Copy acc from `x_a`, `y_a` over to `x_p`, `y_p` on the next row + let acc_x = util::assign_and_constrain( + region, + || "copy acc x_a", + self.add_config.x_p.into(), + row + offset + 2, + &acc.x, + &self.perm, + )?; + let acc_y = util::assign_and_constrain( + region, + || "copy acc y_a", + self.add_config.y_p.into(), + row + offset + 2, + &acc.y, + &self.perm, + )?; + + acc = EccPoint { x: acc_x, y: acc_y }; + + // Acc + P + Acc + acc = self + .add_config + .assign_region::(&acc, &tmp_acc, row + offset + 2, region)?; + } - // Copy acc from `x_a`, `y_a` over to `x_p`, `y_p` on the next row - let acc_x = util::assign_and_constrain( - region, - || "copy acc x_a", - config.P.0.into(), - row + offset + 2, - &acc.x, - &config.perm, + // Process the least significant bit + let k_0_row = k_incomplete_lo.len() + complete_range.len() * 4 + 4; + let k_0 = &k_bits[C::Scalar::NUM_BITS as usize - 1]; + + // Check that we recover the original scalar. + // + // NB: We assume that the scalar fits in the curve's base field. This is not + // true in general, and in particular for the Pallas curve, whose scalar field + // `Fq` is larger than its base field `Fp`. + // + // However, the only use of variable-base scalar mul in the Orchard protocol + // is in deriving diversified addresses `[ivk] g_d`, and `ivk` is guaranteed + // to be in the base field of the curve. (See non-normative notes in + // https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents.) + + z_val = z_val.map(|z_val| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k_0 as u64)); + region.assign_advice( + || "final z", + self.z_complete, + k_0_row + offset, + || z_val.ok_or(Error::SynthesisError), )?; - let acc_y = util::assign_and_constrain( - region, - || "copy acc y_a", - config.P.1.into(), - row + offset + 2, - &acc.y, - &config.perm, + region.assign_fixed( + || "original k", + self.mul_decompose, + k_0_row + offset, + || Ok(C::Base::from_bytes(&scalar.value.unwrap().to_bytes()).unwrap()), )?; - acc = EccPoint { x: acc_x, y: acc_y }; + // If `k_0` is 0, return `Acc - P` + if !k_0 { + let (x_p, y_p) = (base.x.value, base.y.value.map(|y_p| -y_p)); + let x_p_cell = region.assign_advice( + || "x_p", + self.add_config.x_p, + k_0_row + offset, + || x_p.ok_or(Error::SynthesisError), + )?; + + let y_p_cell = region.assign_advice( + || "y_p", + self.add_config.y_p, + k_0_row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; + let p = EccPoint { + x: CellValue::::new(x_p_cell, x_p), + y: CellValue::::new(y_p_cell, y_p), + }; + + // Return the result of the final complete addition as `[scalar]B` + self.add_config + .assign_region::(&p, &acc, k_0_row + offset, region) + } else { + // If `k_0` is 1, simply return `Acc` + Ok(acc) + } + } - // Acc + P + Acc - acc = add::assign_region::(&acc, &tmp_acc, row + offset + 2, region, config.clone())?; + /// Gate used to check scalar decomposition is correct. + /// This is used to check the bits used in complete addition, since the incomplete + /// addition gate (controlled by `q_mul`) already checks scalar decomposition for + /// the other bits. + fn create_decompose_gate(&self, meta: &mut ConstraintSystem) { + let q_mul_complete = meta.query_selector(self.q_mul_complete, Rotation::cur()); + let z_cur = meta.query_advice(self.z_complete, Rotation::cur()); + let z_prev = meta.query_advice(self.z_complete, Rotation::prev()); + + meta.create_gate("Decompose scalar ", |_| { + // k_{i} = z_{i} - 2⋅z_{i+1} + let k = z_cur.clone() - Expression::Constant(F::from_u64(2)) * z_prev; + // (k_i) ⋅ (k_i - 1) = 0 + let bool_check = k.clone() * (k + Expression::Constant(-F::one())); + + q_mul_complete.clone() * bool_check + }); } - // Process the least significant bit - let k_0_row = k_incomplete_lo.len() + complete_range.len() * 4 + 4; - let k_0 = &k_bits[C::Scalar::NUM_BITS as usize - 1]; - - // Check that we recover the original scalar. - // - // NB: We assume that the scalar fits in the curve's base field. This is not - // true in general, and in particular for the Pallas curve, whose scalar field - // `Fq` is larger than its base field `Fp`. - // - // However, the only use of variable-base scalar mul in the Orchard protocol - // is in deriving diversified addresses `[ivk] g_d`, and `ivk` is guaranteed - // to be in the base field of the curve. (See non-normative notes in - // https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents.) - - z_val = z_val.map(|z_val| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k_0 as u64)); - region.assign_advice( - || "final z", - config.bits, - k_0_row + offset, - || z_val.ok_or(Error::SynthesisError), - )?; - region.assign_fixed( - || "original k", - config.mul_decompose, - k_0_row + offset, - || Ok(C::Base::from_bytes(&scalar.value.unwrap().to_bytes()).unwrap()), - )?; - - // If `k_0` is 0, return `Acc - P` - if !k_0 { - let (x_p, y_p) = (base.x.value, base.y.value.map(|y_p| -y_p)); - let x_p_cell = region.assign_advice( - || "x_p", - config.P.0, - k_0_row + offset, - || x_p.ok_or(Error::SynthesisError), - )?; + /// Gate used to check final scalar is recovered. + pub(super) fn create_final_scalar_gate(&self, meta: &mut ConstraintSystem) { + let scalar = meta.query_fixed(self.mul_decompose, Rotation::cur()); + let z_cur = meta.query_advice(self.z_complete, Rotation::cur()); - let y_p_cell = region.assign_advice( - || "y_p", - config.P.1, - k_0_row + offset, - || y_p.ok_or(Error::SynthesisError), - )?; - let p = EccPoint { - x: CellValue::::new(x_p_cell, x_p), - y: CellValue::::new(y_p_cell, y_p), - }; + meta.create_gate("Decompose scalar", |_| { + // q = 2^254 + t_q is the scalar field of Pallas + let t_q = F::from_u128(45560315531506369815346746415080538113); - // Return the result of the final complete addition as `[scalar]B` - add::assign_region::(&p, &acc, k_0_row + offset, region, config) - } else { - // If `k_0` is 1, simply return `Acc` - Ok(acc) + // Check that `k = scalar + t_q` + scalar.clone() * (scalar + Expression::Constant(t_q) - z_cur) + }); } } -#[derive(Copy, Clone, Debug)] -struct IncompleteColumns { - q_mul: Selector, - z: Column, - x_a: Column, - lambda: (Column, Column), -} - #[derive(Clone, Debug)] struct X(CellValue); impl Deref for X { @@ -390,141 +328,6 @@ impl Deref for ZValue { } } -// We perform incomplete addition on all but the last three bits of the -// decomposed scalar. -// We split the bits in the incomplete addition range into "hi" and "lo" -// halves and process them side by side, using the same rows but with -// non-overlapping columns. -// Returns (x, y, z). -#[allow(clippy::type_complexity)] -fn add_incomplete( - region: &mut Region<'_, C::Base>, - base: &EccPoint, - config: EccConfig, - offset: usize, - columns: IncompleteColumns, - bits: &[bool], - acc: (X, Y, ZValue), -) -> Result<(X, Y, ZValue), Error> { - // Initialise the running `z` sum for the scalar bits. - let mut z_val = acc.2.value; - let mut z_cell = region.assign_advice( - || "starting z", - columns.z, - offset, - || z_val.ok_or(Error::SynthesisError), - )?; - region.constrain_equal(&config.perm, z_cell, acc.2.cell)?; - - // Define `x_p`, `y_p` - let x_p = base.x.value; - let y_p = base.y.value; - - let offset = offset + 1; - - // Initialise acc - let mut x_a = acc.0.value; - let mut x_a_cell = region.assign_advice( - || "starting x_a", - columns.x_a, - offset, - || x_a.ok_or(Error::SynthesisError), - )?; - region.constrain_equal(&config.perm, x_a_cell, acc.0.cell)?; - let mut y_a = *acc.1; - - // Enable `q_mul` on all but the last row of the incomplete range. - for row in 1..(bits.len() - 1) { - columns.q_mul.enable(region, offset + row)?; - } - - // Incomplete addition - for (row, k) in bits.iter().enumerate() { - // z_{i} = 2 * z_{i+1} + k_i - z_val = z_val.map(|z_val| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64)); - z_cell = region.assign_advice( - || "z", - columns.z, - row + offset, - || z_val.ok_or(Error::SynthesisError), - )?; - - // Assign `x_p`, `y_p` - region.assign_advice( - || "x_p", - config.P.0, - row + offset, - || x_p.ok_or(Error::SynthesisError), - )?; - region.assign_advice( - || "y_p", - config.P.1, - row + offset, - || y_p.ok_or(Error::SynthesisError), - )?; - - // If the bit is set, use `y`; if the bit is not set, use `-y` - let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); - - // Compute and assign λ1⋅(x_A − x_P) = y_A − y_P - let lambda1 = y_a - .zip(y_p) - .zip(x_a) - .zip(x_p) - .map(|(((y_a, y_p), x_a), x_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap()); - region.assign_advice( - || "lambda1", - columns.lambda.0, - row + offset, - || lambda1.ok_or(Error::SynthesisError), - )?; - - // x_R = λ1^2 - x_A - x_P - let x_r = lambda1 - .zip(x_a) - .zip(x_p) - .map(|((lambda1, x_a), x_p)| lambda1 * lambda1 - x_a - x_p); - - // λ2 = (2(y_A) / (x_A - x_R)) - λ1 - let lambda2 = lambda1 - .zip(y_a) - .zip(x_a) - .zip(x_r) - .map(|(((lambda1, y_a), x_a), x_r)| { - C::Base::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 - }); - region.assign_advice( - || "lambda2", - columns.lambda.1, - row + offset, - || lambda2.ok_or(Error::SynthesisError), - )?; - - // Compute and assign `x_a` for the next row - let x_a_new = lambda2 - .zip(x_a) - .zip(x_r) - .map(|((lambda2, x_a), x_r)| lambda2 * lambda2 - x_a - x_r); - y_a = lambda2 - .zip(x_a) - .zip(x_a_new) - .zip(y_a) - .map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a); - x_a = x_a_new; - x_a_cell = region.assign_advice( - || "x_a", - columns.x_a, - row + offset + 1, - || x_a.ok_or(Error::SynthesisError), - )?; - } - Ok(( - X(CellValue::::new(x_a_cell, x_a)), - Y(y_a), - ZValue(CellValue::::new(z_cell, z_val)), - )) -} - fn decompose_scalar(scalar: C::Base) -> Vec { // Cast into scalar field let scalar = C::Scalar::from_bytes(&scalar.to_bytes()).unwrap(); diff --git a/src/circuit/gadget/ecc/chip/mul/incomplete.rs b/src/circuit/gadget/ecc/chip/mul/incomplete.rs new file mode 100644 index 000000000..1a8d4c753 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/mul/incomplete.rs @@ -0,0 +1,278 @@ +use super::super::{CellValue, EccConfig, EccPoint}; +use super::{ZValue, X, Y}; +use halo2::{ + arithmetic::FieldExt, + circuit::Region, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector}, + poly::Rotation, +}; + +pub(super) struct IncompleteConfig { + // Selector used to constrain the cells used in incomplete addition. + pub(super) q_mul: Selector, + // Cumulative sum used to decompose the scalar. + pub(super) z: Column, + // x-coordinate of the accumulator in each double-and-add iteration. + pub(super) x_a: Column, + // x-coordinate of the point being added in each double-and-add iteration. + pub(super) x_p: Column, + // y-coordinate of the point being added in each double-and-add iteration. + pub(super) y_p: Column, + // lambda1 in each double-and-add iteration. + pub(super) lambda1: Column, + // lambda2 in each double-and-add iteration. + pub(super) lambda2: Column, + // Permutation + pub(super) perm: Permutation, +} + +impl IncompleteConfig { + // Columns used in processing the `hi` bits of the scalar. + // `x_p, y_p` are shared across the `hi` and `lo` halves. + pub(super) fn into_hi_config(ecc_config: &EccConfig) -> Self { + Self { + q_mul: ecc_config.q_mul_hi, + z: ecc_config.bits, + x_a: ecc_config.extras[0], + x_p: ecc_config.P.0, + y_p: ecc_config.P.1, + lambda1: ecc_config.lambda.0, + lambda2: ecc_config.lambda.1, + perm: ecc_config.perm.clone(), + } + } + + // Columns used in processing the `lo` bits of the scalar. + // `x_p, y_p` are shared across the `hi` and `lo` halves. + pub(super) fn into_lo_config(ecc_config: &EccConfig) -> Self { + Self { + q_mul: ecc_config.q_mul_lo, + z: ecc_config.extras[1], + x_a: ecc_config.extras[2], + x_p: ecc_config.P.0, + y_p: ecc_config.P.1, + lambda1: ecc_config.extras[3], + lambda2: ecc_config.extras[4], + perm: ecc_config.perm.clone(), + } + } + + // Gate for incomplete addition part of variable-base scalar multiplication. + pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { + let q_mul = meta.query_selector(self.q_mul, Rotation::cur()); + let z_cur = meta.query_advice(self.z, Rotation::cur()); + let z_prev = meta.query_advice(self.z, Rotation::prev()); + let x_a_cur = meta.query_advice(self.x_a, Rotation::cur()); + let x_a_next = meta.query_advice(self.x_a, Rotation::next()); + let x_p_cur = meta.query_advice(self.x_p, Rotation::cur()); + let x_p_next = meta.query_advice(self.x_p, Rotation::next()); + let y_p_cur = meta.query_advice(self.y_p, Rotation::cur()); + let y_p_next = meta.query_advice(self.y_p, Rotation::next()); + let lambda1_cur = meta.query_advice(self.lambda1, Rotation::cur()); + let lambda2_cur = meta.query_advice(self.lambda2, Rotation::cur()); + let lambda1_next = meta.query_advice(self.lambda1, Rotation::next()); + let lambda2_next = meta.query_advice(self.lambda2, Rotation::next()); + + // The current bit in the scalar decomposition, k_i = z_i - 2⋅z_{i+1}. + // Recall that we assigned the cumulative variable `z_i` in descending order, + // i from n down to 0. So z_{i+1} corresponds to the `z_prev` query. + let k = z_cur - Expression::Constant(F::from_u64(2)) * z_prev; + + // (k_i) ⋅ (k_i - 1) = 0 + meta.create_gate("Scalar boolean decomposition", |_| { + let bool_check = k.clone() * (k.clone() + Expression::Constant(-F::one())); + q_mul.clone() * bool_check + }); + + // The base used in double-and-add remains constant. We check that its + // x- and y- coordinates are the same throughout. + meta.create_gate("x_p equality", |_| { + q_mul.clone() * (x_p_cur.clone() - x_p_next.clone()) + }); + meta.create_gate("y_p equality", |_| { + q_mul.clone() * (y_p_cur.clone() - y_p_next.clone()) + }); + + // y_{A,i} = (λ_{1,i} + λ_{2,i}) + // * (x_{A,i} - (λ_{1,i}^2 - x_{A,i} - x_{P,i})) / 2 + let y_a_cur = (lambda1_cur.clone() + lambda2_cur.clone()) + * (x_a_cur.clone() + - (lambda1_cur.clone() * lambda1_cur.clone() - x_a_cur.clone() - x_p_cur.clone())) + * F::TWO_INV; + + // y_{A,i+1} = (λ_{1,i+1} + λ_{2,i+1}) + // * (x_{A,i+1} - (λ_{1,i+1}^2 - x_{A,i+1} - x_{P,i+1})) / 2 + let y_a_next = (lambda1_next.clone() + lambda2_next) + * (x_a_next.clone() + - (lambda1_next.clone() * lambda1_next - x_a_next.clone() - x_p_next)) + * F::TWO_INV; + + // λ_{1,i}⋅(x_{A,i} − x_{P,i}) − y_{A,i} + (2k_i - 1) y_{P,i} = 0 + meta.create_gate("Double-and-add lambda1", |_| { + let expr = lambda1_cur.clone() * (x_a_cur.clone() - x_p_cur.clone()) - y_a_cur.clone() + + (k * F::from_u64(2) + Expression::Constant(-F::one())) * y_p_cur.clone(); + q_mul.clone() * expr + }); + + // (λ_{1,i} + λ_{2,i})⋅(x_{A,i} − (λ_{1,i}^2 − x_{A,i} − x_{P,i})) − 2y_{A,i}) = 0 + meta.create_gate("Double-and-add expr0", |_| { + let lambda_neg = lambda1_cur.clone() + lambda2_cur.clone(); + let lambda1_expr = + lambda1_cur.clone() * lambda1_cur.clone() - x_a_cur.clone() - x_p_cur.clone(); + let expr = lambda_neg * (x_a_cur.clone() - lambda1_expr) + - Expression::Constant(F::from_u64(2)) * y_a_cur.clone(); + q_mul.clone() * expr + }); + + // λ_{2,i}^2 − x_{A,i+1} −(λ_{1,i}^2 − x_{A,i} − x_{P,i}) − x_{A,i} = 0 + meta.create_gate("Double-and-add expr1", |_| { + let expr1 = lambda2_cur.clone() * lambda2_cur.clone() + - x_a_next.clone() + - (lambda1_cur.clone() * lambda1_cur) + + x_p_cur; + + q_mul.clone() * expr1 + }); + + // λ_{2,i}⋅(x_{A,i} − x_{A,i+1}) − y_{A,i} − y_{A,i+1} = 0 + meta.create_gate("Double-and-add expr2", |_| { + let expr2 = lambda2_cur * (x_a_cur - x_a_next) - y_a_cur - y_a_next; + + q_mul.clone() * expr2 + }); + } + + // We perform incomplete addition on all but the last three bits of the + // decomposed scalar. + // We split the bits in the incomplete addition range into "hi" and "lo" + // halves and process them side by side, using the same rows but with + // non-overlapping columns. + // Returns (x, y, z). + #[allow(clippy::type_complexity)] + pub(super) fn double_and_add( + &self, + region: &mut Region<'_, F>, + base: &EccPoint, + offset: usize, + bits: &[bool], + acc: (X, Y, ZValue), + ) -> Result<(X, Y, ZValue), Error> { + // Initialise the running `z` sum for the scalar bits. + let mut z_val = acc.2.value; + let mut z_cell = region.assign_advice( + || "starting z", + self.z, + offset, + || z_val.ok_or(Error::SynthesisError), + )?; + region.constrain_equal(&self.perm, z_cell, acc.2.cell)?; + + // Define `x_p`, `y_p` + let x_p = base.x.value; + let y_p = base.y.value; + + let offset = offset + 1; + + // Initialise acc + let mut x_a = acc.0.value; + let mut x_a_cell = region.assign_advice( + || "starting x_a", + self.x_a, + offset, + || x_a.ok_or(Error::SynthesisError), + )?; + region.constrain_equal(&self.perm, x_a_cell, acc.0.cell)?; + let mut y_a = *acc.1; + + // Enable `q_mul` on all but the last row of the incomplete range. + for row in 1..(bits.len() - 1) { + self.q_mul.enable(region, offset + row)?; + } + + // Incomplete addition + for (row, k) in bits.iter().enumerate() { + // z_{i} = 2 * z_{i+1} + k_i + z_val = z_val.map(|z_val| F::from_u64(2) * z_val + F::from_u64(*k as u64)); + z_cell = region.assign_advice( + || "z", + self.z, + row + offset, + || z_val.ok_or(Error::SynthesisError), + )?; + + // Assign `x_p`, `y_p` + region.assign_advice( + || "x_p", + self.x_p, + row + offset, + || x_p.ok_or(Error::SynthesisError), + )?; + region.assign_advice( + || "y_p", + self.y_p, + row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; + + // If the bit is set, use `y`; if the bit is not set, use `-y` + let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); + + // Compute and assign λ1⋅(x_A − x_P) = y_A − y_P + let lambda1 = y_a + .zip(y_p) + .zip(x_a) + .zip(x_p) + .map(|(((y_a, y_p), x_a), x_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap()); + region.assign_advice( + || "lambda1", + self.lambda1, + row + offset, + || lambda1.ok_or(Error::SynthesisError), + )?; + + // x_R = λ1^2 - x_A - x_P + let x_r = lambda1 + .zip(x_a) + .zip(x_p) + .map(|((lambda1, x_a), x_p)| lambda1 * lambda1 - x_a - x_p); + + // λ2 = (2(y_A) / (x_A - x_R)) - λ1 + let lambda2 = lambda1 + .zip(y_a) + .zip(x_a) + .zip(x_r) + .map(|(((lambda1, y_a), x_a), x_r)| { + F::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 + }); + region.assign_advice( + || "lambda2", + self.lambda2, + row + offset, + || lambda2.ok_or(Error::SynthesisError), + )?; + + // Compute and assign `x_a` for the next row + let x_a_new = lambda2 + .zip(x_a) + .zip(x_r) + .map(|((lambda2, x_a), x_r)| lambda2 * lambda2 - x_a - x_r); + y_a = lambda2 + .zip(x_a) + .zip(x_a_new) + .zip(y_a) + .map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a); + x_a = x_a_new; + x_a_cell = region.assign_advice( + || "x_a", + self.x_a, + row + offset + 1, + || x_a.ok_or(Error::SynthesisError), + )?; + } + Ok(( + X(CellValue::::new(x_a_cell, x_a)), + Y(y_a), + ZValue(CellValue::::new(z_cell, z_val)), + )) + } +} diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index 0708f983d..f256db2a5 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -1,5 +1,6 @@ use super::{ - add_incomplete, util, witness_point, EccConfig, EccPoint, EccScalarFixed, OrchardFixedBase, + add_incomplete, load::WindowUs, util, witness_point, CellValue, EccConfig, EccPoint, + EccScalarFixed, EccScalarFixedShort, OrchardFixedBase, OrchardFixedBaseShort, OrchardFixedBases, }; use crate::constants; @@ -8,230 +9,439 @@ use group::Curve; use halo2::{ arithmetic::{CurveAffine, Field, FieldExt}, circuit::Region, - plonk::{Column, ConstraintSystem, Error, Expression, Fixed}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, Selector}, poly::Rotation, }; -#[allow(clippy::too_many_arguments)] -pub(super) fn create_gate( - meta: &mut ConstraintSystem, +mod full_width; +mod short; + +pub struct Config { + q_mul_fixed: Selector, + q_mul_fixed_short: Selector, + // The fixed Lagrange interpolation coefficients for `x_p`. lagrange_coeffs: [Column; constants::H], - q_mul_fixed: Expression, - x_p: Expression, - y_p: Expression, - k: Expression, - u: Expression, - z: Expression, -) { - // Check interpolation of x-coordinate - meta.create_gate("fixed-base scalar mul (x)", |meta| { - let k_pow: Vec> = (0..constants::H) - .map(|pow| (0..pow).fold(Expression::Constant(F::one()), |acc, _| acc * k.clone())) - .collect(); - - let interpolated_x = k_pow - .iter() - .zip(lagrange_coeffs.iter()) - .fold(Expression::Constant(F::zero()), |acc, (k_pow, coeff)| { - acc + (k_pow.clone() * meta.query_fixed(*coeff, Rotation::cur())) - }); - - q_mul_fixed.clone() * (interpolated_x - x_p) - }); - - // Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed - meta.create_gate("fixed-base scalar mul (y)", |_| { - q_mul_fixed * (u.clone() * u - y_p - z) - }); + // The fixed `z` for each window such that `y + z = u^2`. + fixed_z: Column, + // k-bit decomposition of an `n-1`-bit scalar: + // a = a_0 + 2^k(a_1) + 2^{2k}(a_2) + ... + 2^{(n-1)k}(a_{n-1}) + k: Column, + // x-coordinate of the multiple of the fixed base at the current window. + x_p: Column, + // y-coordinate of the multiple of the fixed base at the current window. + y_p: Column, + // y-coordinate of accumulator (only used in the final row). + y_a: Column, + // An integer `u` for the current window, s.t. `y + z = u^2`. + u: Column, + // Permutation + perm: Permutation, + // Configuration for `add_incomplete` + add_incomplete_config: add_incomplete::Config, + // Configuration for `witness_point` + witness_point_config: witness_point::Config, } -#[allow(non_snake_case)] -pub(super) fn assign_region( - scalar: &EccScalarFixed, - base: &OrchardFixedBase, - offset: usize, - region: &mut Region<'_, C::Base>, - config: EccConfig, -) -> Result, Error> { - // Rename columns for `mul_fixed` context - let A = (config.extras[0], config.extras[1]); - let mul_fixed_u = config.extras[2]; - - // Assign fixed columns for given fixed base - for w in 0..constants::NUM_WINDOWS { - // Enable `q_mul_fixed` selector - config.q_mul_fixed.enable(region, w + offset)?; - - // Assign x-coordinate Lagrange interpolation coefficients - for k in 0..(constants::H) { - region.assign_fixed( - || { - format!( - "Lagrange interpolation coeff for window: {:?}, k: {:?}", - w, k - ) +impl From<&EccConfig> for Config { + fn from(ecc_config: &EccConfig) -> Self { + Self { + q_mul_fixed: ecc_config.q_mul_fixed, + q_mul_fixed_short: ecc_config.q_mul_fixed_short, + lagrange_coeffs: ecc_config.lagrange_coeffs, + fixed_z: ecc_config.fixed_z, + k: ecc_config.bits, + x_p: ecc_config.P.0, + y_p: ecc_config.P.1, + y_a: ecc_config.extras[1], + u: ecc_config.extras[2], + perm: ecc_config.perm.clone(), + add_incomplete_config: ecc_config.into(), + witness_point_config: ecc_config.into(), + } + } +} + +impl Config { + pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { + self.create_gate_inner(meta); + let short_config: short::Config = self.into(); + short_config.create_gate(meta); + } + + pub(super) fn assign_region_full( + &self, + scalar: &EccScalarFixed, + base: &OrchardFixedBase, + offset: usize, + region: &mut Region<'_, C::Base>, + ) -> Result, Error> { + let full_width_config: full_width::Config = self.into(); + full_width_config.assign_region(region, offset, scalar, base) + } + + pub(super) fn assign_region_short( + &self, + scalar: &EccScalarFixedShort, + base: &OrchardFixedBaseShort, + offset: usize, + region: &mut Region<'_, C::Base>, + ) -> Result, Error> { + let short_config: short::Config = self.into(); + short_config.assign_region(region, offset, scalar, base) + } + + fn create_gate_inner(&self, meta: &mut ConstraintSystem) { + let q_mul_fixed = meta.query_selector(self.q_mul_fixed, Rotation::cur()); + let y_p = meta.query_advice(self.y_p, Rotation::cur()); + + // Check interpolation of x-coordinate + meta.create_gate("fixed-base scalar mul (x)", |meta| { + let k = meta.query_advice(self.k, Rotation::cur()); + let x_p = meta.query_advice(self.x_p, Rotation::cur()); + + let k_pow: Vec> = (0..constants::H) + .map(|pow| (0..pow).fold(Expression::Constant(F::one()), |acc, _| acc * k.clone())) + .collect(); + + let interpolated_x = k_pow.iter().zip(self.lagrange_coeffs.iter()).fold( + Expression::Constant(F::zero()), + |acc, (k_pow, coeff)| { + acc + (k_pow.clone() * meta.query_fixed(*coeff, Rotation::cur())) }, - config.lagrange_coeffs[k], - w + offset, - || Ok(base.lagrange_coeffs.0[w].0[k]), - )?; + ); + + q_mul_fixed.clone() * (interpolated_x - x_p) + }); + + // Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed + meta.create_gate("fixed-base scalar mul (y)", |meta| { + let z = meta.query_fixed(self.fixed_z, Rotation::cur()); + let u = meta.query_advice(self.u, Rotation::cur()); + + q_mul_fixed * (u.clone() * u - y_p - z) + }); + } +} + +enum FixedBase { + FullWidth(OrchardFixedBase), + Short(OrchardFixedBaseShort), +} + +impl From<&OrchardFixedBase> for FixedBase { + fn from(fixed_base: &OrchardFixedBase) -> Self { + Self::FullWidth(fixed_base.clone()) + } +} + +impl From<&OrchardFixedBaseShort> for FixedBase { + fn from(fixed_base: &OrchardFixedBaseShort) -> Self { + Self::Short(fixed_base.clone()) + } +} + +impl FixedBase { + fn value(&self) -> C { + match self { + FixedBase::FullWidth(base) => match base.base { + OrchardFixedBases::CommitIvkR(inner) => inner.0.value(), + OrchardFixedBases::NoteCommitR(inner) => inner.0.value(), + OrchardFixedBases::NullifierK(inner) => inner.0.value(), + OrchardFixedBases::ValueCommitR(inner) => inner.0.value(), + }, + FixedBase::Short(base) => base.base.0 .0.value(), } + } - // Assign z-values for each window - region.assign_fixed( - || format!("z-value for window: {:?}", w), - config.fixed_z.into(), - w + offset, - || Ok(base.z.0[w]), - )?; + fn u(&self) -> Vec> { + match self { + FixedBase::FullWidth(base) => base.u.0.as_ref().to_vec(), + FixedBase::Short(base) => base.u_short.0.as_ref().to_vec(), + } } +} - // Copy the scalar decomposition - for (w, k) in scalar.k_bits.iter().enumerate() { - util::assign_and_constrain( - region, - || format!("k[{:?}]", w), - config.bits.into(), - w + offset, - k, - &config.perm, - )?; +enum ScalarFixed { + FullWidth(EccScalarFixed), + Short(EccScalarFixedShort), +} + +impl From<&EccScalarFixed> for ScalarFixed { + fn from(scalar_fixed: &EccScalarFixed) -> Self { + Self::FullWidth(scalar_fixed.clone()) + } +} + +impl From<&EccScalarFixedShort> for ScalarFixed { + fn from(scalar_fixed: &EccScalarFixedShort) -> Self { + Self::Short(scalar_fixed.clone()) } +} - // Get the value of the fixed base. `ValueCommitV` is excluded here - // since it is only used in multiplication with a short signed exponent. - let b = match base.base { - OrchardFixedBases::CommitIvkR(inner) => inner.0.value(), - OrchardFixedBases::NoteCommitR(inner) => inner.0.value(), - OrchardFixedBases::NullifierK(inner) => inner.0.value(), - OrchardFixedBases::ValueCommitR(inner) => inner.0.value(), - }; +impl ScalarFixed { + fn k_bits(&self) -> &[CellValue] { + match self { + ScalarFixed::FullWidth(scalar) => &scalar.k_bits, + ScalarFixed::Short(scalar) => &scalar.k_bits, + } + } // The scalar decomposition was done in the base field. For computation // outside the circuit, we now convert them back into the scalar field. - let k = scalar - .k_bits - .iter() - .map(|bits| { - bits.value - .map(|value| C::Scalar::from_bytes(&value.to_bytes()).unwrap()) - }) - .collect::>(); + fn k_field(&self) -> Vec> { + self.k_bits() + .iter() + .map(|bits| { + bits.value + .map(|value| C::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 - let k_usize = scalar - .k_bits - .iter() - .map(|bits| bits.value.map(|value| value.to_bytes()[0] as usize)) - .collect::>(); - - // This is 2^w, where w is the window width - let h = C::Scalar::from_u64(constants::H as u64); - - // Process the least significant window outside the for loop - let mul_b = k[0].map(|k_0| b * (k_0 + C::Scalar::one())); - let mul_b = witness_point::assign_region( - mul_b.map(|point| point.to_affine()), - 0, - region, - config.clone(), - )?; - - // Assign u = (y_p + z_w).sqrt() for the least significant window - { - let u_val = k_usize[0].map(|k_0| base.u.0[0].0[k_0]); - region.assign_advice( - || "u", - mul_fixed_u, - offset, - || u_val.ok_or(Error::SynthesisError), - )?; + fn k_usize(&self) -> Vec> { + self.k_bits() + .iter() + .map(|bits| bits.value.map(|value| value.to_bytes()[0] as usize)) + .collect::>() } +} - // Initialise the point which will cumulatively sum to [scalar]B. - // Copy and assign `mul_b` to x_a, y_a columns on the next row - let x_sum = util::assign_and_constrain( - region, - || "initialize sum x", - A.0.into(), - offset + 1, - &mul_b.x, - &config.perm, - )?; - let y_sum = util::assign_and_constrain( - region, - || "initialize sum y", - A.1.into(), - offset + 1, - &mul_b.y, - &config.perm, - )?; - - let mut sum = EccPoint { x: x_sum, y: y_sum }; - - // Process all windows excluding least and most significant windows - for (w, k) in k[1..(k.len() - 1)].iter().enumerate() { - // Offset index by 1 since we already assigned row 0 outside this loop - let w = w + 1; - - // Compute [(k_w + 1) ⋅ 8^w]B - let mul_b = k.map(|k| b * (k + C::Scalar::one()) * h.pow(&[w as u64, 0, 0, 0])); - let mul_b = witness_point::assign_region( +trait MulFixed { + const NUM_WINDOWS: usize; + + fn q_mul_fixed(&self) -> Selector; + fn lagrange_coeffs(&self) -> [Column; constants::H]; + fn fixed_z(&self) -> Column; + fn k(&self) -> Column; + fn u(&self) -> Column; + fn perm(&self) -> &Permutation; + fn witness_point_config(&self) -> &witness_point::Config; + fn add_incomplete_config(&self) -> &add_incomplete::Config; + + fn assign_region_inner( + &self, + region: &mut Region<'_, C::Base>, + offset: usize, + scalar: &ScalarFixed, + base: &FixedBase, + ) -> 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)?; + + // Initialize accumulator + let acc = self.initialize_accumulator(region, offset, base, scalar)?; + + // Process all windows excluding least and most significant windows + let acc = self.add_incomplete(region, offset, acc, base, scalar)?; + + // Process most significant window outside the for loop + let mul_b = self.process_msb(region, offset, base, scalar)?; + + Ok((acc, mul_b)) + } + + fn assign_fixed_constants( + &self, + region: &mut Region<'_, C::Base>, + offset: usize, + base: &FixedBase, + ) -> Result<(), Error> { + let (base_lagrange_coeffs, base_z) = match base { + FixedBase::FullWidth(base) => ( + base.lagrange_coeffs.0.as_ref().to_vec(), + base.z.0.as_ref().to_vec(), + ), + FixedBase::Short(base) => ( + base.lagrange_coeffs_short.0.as_ref().to_vec(), + base.z_short.0.as_ref().to_vec(), + ), + }; + // Assign fixed columns for given fixed base + for w in 0..Self::NUM_WINDOWS { + // Enable `q_mul_fixed` selector + self.q_mul_fixed().enable(region, w + offset)?; + + // Assign x-coordinate Lagrange interpolation coefficients + for k in 0..(constants::H) { + region.assign_fixed( + || { + format!( + "Lagrange interpolation coeff for window: {:?}, k: {:?}", + w, k + ) + }, + self.lagrange_coeffs()[k], + w + offset, + || Ok(base_lagrange_coeffs[w].0[k]), + )?; + } + + // Assign z-values for each window + region.assign_fixed( + || format!("z-value for window: {:?}", w), + self.fixed_z().into(), + w + offset, + || Ok(base_z[w]), + )?; + } + + Ok(()) + } + + fn copy_scalar( + &self, + region: &mut Region<'_, C::Base>, + offset: usize, + scalar: &ScalarFixed, + ) -> Result<(), Error> { + // Copy the scalar decomposition + for (w, k) in scalar.k_bits().iter().enumerate() { + util::assign_and_constrain( + region, + || format!("k[{:?}]", w), + self.k().into(), + w + offset, + k, + self.perm(), + )?; + } + + Ok(()) + } + + fn initialize_accumulator( + &self, + region: &mut Region<'_, C::Base>, + offset: usize, + base: &FixedBase, + scalar: &ScalarFixed, + ) -> Result, Error> { + // Process the least significant window outside the for loop + let mul_b = scalar.k_field()[0].map(|k_0| base.value() * (k_0 + C::Scalar::one())); + let mul_b = self.witness_point_config().assign_region( mul_b.map(|point| point.to_affine()), - offset + w, + 0, region, - config.clone(), )?; - // Assign u = (y_p + z_w).sqrt() - let u_val = k_usize[w].map(|k| base.u.0[w].0[k]); - region.assign_advice( - || "u", - mul_fixed_u, - w, - || u_val.ok_or(Error::SynthesisError), - )?; + // Assign u = (y_p + z_w).sqrt() for the least significant window + { + let u_val = scalar.k_usize()[0].map(|k_0| base.u()[0].0[k_0]); + region.assign_advice( + || "u", + self.u(), + offset, + || u_val.ok_or(Error::SynthesisError), + )?; + } - // Add to the cumulative sum - sum = add_incomplete::assign_region(&mul_b, &sum, offset + w, region, config.clone())?; - } - - // Process most significant window outside the for loop - let offset_sum = (0..(constants::NUM_WINDOWS - 1)).fold(C::ScalarExt::zero(), |acc, w| { - acc + h.pow(&[w as u64, 0, 0, 0]) - }); - - // `scalar = [k * 8^84 - offset_sum]`, where `offset_sum = \sum_{j = 0}^{83} 8^j`. - let scalar = k[k.len() - 1] - .map(|k| k * h.pow(&[(constants::NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_sum); - let mul_b = scalar.map(|scalar| b * scalar); - let mul_b = witness_point::assign_region( - mul_b.map(|point| point.to_affine()), - offset + constants::NUM_WINDOWS - 1, - region, - config.clone(), - )?; - - // Assign u = (y_p + z_w).sqrt() for the most significant window - { - let u_val = - k_usize[constants::NUM_WINDOWS - 1].map(|k| base.u.0[constants::NUM_WINDOWS - 1].0[k]); - region.assign_advice( - || "u", - mul_fixed_u, - offset + constants::NUM_WINDOWS - 1, - || u_val.ok_or(Error::SynthesisError), + // Initialise the point which will cumulatively sum to [scalar]B. + // Copy and assign `mul_b` to x_a, y_a columns on the next row + let x_sum = util::assign_and_constrain( + region, + || "initialize sum x", + self.add_incomplete_config().x_qr.into(), + offset + 1, + &mul_b.x, + self.perm(), )?; + let y_sum = util::assign_and_constrain( + region, + || "initialize sum y", + self.add_incomplete_config().y_qr.into(), + offset + 1, + &mul_b.y, + self.perm(), + )?; + + Ok(EccPoint { x: x_sum, y: y_sum }) } - // Add to the cumulative sum and return the final result as `[scalar]B`. - add_incomplete::assign_region( - &mul_b, - &sum, - offset + constants::NUM_WINDOWS - 1, - region, - config, - ) + fn add_incomplete( + &self, + region: &mut Region<'_, C::Base>, + offset: usize, + mut acc: EccPoint, + base: &FixedBase, + scalar: &ScalarFixed, + ) -> Result, Error> { + // This is 2^w, where w is the window width + let h = C::Scalar::from_u64(constants::H as u64); + + let base_value = base.value(); + let base_u = base.u(); + let scalar_k_field = scalar.k_field(); + let scalar_k_usize = scalar.k_usize(); + + for (w, k) in scalar_k_field[1..(scalar_k_field.len() - 1)] + .iter() + .enumerate() + { + // Offset index by 1 since we already assigned row 0 outside this loop + let w = w + 1; + + // Compute [(k_w + 1) ⋅ 8^w]B + let mul_b = + k.map(|k| base_value * (k + C::Scalar::one()) * h.pow(&[w as u64, 0, 0, 0])); + let mul_b = self.witness_point_config().assign_region( + mul_b.map(|point| point.to_affine()), + offset + w, + region, + )?; + + // Assign u = (y_p + z_w).sqrt() + let u_val = scalar_k_usize[w].map(|k| base_u[w].0[k]); + region.assign_advice(|| "u", self.u(), w, || u_val.ok_or(Error::SynthesisError))?; + + // Add to the accumulator + acc = self + .add_incomplete_config() + .assign_region(&mul_b, &acc, offset + w, region)?; + } + + Ok(acc) + } + + fn process_msb( + &self, + region: &mut Region<'_, C::Base>, + offset: usize, + base: &FixedBase, + scalar: &ScalarFixed, + ) -> Result, Error> { + // This is 2^w, where w is the window width + let h = C::Scalar::from_u64(constants::H as u64); + + let offset_acc = (0..(Self::NUM_WINDOWS - 1)).fold(C::ScalarExt::zero(), |acc, w| { + acc + h.pow(&[w as u64, 0, 0, 0]) + }); + + // Assign u = (y_p + z_w).sqrt() for the most significant window + { + let u_val = scalar.k_usize()[Self::NUM_WINDOWS - 1] + .map(|k| base.u()[Self::NUM_WINDOWS - 1].0[k]); + region.assign_advice( + || "u", + self.u(), + offset + Self::NUM_WINDOWS - 1, + || u_val.ok_or(Error::SynthesisError), + )?; + } + + // `scalar = [k * 8^84 - offset_acc]`, where `offset_acc = \sum_{j = 0}^{83} 8^j`. + let scalar = scalar.k_field()[scalar.k_field().len() - 1] + .map(|k| k * h.pow(&[(Self::NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_acc); + let mul_b = scalar.map(|scalar| base.value() * scalar); + self.witness_point_config().assign_region( + mul_b.map(|point| point.to_affine()), + offset + Self::NUM_WINDOWS - 1, + region, + ) + } } diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs new file mode 100644 index 000000000..957f567a4 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -0,0 +1,104 @@ +use super::super::{add_incomplete, witness_point, EccPoint, EccScalarFixed, OrchardFixedBase}; +use super::MulFixed; + +use crate::constants; +use std::marker::PhantomData; + +use halo2::{ + arithmetic::CurveAffine, + circuit::Region, + plonk::{Advice, Column, Error, Fixed, Permutation, Selector}, +}; + +pub struct Config { + q_mul_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`. + fixed_z: Column, + // k-bit decomposition of an `n-1`-bit scalar: + // a = a_0 + 2^k(a_1) + 2^{2k}(a_2) + ... + 2^{(n-1)k}(a_{n-1}) + k: Column, + // x-coordinate of the multiple of the fixed base at the current window. + x_p: Column, + // y-coordinate of the multiple of the fixed base at the current window. + y_p: Column, + // An integer `u` for the current window, s.t. `y + z = u^2`. + u: Column, + // Permutation + perm: Permutation, + // Configuration for `add_incomplete` + add_incomplete_config: add_incomplete::Config, + // Configuration for `witness_point` + witness_point_config: witness_point::Config, + _marker: PhantomData, +} + +impl From<&super::Config> for Config { + fn from(config: &super::Config) -> Self { + Self { + q_mul_fixed: config.q_mul_fixed, + lagrange_coeffs: config.lagrange_coeffs, + fixed_z: config.fixed_z, + k: config.k, + x_p: config.x_p, + y_p: config.y_p, + u: config.u, + perm: config.perm.clone(), + add_incomplete_config: config.add_incomplete_config.clone(), + witness_point_config: config.witness_point_config.clone(), + _marker: PhantomData, + } + } +} + +impl MulFixed for Config { + const NUM_WINDOWS: usize = constants::NUM_WINDOWS; + + fn q_mul_fixed(&self) -> Selector { + self.q_mul_fixed + } + fn lagrange_coeffs(&self) -> [Column; constants::H] { + self.lagrange_coeffs + } + fn fixed_z(&self) -> Column { + self.fixed_z + } + fn k(&self) -> Column { + self.k + } + fn u(&self) -> Column { + self.u + } + fn perm(&self) -> &Permutation { + &self.perm + } + fn witness_point_config(&self) -> &witness_point::Config { + &self.witness_point_config + } + fn add_incomplete_config(&self) -> &add_incomplete::Config { + &self.add_incomplete_config + } +} + +impl Config { + #[allow(non_snake_case)] + pub(super) fn assign_region( + &self, + region: &mut Region<'_, C::Base>, + offset: usize, + scalar: &EccScalarFixed, + base: &OrchardFixedBase, + ) -> Result, Error> { + let (acc, mul_b) = + self.assign_region_inner(region, offset, &scalar.into(), &base.into())?; + + // Add to the accumulator and return the final result as `[scalar]B`. + self.add_incomplete_config.assign_region( + &mul_b, + &acc, + offset + constants::NUM_WINDOWS - 1, + region, + ) + } +} diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs new file mode 100644 index 000000000..7e70963ab --- /dev/null +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -0,0 +1,171 @@ +use super::super::{ + add_incomplete, util, witness_point, EccPoint, EccScalarFixedShort, OrchardFixedBaseShort, +}; +use super::MulFixed; +use crate::constants; + +use group::Curve; +use halo2::{ + arithmetic::{CurveAffine, Field}, + circuit::Region, + plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Permutation, Selector}, + poly::Rotation, +}; +use std::marker::PhantomData; + +pub struct Config { + q_mul_fixed: Selector, + q_mul_fixed_short: 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`. + fixed_z: Column, + // k-bit decomposition of an `n-1`-bit scalar: + // a = a_0 + 2^k(a_1) + 2^{2k}(a_2) + ... + 2^{(n-1)k}(a_{n-1}) + k_s: Column, + // x-coordinate of the multiple of the fixed base at the current window. + x_p: Column, + // y-coordinate of the multiple of the fixed base at the current window. + y_p: Column, + // y-coordinate of accumulator (only used in the final row). + y_a: Column, + // An integer `u` for the current window, s.t. `y + z = u^2`. + u: Column, + // Permutation + perm: Permutation, + // Configuration for `add_incomplete` + add_incomplete_config: add_incomplete::Config, + // Configuration for `witness_point` + witness_point_config: witness_point::Config, + _marker: PhantomData, +} + +impl From<&super::Config> for Config { + fn from(config: &super::Config) -> Self { + Self { + q_mul_fixed: config.q_mul_fixed, + q_mul_fixed_short: config.q_mul_fixed_short, + lagrange_coeffs: config.lagrange_coeffs, + fixed_z: config.fixed_z, + k_s: config.k, + x_p: config.x_p, + y_p: config.y_p, + y_a: config.y_a, + u: config.u, + perm: config.perm.clone(), + add_incomplete_config: config.add_incomplete_config.clone(), + witness_point_config: config.witness_point_config.clone(), + _marker: PhantomData, + } + } +} + +impl MulFixed for Config { + const NUM_WINDOWS: usize = constants::NUM_WINDOWS_SHORT; + + fn q_mul_fixed(&self) -> Selector { + self.q_mul_fixed + } + fn lagrange_coeffs(&self) -> [Column; constants::H] { + self.lagrange_coeffs + } + fn fixed_z(&self) -> Column { + self.fixed_z + } + fn k(&self) -> Column { + self.k_s + } + fn u(&self) -> Column { + self.u + } + fn perm(&self) -> &Permutation { + &self.perm + } + fn witness_point_config(&self) -> &witness_point::Config { + &self.witness_point_config + } + fn add_incomplete_config(&self) -> &add_incomplete::Config { + &self.add_incomplete_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(super) fn create_gate(&self, meta: &mut ConstraintSystem) { + let q_mul_fixed_short = meta.query_selector(self.q_mul_fixed_short, Rotation::cur()); + let y_p = meta.query_advice(self.y_p, Rotation::cur()); + let y_a = meta.query_advice(self.y_a, Rotation::cur()); + + // `(x_a, y_a)` is the result of `[m]B`, where `m` is the magnitude. + // We conditionally negate this result using `y_p = y_a * s`, where `s` is the sign. + + // Check that the final `y_p = y_a` or `y_p = -y_a` + meta.create_gate("check y", |_| { + q_mul_fixed_short.clone() * (y_p.clone() - y_a.clone()) * (y_p.clone() + y_a.clone()) + }); + + // Check that s * y_p = y_a + meta.create_gate("check negation", |meta| { + let s = meta.query_advice(self.k_s, Rotation::cur()); + q_mul_fixed_short * (s * y_p - y_a) + }); + } + + #[allow(non_snake_case)] + pub(super) fn assign_region( + &self, + region: &mut Region<'_, C::Base>, + offset: usize, + scalar: &EccScalarFixedShort, + base: &OrchardFixedBaseShort, + ) -> Result, Error> { + let (acc, mul_b) = + self.assign_region_inner(region, offset, &scalar.into(), &base.into())?; + + // Add to the cumulative sum to get `[magnitude]B`. + let magnitude_mul = self.add_incomplete_config.assign_region( + &mul_b, + &acc, + offset + constants::NUM_WINDOWS_SHORT - 1, + region, + )?; + + // Assign sign to `bits` column + let sign = util::assign_and_constrain( + region, + || "sign", + self.k_s.into(), + offset + constants::NUM_WINDOWS_SHORT, + &scalar.sign, + &self.perm, + )?; + + // Conditionally negate `y`-coordinate + let y_val = match sign.value { + Some(sign) => { + if sign == -C::Base::one() { + magnitude_mul.y.value.map(|y: C::Base| -y) + } else { + magnitude_mul.y.value + } + } + None => None, + }; + + // Enable mul_fixed_short selector on final row + self.q_mul_fixed_short + .enable(region, offset + constants::NUM_WINDOWS_SHORT)?; + + // Assign final `x, y` to `x_p, y_p` columns and return final point + let x_val = magnitude_mul.x.value; + let mul = x_val + .zip(y_val) + .map(|(x, y)| C::from_xy(x, y).unwrap().to_curve()); + self.witness_point_config.assign_region( + mul.map(|point| point.to_affine()), + offset + constants::NUM_WINDOWS_SHORT, + region, + ) + } +} diff --git a/src/circuit/gadget/ecc/chip/mul_fixed_short.rs b/src/circuit/gadget/ecc/chip/mul_fixed_short.rs deleted file mode 100644 index d2d4cb7ae..000000000 --- a/src/circuit/gadget/ecc/chip/mul_fixed_short.rs +++ /dev/null @@ -1,259 +0,0 @@ -use super::{ - add_incomplete, util, witness_point, EccConfig, EccPoint, EccScalarFixedShort, - OrchardFixedBaseShort, -}; -use crate::constants; - -use group::Curve; -use halo2::{ - arithmetic::{CurveAffine, Field, FieldExt}, - circuit::Region, - plonk::{ConstraintSystem, Error, Expression}, -}; - -// 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(super) fn create_gate( - meta: &mut ConstraintSystem, - q_mul_fixed_short: Expression, - s: Expression, - y_a: Expression, - y_p: Expression, -) { - // `(x_a, y_a)` is the result of `[m]B`, where `m` is the magnitude. - // We conditionally negate this result using `y_p = y_a * s`, where `s` is the sign. - - // Check that the final `y_p = y_a` or `y_p = -y_a` - meta.create_gate("check y", |_| { - q_mul_fixed_short.clone() * (y_p.clone() - y_a.clone()) * (y_p.clone() + y_a.clone()) - }); - - // Check that s * y_p = y_a - meta.create_gate("check negation", |_| q_mul_fixed_short * (s * y_p - y_a)); -} - -#[allow(non_snake_case)] -pub(super) fn assign_region( - scalar: &EccScalarFixedShort, - base: &OrchardFixedBaseShort, - offset: usize, - region: &mut Region<'_, C::Base>, - config: EccConfig, -) -> Result, Error> { - // Rename columns for `mul_fixed` context - let A = (config.extras[0], config.extras[1]); - let mul_fixed_u = config.extras[2]; - - // Assign fixed columns for given fixed base - for w in 0..constants::NUM_WINDOWS_SHORT { - // Enable `q_mul_fixed` selector - config.q_mul_fixed.enable(region, w + offset)?; - - // Assign x-coordinate Lagrange interpolation coefficients - for k in 0..(constants::H) { - region.assign_fixed( - || { - format!( - "Lagrange interpolation coeff for window: {:?}, k: {:?}", - w, k - ) - }, - config.lagrange_coeffs[k], - w + offset, - || Ok(base.lagrange_coeffs_short.0[w].0[k]), - )?; - } - - // Assign z-values for each window - region.assign_fixed( - || format!("z-value for window: {:?}", w), - config.fixed_z.into(), - w + offset, - || Ok(base.z_short.0[w]), - )?; - } - - // Copy the scalar decomposition - for (w, k) in scalar.k_bits.iter().enumerate() { - util::assign_and_constrain( - region, - || format!("k[{:?}]", w), - config.bits.into(), - w + offset, - k, - &config.perm, - )?; - } - - // Get the value of the fixed base. We only use `ValueCommitV` here. - let b = base.base.0 .0.value(); - - // The scalar decomposition was done in the base field. For computation - // outside the circuit, we now convert them back into the scalar field. - let k = scalar - .k_bits - .iter() - .map(|bits| { - bits.value - .map(|value| C::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 - let k_usize = scalar - .k_bits - .iter() - .map(|bits| bits.value.map(|value| value.to_bytes()[0] as usize)) - .collect::>(); - - // This is 2^w, where w is the window width - let h = C::Scalar::from_u64(constants::H as u64); - - // Process the least significant window outside the for loop - let mul_b = k[0].map(|k_0| b * (k_0 + C::Scalar::one())); - let mul_b = witness_point::assign_region( - mul_b.map(|point| point.to_affine()), - 0, - region, - config.clone(), - )?; - - // Assign u = (y_p + z_w).sqrt() for the least significant window - { - let u_val = k_usize[0].map(|k_0| base.u_short.0[0].0[k_0]); - region.assign_advice( - || "u", - mul_fixed_u, - offset, - || u_val.ok_or(Error::SynthesisError), - )?; - } - - // Initialise the point which will cumulatively sum to [scalar]B. - // Copy and assign `mul_b` to x_a, y_a columns on the next row - let x_sum = util::assign_and_constrain( - region, - || "initialize sum x", - A.0.into(), - offset + 1, - &mul_b.x, - &config.perm, - )?; - let y_sum = util::assign_and_constrain( - region, - || "initialize sum y", - A.1.into(), - offset + 1, - &mul_b.y, - &config.perm, - )?; - - let mut sum = EccPoint { x: x_sum, y: y_sum }; - - // Process all windows excluding least and most significant windows - for (w, k) in k[1..(k.len() - 1)].iter().enumerate() { - // Offset index by 1 since we already assigned row 0 outside this loop - let w = w + 1; - - // Compute [(k_w + 1) ⋅ 8^w]B - let mul_b = k.map(|k| b * (k + C::Scalar::one()) * h.pow(&[w as u64, 0, 0, 0])); - let mul_b = witness_point::assign_region( - mul_b.map(|point| point.to_affine()), - offset + w, - region, - config.clone(), - )?; - - // Assign u = (y_p + z_w).sqrt() - let u_val = k_usize[w].map(|k| base.u_short.0[w].0[k]); - region.assign_advice( - || "u", - mul_fixed_u, - w, - || u_val.ok_or(Error::SynthesisError), - )?; - - // Add to the cumulative sum - sum = add_incomplete::assign_region(&mul_b, &sum, offset + w, region, config.clone()) - .unwrap(); - } - - // Process most significant window outside the for loop - let offset_sum = (0..(constants::NUM_WINDOWS_SHORT - 1)) - .fold(C::ScalarExt::zero(), |acc, w| { - acc + h.pow(&[w as u64, 0, 0, 0]) - }); - - // `scalar = [k * 8^21 - offset_sum]`, where `offset_sum = \sum_{j = 0}^{20} 8^j`. - let last_scalar = k[k.len() - 1] - .map(|k| k * h.pow(&[(constants::NUM_WINDOWS_SHORT - 1) as u64, 0, 0, 0]) - offset_sum); - let mul_b = last_scalar.map(|last_scalar| b * last_scalar); - let mul_b = witness_point::assign_region( - mul_b.map(|point| point.to_affine()), - offset + constants::NUM_WINDOWS_SHORT - 1, - region, - config.clone(), - )?; - - // Assign u = (y_p + z_w).sqrt() for the most significant window - { - let u_val = k_usize[constants::NUM_WINDOWS_SHORT - 1] - .map(|k| base.u_short.0[constants::NUM_WINDOWS_SHORT - 1].0[k]); - region.assign_advice( - || "u", - mul_fixed_u, - offset + constants::NUM_WINDOWS_SHORT - 1, - || u_val.ok_or(Error::SynthesisError), - )?; - } - - // Add to the cumulative sum to get `[magnitude]B`. - let magnitude_mul = add_incomplete::assign_region( - &mul_b, - &sum, - offset + constants::NUM_WINDOWS_SHORT - 1, - region, - config.clone(), - )?; - - // Assign sign to `bits` column - let sign = util::assign_and_constrain( - region, - || "sign", - config.bits.into(), - offset + constants::NUM_WINDOWS_SHORT, - &scalar.sign, - &config.perm, - )?; - - // Conditionally negate `y`-coordinate - let y_val = match sign.value { - Some(sign) => { - if sign == -C::Base::one() { - magnitude_mul.y.value.map(|y: C::Base| -y) - } else { - magnitude_mul.y.value - } - } - None => None, - }; - - // Enable mul_fixed_short selector on final row - config - .q_mul_fixed_short - .enable(region, offset + constants::NUM_WINDOWS_SHORT)?; - - // Assign final `x, y` to `x_p, y_p` columns and return final point - let x_val = magnitude_mul.x.value; - let mul = x_val - .zip(y_val) - .map(|(x, y)| C::from_xy(x, y).unwrap().to_curve()); - witness_point::assign_region( - mul.map(|point| point.to_affine()), - offset + constants::NUM_WINDOWS_SHORT, - region, - config, - ) -} diff --git a/src/circuit/gadget/ecc/chip/witness_point.rs b/src/circuit/gadget/ecc/chip/witness_point.rs index 43c7a0f27..b0b6671ac 100644 --- a/src/circuit/gadget/ecc/chip/witness_point.rs +++ b/src/circuit/gadget/ecc/chip/witness_point.rs @@ -3,53 +3,73 @@ use super::{CellValue, EccConfig, EccPoint}; use halo2::{ arithmetic::CurveAffine, circuit::Region, - plonk::{ConstraintSystem, Error, Expression}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, + poly::Rotation, }; -pub(super) fn create_gate( - meta: &mut ConstraintSystem, - q_point: Expression, - x_p: Expression, - y_p: Expression, -) { - meta.create_gate("witness point", |_| { - // Check that y^2 = x^3 + b, where b = 5 in the Pallas equation - q_point - * (y_p.clone() * y_p - (x_p.clone() * x_p.clone() * x_p) - Expression::Constant(C::b())) - }); +#[derive(Clone, Debug)] +pub struct Config { + q_point: Selector, + // x-coordinate + x: Column, + // y-coordinate + y: Column, } -pub(super) fn assign_region( - value: Option, - offset: usize, - region: &mut Region<'_, C::Base>, - config: EccConfig, -) -> Result, Error> { - // Enable `q_point` selector - config.q_point.enable(region, offset)?; - - let value = value.map(|value| value.coordinates().unwrap()); - - // Assign `x_p` value - let x_p_val = value.map(|value| *value.x()); - let x_p_var = region.assign_advice( - || "x_p", - config.P.0, - offset, - || x_p_val.ok_or(Error::SynthesisError), - )?; - - // Assign `y_p` value - let y_p_val = value.map(|value| *value.y()); - let y_p_var = region.assign_advice( - || "y_p", - config.P.1, - offset, - || y_p_val.ok_or(Error::SynthesisError), - )?; - - Ok(EccPoint { - x: CellValue::::new(x_p_var, x_p_val), - y: CellValue::::new(y_p_var, y_p_val), - }) +impl From<&EccConfig> for Config { + fn from(ecc_config: &EccConfig) -> Self { + Self { + q_point: ecc_config.q_point, + x: ecc_config.P.0, + y: ecc_config.P.1, + } + } +} + +impl Config { + pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { + meta.create_gate("witness point", |meta| { + let q_point = meta.query_selector(self.q_point, Rotation::cur()); + let x = meta.query_advice(self.x, Rotation::cur()); + let y = meta.query_advice(self.y, Rotation::cur()); + + // Check that y^2 = x^3 + b, where b = 5 in the Pallas equation + q_point * (y.clone() * y - (x.clone() * x.clone() * x) - Expression::Constant(C::b())) + }); + } + + pub(super) fn assign_region( + &self, + value: Option, + offset: usize, + region: &mut Region<'_, C::Base>, + ) -> Result, Error> { + // Enable `q_point` selector + self.q_point.enable(region, offset)?; + + let value = value.map(|value| value.coordinates().unwrap()); + + // Assign `x` value + let x_val = value.map(|value| *value.x()); + let x_var = region.assign_advice( + || "x", + self.x, + offset, + || x_val.ok_or(Error::SynthesisError), + )?; + + // Assign `y` value + let y_val = value.map(|value| *value.y()); + let y_var = region.assign_advice( + || "y", + self.y, + offset, + || y_val.ok_or(Error::SynthesisError), + )?; + + Ok(EccPoint { + x: CellValue::::new(x_var, x_val), + y: CellValue::::new(y_var, y_val), + }) + } } diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs index 9ea33c9b5..30a89acc5 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs @@ -1,55 +1,124 @@ -use super::{CellValue, EccConfig, EccScalarFixed}; +use super::{CellValue, EccConfig, EccScalarFixed, EccScalarFixedShort}; use crate::constants::{self, util}; use halo2::{ arithmetic::{CurveAffine, FieldExt}, circuit::Region, - plonk::{ConstraintSystem, Error, Expression}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, + poly::Rotation, }; -pub(super) fn create_gate( - meta: &mut ConstraintSystem, - q_scalar_fixed: Expression, - k: Expression, -) { - meta.create_gate("witness scalar fixed", |_| { +mod full_width; +mod short; + +pub(super) struct Config { + q_scalar_fixed: Selector, + q_scalar_fixed_short: Selector, + // k-bit decomposition of scalar. + k_s: Column, +} + +impl From<&EccConfig> for Config { + fn from(ecc_config: &EccConfig) -> Self { + Self { + q_scalar_fixed: ecc_config.q_scalar_fixed, + q_scalar_fixed_short: ecc_config.q_scalar_fixed_short, + k_s: ecc_config.bits, + } + } +} + +impl Config { + pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { + // Range check gate applies to both full-width and short scalars + self.range_check_gate(meta); + + // Gate for short scalar + let short_config: short::Config = self.into(); + short_config.sign_check_gate(meta); + } + + pub(super) fn assign_region_full( + &self, + value: Option, + offset: usize, + region: &mut Region<'_, C::Base>, + ) -> Result, Error> { + let full_width_config: full_width::Config = self.into(); + full_width_config.assign_region(value, offset, region) + } + + pub(super) fn assign_region_short( + &self, + value: Option, + offset: usize, + region: &mut Region<'_, C::Base>, + ) -> Result, Error> { + let short_config: short::Config = self.into(); + short_config.assign_region(value, offset, region) + } + + fn range_check_gate(&self, meta: &mut ConstraintSystem) { // Check that `k` is within the allowed window size - let range_check = (0..constants::H).fold(Expression::Constant(F::one()), |acc, i| { - acc * (k.clone() - Expression::Constant(F::from_u64(i as u64))) + meta.create_gate("witness scalar fixed", |meta| { + let q_scalar_fixed = meta.query_selector(self.q_scalar_fixed, Rotation::cur()); + let k = meta.query_advice(self.k_s, Rotation::cur()); + + let range_check = (0..constants::H).fold(Expression::Constant(F::one()), |acc, i| { + acc * (k.clone() - Expression::Constant(F::from_u64(i as u64))) + }); + q_scalar_fixed * range_check }); - q_scalar_fixed * range_check - }); + } } -pub(super) fn assign_region( - value: Option, - scalar_num_bits: usize, - offset: usize, - region: &mut Region<'_, C::Base>, - config: EccConfig, -) -> Result, Error> { - // Decompose scalar into windows - let bits: Option> = value.map(|value| { - util::decompose_scalar_fixed::(value, scalar_num_bits, constants::FIXED_BASE_WINDOW_SIZE) - }); - - // Store the scalar decomposition - let mut k_bits: Vec> = Vec::new(); - - if let Some(bits) = bits { - for (idx, window) in bits.iter().enumerate() { - // Enable `q_scalar_fixed` selector - config.q_scalar_fixed.enable(region, offset + idx)?; - - let window = C::Base::from_u64(*window as u64); - let k_var = region.assign_advice( - || format!("k[{:?}]", offset + idx), - config.bits, - offset + idx, - || Ok(window), - )?; - k_bits.push(CellValue::new(k_var, Some(window))); +trait WitnessScalarFixed { + const SCALAR_NUM_BITS: usize; + type Scalar: Clone + std::fmt::Debug; + + fn q_scalar_fixed(&self) -> Selector; + fn k(&self) -> Column; + + fn assign_region( + &self, + value: Option, + offset: usize, + region: &mut Region<'_, C::Base>, + ) -> Result; + + fn decompose_scalar_fixed( + &self, + value: Option, + offset: usize, + region: &mut Region<'_, C::Base>, + ) -> Result>, Error> { + // Decompose scalar into windows + let bits: Option> = value.map(|value| { + util::decompose_scalar_fixed::( + value, + Self::SCALAR_NUM_BITS, + constants::FIXED_BASE_WINDOW_SIZE, + ) + }); + + // Store the scalar decomposition + let mut k_bits: Vec> = Vec::new(); + + if let Some(bits) = bits { + for (idx, window) in bits.iter().enumerate() { + // Enable `q_scalar_fixed` selector + self.q_scalar_fixed().enable(region, offset + idx)?; + + let window = C::Base::from_u64(*window as u64); + let k_var = region.assign_advice( + || format!("k[{:?}]", offset + idx), + self.k(), + offset + idx, + || Ok(window), + )?; + k_bits.push(CellValue::new(k_var, Some(window))); + } } - } - Ok(EccScalarFixed { value, k_bits }) + Ok(k_bits) + } } 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 000000000..a03f8c217 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/full_width.rs @@ -0,0 +1,48 @@ +use super::super::EccScalarFixed; +use ff::PrimeField; +use halo2::{ + arithmetic::CurveAffine, + circuit::Region, + plonk::{Advice, Column, Error, Selector}, +}; +use std::marker::PhantomData; + +pub(super) struct Config { + pub q_scalar_fixed: Selector, + // k-bit decomposition of scalar + pub k: Column, + _marker: PhantomData, +} + +impl From<&super::Config> for Config { + fn from(config: &super::Config) -> Self { + Self { + q_scalar_fixed: config.q_scalar_fixed, + k: config.k_s, + _marker: PhantomData, + } + } +} + +impl super::WitnessScalarFixed for Config { + const SCALAR_NUM_BITS: usize = C::Scalar::NUM_BITS as usize; + type Scalar = EccScalarFixed; + + fn q_scalar_fixed(&self) -> Selector { + self.q_scalar_fixed + } + fn k(&self) -> Column { + self.k + } + + fn assign_region( + &self, + value: Option, + offset: usize, + region: &mut Region<'_, C::Base>, + ) -> Result, Error> { + let k_bits = self.decompose_scalar_fixed(value, offset, region)?; + + Ok(EccScalarFixed { value, k_bits }) + } +} 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 000000000..c41024aa6 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs @@ -0,0 +1,103 @@ +use super::super::{CellValue, EccScalarFixedShort}; +use crate::constants; +use halo2::{ + arithmetic::{CurveAffine, Field, FieldExt}, + circuit::Region, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, + poly::Rotation, +}; +use std::marker::PhantomData; + +pub(super) struct Config { + q_scalar_fixed: Selector, + q_scalar_fixed_short: Selector, + // k-bit decomposition of scalar. Also used to witness the sign `s` + // in the last row. + k_s: Column, + _marker: PhantomData, +} + +impl From<&super::Config> for Config { + fn from(config: &super::Config) -> Self { + Self { + q_scalar_fixed: config.q_scalar_fixed, + q_scalar_fixed_short: config.q_scalar_fixed_short, + k_s: config.k_s, + _marker: PhantomData, + } + } +} + +impl Config { + pub(super) fn sign_check_gate(&self, meta: &mut ConstraintSystem) { + // Check that sign s \in {1, -1} + meta.create_gate("check sign", |meta| { + let q_scalar_fixed_short = + meta.query_selector(self.q_scalar_fixed_short, Rotation::cur()); + let s = meta.query_advice(self.k_s, Rotation::cur()); + + q_scalar_fixed_short + * (s.clone() + Expression::Constant(C::Base::one())) + * (s - Expression::Constant(C::Base::one())) + }); + } +} + +impl super::WitnessScalarFixed for Config { + const SCALAR_NUM_BITS: usize = constants::L_VALUE as usize; + type Scalar = EccScalarFixedShort; + + fn q_scalar_fixed(&self) -> Selector { + self.q_scalar_fixed + } + fn k(&self) -> Column { + self.k_s + } + + fn assign_region( + &self, + value: Option, + offset: usize, + region: &mut Region<'_, C::Base>, + ) -> Result, Error> { + // Compute the scalar's sign and magnitude + let sign = value.map(|value| { + // t = (p - 1)/2 + let t = (C::Scalar::zero() - C::Scalar::one()) * C::Scalar::TWO_INV; + if value > t { + -C::Scalar::one() + } else { + C::Scalar::one() + } + }); + + let magnitude = sign.zip(value).map(|(sign, value)| sign * value); + + // Decompose magnitude into `k`-bit windows + let k_bits = self.decompose_scalar_fixed(magnitude, offset, region)?; + + // Assign the sign and enable `q_scalar_fixed_short` + let sign = sign.map(|sign| { + assert!(sign == C::Scalar::one() || sign == -C::Scalar::one()); + if sign == C::Scalar::one() { + C::Base::one() + } else { + -C::Base::one() + } + }); + let sign_cell = region.assign_advice( + || "sign", + self.k_s, + offset + k_bits.len(), + || sign.ok_or(Error::SynthesisError), + )?; + self.q_scalar_fixed_short + .enable(region, offset + k_bits.len())?; + + Ok(EccScalarFixedShort { + magnitude, + sign: CellValue::::new(sign_cell, sign), + k_bits, + }) + } +} 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 e0026b2c7..000000000 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed_short.rs +++ /dev/null @@ -1,92 +0,0 @@ -use super::{CellValue, EccConfig, EccScalarFixedShort}; -use crate::constants::{self, util}; -use halo2::{ - arithmetic::{CurveAffine, Field, FieldExt}, - circuit::Region, - plonk::{ConstraintSystem, Error, Expression}, -}; - -pub(super) fn create_gate( - meta: &mut ConstraintSystem, - q_scalar_fixed_short: Expression, - s: Expression, -) { - // Check that s \in {1, -1} - meta.create_gate("check sign", |_| { - q_scalar_fixed_short - * (s.clone() + Expression::Constant(F::one())) - * (s - Expression::Constant(F::one())) - }); -} - -pub(super) fn assign_region( - value: Option, - offset: usize, - region: &mut Region<'_, C::Base>, - config: EccConfig, -) -> Result, Error> { - // Compute the scalar's sign and magnitude - let sign = value.map(|value| { - // t = (p - 1)/2 - let t = (C::Scalar::zero() - C::Scalar::one()) * C::Scalar::TWO_INV; - if value > t { - -C::Scalar::one() - } else { - C::Scalar::one() - } - }); - let magnitude = sign.zip(value).map(|(sign, value)| sign * value); - - // Decompose magnitude into windows - let bits: Option> = magnitude.map(|magnitude| { - util::decompose_scalar_fixed::( - magnitude, - constants::L_VALUE, - constants::FIXED_BASE_WINDOW_SIZE, - ) - }); - - // Assign and store the magnitude decomposition - let mut k_bits: Vec> = Vec::new(); - - if let Some(bits) = bits { - for (idx, window) in bits.iter().enumerate() { - // Enable `q_scalar_fixed` selector - config.q_scalar_fixed.enable(region, offset + idx)?; - - let window = C::Base::from_u64(*window as u64); - let k_var = region.assign_advice( - || format!("k[{:?}]", offset + idx), - config.bits, - offset + idx, - || Ok(window), - )?; - k_bits.push(CellValue::new(k_var, Some(window))); - } - } - - // Assign the sign and enable `q_scalar_fixed_short` - let sign = sign.map(|sign| { - assert!(sign == C::Scalar::one() || sign == -C::Scalar::one()); - if sign == C::Scalar::one() { - C::Base::one() - } else { - -C::Base::one() - } - }); - let sign_cell = region.assign_advice( - || "sign", - config.bits, - offset + k_bits.len(), - || sign.ok_or(Error::SynthesisError), - )?; - config - .q_scalar_fixed_short - .enable(region, offset + k_bits.len())?; - - Ok(EccScalarFixedShort { - magnitude, - sign: CellValue::::new(sign_cell, sign), - k_bits, - }) -} From a236f635f620854f6992d4054e7f1e673fe62311 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 24 May 2021 20:14:57 +0800 Subject: [PATCH 22/49] Use complete addition for last addition in fixed-base scalar mul This allows fixed-base scalar mul to return (0,0) for [0]B. --- src/circuit/gadget/ecc.rs | 25 +++-- src/circuit/gadget/ecc/chip/add.rs | 3 +- src/circuit/gadget/ecc/chip/mul_fixed.rs | 106 ++++++++++-------- .../gadget/ecc/chip/mul_fixed/full_width.rs | 24 ++-- .../gadget/ecc/chip/mul_fixed/short.rs | 46 ++++++-- 5 files changed, 130 insertions(+), 74 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 24267c0ea..ece507412 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -554,17 +554,19 @@ mod tests { assert_eq!(real_mul_fixed.to_affine(), C::from_xy(x, y).unwrap()); } - // [0]B should return an error since fixed-base scalar multiplication - // uses incomplete addition internally. + // [0]B should return (0,0) since it uses complete addition + // on the last step. let scalar_fixed = C::Scalar::zero(); let scalar_fixed = super::ScalarFixed::new( chip.clone(), layouter.namespace(|| "ScalarFixed"), Some(scalar_fixed), )?; - nullifier_k - .mul(layouter.namespace(|| "mul"), &scalar_fixed) - .expect_err("[0]B should return an error"); + let mul_fixed = nullifier_k.mul(layouter.namespace(|| "mul"), &scalar_fixed)?; + if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { + assert_eq!(C::Base::zero(), x); + assert_eq!(C::Base::zero(), y); + } } // Check short signed fixed-base scalar multiplication @@ -575,8 +577,8 @@ mod tests { OrchardFixedBasesShort(value_commit_v_inner), )?; - // [0]B should return an error since fixed-base scalar multiplication - // uses incomplete addition internally. + // [0]B should return (0,0) since it uses complete addition + // on the last step. { let scalar_fixed = C::Scalar::zero(); let scalar_fixed = super::ScalarFixedShort::new( @@ -584,9 +586,12 @@ mod tests { layouter.namespace(|| "ScalarFixedShort"), Some(scalar_fixed), )?; - value_commit_v - .mul(layouter.namespace(|| "mul"), &scalar_fixed) - .expect_err("[0]B should return an error"); + let mul_fixed = + value_commit_v.mul(layouter.namespace(|| "mul"), &scalar_fixed)?; + if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { + assert_eq!(C::Base::zero(), x); + assert_eq!(C::Base::zero(), y); + } } let mut checks = Vec::<(C::CurveExt, super::Point>)>::new(); diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 23c29e6bf..4636e6cd2 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -8,6 +8,7 @@ use halo2::{ poly::Rotation, }; +#[derive(Clone, Debug)] pub struct Config { q_add: Selector, // lambda @@ -17,7 +18,7 @@ pub struct Config { // y-coordinate of P in P + Q = R pub y_p: Column, // x-coordinate of Q or R in P + Q = R - x_qr: Column, + pub x_qr: Column, // y-coordinate of Q or R in P + Q = R pub y_qr: Column, // a or alpha diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index f256db2a5..dc5837b5e 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -1,5 +1,5 @@ use super::{ - add_incomplete, load::WindowUs, util, witness_point, CellValue, EccConfig, EccPoint, + add, add_incomplete, load::WindowUs, util, witness_point, CellValue, EccConfig, EccPoint, EccScalarFixed, EccScalarFixedShort, OrchardFixedBase, OrchardFixedBaseShort, OrchardFixedBases, }; @@ -36,6 +36,8 @@ pub struct Config { u: Column, // Permutation perm: Permutation, + // Configuration for `add` + add_config: add::Config, // Configuration for `add_incomplete` add_incomplete_config: add_incomplete::Config, // Configuration for `witness_point` @@ -55,6 +57,7 @@ impl From<&EccConfig> for Config { y_a: ecc_config.extras[1], u: ecc_config.extras[2], perm: ecc_config.perm.clone(), + add_config: ecc_config.into(), add_incomplete_config: ecc_config.into(), witness_point_config: ecc_config.into(), } @@ -217,8 +220,11 @@ trait MulFixed { fn fixed_z(&self) -> Column; fn k(&self) -> Column; fn u(&self) -> Column; + fn x_p(&self) -> Column; + fn y_p(&self) -> Column; fn perm(&self) -> &Permutation; fn witness_point_config(&self) -> &witness_point::Config; + fn add_config(&self) -> &add::Config; fn add_incomplete_config(&self) -> &add_incomplete::Config; fn assign_region_inner( @@ -240,7 +246,7 @@ trait MulFixed { // Process all windows excluding least and most significant windows let acc = self.add_incomplete(region, offset, acc, base, scalar)?; - // Process most significant window outside the for loop + // Process most significant window using complete addition let mul_b = self.process_msb(region, offset, base, scalar)?; Ok((acc, mul_b)) @@ -262,10 +268,11 @@ trait MulFixed { base.z_short.0.as_ref().to_vec(), ), }; + // Assign fixed columns for given fixed base - for w in 0..Self::NUM_WINDOWS { + for window in 0..Self::NUM_WINDOWS { // Enable `q_mul_fixed` selector - self.q_mul_fixed().enable(region, w + offset)?; + self.q_mul_fixed().enable(region, window + offset)?; // Assign x-coordinate Lagrange interpolation coefficients for k in 0..(constants::H) { @@ -273,21 +280,21 @@ trait MulFixed { || { format!( "Lagrange interpolation coeff for window: {:?}, k: {:?}", - w, k + window, k ) }, self.lagrange_coeffs()[k], - w + offset, - || Ok(base_lagrange_coeffs[w].0[k]), + window + offset, + || Ok(base_lagrange_coeffs[window].0[k]), )?; } // Assign z-values for each window region.assign_fixed( - || format!("z-value for window: {:?}", w), + || format!("z-value for window: {:?}", window), self.fixed_z().into(), - w + offset, - || Ok(base_z[w]), + window + offset, + || Ok(base_z[window]), )?; } @@ -301,12 +308,12 @@ trait MulFixed { scalar: &ScalarFixed, ) -> Result<(), Error> { // Copy the scalar decomposition - for (w, k) in scalar.k_bits().iter().enumerate() { + for (window, k) in scalar.k_bits().iter().enumerate() { util::assign_and_constrain( region, - || format!("k[{:?}]", w), + || format!("k[{:?}]", window), self.k().into(), - w + offset, + window + offset, k, self.perm(), )?; @@ -322,45 +329,43 @@ trait MulFixed { base: &FixedBase, scalar: &ScalarFixed, ) -> Result, Error> { - // Process the least significant window outside the for loop - let mul_b = scalar.k_field()[0].map(|k_0| base.value() * (k_0 + C::Scalar::one())); - let mul_b = self.witness_point_config().assign_region( - mul_b.map(|point| point.to_affine()), - 0, + // Witness `m0` in `x_p`, `y_p` cells on row 0 + let k0 = scalar.k_field()[0]; + let m0 = k0.map(|k0| base.value() * (k0 + C::Scalar::from_u64(2))); + let m0 = self.witness_point_config().assign_region( + m0.map(|point| point.to_affine()), + offset, region, )?; - // Assign u = (y_p + z_w).sqrt() for the least significant window + // Assign u = (y_p + z_w).sqrt() for `m0` { - let u_val = scalar.k_usize()[0].map(|k_0| base.u()[0].0[k_0]); - region.assign_advice( - || "u", - self.u(), - offset, - || u_val.ok_or(Error::SynthesisError), - )?; + let k0 = scalar.k_usize()[0]; + let u0 = &base.u()[0]; + let u0 = k0.map(|k0| u0.0[k0]); + + region.assign_advice(|| "u", self.u(), offset, || u0.ok_or(Error::SynthesisError))?; } - // Initialise the point which will cumulatively sum to [scalar]B. - // Copy and assign `mul_b` to x_a, y_a columns on the next row - let x_sum = util::assign_and_constrain( + // Copy `m0` into `x_qr`, `y_qr` cells on row 1 + let x = util::assign_and_constrain( region, - || "initialize sum x", + || "initialize acc x", self.add_incomplete_config().x_qr.into(), offset + 1, - &mul_b.x, + &m0.x, self.perm(), )?; - let y_sum = util::assign_and_constrain( + let y = util::assign_and_constrain( region, - || "initialize sum y", + || "initialize acc y", self.add_incomplete_config().y_qr.into(), offset + 1, - &mul_b.y, + &m0.y, self.perm(), )?; - Ok(EccPoint { x: x_sum, y: y_sum }) + Ok(EccPoint { x, y }) } fn add_incomplete( @@ -383,12 +388,12 @@ trait MulFixed { .iter() .enumerate() { - // Offset index by 1 since we already assigned row 0 outside this loop + // Offset window index by 1 since we are starting on k_1 let w = w + 1; - // Compute [(k_w + 1) ⋅ 8^w]B + // Compute [(k_w + 2) ⋅ 8^w]B let mul_b = - k.map(|k| base_value * (k + C::Scalar::one()) * h.pow(&[w as u64, 0, 0, 0])); + k.map(|k| base_value * (k + C::Scalar::from_u64(2)) * h.pow(&[w as u64, 0, 0, 0])); let mul_b = self.witness_point_config().assign_region( mul_b.map(|point| point.to_affine()), offset + w, @@ -397,14 +402,18 @@ trait MulFixed { // Assign u = (y_p + z_w).sqrt() let u_val = scalar_k_usize[w].map(|k| base_u[w].0[k]); - region.assign_advice(|| "u", self.u(), w, || u_val.ok_or(Error::SynthesisError))?; + region.assign_advice( + || "u", + self.u(), + offset + w, + || u_val.ok_or(Error::SynthesisError), + )?; // Add to the accumulator acc = self .add_incomplete_config() .assign_region(&mul_b, &acc, offset + w, region)?; } - Ok(acc) } @@ -418,10 +427,6 @@ trait MulFixed { // This is 2^w, where w is the window width let h = C::Scalar::from_u64(constants::H as u64); - let offset_acc = (0..(Self::NUM_WINDOWS - 1)).fold(C::ScalarExt::zero(), |acc, w| { - acc + h.pow(&[w as u64, 0, 0, 0]) - }); - // Assign u = (y_p + z_w).sqrt() for the most significant window { let u_val = scalar.k_usize()[Self::NUM_WINDOWS - 1] @@ -434,9 +439,20 @@ trait MulFixed { )?; } - // `scalar = [k * 8^84 - offset_acc]`, where `offset_acc = \sum_{j = 0}^{83} 8^j`. + // offset_acc = \sum_{j = 0}^{NUM_WINDOWS - 2} 2^{FIXED_BASE_WINDOW_SIZE * j+1} + let offset_acc = (0..(Self::NUM_WINDOWS - 1)).fold(C::ScalarExt::zero(), |acc, w| { + acc + C::Scalar::from_u64(2).pow(&[ + constants::FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, + 0, + 0, + 0, + ]) + }); + + // `scalar = [k * 8^84 - offset_acc]`, where `offset_acc = \sum_{j = 0}^{83} 2^{FIXED_BASE_WINDOW_SIZE * j + 1}`. let scalar = scalar.k_field()[scalar.k_field().len() - 1] .map(|k| k * h.pow(&[(Self::NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_acc); + let mul_b = scalar.map(|scalar| base.value() * scalar); self.witness_point_config().assign_region( mul_b.map(|point| point.to_affine()), 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 957f567a4..e8fffe01f 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -1,4 +1,6 @@ -use super::super::{add_incomplete, witness_point, EccPoint, EccScalarFixed, OrchardFixedBase}; +use super::super::{ + add, add_incomplete, witness_point, EccPoint, EccScalarFixed, OrchardFixedBase, +}; use super::MulFixed; use crate::constants; @@ -27,6 +29,8 @@ pub struct Config { u: Column, // Permutation perm: Permutation, + // Configuration for `add` + add_config: add::Config, // Configuration for `add_incomplete` add_incomplete_config: add_incomplete::Config, // Configuration for `witness_point` @@ -45,6 +49,7 @@ impl From<&super::Config> for Config { y_p: config.y_p, u: config.u, perm: config.perm.clone(), + add_config: config.add_config.clone(), add_incomplete_config: config.add_incomplete_config.clone(), witness_point_config: config.witness_point_config.clone(), _marker: PhantomData, @@ -67,6 +72,12 @@ impl MulFixed for Config { fn k(&self) -> Column { self.k } + fn x_p(&self) -> Column { + self.x_p + } + fn y_p(&self) -> Column { + self.y_p + } fn u(&self) -> Column { self.u } @@ -76,6 +87,9 @@ impl MulFixed for Config { fn witness_point_config(&self) -> &witness_point::Config { &self.witness_point_config } + fn add_config(&self) -> &add::Config { + &self.add_config + } fn add_incomplete_config(&self) -> &add_incomplete::Config { &self.add_incomplete_config } @@ -94,11 +108,7 @@ impl Config { self.assign_region_inner(region, offset, &scalar.into(), &base.into())?; // Add to the accumulator and return the final result as `[scalar]B`. - self.add_incomplete_config.assign_region( - &mul_b, - &acc, - offset + constants::NUM_WINDOWS - 1, - region, - ) + self.add_config + .assign_region::(&mul_b, &acc, offset + constants::NUM_WINDOWS, region) } } diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index 7e70963ab..691ac212d 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -1,10 +1,10 @@ use super::super::{ - add_incomplete, util, witness_point, EccPoint, EccScalarFixedShort, OrchardFixedBaseShort, + add, add_incomplete, util, witness_point, CellValue, EccPoint, EccScalarFixedShort, + OrchardFixedBaseShort, }; use super::MulFixed; use crate::constants; -use group::Curve; use halo2::{ arithmetic::{CurveAffine, Field}, circuit::Region, @@ -33,6 +33,8 @@ pub struct Config { u: Column, // Permutation perm: Permutation, + // Configuration for `add` + add_config: add::Config, // Configuration for `add_incomplete` add_incomplete_config: add_incomplete::Config, // Configuration for `witness_point` @@ -53,6 +55,7 @@ impl From<&super::Config> for Config { y_a: config.y_a, u: config.u, perm: config.perm.clone(), + add_config: config.add_config.clone(), add_incomplete_config: config.add_incomplete_config.clone(), witness_point_config: config.witness_point_config.clone(), _marker: PhantomData, @@ -75,6 +78,12 @@ impl MulFixed for Config { fn k(&self) -> Column { self.k_s } + fn x_p(&self) -> Column { + self.x_p + } + fn y_p(&self) -> Column { + self.y_p + } fn u(&self) -> Column { self.u } @@ -84,6 +93,9 @@ impl MulFixed for Config { fn witness_point_config(&self) -> &witness_point::Config { &self.witness_point_config } + fn add_config(&self) -> &add::Config { + &self.add_config + } fn add_incomplete_config(&self) -> &add_incomplete::Config { &self.add_incomplete_config } @@ -124,13 +136,16 @@ impl Config { self.assign_region_inner(region, offset, &scalar.into(), &base.into())?; // Add to the cumulative sum to get `[magnitude]B`. - let magnitude_mul = self.add_incomplete_config.assign_region( + let magnitude_mul = self.add_config.assign_region::( &mul_b, &acc, - offset + constants::NUM_WINDOWS_SHORT - 1, + offset + constants::NUM_WINDOWS_SHORT, region, )?; + // Increase offset by 1 after complete addition + let offset = offset + 1; + // Assign sign to `bits` column let sign = util::assign_and_constrain( region, @@ -159,13 +174,22 @@ impl Config { // Assign final `x, y` to `x_p, y_p` columns and return final point let x_val = magnitude_mul.x.value; - let mul = x_val - .zip(y_val) - .map(|(x, y)| C::from_xy(x, y).unwrap().to_curve()); - self.witness_point_config.assign_region( - mul.map(|point| point.to_affine()), + let x_var = region.assign_advice( + || "x_var", + self.x_p, offset + constants::NUM_WINDOWS_SHORT, - region, - ) + || x_val.ok_or(Error::SynthesisError), + )?; + let y_var = region.assign_advice( + || "y_var", + self.y_p, + offset + constants::NUM_WINDOWS_SHORT, + || y_val.ok_or(Error::SynthesisError), + )?; + + Ok(EccPoint { + x: CellValue::new(x_var, x_val), + y: CellValue::new(y_var, y_val), + }) } } From 6102e4577065f334785edc56de4398b65f646476 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 24 May 2021 21:54:13 +0800 Subject: [PATCH 23/49] Document or remove unwrap()s --- src/circuit/gadget/ecc/chip.rs | 6 +- src/circuit/gadget/ecc/chip/add.rs | 8 +- src/circuit/gadget/ecc/chip/add_incomplete.rs | 30 +- src/circuit/gadget/ecc/chip/double.rs | 22 +- src/circuit/gadget/ecc/chip/load.rs | 4 +- src/circuit/gadget/ecc/chip/mul.rs | 263 ++++++++++-------- src/circuit/gadget/ecc/chip/mul/incomplete.rs | 188 +++++++------ src/circuit/gadget/ecc/chip/mul_fixed.rs | 9 +- .../gadget/ecc/chip/mul_fixed/short.rs | 2 +- src/circuit/gadget/ecc/chip/util.rs | 19 +- .../gadget/ecc/chip/witness_scalar_fixed.rs | 9 +- .../chip/witness_scalar_fixed/full_width.rs | 1 + .../ecc/chip/witness_scalar_fixed/short.rs | 7 +- 13 files changed, 309 insertions(+), 259 deletions(-) diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 7e786ad60..3de6cf35c 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -247,7 +247,7 @@ impl EccChip { // Create variable-base scalar mul gates { - let mul_config: mul::Config = (&config).into(); + let mul_config: mul::Config = (&config).into(); mul_config.create_gate(meta); } @@ -418,10 +418,10 @@ impl EccInstructions for EccChip { scalar: &Self::ScalarVar, base: &Self::Point, ) -> Result { - let config: mul::Config = self.config().into(); + let config: mul::Config = self.config().into(); layouter.assign_region( || "variable-base mul", - |mut region| config.assign_region::(scalar, base, 0, &mut region), + |mut region| config.assign_region(scalar, base, 0, &mut region), ) } diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 4636e6cd2..231136769 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -200,12 +200,12 @@ impl Config { self.q_add.enable(region, offset)?; // Copy point `a` into `x_p`, `y_p` columns - util::assign_and_constrain(region, || "x_p", self.x_p.into(), offset, &a.x, &self.perm)?; - util::assign_and_constrain(region, || "y_p", self.y_p.into(), offset, &a.y, &self.perm)?; + util::assign_and_constrain(region, || "x_p", self.x_p, offset, &a.x, &self.perm)?; + util::assign_and_constrain(region, || "y_p", self.y_p, offset, &a.y, &self.perm)?; // Copy point `b` into `x_qr`, `y_qr` columns - util::assign_and_constrain(region, || "x_q", self.x_qr.into(), offset, &b.x, &self.perm)?; - util::assign_and_constrain(region, || "y_q", self.y_qr.into(), offset, &b.y, &self.perm)?; + util::assign_and_constrain(region, || "x_q", self.x_qr, offset, &b.x, &self.perm)?; + util::assign_and_constrain(region, || "y_q", self.y_qr, offset, &b.y, &self.perm)?; let (x_p, y_p) = (a.x.value, a.y.value); let (x_q, y_q) = (b.x.value, b.y.value); diff --git a/src/circuit/gadget/ecc/chip/add_incomplete.rs b/src/circuit/gadget/ecc/chip/add_incomplete.rs index b98db9879..e2a59b5af 100644 --- a/src/circuit/gadget/ecc/chip/add_incomplete.rs +++ b/src/circuit/gadget/ecc/chip/add_incomplete.rs @@ -66,16 +66,17 @@ impl Config { #[allow(non_snake_case)] pub(super) fn assign_region( &self, - a: &EccPoint, - b: &EccPoint, + p: &EccPoint, + q: &EccPoint, offset: usize, region: &mut Region<'_, F>, ) -> Result, Error> { - // Compute the sum `a + b` - let (x_p, y_p) = (a.x.value, a.y.value); - let (x_q, y_q) = (b.x.value, b.y.value); + // Enable `q_add_incomplete` selector + self.q_add_incomplete.enable(region, offset)?; // Handle exceptional cases + let (x_p, y_p) = (p.x.value, p.y.value); + let (x_q, y_q) = (q.x.value, q.y.value); x_p.zip(y_p) .zip(x_q) .zip(y_q) @@ -93,29 +94,28 @@ impl Config { }) .unwrap_or(Err(Error::SynthesisError))?; - // Enable `q_add_incomplete` selector - self.q_add_incomplete.enable(region, offset)?; - - // Copy point `a` into `x_p`, `y_p` columns - util::assign_and_constrain(region, || "x_p", self.x_p.into(), offset, &a.x, &self.perm)?; - util::assign_and_constrain(region, || "y_p", self.y_p.into(), offset, &a.y, &self.perm)?; + // Copy point `p` into `x_p`, `y_p` columns + util::assign_and_constrain(region, || "x_p", self.x_p, offset, &p.x, &self.perm)?; + util::assign_and_constrain(region, || "y_p", self.y_p, offset, &p.y, &self.perm)?; - // Copy point `b` into `x_a`, `y_a` columns - util::assign_and_constrain(region, || "x_q", self.x_qr.into(), offset, &b.x, &self.perm)?; - util::assign_and_constrain(region, || "y_q", self.y_qr.into(), offset, &b.y, &self.perm)?; + // Copy point `q` into `x_qr`, `y_qr` columns + util::assign_and_constrain(region, || "x_q", self.x_qr, offset, &q.x, &self.perm)?; + util::assign_and_constrain(region, || "y_q", self.y_qr, offset, &q.y, &self.perm)?; + // Compute the sum `P + Q = R` let r = x_p .zip(y_p) .zip(x_q) .zip(y_q) .map(|(((x_p, y_p), x_q), y_q)| { + // We can invert `(x_q - x_p)` because we rejected the `x_q == x_p` case. let lambda = (y_q - y_p) * (x_q - x_p).invert().unwrap(); let x_r = lambda * lambda - x_p - x_q; let y_r = lambda * (x_p - x_r) - y_p; (x_r, y_r) }); - // Assign the sum to `x_a`, `y_a` columns in the next row + // Assign the sum to `x_qr`, `y_qr` columns in the next row let x_r = r.map(|r| r.0); let x_r_var = region.assign_advice( || "x_r", diff --git a/src/circuit/gadget/ecc/chip/double.rs b/src/circuit/gadget/ecc/chip/double.rs index 5b74bd577..a21376b38 100644 --- a/src/circuit/gadget/ecc/chip/double.rs +++ b/src/circuit/gadget/ecc/chip/double.rs @@ -67,13 +67,16 @@ impl Config { #[allow(non_snake_case)] pub(super) fn assign_region( &self, - a: &EccPoint, + p: &EccPoint, offset: usize, region: &mut Region<'_, F>, ) -> Result, Error> { - // Handle point at infinity - (a.x.value) - .zip(a.y.value) + // Enable `q_double` selector + self.q_double.enable(region, offset)?; + + // Return error if `P` is point at infinity + let (x_p, y_p) = (p.x.value, p.y.value); + x_p.zip(y_p) .map(|(x, y)| { if x == F::zero() && y == F::zero() { return Err(Error::SynthesisError); @@ -82,17 +85,14 @@ impl Config { }) .unwrap_or(Err(Error::SynthesisError))?; - // Enable `q_double` selector - self.q_double.enable(region, offset)?; - - // Copy the point into `x_p`, `y_p` columns - util::assign_and_constrain(region, || "x_p", self.x_p.into(), offset, &a.x, &self.perm)?; - util::assign_and_constrain(region, || "y_p", self.y_p.into(), offset, &a.y, &self.perm)?; + // Copy the point `P` into `x_p`, `y_p` columns + util::assign_and_constrain(region, || "x_p", self.x_p, offset, &p.x, &self.perm)?; + util::assign_and_constrain(region, || "y_p", self.y_p, offset, &p.y, &self.perm)?; // Compute the doubled point - let (x_p, y_p) = (a.x.value, a.y.value); let r = x_p.zip(y_p).map(|(x_p, y_p)| { // λ = 3(x_p)^2 / (2 * y_p) + // We can invert `y_p` since we already rejected the case where `y_p == 0`. let lambda = F::from_u64(3) * x_p * x_p * F::TWO_INV * y_p.invert().unwrap(); let x_r = lambda * lambda - x_p - x_p; let y_r = lambda * (x_p - x_r) - y_p; diff --git a/src/circuit/gadget/ecc/chip/load.rs b/src/circuit/gadget/ecc/chip/load.rs index 1d5d3fa39..323b9163e 100644 --- a/src/circuit/gadget/ecc/chip/load.rs +++ b/src/circuit/gadget/ecc/chip/load.rs @@ -126,7 +126,7 @@ fn load_lagrange_coeffs(coeffs: Vec<[F; H]>) -> LagrangeCoeffs { WindowLagrangeCoeffs( window .iter() - .map(|&coeff| coeff) + .copied() .collect::>() .into_boxed_slice() .try_into() @@ -148,7 +148,7 @@ fn load_lagrange_coeffs_short(coeffs: Vec<[F; H]>) -> LagrangeCoeff WindowLagrangeCoeffs( window .iter() - .map(|&coeff| coeff) + .copied() .collect::>() .into_boxed_slice() .try_into() diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index cb3ab401d..0ebe51ff7 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -13,7 +13,7 @@ use halo2::{ mod incomplete; use incomplete::IncompleteConfig; -pub struct Config { +pub struct Config { // Selector used to constrain the cells used in complete addition. q_mul_complete: Selector, // Fixed column used to check recovery of the original scalar after decomposition. @@ -27,12 +27,12 @@ pub struct Config { // Configuration used in point doubling double_config: double::Config, // Configuration used for `hi` bits of the scalar - hi_config: IncompleteConfig, + hi_config: IncompleteConfig, // Configuration used for `lo` bits of the scalar - lo_config: IncompleteConfig, + lo_config: IncompleteConfig, } -impl From<&EccConfig> for Config { +impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { Self { q_mul_complete: ecc_config.q_mul_complete, @@ -47,15 +47,15 @@ impl From<&EccConfig> for Config { } } -impl Config { - pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { +impl Config { + pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { self.hi_config.create_gate(meta); self.lo_config.create_gate(meta); self.create_decompose_gate(meta); self.create_final_scalar_gate(meta); } - pub(super) fn assign_region( + pub(super) fn assign_region( &self, scalar: &CellValue, base: &EccPoint, @@ -63,19 +63,27 @@ impl Config { region: &mut Region<'_, C::Base>, ) -> Result, Error> { // Decompose the scalar bitwise (big-endian bit order). - let k_bits = decompose_scalar::(scalar.value.unwrap()); + let k_bits = scalar.value.map(|scalar| decompose_scalar::(scalar)); // Bits used in incomplete addition. k_{254} to k_{4} inclusive let incomplete_range = 0..(C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS); - let k_incomplete = &k_bits[incomplete_range]; - let k_incomplete_hi = &k_incomplete[..k_incomplete.len() / 2]; - let k_incomplete_lo = &k_incomplete[k_incomplete.len() / 2..]; + let k_incomplete = k_bits + .clone() + .map(|k_bits| k_bits[incomplete_range].to_vec()); + let incomplete_lo_len = (C::Scalar::NUM_BITS as usize - NUM_COMPLETE_BITS) / 2; // Bits used in complete addition. k_{3} to k_{1} inclusive // The LSB k_{0} is handled separately. let complete_range = (C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS) ..(C::Scalar::NUM_BITS as usize - 1); - let k_complete = &k_bits[complete_range.clone()]; + let k_complete = k_bits + .clone() + .map(|k_bits| k_bits[complete_range.clone()].to_vec()); + + // Enable selectors for complete range + for row in complete_range.clone() { + self.q_mul_complete.enable(region, row + offset)?; + } // Initialize the accumulator a [2]base let acc = self.double_config.assign_region(&base, offset, region)?; @@ -87,6 +95,9 @@ impl Config { let z = CellValue::new(z_cell, Some(z_val)); // Double-and-add (incomplete addition) for the `hi` half of the scalar decomposition + let k_incomplete_hi = k_incomplete + .clone() + .map(|k_incomplete| k_incomplete[..k_incomplete.len() / 2].to_vec()); let (x, y_a, z) = self.hi_config.double_and_add( region, &base, @@ -96,6 +107,8 @@ impl Config { )?; // Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition + let k_incomplete_lo = + k_incomplete.map(|k_incomplete| k_incomplete[k_incomplete.len() / 2..].to_vec()); let (x, y_a, z) = self.lo_config.double_and_add( region, &base, @@ -107,7 +120,7 @@ impl Config { // Move from incomplete addition to complete addition let mut acc = { let y_a_col = self.add_config.y_qr; - let row = k_incomplete_lo.len() + 2; + let row = incomplete_lo_len + 2; let y_a_cell = region.assign_advice( || "y_a", @@ -118,7 +131,7 @@ impl Config { util::assign_and_constrain( region, || "Copy z from incomplete to complete", - self.z_complete.into(), + self.z_complete, row + offset, &z, &self.perm, @@ -131,83 +144,87 @@ impl Config { let mut z_val = z.value; // Complete addition - for (iter, k) in k_complete.iter().enumerate() { - // Each iteration uses 4 rows (two complete additions) - let row = k_incomplete_lo.len() + 4 * iter + 3; - - // Check scalar decomposition here - region.assign_advice( - || "z", - self.z_complete, - row + offset - 1, - || z_val.ok_or(Error::SynthesisError), - )?; - z_val = z_val.map(|z_val| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64)); - region.assign_advice( - || "z", - self.z_complete, - row + offset, - || z_val.ok_or(Error::SynthesisError), - )?; - self.q_mul_complete.enable(region, row + offset)?; - - let x_p = base.x.value; - let x_p_cell = region.assign_advice( - || "x_p", - self.add_config.x_p, - row + offset, - || x_p.ok_or(Error::SynthesisError), - )?; - - // If the bit is set, use `y`; if the bit is not set, use `-y` - let y_p = base.y.value; - let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); - - let y_p_cell = region.assign_advice( - || "y_p", - self.add_config.y_p, - row + offset, - || y_p.ok_or(Error::SynthesisError), - )?; - let p = EccPoint { - x: CellValue::::new(x_p_cell, x_p), - y: CellValue::::new(y_p_cell, y_p), - }; - - // Acc + U - let tmp_acc = self - .add_config - .assign_region::(&p, &acc, row + offset, region)?; - - // Copy acc from `x_a`, `y_a` over to `x_p`, `y_p` on the next row - let acc_x = util::assign_and_constrain( - region, - || "copy acc x_a", - self.add_config.x_p.into(), - row + offset + 2, - &acc.x, - &self.perm, - )?; - let acc_y = util::assign_and_constrain( - region, - || "copy acc y_a", - self.add_config.y_p.into(), - row + offset + 2, - &acc.y, - &self.perm, - )?; - - acc = EccPoint { x: acc_x, y: acc_y }; + k_complete.map(|k_complete| -> Result<(), Error> { + for (iter, k) in k_complete.iter().enumerate() { + // Each iteration uses 4 rows (two complete additions) + let row = incomplete_lo_len + 4 * iter + 3; + + // Check scalar decomposition here + region.assign_advice( + || "z", + self.z_complete, + row + offset - 1, + || z_val.ok_or(Error::SynthesisError), + )?; + z_val = + z_val.map(|z_val| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64)); + region.assign_advice( + || "z", + self.z_complete, + row + offset, + || z_val.ok_or(Error::SynthesisError), + )?; + + let x_p = base.x.value; + let x_p_cell = region.assign_advice( + || "x_p", + self.add_config.x_p, + row + offset, + || x_p.ok_or(Error::SynthesisError), + )?; + + // If the bit is set, use `y`; if the bit is not set, use `-y` + let y_p = base.y.value; + let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); + + let y_p_cell = region.assign_advice( + || "y_p", + self.add_config.y_p, + row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; + let p = EccPoint { + x: CellValue::::new(x_p_cell, x_p), + y: CellValue::::new(y_p_cell, y_p), + }; + + // Acc + U + let tmp_acc = self + .add_config + .assign_region::(&p, &acc, row + offset, region)?; + + // Copy acc from `x_a`, `y_a` over to `x_p`, `y_p` on the next row + let acc_x = util::assign_and_constrain( + region, + || "copy acc x_a", + self.add_config.x_p, + row + offset + 2, + &acc.x, + &self.perm, + )?; + let acc_y = util::assign_and_constrain( + region, + || "copy acc y_a", + self.add_config.y_p, + row + offset + 2, + &acc.y, + &self.perm, + )?; + + acc = EccPoint { x: acc_x, y: acc_y }; + + // Acc + P + Acc + acc = + self.add_config + .assign_region::(&acc, &tmp_acc, row + offset + 2, region)?; + } - // Acc + P + Acc - acc = self - .add_config - .assign_region::(&acc, &tmp_acc, row + offset + 2, region)?; - } + Ok(()) + }); // Process the least significant bit - let k_0_row = k_incomplete_lo.len() + complete_range.len() * 4 + 4; - let k_0 = &k_bits[C::Scalar::NUM_BITS as usize - 1]; + let k_0_row = incomplete_lo_len + complete_range.len() * 4 + 4; + let k_0 = k_bits.map(|k_bits| k_bits[C::Scalar::NUM_BITS as usize - 1].clone()); // Check that we recover the original scalar. // @@ -220,48 +237,58 @@ impl Config { // to be in the base field of the curve. (See non-normative notes in // https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents.) - z_val = z_val.map(|z_val| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k_0 as u64)); + z_val = z_val + .zip(k_0) + .map(|(z_val, k_0)| C::Base::from_u64(2) * z_val + C::Base::from_u64(k_0 as u64)); region.assign_advice( || "final z", self.z_complete, k_0_row + offset, || z_val.ok_or(Error::SynthesisError), )?; + + // Cast scalar into base field. + let scalar = scalar + .value + .map(|scalar| C::Base::from_bytes(&scalar.to_bytes()).unwrap()); region.assign_fixed( || "original k", self.mul_decompose, k_0_row + offset, - || Ok(C::Base::from_bytes(&scalar.value.unwrap().to_bytes()).unwrap()), + || scalar.ok_or(Error::SynthesisError), )?; // If `k_0` is 0, return `Acc - P` - if !k_0 { - let (x_p, y_p) = (base.x.value, base.y.value.map(|y_p| -y_p)); - let x_p_cell = region.assign_advice( - || "x_p", - self.add_config.x_p, - k_0_row + offset, - || x_p.ok_or(Error::SynthesisError), - )?; - - let y_p_cell = region.assign_advice( - || "y_p", - self.add_config.y_p, - k_0_row + offset, - || y_p.ok_or(Error::SynthesisError), - )?; - let p = EccPoint { - x: CellValue::::new(x_p_cell, x_p), - y: CellValue::::new(y_p_cell, y_p), - }; - - // Return the result of the final complete addition as `[scalar]B` - self.add_config - .assign_region::(&p, &acc, k_0_row + offset, region) - } else { - // If `k_0` is 1, simply return `Acc` - Ok(acc) - } + k_0.map(|k_0| { + if !k_0 { + let (x_p, y_p) = (base.x.value, base.y.value.map(|y_p| -y_p)); + let x_p_cell = region.assign_advice( + || "x_p", + self.add_config.x_p, + k_0_row + offset, + || x_p.ok_or(Error::SynthesisError), + )?; + + let y_p_cell = region.assign_advice( + || "y_p", + self.add_config.y_p, + k_0_row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; + let p = EccPoint { + x: CellValue::::new(x_p_cell, x_p), + y: CellValue::::new(y_p_cell, y_p), + }; + + // Return the result of the final complete addition as `[scalar]B` + self.add_config + .assign_region::(&p, &acc, k_0_row + offset, region) + } else { + // If `k_0` is 1, simply return `Acc` + Ok(acc) + } + }) + .unwrap_or(Err(Error::SynthesisError)) } /// Gate used to check scalar decomposition is correct. @@ -329,7 +356,7 @@ impl Deref for ZValue { } fn decompose_scalar(scalar: C::Base) -> Vec { - // Cast into scalar field + // Cast from base field into scalar field. let scalar = C::Scalar::from_bytes(&scalar.to_bytes()).unwrap(); // The scalar field `F_q = 2^254 + t_q` diff --git a/src/circuit/gadget/ecc/chip/mul/incomplete.rs b/src/circuit/gadget/ecc/chip/mul/incomplete.rs index 1a8d4c753..eaec2b5c3 100644 --- a/src/circuit/gadget/ecc/chip/mul/incomplete.rs +++ b/src/circuit/gadget/ecc/chip/mul/incomplete.rs @@ -1,13 +1,18 @@ use super::super::{CellValue, EccConfig, EccPoint}; use super::{ZValue, X, Y}; +use crate::constants; +use ff::PrimeField; use halo2::{ - arithmetic::FieldExt, + arithmetic::{CurveAffine, FieldExt}, circuit::Region, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector}, poly::Rotation, }; +use std::marker::PhantomData; -pub(super) struct IncompleteConfig { +pub(super) struct IncompleteConfig { + // Number of bits covered by this incomplete range. + num_bits: usize, // Selector used to constrain the cells used in incomplete addition. pub(super) q_mul: Selector, // Cumulative sum used to decompose the scalar. @@ -24,13 +29,20 @@ pub(super) struct IncompleteConfig { pub(super) lambda2: Column, // Permutation pub(super) perm: Permutation, + _marker: PhantomData, } -impl IncompleteConfig { +impl IncompleteConfig { // Columns used in processing the `hi` bits of the scalar. // `x_p, y_p` are shared across the `hi` and `lo` halves. pub(super) fn into_hi_config(ecc_config: &EccConfig) -> Self { + // Bits k_{254} to k_{4} inclusive are used in incomplete addition. + // The `hi` half is k_{254} to k_{130} inclusive (length 125 bits). + let incomplete_hi_len = + (C::Scalar::NUM_BITS as usize - 1 - constants::NUM_COMPLETE_BITS) / 2; + Self { + num_bits: incomplete_hi_len, q_mul: ecc_config.q_mul_hi, z: ecc_config.bits, x_a: ecc_config.extras[0], @@ -39,13 +51,19 @@ impl IncompleteConfig { lambda1: ecc_config.lambda.0, lambda2: ecc_config.lambda.1, perm: ecc_config.perm.clone(), + _marker: PhantomData, } } // Columns used in processing the `lo` bits of the scalar. // `x_p, y_p` are shared across the `hi` and `lo` halves. pub(super) fn into_lo_config(ecc_config: &EccConfig) -> Self { + // Bits k_{254} to k_{4} inclusive are used in incomplete addition. + // The `lo` half is k_{129} to k_{4} inclusive (length 126 bits). + let incomplete_lo_len = (C::Scalar::NUM_BITS as usize - constants::NUM_COMPLETE_BITS) / 2; + Self { + num_bits: incomplete_lo_len, q_mul: ecc_config.q_mul_lo, z: ecc_config.extras[1], x_a: ecc_config.extras[2], @@ -54,6 +72,7 @@ impl IncompleteConfig { lambda1: ecc_config.extras[3], lambda2: ecc_config.extras[4], perm: ecc_config.perm.clone(), + _marker: PhantomData, } } @@ -154,9 +173,14 @@ impl IncompleteConfig { region: &mut Region<'_, F>, base: &EccPoint, offset: usize, - bits: &[bool], + bits: Option>, acc: (X, Y, ZValue), ) -> Result<(X, Y, ZValue), Error> { + // Enable `q_mul` on all but the last row of the incomplete range. + for row in 1..(self.num_bits - 1) { + self.q_mul.enable(region, offset + row)?; + } + // Initialise the running `z` sum for the scalar bits. let mut z_val = acc.2.value; let mut z_cell = region.assign_advice( @@ -184,91 +208,91 @@ impl IncompleteConfig { region.constrain_equal(&self.perm, x_a_cell, acc.0.cell)?; let mut y_a = *acc.1; - // Enable `q_mul` on all but the last row of the incomplete range. - for row in 1..(bits.len() - 1) { - self.q_mul.enable(region, offset + row)?; - } - // Incomplete addition - for (row, k) in bits.iter().enumerate() { - // z_{i} = 2 * z_{i+1} + k_i - z_val = z_val.map(|z_val| F::from_u64(2) * z_val + F::from_u64(*k as u64)); - z_cell = region.assign_advice( - || "z", - self.z, - row + offset, - || z_val.ok_or(Error::SynthesisError), - )?; + bits.map(|bits| -> Result<(), Error> { + for (row, k) in bits.iter().enumerate() { + // z_{i} = 2 * z_{i+1} + k_i + z_val = z_val.map(|z_val| F::from_u64(2) * z_val + F::from_u64(*k as u64)); + z_cell = region.assign_advice( + || "z", + self.z, + row + offset, + || z_val.ok_or(Error::SynthesisError), + )?; - // Assign `x_p`, `y_p` - region.assign_advice( - || "x_p", - self.x_p, - row + offset, - || x_p.ok_or(Error::SynthesisError), - )?; - region.assign_advice( - || "y_p", - self.y_p, - row + offset, - || y_p.ok_or(Error::SynthesisError), - )?; + // Assign `x_p`, `y_p` + region.assign_advice( + || "x_p", + self.x_p, + row + offset, + || x_p.ok_or(Error::SynthesisError), + )?; + region.assign_advice( + || "y_p", + self.y_p, + row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; - // If the bit is set, use `y`; if the bit is not set, use `-y` - let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); + // If the bit is set, use `y`; if the bit is not set, use `-y` + let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); - // Compute and assign λ1⋅(x_A − x_P) = y_A − y_P - let lambda1 = y_a - .zip(y_p) - .zip(x_a) - .zip(x_p) - .map(|(((y_a, y_p), x_a), x_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap()); - region.assign_advice( - || "lambda1", - self.lambda1, - row + offset, - || lambda1.ok_or(Error::SynthesisError), - )?; + // Compute and assign λ1⋅(x_A − x_P) = y_A − y_P + let lambda1 = y_a + .zip(y_p) + .zip(x_a) + .zip(x_p) + .map(|(((y_a, y_p), x_a), x_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap()); + region.assign_advice( + || "lambda1", + self.lambda1, + row + offset, + || lambda1.ok_or(Error::SynthesisError), + )?; - // x_R = λ1^2 - x_A - x_P - let x_r = lambda1 - .zip(x_a) - .zip(x_p) - .map(|((lambda1, x_a), x_p)| lambda1 * lambda1 - x_a - x_p); + // x_R = λ1^2 - x_A - x_P + let x_r = lambda1 + .zip(x_a) + .zip(x_p) + .map(|((lambda1, x_a), x_p)| lambda1 * lambda1 - x_a - x_p); - // λ2 = (2(y_A) / (x_A - x_R)) - λ1 - let lambda2 = lambda1 - .zip(y_a) - .zip(x_a) - .zip(x_r) - .map(|(((lambda1, y_a), x_a), x_r)| { - F::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 - }); - region.assign_advice( - || "lambda2", - self.lambda2, - row + offset, - || lambda2.ok_or(Error::SynthesisError), - )?; + // λ2 = (2(y_A) / (x_A - x_R)) - λ1 + let lambda2 = + lambda1 + .zip(y_a) + .zip(x_a) + .zip(x_r) + .map(|(((lambda1, y_a), x_a), x_r)| { + F::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 + }); + region.assign_advice( + || "lambda2", + self.lambda2, + row + offset, + || lambda2.ok_or(Error::SynthesisError), + )?; + + // Compute and assign `x_a` for the next row + let x_a_new = lambda2 + .zip(x_a) + .zip(x_r) + .map(|((lambda2, x_a), x_r)| lambda2 * lambda2 - x_a - x_r); + y_a = lambda2 + .zip(x_a) + .zip(x_a_new) + .zip(y_a) + .map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a); + x_a = x_a_new; + x_a_cell = region.assign_advice( + || "x_a", + self.x_a, + row + offset + 1, + || x_a.ok_or(Error::SynthesisError), + )?; + } + Ok(()) + }); - // Compute and assign `x_a` for the next row - let x_a_new = lambda2 - .zip(x_a) - .zip(x_r) - .map(|((lambda2, x_a), x_r)| lambda2 * lambda2 - x_a - x_r); - y_a = lambda2 - .zip(x_a) - .zip(x_a_new) - .zip(y_a) - .map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a); - x_a = x_a_new; - x_a_cell = region.assign_advice( - || "x_a", - self.x_a, - row + offset + 1, - || x_a.ok_or(Error::SynthesisError), - )?; - } Ok(( X(CellValue::::new(x_a_cell, x_a)), Y(y_a), diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index dc5837b5e..7ec9921f6 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -227,6 +227,7 @@ trait MulFixed { fn add_config(&self) -> &add::Config; fn add_incomplete_config(&self) -> &add_incomplete::Config; + #[allow(clippy::type_complexity)] fn assign_region_inner( &self, region: &mut Region<'_, C::Base>, @@ -292,7 +293,7 @@ trait MulFixed { // Assign z-values for each window region.assign_fixed( || format!("z-value for window: {:?}", window), - self.fixed_z().into(), + self.fixed_z(), window + offset, || Ok(base_z[window]), )?; @@ -312,7 +313,7 @@ trait MulFixed { util::assign_and_constrain( region, || format!("k[{:?}]", window), - self.k().into(), + self.k(), window + offset, k, self.perm(), @@ -351,7 +352,7 @@ trait MulFixed { let x = util::assign_and_constrain( region, || "initialize acc x", - self.add_incomplete_config().x_qr.into(), + self.add_incomplete_config().x_qr, offset + 1, &m0.x, self.perm(), @@ -359,7 +360,7 @@ trait MulFixed { let y = util::assign_and_constrain( region, || "initialize acc y", - self.add_incomplete_config().y_qr.into(), + self.add_incomplete_config().y_qr, offset + 1, &m0.y, self.perm(), diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index 691ac212d..275e30b6a 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -150,7 +150,7 @@ impl Config { let sign = util::assign_and_constrain( region, || "sign", - self.k_s.into(), + self.k_s, offset + constants::NUM_WINDOWS_SHORT, &scalar.sign, &self.perm, diff --git a/src/circuit/gadget/ecc/chip/util.rs b/src/circuit/gadget/ecc/chip/util.rs index 6a24975dd..af2fe6d73 100644 --- a/src/circuit/gadget/ecc/chip/util.rs +++ b/src/circuit/gadget/ecc/chip/util.rs @@ -2,15 +2,14 @@ use super::CellValue; use halo2::{ arithmetic::FieldExt, circuit::Region, - plonk::{Advice, Any, Column, Error, Fixed, Permutation}, + plonk::{Advice, Column, Error, Permutation}, }; -use std::convert::TryFrom; /// Assign a cell the same value as another cell and set up a copy constraint between them. pub(super) fn assign_and_constrain( region: &mut Region<'_, F>, annotation: A, - column: Column, + column: Column, row: usize, copy: &CellValue, perm: &Permutation, @@ -19,17 +18,9 @@ where A: Fn() -> AR, AR: Into, { - let cell = if let Ok(column) = Column::::try_from(column) { - region.assign_advice(annotation, column, row, || { - copy.value.ok_or(Error::SynthesisError) - })? - } else if let Ok(column) = Column::::try_from(column) { - region.assign_fixed(annotation, column, row, || { - copy.value.ok_or(Error::SynthesisError) - })? - } else { - return Err(Error::SynthesisError); - }; + let cell = region.assign_advice(annotation, column, row, || { + copy.value.ok_or(Error::SynthesisError) + })?; region.constrain_equal(perm, cell, copy.cell)?; diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs index 30a89acc5..d473a54b4 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs @@ -73,6 +73,7 @@ impl Config { trait WitnessScalarFixed { const SCALAR_NUM_BITS: usize; + const NUM_WINDOWS: usize; type Scalar: Clone + std::fmt::Debug; fn q_scalar_fixed(&self) -> Selector; @@ -91,6 +92,11 @@ trait WitnessScalarFixed { offset: usize, region: &mut Region<'_, C::Base>, ) -> Result>, Error> { + // Enable `q_scalar_fixed` selector + for idx in 0..Self::NUM_WINDOWS { + self.q_scalar_fixed().enable(region, offset + idx)?; + } + // Decompose scalar into windows let bits: Option> = value.map(|value| { util::decompose_scalar_fixed::( @@ -105,9 +111,6 @@ trait WitnessScalarFixed { if let Some(bits) = bits { for (idx, window) in bits.iter().enumerate() { - // Enable `q_scalar_fixed` selector - self.q_scalar_fixed().enable(region, offset + idx)?; - let window = C::Base::from_u64(*window as u64); let k_var = region.assign_advice( || format!("k[{:?}]", offset + idx), 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 index a03f8c217..fa57f344d 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/full_width.rs @@ -26,6 +26,7 @@ impl From<&super::Config> for Config { impl super::WitnessScalarFixed for Config { const SCALAR_NUM_BITS: usize = C::Scalar::NUM_BITS as usize; + const NUM_WINDOWS: usize = crate::constants::NUM_WINDOWS as usize; type Scalar = EccScalarFixed; fn q_scalar_fixed(&self) -> Selector { diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs index c41024aa6..a167d7ed3 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs @@ -45,6 +45,7 @@ impl Config { impl super::WitnessScalarFixed for Config { const SCALAR_NUM_BITS: usize = constants::L_VALUE as usize; + const NUM_WINDOWS: usize = crate::constants::NUM_WINDOWS_SHORT as usize; type Scalar = EccScalarFixedShort; fn q_scalar_fixed(&self) -> Selector { @@ -60,6 +61,10 @@ impl super::WitnessScalarFixed for Config { offset: usize, region: &mut Region<'_, C::Base>, ) -> Result, Error> { + // Enable `q_scalar_fixed_short` + self.q_scalar_fixed_short + .enable(region, offset + Self::NUM_WINDOWS)?; + // Compute the scalar's sign and magnitude let sign = value.map(|value| { // t = (p - 1)/2 @@ -91,8 +96,6 @@ impl super::WitnessScalarFixed for Config { offset + k_bits.len(), || sign.ok_or(Error::SynthesisError), )?; - self.q_scalar_fixed_short - .enable(region, offset + k_bits.len())?; Ok(EccScalarFixedShort { magnitude, From 70d6a5676a38152fee72a9cbb758f277fb349e83 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 25 May 2021 08:56:31 +0800 Subject: [PATCH 24/49] Refactor mul.rs into complete and incomplete submodules --- src/circuit/gadget/ecc/chip/double.rs | 1 + src/circuit/gadget/ecc/chip/mul.rs | 339 ++++++++---------- src/circuit/gadget/ecc/chip/mul/complete.rs | 160 +++++++++ src/circuit/gadget/ecc/chip/mul/incomplete.rs | 225 ++++++------ 4 files changed, 421 insertions(+), 304 deletions(-) create mode 100644 src/circuit/gadget/ecc/chip/mul/complete.rs diff --git a/src/circuit/gadget/ecc/chip/double.rs b/src/circuit/gadget/ecc/chip/double.rs index a21376b38..824388c0f 100644 --- a/src/circuit/gadget/ecc/chip/double.rs +++ b/src/circuit/gadget/ecc/chip/double.rs @@ -7,6 +7,7 @@ use halo2::{ poly::Rotation, }; +#[derive(Clone, Debug)] pub struct Config { q_double: Selector, // x-coordinate of P in [2]P = R diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 0ebe51ff7..b301778fa 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -1,6 +1,6 @@ use super::{add, double, util, CellValue, EccConfig, EccPoint}; use crate::constants::NUM_COMPLETE_BITS; -use std::ops::Deref; +use std::ops::{Deref, Range}; use ff::PrimeField; use halo2::{ @@ -10,8 +10,8 @@ use halo2::{ poly::Rotation, }; +mod complete; mod incomplete; -use incomplete::IncompleteConfig; pub struct Config { // Selector used to constrain the cells used in complete addition. @@ -27,9 +27,9 @@ pub struct Config { // Configuration used in point doubling double_config: double::Config, // Configuration used for `hi` bits of the scalar - hi_config: IncompleteConfig, + hi_config: incomplete::Config, // Configuration used for `lo` bits of the scalar - lo_config: IncompleteConfig, + lo_config: incomplete::Config, } impl From<&EccConfig> for Config { @@ -41,20 +41,66 @@ impl From<&EccConfig> for Config { perm: ecc_config.perm.clone(), add_config: ecc_config.into(), double_config: ecc_config.into(), - hi_config: IncompleteConfig::into_hi_config(ecc_config), - lo_config: IncompleteConfig::into_lo_config(ecc_config), + hi_config: incomplete::Config::hi_config(ecc_config), + lo_config: incomplete::Config::lo_config(ecc_config), } } } +trait Mul { + // Bits used in incomplete addition. k_{254} to k_{4} inclusive + fn incomplete_range(&self) -> Range { + 0..(C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS) + } + + // Bits k_{254} to k_{4} inclusive are used in incomplete addition. + // The `lo` half is k_{129} to k_{4} inclusive (length 126 bits). + fn incomplete_lo_len() -> usize { + (C::Scalar::NUM_BITS as usize - NUM_COMPLETE_BITS) / 2 + } + + // Bits k_{254} to k_{4} inclusive are used in incomplete addition. + // The `hi` half is k_{254} to k_{130} inclusive (length 125 bits). + fn incomplete_hi_len() -> usize { + (C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS) / 2 + } + + fn complete_range(&self) -> Range { + (C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS)..(C::Scalar::NUM_BITS as usize - 1) + } + + fn complete_len(&self) -> usize { + NUM_COMPLETE_BITS as usize + } +} + +impl Mul for Config {} + impl Config { pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { self.hi_config.create_gate(meta); self.lo_config.create_gate(meta); - self.create_decompose_gate(meta); + + let complete_config: complete::Config = self.into(); + complete_config.create_gate(meta); + self.create_final_scalar_gate(meta); } + /// Gate used to check final scalar is recovered. + fn create_final_scalar_gate(&self, meta: &mut ConstraintSystem) { + let scalar = meta.query_fixed(self.mul_decompose, Rotation::cur()); + let z_cur = meta.query_advice(self.z_complete, Rotation::cur()); + + meta.create_gate("Decompose scalar", |_| { + // q = 2^254 + t_q is the scalar field of Pallas + let t_q = C::Base::from_u128(45560315531506369815346746415080538113); + + // Check that `k = scalar + t_q` + scalar.clone() * (scalar + Expression::Constant(t_q) - z_cur) + }); + } + pub(super) fn assign_region( &self, scalar: &CellValue, @@ -62,31 +108,16 @@ impl Config { offset: usize, region: &mut Region<'_, C::Base>, ) -> Result, Error> { + // Initialize the accumulator a [2]base + let acc = self.double_config.assign_region(&base, offset, region)?; + // Decompose the scalar bitwise (big-endian bit order). let k_bits = scalar.value.map(|scalar| decompose_scalar::(scalar)); // Bits used in incomplete addition. k_{254} to k_{4} inclusive - let incomplete_range = 0..(C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS); let k_incomplete = k_bits .clone() - .map(|k_bits| k_bits[incomplete_range].to_vec()); - let incomplete_lo_len = (C::Scalar::NUM_BITS as usize - NUM_COMPLETE_BITS) / 2; - - // Bits used in complete addition. k_{3} to k_{1} inclusive - // The LSB k_{0} is handled separately. - let complete_range = (C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS) - ..(C::Scalar::NUM_BITS as usize - 1); - let k_complete = k_bits - .clone() - .map(|k_bits| k_bits[complete_range.clone()].to_vec()); - - // Enable selectors for complete range - for row in complete_range.clone() { - self.q_mul_complete.enable(region, row + offset)?; - } - - // Initialize the accumulator a [2]base - let acc = self.double_config.assign_region(&base, offset, region)?; + .map(|k_bits| k_bits[self.incomplete_range()].to_vec()); // Initialize the running sum for scalar decomposition to zero let z_val = C::Base::zero(); @@ -100,10 +131,10 @@ impl Config { .map(|k_incomplete| k_incomplete[..k_incomplete.len() / 2].to_vec()); let (x, y_a, z) = self.hi_config.double_and_add( region, - &base, offset + 1, + &base, k_incomplete_hi, - (X(acc.x.clone()), Y(acc.y.value), ZValue(z)), + (X(acc.x.clone()), Y(acc.y.value), Z(z)), )?; // Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition @@ -111,16 +142,16 @@ impl Config { k_incomplete.map(|k_incomplete| k_incomplete[k_incomplete.len() / 2..].to_vec()); let (x, y_a, z) = self.lo_config.double_and_add( region, - &base, offset + 1, + &base, k_incomplete_lo, (x, y_a, z), )?; // Move from incomplete addition to complete addition - let mut acc = { + let acc = { let y_a_col = self.add_config.y_qr; - let row = incomplete_lo_len + 2; + let row = Self::incomplete_lo_len() + 2; let y_a_cell = region.assign_advice( || "y_a", @@ -142,101 +173,32 @@ impl Config { } }; - let mut z_val = z.value; - // Complete addition - k_complete.map(|k_complete| -> Result<(), Error> { - for (iter, k) in k_complete.iter().enumerate() { - // Each iteration uses 4 rows (two complete additions) - let row = incomplete_lo_len + 4 * iter + 3; - - // Check scalar decomposition here - region.assign_advice( - || "z", - self.z_complete, - row + offset - 1, - || z_val.ok_or(Error::SynthesisError), - )?; - z_val = - z_val.map(|z_val| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64)); - region.assign_advice( - || "z", - self.z_complete, - row + offset, - || z_val.ok_or(Error::SynthesisError), - )?; - - let x_p = base.x.value; - let x_p_cell = region.assign_advice( - || "x_p", - self.add_config.x_p, - row + offset, - || x_p.ok_or(Error::SynthesisError), - )?; - - // If the bit is set, use `y`; if the bit is not set, use `-y` - let y_p = base.y.value; - let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); - - let y_p_cell = region.assign_advice( - || "y_p", - self.add_config.y_p, - row + offset, - || y_p.ok_or(Error::SynthesisError), - )?; - let p = EccPoint { - x: CellValue::::new(x_p_cell, x_p), - y: CellValue::::new(y_p_cell, y_p), - }; - - // Acc + U - let tmp_acc = self - .add_config - .assign_region::(&p, &acc, row + offset, region)?; - - // Copy acc from `x_a`, `y_a` over to `x_p`, `y_p` on the next row - let acc_x = util::assign_and_constrain( - region, - || "copy acc x_a", - self.add_config.x_p, - row + offset + 2, - &acc.x, - &self.perm, - )?; - let acc_y = util::assign_and_constrain( - region, - || "copy acc y_a", - self.add_config.y_p, - row + offset + 2, - &acc.y, - &self.perm, - )?; - - acc = EccPoint { x: acc_x, y: acc_y }; - - // Acc + P + Acc - acc = - self.add_config - .assign_region::(&acc, &tmp_acc, row + offset + 2, region)?; - } - - Ok(()) - }); + let complete_config: complete::Config = self.into(); + // Bits used in complete addition. k_{3} to k_{1} inclusive + // The LSB k_{0} is handled separately. + let k_complete = k_bits + .clone() + .map(|k_bits| k_bits[self.complete_range()].to_vec()); + let (acc, z_val) = + complete_config.assign_region(region, offset, k_complete, base, acc, z.value)?; // Process the least significant bit - let k_0_row = incomplete_lo_len + complete_range.len() * 4 + 4; let k_0 = k_bits.map(|k_bits| k_bits[C::Scalar::NUM_BITS as usize - 1].clone()); + self.process_lsb(region, offset, scalar, base, acc, k_0, z_val) + } - // Check that we recover the original scalar. - // - // NB: We assume that the scalar fits in the curve's base field. This is not - // true in general, and in particular for the Pallas curve, whose scalar field - // `Fq` is larger than its base field `Fp`. - // - // However, the only use of variable-base scalar mul in the Orchard protocol - // is in deriving diversified addresses `[ivk] g_d`, and `ivk` is guaranteed - // to be in the base field of the curve. (See non-normative notes in - // https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents.) - + fn process_lsb( + &self, + region: &mut Region<'_, C::Base>, + offset: usize, + scalar: &CellValue, + base: &EccPoint, + acc: EccPoint, + k_0: Option, + mut z_val: Option, + ) -> Result, Error> { + // Assign the final `z` value. + let k_0_row = Self::incomplete_lo_len() + self.complete_range().len() * 4 + 4; z_val = z_val .zip(k_0) .map(|(z_val, k_0)| C::Base::from_u64(2) * z_val + C::Base::from_u64(k_0 as u64)); @@ -247,7 +209,16 @@ impl Config { || z_val.ok_or(Error::SynthesisError), )?; - // Cast scalar into base field. + // Check that we recover the original scalar. + // + // NB: We assume that the scalar fits in the curve's base field. This is not + // true in general, and in particular for the Pallas curve, whose scalar field + // `Fq` is larger than its base field `Fp`. + // + // However, the only use of variable-base scalar mul in the Orchard protocol + // is in deriving diversified addresses `[ivk] g_d`, and `ivk` is guaranteed + // to be in the base field of the curve. (See non-normative notes in + // https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents.) let scalar = scalar .value .map(|scalar| C::Base::from_bytes(&scalar.to_bytes()).unwrap()); @@ -258,74 +229,60 @@ impl Config { || scalar.ok_or(Error::SynthesisError), )?; - // If `k_0` is 0, return `Acc - P` - k_0.map(|k_0| { - if !k_0 { - let (x_p, y_p) = (base.x.value, base.y.value.map(|y_p| -y_p)); - let x_p_cell = region.assign_advice( - || "x_p", - self.add_config.x_p, - k_0_row + offset, - || x_p.ok_or(Error::SynthesisError), - )?; - - let y_p_cell = region.assign_advice( - || "y_p", - self.add_config.y_p, - k_0_row + offset, - || y_p.ok_or(Error::SynthesisError), - )?; - let p = EccPoint { - x: CellValue::::new(x_p_cell, x_p), - y: CellValue::::new(y_p_cell, y_p), - }; - - // Return the result of the final complete addition as `[scalar]B` - self.add_config - .assign_region::(&p, &acc, k_0_row + offset, region) - } else { - // If `k_0` is 1, simply return `Acc` - Ok(acc) - } - }) - .unwrap_or(Err(Error::SynthesisError)) - } - - /// Gate used to check scalar decomposition is correct. - /// This is used to check the bits used in complete addition, since the incomplete - /// addition gate (controlled by `q_mul`) already checks scalar decomposition for - /// the other bits. - fn create_decompose_gate(&self, meta: &mut ConstraintSystem) { - let q_mul_complete = meta.query_selector(self.q_mul_complete, Rotation::cur()); - let z_cur = meta.query_advice(self.z_complete, Rotation::cur()); - let z_prev = meta.query_advice(self.z_complete, Rotation::prev()); - - meta.create_gate("Decompose scalar ", |_| { - // k_{i} = z_{i} - 2⋅z_{i+1} - let k = z_cur.clone() - Expression::Constant(F::from_u64(2)) * z_prev; - // (k_i) ⋅ (k_i - 1) = 0 - let bool_check = k.clone() * (k + Expression::Constant(-F::one())); - - q_mul_complete.clone() * bool_check - }); - } - - /// Gate used to check final scalar is recovered. - pub(super) fn create_final_scalar_gate(&self, meta: &mut ConstraintSystem) { - let scalar = meta.query_fixed(self.mul_decompose, Rotation::cur()); - let z_cur = meta.query_advice(self.z_complete, Rotation::cur()); - - meta.create_gate("Decompose scalar", |_| { - // q = 2^254 + t_q is the scalar field of Pallas - let t_q = F::from_u128(45560315531506369815346746415080538113); - - // Check that `k = scalar + t_q` - scalar.clone() * (scalar + Expression::Constant(t_q) - z_cur) - }); + // If `k_0` is 0, return `Acc - P`. If `k_0` is 1, simply return `Acc`. + let p = k_0 + .map(|k_0| { + if !k_0 { + // If `k_0` is 0, return `Acc - P` + let (x_p, y_p) = (base.x.value, base.y.value.map(|y_p| -y_p)); + let x_p_cell = region.assign_advice( + || "x_p", + self.add_config.x_p, + k_0_row + offset, + || x_p.ok_or(Error::SynthesisError), + )?; + + let y_p_cell = region.assign_advice( + || "y_p", + self.add_config.y_p, + k_0_row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; + Ok(EccPoint { + x: CellValue::::new(x_p_cell, x_p), + y: CellValue::::new(y_p_cell, y_p), + }) + } else { + // If `k_0` is 1, simply return `Acc` + let x_p_cell = region.assign_advice( + || "x_p", + self.add_config.x_p, + k_0_row + offset, + || Ok(C::Base::zero()), + )?; + + let y_p_cell = region.assign_advice( + || "y_p", + self.add_config.y_p, + k_0_row + offset, + || Ok(C::Base::zero()), + )?; + Ok(EccPoint { + x: CellValue::::new(x_p_cell, Some(C::Base::zero())), + y: CellValue::::new(y_p_cell, Some(C::Base::zero())), + }) + } + }) + .unwrap_or(Err(Error::SynthesisError))?; + + // Return the result of the final complete addition as `[scalar]B` + self.add_config + .assign_region::(&p, &acc, k_0_row + offset, region) } } #[derive(Clone, Debug)] +// `x`-coordinate of the accumulator. struct X(CellValue); impl Deref for X { type Target = CellValue; @@ -336,6 +293,7 @@ impl Deref for X { } #[derive(Copy, Clone, Debug)] +// `y`-coordinate of the accumulator. struct Y(Option); impl Deref for Y { type Target = Option; @@ -346,8 +304,9 @@ impl Deref for Y { } #[derive(Clone, Debug)] -struct ZValue(CellValue); -impl Deref for ZValue { +// Cumulative sum `z` used to decompose the scalar. +struct Z(CellValue); +impl Deref for Z { type Target = CellValue; fn deref(&self) -> &Self::Target { @@ -359,7 +318,7 @@ fn decompose_scalar(scalar: C::Base) -> Vec { // Cast from base field into scalar field. let scalar = C::Scalar::from_bytes(&scalar.to_bytes()).unwrap(); - // The scalar field `F_q = 2^254 + t_q` + // The scalar field `F_q = 2^254 + t_q`. let t_q = -(C::Scalar::from_u128(1u128 << 127).square()); // We will witness `k = scalar + t_q` diff --git a/src/circuit/gadget/ecc/chip/mul/complete.rs b/src/circuit/gadget/ecc/chip/mul/complete.rs new file mode 100644 index 000000000..8f8323011 --- /dev/null +++ b/src/circuit/gadget/ecc/chip/mul/complete.rs @@ -0,0 +1,160 @@ +use super::super::{add, double, util, CellValue, EccPoint}; +use super::Mul; +use ff::Field; + +use halo2::{ + arithmetic::{CurveAffine, FieldExt}, + circuit::Region, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector}, + poly::Rotation, +}; +use std::marker::PhantomData; + +pub struct Config { + // Selector used to constrain the cells used in complete addition. + q_mul_complete: Selector, + // Advice column used to decompose scalar in complete addition. + z_complete: Column, + // Permutation + perm: Permutation, + // Configuration used in complete addition + add_config: add::Config, + // Configuration used in point doubling + double_config: double::Config, + _marker: PhantomData, +} + +impl From<&super::Config> for Config { + fn from(config: &super::Config) -> Self { + Self { + q_mul_complete: config.q_mul_complete, + z_complete: config.z_complete, + perm: config.perm.clone(), + add_config: config.add_config.clone(), + double_config: config.double_config.clone(), + _marker: PhantomData, + } + } +} + +impl Mul for Config {} + +impl Config { + /// Gate used to check scalar decomposition is correct. + /// This is used to check the bits used in complete addition, since the incomplete + /// addition gate (controlled by `q_mul`) already checks scalar decomposition for + /// the other bits. + pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { + let q_mul_complete = meta.query_selector(self.q_mul_complete, Rotation::cur()); + let z_cur = meta.query_advice(self.z_complete, Rotation::cur()); + let z_prev = meta.query_advice(self.z_complete, Rotation::prev()); + + meta.create_gate("Decompose scalar ", |_| { + // k_{i} = z_{i} - 2⋅z_{i+1} + let k = z_cur.clone() - Expression::Constant(C::Base::from_u64(2)) * z_prev; + // (k_i) ⋅ (k_i - 1) = 0 + let bool_check = k.clone() * (k + Expression::Constant(-C::Base::one())); + + q_mul_complete.clone() * bool_check + }); + } + + pub(super) fn assign_region( + &self, + region: &mut Region<'_, C::Base>, + offset: usize, + bits: Option>, + base: &EccPoint, + mut acc: EccPoint, + mut z_val: Option, + ) -> Result<(EccPoint, Option), Error> { + // Enable selectors for complete range + for row in self.complete_range() { + self.q_mul_complete.enable(region, row + offset)?; + } + + // Convert Option> into Vec> + let bits: Vec> = if let Some(b) = bits { + b.into_iter().map(|v| Some(v)).collect() + } else { + return Err(Error::SynthesisError); + }; + + // Complete addition + for (iter, k) in bits.into_iter().enumerate() { + // Each iteration uses 4 rows (two complete additions) + let row = Self::incomplete_lo_len() + 4 * iter + 3; + + // Check scalar decomposition here + region.assign_advice( + || "z", + self.z_complete, + row + offset - 1, + || z_val.ok_or(Error::SynthesisError), + )?; + z_val = z_val + .zip(k) + .map(|(z_val, k)| C::Base::from_u64(2) * z_val + C::Base::from_u64(k as u64)); + region.assign_advice( + || "z", + self.z_complete, + row + offset, + || z_val.ok_or(Error::SynthesisError), + )?; + + let x_p = base.x.value; + let x_p_cell = region.assign_advice( + || "x_p", + self.add_config.x_p, + row + offset, + || x_p.ok_or(Error::SynthesisError), + )?; + + // If the bit is set, use `y`; if the bit is not set, use `-y` + let y_p = base.y.value; + let y_p = y_p.zip(k).map(|(y_p, k)| if !k { -y_p } else { y_p }); + + let y_p_cell = region.assign_advice( + || "y_p", + self.add_config.y_p, + row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; + let p = EccPoint { + x: CellValue::::new(x_p_cell, x_p), + y: CellValue::::new(y_p_cell, y_p), + }; + + // Acc + U + let tmp_acc = self + .add_config + .assign_region::(&p, &acc, row + offset, region)?; + + // Copy acc from `x_a`, `y_a` over to `x_p`, `y_p` on the next row + let acc_x = util::assign_and_constrain( + region, + || "copy acc x_a", + self.add_config.x_p, + row + offset + 2, + &acc.x, + &self.perm, + )?; + let acc_y = util::assign_and_constrain( + region, + || "copy acc y_a", + self.add_config.y_p, + row + offset + 2, + &acc.y, + &self.perm, + )?; + + acc = EccPoint { x: acc_x, y: acc_y }; + + // Acc + P + Acc + acc = self + .add_config + .assign_region::(&acc, &tmp_acc, row + offset + 2, region)?; + } + Ok((acc, z_val)) + } +} diff --git a/src/circuit/gadget/ecc/chip/mul/incomplete.rs b/src/circuit/gadget/ecc/chip/mul/incomplete.rs index eaec2b5c3..11cba7188 100644 --- a/src/circuit/gadget/ecc/chip/mul/incomplete.rs +++ b/src/circuit/gadget/ecc/chip/mul/incomplete.rs @@ -1,7 +1,5 @@ -use super::super::{CellValue, EccConfig, EccPoint}; -use super::{ZValue, X, Y}; -use crate::constants; -use ff::PrimeField; +use super::super::{util, CellValue, EccConfig, EccPoint}; +use super::{Mul, X, Y, Z}; use halo2::{ arithmetic::{CurveAffine, FieldExt}, circuit::Region, @@ -10,7 +8,7 @@ use halo2::{ }; use std::marker::PhantomData; -pub(super) struct IncompleteConfig { +pub(super) struct Config { // Number of bits covered by this incomplete range. num_bits: usize, // Selector used to constrain the cells used in incomplete addition. @@ -32,17 +30,14 @@ pub(super) struct IncompleteConfig { _marker: PhantomData, } -impl IncompleteConfig { +impl Mul for Config {} + +impl Config { // Columns used in processing the `hi` bits of the scalar. // `x_p, y_p` are shared across the `hi` and `lo` halves. - pub(super) fn into_hi_config(ecc_config: &EccConfig) -> Self { - // Bits k_{254} to k_{4} inclusive are used in incomplete addition. - // The `hi` half is k_{254} to k_{130} inclusive (length 125 bits). - let incomplete_hi_len = - (C::Scalar::NUM_BITS as usize - 1 - constants::NUM_COMPLETE_BITS) / 2; - + pub(super) fn hi_config(ecc_config: &EccConfig) -> Self { Self { - num_bits: incomplete_hi_len, + num_bits: Self::incomplete_hi_len(), q_mul: ecc_config.q_mul_hi, z: ecc_config.bits, x_a: ecc_config.extras[0], @@ -57,13 +52,9 @@ impl IncompleteConfig { // Columns used in processing the `lo` bits of the scalar. // `x_p, y_p` are shared across the `hi` and `lo` halves. - pub(super) fn into_lo_config(ecc_config: &EccConfig) -> Self { - // Bits k_{254} to k_{4} inclusive are used in incomplete addition. - // The `lo` half is k_{129} to k_{4} inclusive (length 126 bits). - let incomplete_lo_len = (C::Scalar::NUM_BITS as usize - constants::NUM_COMPLETE_BITS) / 2; - + pub(super) fn lo_config(ecc_config: &EccConfig) -> Self { Self { - num_bits: incomplete_lo_len, + num_bits: Self::incomplete_lo_len(), q_mul: ecc_config.q_mul_lo, z: ecc_config.extras[1], x_a: ecc_config.extras[2], @@ -171,132 +162,138 @@ impl IncompleteConfig { pub(super) fn double_and_add( &self, region: &mut Region<'_, F>, - base: &EccPoint, offset: usize, + base: &EccPoint, bits: Option>, - acc: (X, Y, ZValue), - ) -> Result<(X, Y, ZValue), Error> { + acc: (X, Y, Z), + ) -> Result<(X, Y, Z), Error> { // Enable `q_mul` on all but the last row of the incomplete range. for row in 1..(self.num_bits - 1) { self.q_mul.enable(region, offset + row)?; } // Initialise the running `z` sum for the scalar bits. - let mut z_val = acc.2.value; - let mut z_cell = region.assign_advice( + let mut z = util::assign_and_constrain( + region, || "starting z", self.z, offset, - || z_val.ok_or(Error::SynthesisError), + &acc.2, + &self.perm, )?; - region.constrain_equal(&self.perm, z_cell, acc.2.cell)?; + + // Increase offset by 1; we used row 0 for initializing `z`. + let offset = offset + 1; // Define `x_p`, `y_p` let x_p = base.x.value; let y_p = base.y.value; - let offset = offset + 1; - // Initialise acc - let mut x_a = acc.0.value; - let mut x_a_cell = region.assign_advice( + let mut x_a = util::assign_and_constrain( + region, || "starting x_a", self.x_a, offset, - || x_a.ok_or(Error::SynthesisError), + &acc.0, + &self.perm, )?; - region.constrain_equal(&self.perm, x_a_cell, acc.0.cell)?; let mut y_a = *acc.1; - // Incomplete addition - bits.map(|bits| -> Result<(), Error> { - for (row, k) in bits.iter().enumerate() { - // z_{i} = 2 * z_{i+1} + k_i - z_val = z_val.map(|z_val| F::from_u64(2) * z_val + F::from_u64(*k as u64)); - z_cell = region.assign_advice( - || "z", - self.z, - row + offset, - || z_val.ok_or(Error::SynthesisError), - )?; + // Convert Option> into Vec> + let bits: Vec> = if let Some(b) = bits { + b.into_iter().map(|v| Some(v)).collect() + } else { + return Err(Error::SynthesisError); + }; - // Assign `x_p`, `y_p` - region.assign_advice( - || "x_p", - self.x_p, - row + offset, - || x_p.ok_or(Error::SynthesisError), - )?; - region.assign_advice( - || "y_p", - self.y_p, - row + offset, - || y_p.ok_or(Error::SynthesisError), - )?; + // Incomplete addition + for (row, k) in bits.into_iter().enumerate() { + // z_{i} = 2 * z_{i+1} + k_i + let z_val = z + .value + .zip(k) + .map(|(z_val, k)| F::from_u64(2) * z_val + F::from_u64(k as u64)); + let z_cell = region.assign_advice( + || "z", + self.z, + row + offset, + || z_val.ok_or(Error::SynthesisError), + )?; + z = CellValue::new(z_cell, z_val); - // If the bit is set, use `y`; if the bit is not set, use `-y` - let y_p = y_p.map(|y_p| if !k { -y_p } else { y_p }); + // Assign `x_p`, `y_p` + region.assign_advice( + || "x_p", + self.x_p, + row + offset, + || x_p.ok_or(Error::SynthesisError), + )?; + region.assign_advice( + || "y_p", + self.y_p, + row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; - // Compute and assign λ1⋅(x_A − x_P) = y_A − y_P - let lambda1 = y_a - .zip(y_p) - .zip(x_a) - .zip(x_p) - .map(|(((y_a, y_p), x_a), x_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap()); - region.assign_advice( - || "lambda1", - self.lambda1, - row + offset, - || lambda1.ok_or(Error::SynthesisError), - )?; + // If the bit is set, use `y`; if the bit is not set, use `-y` + let y_p = y_p.zip(k).map(|(y_p, k)| if !k { -y_p } else { y_p }); - // x_R = λ1^2 - x_A - x_P - let x_r = lambda1 - .zip(x_a) - .zip(x_p) - .map(|((lambda1, x_a), x_p)| lambda1 * lambda1 - x_a - x_p); + // Compute and assign λ1⋅(x_A − x_P) = y_A − y_P + let lambda1 = y_a + .zip(y_p) + .zip(x_a.value) + .zip(x_p) + .map(|(((y_a, y_p), x_a), x_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap()); + region.assign_advice( + || "lambda1", + self.lambda1, + row + offset, + || lambda1.ok_or(Error::SynthesisError), + )?; - // λ2 = (2(y_A) / (x_A - x_R)) - λ1 - let lambda2 = - lambda1 - .zip(y_a) - .zip(x_a) - .zip(x_r) - .map(|(((lambda1, y_a), x_a), x_r)| { - F::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 - }); - region.assign_advice( - || "lambda2", - self.lambda2, - row + offset, - || lambda2.ok_or(Error::SynthesisError), - )?; + // x_R = λ1^2 - x_A - x_P + let x_r = lambda1 + .zip(x_a.value) + .zip(x_p) + .map(|((lambda1, x_a), x_p)| lambda1 * lambda1 - x_a - x_p); - // Compute and assign `x_a` for the next row - let x_a_new = lambda2 - .zip(x_a) - .zip(x_r) - .map(|((lambda2, x_a), x_r)| lambda2 * lambda2 - x_a - x_r); - y_a = lambda2 - .zip(x_a) - .zip(x_a_new) + // λ2 = (2(y_A) / (x_A - x_R)) - λ1 + let lambda2 = + lambda1 .zip(y_a) - .map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a); - x_a = x_a_new; - x_a_cell = region.assign_advice( - || "x_a", - self.x_a, - row + offset + 1, - || x_a.ok_or(Error::SynthesisError), - )?; - } - Ok(()) - }); + .zip(x_a.value) + .zip(x_r) + .map(|(((lambda1, y_a), x_a), x_r)| { + F::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 + }); + region.assign_advice( + || "lambda2", + self.lambda2, + row + offset, + || lambda2.ok_or(Error::SynthesisError), + )?; + + // Compute and assign `x_a` for the next row + let x_a_new = lambda2 + .zip(x_a.value) + .zip(x_r) + .map(|((lambda2, x_a), x_r)| lambda2 * lambda2 - x_a - x_r); + y_a = lambda2 + .zip(x_a.value) + .zip(x_a_new) + .zip(y_a) + .map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a); + let x_a_val = x_a_new; + let x_a_cell = region.assign_advice( + || "x_a", + self.x_a, + row + offset + 1, + || x_a_val.ok_or(Error::SynthesisError), + )?; + x_a = CellValue::new(x_a_cell, x_a_val); + } - Ok(( - X(CellValue::::new(x_a_cell, x_a)), - Y(y_a), - ZValue(CellValue::::new(z_cell, z_val)), - )) + Ok((X(x_a), Y(y_a), Z(z))) } } From 46ac3f28b2eb80b7ed0a6b154c5c3fa29364d114 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 25 May 2021 13:17:42 +0800 Subject: [PATCH 25/49] Fix bugs in complete addition formulae. Add tests to check that points related by the cubic endomorphism are not constrained to add up to (0,0). --- src/circuit/gadget/ecc.rs | 25 +- src/circuit/gadget/ecc/chip.rs | 2 +- src/circuit/gadget/ecc/chip/add.rs | 495 ++++++++---------- src/circuit/gadget/ecc/chip/mul.rs | 4 +- src/circuit/gadget/ecc/chip/mul/complete.rs | 12 +- .../gadget/ecc/chip/mul_fixed/full_width.rs | 2 +- .../gadget/ecc/chip/mul_fixed/short.rs | 2 +- 7 files changed, 253 insertions(+), 289 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index ece507412..b5b81fb51 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -385,7 +385,7 @@ mod tests { use ff::Field; use group::{Curve, Group}; use halo2::{ - arithmetic::{CurveAffine, FieldExt}, + arithmetic::{CurveAffine, CurveExt, FieldExt}, circuit::{layouter::SingleChipLayouter, Layouter}, dev::MockProver, pasta::pallas, @@ -462,7 +462,6 @@ mod tests { // Check complete addition (other cases) { let mut checks = Vec::<(C::CurveExt, super::Point>)>::new(); - // P + Q let real_added = p_val + q_val; let added_complete = p.add(layouter.namespace(|| "P + Q"), &q)?; @@ -483,6 +482,28 @@ mod tests { let added_complete = zero.add(layouter.namespace(|| "P + 𝒪"), &p)?; checks.push((real_added, added_complete)); + // (x, y) + (ζx, -y) should behave like normal P + Q. + let endo_p = p_val.to_curve().endo(); + let real_added = p_val.to_curve() + endo_p; + let endo_p = super::Point::new( + chip.clone(), + layouter.namespace(|| "point"), + Some(endo_p.to_affine()), + )?; + let added_complete = p.add(layouter.namespace(|| "P + endo(P)"), &endo_p)?; + checks.push((real_added, added_complete)); + + // (x, y) + ((ζ^2)x, -y) + let endo_p = p_val.to_curve().endo().endo(); + let real_added = p_val.to_curve() + endo_p; + let endo_p = super::Point::new( + chip.clone(), + layouter.namespace(|| "point"), + Some(endo_p.to_affine()), + )?; + let added_complete = p.add(layouter.namespace(|| "P + endo(P)"), &endo_p)?; + checks.push((real_added, added_complete)); + for check in checks.into_iter() { let real_added = check.0; let added_complete = check.1; diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 3de6cf35c..8c01d2af8 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -396,7 +396,7 @@ impl EccInstructions for EccChip { let config: add::Config = self.config().into(); layouter.assign_region( || "point addition", - |mut region| config.assign_region::(a, b, 0, &mut region), + |mut region| config.assign_region(a, b, 0, &mut region), ) } diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 231136769..792b9b340 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -1,8 +1,6 @@ use super::{util, CellValue, EccConfig, EccPoint}; -use ff::Field; -use group::{Curve, Group}; use halo2::{ - arithmetic::{CurveAffine, FieldExt}, + arithmetic::FieldExt, circuit::Region, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector}, poly::Rotation, @@ -21,14 +19,14 @@ pub struct Config { pub x_qr: Column, // y-coordinate of Q or R in P + Q = R pub y_qr: Column, - // a or alpha - a_alpha: Column, - // b or beta - b_beta: Column, - // c or gamma - c_gamma: Column, - // d or delta - d_delta: Column, + // α = inv0(x_q - x_p) + alpha: Column, + // β = inv0(x_p) + beta: Column, + // γ = inv0(x_q) + gamma: Column, + // δ = inv0(y_p + y_q) if x_q = x_p, 0 otherwise + delta: Column, // Permutation perm: Permutation, } @@ -42,10 +40,10 @@ impl From<&EccConfig> for Config { y_p: ecc_config.P.1, x_qr: ecc_config.extras[0], y_qr: ecc_config.extras[1], - a_alpha: ecc_config.extras[2], - b_beta: ecc_config.extras[3], - c_gamma: ecc_config.extras[4], - d_delta: ecc_config.lambda.1, + alpha: ecc_config.extras[2], + beta: ecc_config.extras[3], + gamma: ecc_config.extras[4], + delta: ecc_config.lambda.1, perm: ecc_config.perm.clone(), } } @@ -63,139 +61,149 @@ impl Config { let y_r = meta.query_advice(self.y_qr, Rotation::next()); let lambda = meta.query_advice(self.lambda, Rotation::cur()); - let a = meta.query_advice(self.a_alpha, Rotation::cur()); - let b = meta.query_advice(self.b_beta, Rotation::cur()); - let c = meta.query_advice(self.c_gamma, Rotation::cur()); - let d = meta.query_advice(self.d_delta, Rotation::cur()); - - // \alpha = (x_q - x_p)^{-1} - let alpha = meta.query_advice(self.a_alpha, Rotation::next()); - // \beta = x_p^{-1} - let beta = meta.query_advice(self.b_beta, Rotation::next()); - // \gamma = x_q^{-1} - let gamma = meta.query_advice(self.c_gamma, Rotation::next()); - // \delta = (y_p + y_q)^{-1} - let delta = meta.query_advice(self.d_delta, Rotation::next()); - - // Boolean checks on A, B, C, D - { - meta.create_gate("Check A is boolean", |_| { - q_add.clone() * a.clone() * (Expression::Constant(F::one()) - a.clone()) - }); - meta.create_gate("Check B is boolean", |_| { - q_add.clone() * b.clone() * (Expression::Constant(F::one()) - b.clone()) - }); - meta.create_gate("Check C is boolean", |_| { - q_add.clone() * c.clone() * (Expression::Constant(F::one()) - c.clone()) - }); - meta.create_gate("Check D is boolean", |_| { - q_add.clone() * d.clone() * (Expression::Constant(F::one()) - d.clone()) - }); - } + // inv0(x) is 0 if x = 0, 1/x otherwise. + // α = inv0(x_q - x_p) + let alpha = meta.query_advice(self.alpha, Rotation::cur()); + // β = inv0(x_p) + let beta = meta.query_advice(self.beta, Rotation::cur()); + // γ = inv0(x_q) + let gamma = meta.query_advice(self.gamma, Rotation::cur()); + // δ = inv0(y_p + y_q) if x_q = x_p, 0 otherwise + let delta = meta.query_advice(self.delta, Rotation::cur()); + + // Useful composite expressions + // α ⋅(x_q - x_p) + let if_alpha = (x_q.clone() - x_p.clone()) * alpha; + // β ⋅ x_p + let if_beta = x_p.clone() * beta; + // γ ⋅ x_q + let if_gamma = x_q.clone() * gamma; + // δ ⋅(y_p + y_q) + let if_delta = (y_q.clone() + y_p.clone()) * delta; + + // Useful constants + let one = Expression::Constant(F::one()); + let two = Expression::Constant(F::from_u64(2)); + let three = Expression::Constant(F::from_u64(3)); - // Logical implications of Boolean flags + // Handle cases in incomplete addition { - // (x_q − x_p)⋅α = 1 − A - meta.create_gate("x_q = x_p ⟹ A", |_| { - let lhs = (x_q.clone() - x_p.clone()) * alpha.clone(); - let rhs = Expression::Constant(F::one()) - a.clone(); - q_add.clone() * (lhs - rhs) - }); - - // x_p⋅β = 1 − B - meta.create_gate("x_p = 0 ⟹ B", |_| { - let lhs = x_p.clone() * beta.clone(); - let rhs = Expression::Constant(F::one()) - b.clone(); - q_add.clone() * (lhs - rhs) - }); - - // B⋅x_p = 0 - meta.create_gate("B ⟹ x_p = 0", |_| q_add.clone() * b.clone() * x_p.clone()); - - // x_q⋅γ = 1 − C - meta.create_gate("x_q = 0 ⟹ C", |_| { - let lhs = x_q.clone() * gamma.clone(); - let rhs = Expression::Constant(F::one()) - c.clone(); - q_add.clone() * (lhs - rhs) + // (x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p))=0 + meta.create_gate( + "(x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p))=0", + |_| { + let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q − x_p) + + let y_q_minus_y_p = y_q.clone() - y_p.clone(); // (y_q − y_p) + let incomplete = x_q_minus_x_p.clone() * lambda.clone() - y_q_minus_y_p; // (x_q − x_p)⋅λ − (y_q−y_p) + + // q_add ⋅(x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p)) + q_add.clone() * x_q_minus_x_p * incomplete + }, + ); + + // (1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2) = 0 + meta.create_gate("(1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2) = 0", |_| { + let three_x_p_sq = three * x_p.clone() * x_p.clone(); // 3x_p^2 + let two_y_p = two.clone() * y_p.clone(); // 2y_p + let derivative = two_y_p * lambda.clone() - three_x_p_sq; // (2y_p ⋅λ - 3x_p^2) + + // q_add ⋅(1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2) + q_add.clone() * (one.clone() - if_alpha.clone()) * derivative }); - // C⋅x_q = 0 - meta.create_gate("C ⟹ x_q = 0", |_| q_add.clone() * c.clone() * x_q.clone()); + // x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r) = 0 + meta.create_gate( + "x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r) = 0", + |_| { + let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q - x_p) + let incomplete = + lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); // (λ^2 - x_p - x_q - x_r) + + // q_add ⋅ x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r) + q_add.clone() * x_p.clone() * x_q.clone() * x_q_minus_x_p * incomplete + }, + ); + + // x_p⋅x_q⋅(x_q - x_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0 + meta.create_gate( + "x_p⋅x_q⋅(x_q - x_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0", + |_| { + let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q - x_p) + let x_p_minus_x_r = x_p.clone() - x_r.clone(); // (x_p - x_r) + + // q_add ⋅ x_p⋅x_q⋅(x_q - x_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) + q_add.clone() + * x_p.clone() + * x_q.clone() + * x_q_minus_x_p + * (lambda.clone() * x_p_minus_x_r - y_p.clone() - y_r.clone()) + }, + ); + + // x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) = 0 + meta.create_gate("y_r check", |_| { + let y_q_plus_y_p = y_q.clone() + y_p.clone(); // (y_q + y_p) + let incomplete = + lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); // (λ^2 - x_p - x_q - x_r) - // (y_q + y_p)⋅δ = 1 − D - meta.create_gate("y_q = y_p ⟹ D", |_| { - let lhs = (y_q.clone() + y_p.clone()) * delta.clone(); - let rhs = Expression::Constant(F::one()) - d.clone(); - q_add.clone() * (lhs - rhs) + // q_add ⋅ x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) + q_add.clone() * x_p.clone() * x_q.clone() * y_q_plus_y_p * incomplete }); - } - // Handle cases in incomplete addition - { - // (x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p))=0 - meta.create_gate("x equality", |_| { - let equal = x_q.clone() - x_p.clone(); - let unequal = equal.clone() * lambda.clone() - (y_q.clone() - y_p.clone()); - q_add.clone() * equal * unequal + // x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0 + meta.create_gate( + "x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0", + |_| { + let y_q_plus_y_p = y_q.clone() + y_p.clone(); // (y_q + y_p) + let x_p_minus_x_r = x_p.clone() - x_r.clone(); // (x_p - x_r) + + // q_add ⋅ x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) + q_add.clone() + * x_p.clone() + * x_q.clone() + * y_q_plus_y_p + * (lambda.clone() * x_p_minus_x_r - y_p.clone() - y_r.clone()) + }, + ); + + meta.create_gate("(1 - x_p * β) * (x_r - x_q) = 0", |_| { + q_add.clone() * (one.clone() - if_beta.clone()) * (x_r.clone() - x_q.clone()) }); - // A⋅(2y_p⋅λ − 3x_p^2) = 0 - meta.create_gate("x equal, y nonzero", |_| { - let three_x_p_sq = Expression::Constant(F::from_u64(3)) * x_p.clone() * x_p.clone(); - let two_y_p = Expression::Constant(F::from_u64(2)) * y_p.clone(); - let gradient = two_y_p * lambda.clone() - three_x_p_sq; - q_add.clone() * a.clone() * gradient + meta.create_gate("(1 - x_p * β) * (y_r - y_q) = 0", |_| { + q_add.clone() * (one.clone() - if_beta) * (y_r.clone() - y_q.clone()) }); - // (1 − B)⋅(1 − C)⋅(1 − D)⋅(λ^2 − x_p − x_q − x_r) + B⋅(x_r − x_q) = 0 - meta.create_gate("x_r check", |_| { - let not_b = Expression::Constant(F::one()) - b.clone(); - let not_c = Expression::Constant(F::one()) - c.clone(); - let not_d = Expression::Constant(F::one()) - d.clone(); - let x_r_lambda = - lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); - let x_r_x_q = b.clone() * (x_r.clone() - x_q.clone()); - q_add.clone() * (not_b * not_c * not_d * x_r_lambda + x_r_x_q) + meta.create_gate("(1 - x_q * γ) * (x_r - x_p) = 0", |_| { + q_add.clone() * (one.clone() - if_gamma.clone()) * (x_r.clone() - x_p.clone()) }); - // (1 − B)⋅(1 − C)⋅(1 − D)⋅(λ⋅(x_p − x_r) − y_p − y_r) + B⋅(y_r − y_q) = 0 - meta.create_gate("y_r check", |_| { - let not_b = Expression::Constant(F::one()) - b.clone(); - let not_c = Expression::Constant(F::one()) - c.clone(); - let not_d = Expression::Constant(F::one()) - d.clone(); - let y_r_lambda = - lambda.clone() * (x_p.clone() - x_r.clone()) - y_p.clone() - y_r.clone(); - let y_r_y_q = b.clone() * (y_r.clone() - y_q.clone()); - q_add.clone() * (not_b * not_c * not_d * y_r_lambda + y_r_y_q) + meta.create_gate("(1 - x_q * γ) * (y_r - y_p) = 0", |_| { + q_add.clone() * (one.clone() - if_gamma) * (y_r.clone() - y_p.clone()) }); - // C⋅(x_r − x_p) = 0 - meta.create_gate("x_r = x_p when x_q = 0", |_| { - q_add.clone() * (c.clone() * (x_r.clone() - x_p.clone())) + // ((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * x_r + meta.create_gate("((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * x_r", |_| { + q_add.clone() * (one.clone() - if_alpha.clone() - if_delta.clone()) * x_r.clone() }); - // C⋅(y_r − y_p) = 0 - meta.create_gate("y_r = y_p when x_q = 0", |_| { - q_add.clone() * (c.clone() * (y_r.clone() - y_p.clone())) + // ((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * y_r + meta.create_gate("((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * y_r", |_| { + q_add * (one - if_alpha - if_delta) * y_r }); - - // D⋅x_r = 0 - meta.create_gate("D ⟹ x_r = 0", |_| q_add.clone() * d.clone() * x_r.clone()); - - // D⋅y_r = 0 - meta.create_gate("D ⟹ y_r = 0", |_| q_add.clone() * d.clone() * y_r.clone()); } } #[allow(clippy::many_single_char_names)] #[allow(non_snake_case)] - pub(super) fn assign_region( + pub(super) fn assign_region( &self, - a: &EccPoint, - b: &EccPoint, + a: &EccPoint, + b: &EccPoint, offset: usize, - region: &mut Region<'_, C::Base>, - ) -> Result, Error> { + region: &mut Region<'_, F>, + ) -> Result, Error> { // Enable `q_add` selector self.q_add.enable(region, offset)?; @@ -210,168 +218,103 @@ impl Config { let (x_p, y_p) = (a.x.value, a.y.value); let (x_q, y_q) = (b.x.value, b.y.value); - // Assign A, B, C, D, α, β, γ, δ - { - x_p.zip(x_q) - .zip(y_p) - .zip(y_q) - .map(|(((x_p, x_q), y_p), y_q)| -> Result<(), Error> { - if x_q == x_p { - // x_q = x_p ⟹ A - region.assign_advice( - || "set A", - self.a_alpha, - offset, - || Ok(C::Base::one()), - )?; - - // Doubling case, λ = 3(x_p)^2 / (2 * y_p) - if y_p != C::Base::zero() { - let lambda_val = C::Base::from_u64(3) - * x_p - * x_p - * (C::Base::from_u64(2) * y_p).invert().unwrap(); - region.assign_advice( - || "set lambda", - self.lambda, - offset, - || Ok(lambda_val), - )?; - } - } else { - // α = 1 / (x_q - x_p) - let alpha_val = (x_q - x_p).invert().unwrap(); - region.assign_advice( - || "set alpha", - self.a_alpha, - offset + 1, - || Ok(alpha_val), - )?; - - // Non-doubling case, λ = (y_q - y_p) / (x_q - x_p) - let lambda_val = (x_q - x_p).invert().unwrap() * (y_q - y_p); - region.assign_advice( - || "set lambda", - self.lambda, - offset, - || Ok(lambda_val), - )?; - } + x_p.zip(x_q) + .zip(y_p) + .zip(y_q) + .map(|(((x_p, x_q), y_p), y_q)| -> Result, Error> { + // inv0(x) evaluates to 0 if x = 0, and 1/x otherwise. - if x_p == C::Base::zero() { - // x_p = 0 ⟹ B - region.assign_advice( - || "set B", - self.b_beta, - offset, - || Ok(C::Base::one()), - )?; - } else { - // β = 1 / x_p - let beta_val = x_p.invert().unwrap(); - region.assign_advice( - || "set beta", - self.b_beta, - offset + 1, - || Ok(beta_val), - )?; - } - if x_q == C::Base::zero() { - // x_q = 0 ⟹ C - region.assign_advice( - || "set C", - self.c_gamma, - offset, - || Ok(C::Base::one()), - )?; + // Assign α = inv0(x_q - x_p) + let alpha = if x_q != x_p { + (x_q - x_p).invert().unwrap() + } else { + F::zero() + }; + region.assign_advice(|| "α", self.alpha, offset, || Ok(alpha))?; + + // Assign β = inv0(x_p) + let beta = if x_p != F::zero() { + x_p.invert().unwrap() + } else { + F::zero() + }; + region.assign_advice(|| "β", self.beta, offset, || Ok(beta))?; + + // Assign γ = inv0(x_q) + let gamma = if x_q != F::zero() { + x_q.invert().unwrap() + } else { + F::zero() + }; + region.assign_advice(|| "γ", self.gamma, offset, || Ok(gamma))?; + + // Assign δ = inv0(y_q + y_p) if x_q = x_p, 0 otherwise + let delta = if x_q == x_p { + if y_q != -y_p { + (y_q + y_p).invert().unwrap() } else { - // γ = 1 / x_q - let gamma_val = x_q.invert().unwrap(); - region.assign_advice( - || "set gamma", - self.c_gamma, - offset + 1, - || Ok(gamma_val), - )?; + F::zero() } + } else { + F::zero() + }; + region.assign_advice(|| "δ", self.delta, offset, || Ok(delta))?; - if y_p == -y_q { - // y_p = -y_p ⟹ D - region.assign_advice( - || "set D", - self.d_delta, - offset, - || Ok(C::Base::one()), - )?; + // Assign lambda + let lambda = if x_q != x_p { + // λ = (y_q - y_p)/(x_q - x_p) + (y_q - y_p) * (x_q - x_p).invert().unwrap() + } else { + if y_p != F::zero() { + // 3(x_p)^2 + let three_x_p_sq = F::from_u64(3) * x_p * x_p; + // 2(y_p) + let two_y_p = F::from_u64(2) * y_p; + // λ = 3(x_p)^2 / 2(y_p) + three_x_p_sq * two_y_p.invert().unwrap() } else { - // δ = 1 / (y_q + y_p) - let delta_val = (y_q + y_p).invert().unwrap(); - region.assign_advice( - || "set delta", - self.d_delta, - offset + 1, - || Ok(delta_val), - )?; + F::zero() } - Ok(()) - }); - } - - // Compute R = P + Q - let r = x_p - .zip(y_p) - .zip(x_q) - .zip(y_q) - .map(|(((x_p, y_p), x_q), y_q)| { - // If either `p` or `q` are (0,0) represent them as C::zero() - let p = if x_p == C::Base::zero() && y_p == C::Base::zero() { - C::identity() + }; + region.assign_advice(|| "λ", self.lambda, offset, || Ok(lambda))?; + + // Assign x_r + let x_r = if x_p == F::zero() { + // 0 + Q = Q + x_q + } else if x_q == F::zero() { + // P + 0 = P + x_p + } else if (x_q == x_p) && (y_q == -y_p) { + // P + (-P) maps to (0,0) + F::zero() } else { - C::from_xy(x_p, y_p).unwrap() + // x_r = λ^2 - x_p - x_q + lambda * lambda - x_p - x_q }; - let q = if x_q == C::Base::zero() && y_q == C::Base::zero() { - C::identity() + let x_r_cell = region.assign_advice(|| "x_r", self.x_qr, offset + 1, || Ok(x_r))?; + + // Assign y_r + let y_r = if x_p == F::zero() { + // 0 + Q = Q + y_q + } else if x_q == F::zero() { + // P + 0 = P + y_p + } else if (x_q == x_p) && (y_q == -y_p) { + // P + (-P) maps to (0,0) + F::zero() } else { - C::from_xy(x_q, y_q).unwrap() + // y_r = λ(x_p - x_r) - y_p + lambda * (x_p - x_r) - y_p }; - p + q - }); - - let x_r_val = r.map(|r| { - if r.is_identity().into() { - C::Base::zero() - } else { - *r.to_affine().coordinates().unwrap().x() - } - }); - - let y_r_val = r.map(|r| { - if r.is_identity().into() { - C::Base::zero() - } else { - *r.to_affine().coordinates().unwrap().y() - } - }); - - // Assign `x_r` - let x_r_cell = region.assign_advice( - || "set x_r", - self.x_qr, - offset + 1, - || x_r_val.ok_or(Error::SynthesisError), - )?; - - // Assign `y_r` - let y_r_cell = region.assign_advice( - || "set y_r", - self.y_qr, - offset + 1, - || y_r_val.ok_or(Error::SynthesisError), - )?; - - Ok(EccPoint { - x: CellValue::::new(x_r_cell, x_r_val), - y: CellValue::::new(y_r_cell, y_r_val), - }) + let y_r_cell = region.assign_advice(|| "y_r", self.y_qr, offset + 1, || Ok(y_r))?; + + Ok(EccPoint { + x: CellValue::::new(x_r_cell, Some(x_r)), + y: CellValue::::new(y_r_cell, Some(y_r)), + }) + }) + .unwrap_or(Err(Error::SynthesisError)) } } diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index b301778fa..32c0bf843 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -198,7 +198,7 @@ impl Config { mut z_val: Option, ) -> Result, Error> { // Assign the final `z` value. - let k_0_row = Self::incomplete_lo_len() + self.complete_range().len() * 4 + 4; + let k_0_row = Self::incomplete_lo_len() + self.complete_range().len() * 2 + 4; z_val = z_val .zip(k_0) .map(|(z_val, k_0)| C::Base::from_u64(2) * z_val + C::Base::from_u64(k_0 as u64)); @@ -277,7 +277,7 @@ impl Config { // Return the result of the final complete addition as `[scalar]B` self.add_config - .assign_region::(&p, &acc, k_0_row + offset, region) + .assign_region(&p, &acc, k_0_row + offset, region) } } diff --git a/src/circuit/gadget/ecc/chip/mul/complete.rs b/src/circuit/gadget/ecc/chip/mul/complete.rs index 8f8323011..d7395906e 100644 --- a/src/circuit/gadget/ecc/chip/mul/complete.rs +++ b/src/circuit/gadget/ecc/chip/mul/complete.rs @@ -82,8 +82,8 @@ impl Config { // Complete addition for (iter, k) in bits.into_iter().enumerate() { - // Each iteration uses 4 rows (two complete additions) - let row = Self::incomplete_lo_len() + 4 * iter + 3; + // Each iteration uses 2 rows (two complete additions) + let row = Self::incomplete_lo_len() + 2 * iter + 3; // Check scalar decomposition here region.assign_advice( @@ -128,14 +128,14 @@ impl Config { // Acc + U let tmp_acc = self .add_config - .assign_region::(&p, &acc, row + offset, region)?; + .assign_region(&p, &acc, row + offset, region)?; // Copy acc from `x_a`, `y_a` over to `x_p`, `y_p` on the next row let acc_x = util::assign_and_constrain( region, || "copy acc x_a", self.add_config.x_p, - row + offset + 2, + row + offset + 1, &acc.x, &self.perm, )?; @@ -143,7 +143,7 @@ impl Config { region, || "copy acc y_a", self.add_config.y_p, - row + offset + 2, + row + offset + 1, &acc.y, &self.perm, )?; @@ -153,7 +153,7 @@ impl Config { // Acc + P + Acc acc = self .add_config - .assign_region::(&acc, &tmp_acc, row + offset + 2, region)?; + .assign_region(&acc, &tmp_acc, row + offset + 1, region)?; } Ok((acc, z_val)) } 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 e8fffe01f..5b6dae891 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -109,6 +109,6 @@ impl Config { // Add to the accumulator and return the final result as `[scalar]B`. self.add_config - .assign_region::(&mul_b, &acc, offset + constants::NUM_WINDOWS, region) + .assign_region(&mul_b, &acc, offset + constants::NUM_WINDOWS, region) } } diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index 275e30b6a..15ad24e6c 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -136,7 +136,7 @@ impl Config { self.assign_region_inner(region, offset, &scalar.into(), &base.into())?; // Add to the cumulative sum to get `[magnitude]B`. - let magnitude_mul = self.add_config.assign_region::( + let magnitude_mul = self.add_config.assign_region( &mul_b, &acc, offset + constants::NUM_WINDOWS_SHORT, From f911b7091ce4cc28f0c2420d90fdf6ce9384a3e4 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 25 May 2021 16:51:36 +0800 Subject: [PATCH 26/49] Clean up load.rs --- src/circuit/gadget/ecc/chip/load.rs | 295 ++++++++++++++-------------- 1 file changed, 145 insertions(+), 150 deletions(-) diff --git a/src/circuit/gadget/ecc/chip/load.rs b/src/circuit/gadget/ecc/chip/load.rs index 323b9163e..dd117d149 100644 --- a/src/circuit/gadget/ecc/chip/load.rs +++ b/src/circuit/gadget/ecc/chip/load.rs @@ -32,192 +32,187 @@ pub struct OrchardFixedBaseShort { pub u_short: UShort, } +pub(super) fn commit_ivk_r() -> OrchardFixedBase { + let commit_ivk_r = constants::commit_ivk_r::generator(); + OrchardFixedBase { + base: OrchardFixedBases::CommitIvkR(commit_ivk_r), + lagrange_coeffs: commit_ivk_r.0.compute_lagrange_coeffs(NUM_WINDOWS).into(), + z: constants::commit_ivk_r::Z.into(), + u: constants::commit_ivk_r::U.into(), + } +} + +pub(super) fn note_commit_r() -> OrchardFixedBase { + let note_commit_r = constants::note_commit_r::generator(); + OrchardFixedBase { + base: OrchardFixedBases::NoteCommitR(note_commit_r), + lagrange_coeffs: note_commit_r.0.compute_lagrange_coeffs(NUM_WINDOWS).into(), + z: constants::note_commit_r::Z.into(), + u: constants::note_commit_r::U.into(), + } +} + +pub(super) fn nullifier_k() -> OrchardFixedBase { + let nullifier_k = constants::nullifier_k::generator(); + OrchardFixedBase { + base: OrchardFixedBases::NullifierK(nullifier_k), + lagrange_coeffs: nullifier_k.0.compute_lagrange_coeffs(NUM_WINDOWS).into(), + z: constants::nullifier_k::Z.into(), + u: constants::nullifier_k::U.into(), + } +} + +pub(super) fn value_commit_r() -> OrchardFixedBase { + let value_commit_r = constants::value_commit_r::generator(); + OrchardFixedBase { + base: OrchardFixedBases::ValueCommitR(value_commit_r), + lagrange_coeffs: value_commit_r.0.compute_lagrange_coeffs(NUM_WINDOWS).into(), + z: constants::value_commit_r::Z.into(), + u: constants::value_commit_r::U.into(), + } +} + +pub(super) fn value_commit_v() -> OrchardFixedBaseShort { + let value_commit_v = constants::value_commit_v::generator(); + OrchardFixedBaseShort { + base: OrchardFixedBasesShort(value_commit_v), + lagrange_coeffs_short: value_commit_v + .0 + .compute_lagrange_coeffs(NUM_WINDOWS_SHORT) + .into(), + z_short: constants::value_commit_v::Z_SHORT.into(), + u_short: constants::value_commit_v::U_SHORT.into(), + } +} + #[derive(Clone, Debug)] // 8 coefficients per window pub struct WindowLagrangeCoeffs(pub Box<[F; H]>); +impl From<&[F; H]> for WindowLagrangeCoeffs { + fn from(array: &[F; H]) -> Self { + Self(Box::new(*array)) + } +} + #[derive(Clone, Debug)] // 85 windows per base (with the exception of ValueCommitV) pub struct LagrangeCoeffs(pub Box<[WindowLagrangeCoeffs; constants::NUM_WINDOWS]>); +impl From>> for LagrangeCoeffs { + fn from(windows: Vec>) -> Self { + Self(windows.into_boxed_slice().try_into().unwrap()) + } +} + +impl From> for LagrangeCoeffs { + fn from(arrays: Vec<[F; H]>) -> Self { + let windows: Vec> = + arrays.iter().map(|array| array.into()).collect(); + windows.into() + } +} + #[derive(Clone, Debug)] // 22 windows for ValueCommitV pub struct LagrangeCoeffsShort(pub Box<[WindowLagrangeCoeffs; NUM_WINDOWS_SHORT]>); +impl From>> for LagrangeCoeffsShort { + fn from(windows: Vec>) -> Self { + Self(windows.into_boxed_slice().try_into().unwrap()) + } +} + +impl From> for LagrangeCoeffsShort { + fn from(arrays: Vec<[F; H]>) -> Self { + let windows: Vec> = + arrays.iter().map(|array| array.into()).collect(); + windows.into() + } +} + #[derive(Clone, Debug)] // 85 Z's per base (with the exception of ValueCommitV) pub struct Z(pub Box<[F; NUM_WINDOWS]>); +impl From<[u64; NUM_WINDOWS]> for Z { + fn from(zs: [u64; NUM_WINDOWS]) -> Self { + Self( + zs.iter() + .map(|z| F::from_u64(*z)) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap(), + ) + } +} + #[derive(Clone, Debug)] // 22 Z's for ValueCommitV pub struct ZShort(pub Box<[F; NUM_WINDOWS_SHORT]>); +impl From<[u64; NUM_WINDOWS_SHORT]> for ZShort { + fn from(zs: [u64; NUM_WINDOWS_SHORT]) -> Self { + Self( + zs.iter() + .map(|z| F::from_u64(*z)) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap(), + ) + } +} + #[derive(Clone, Debug)] // 8 u's per window pub struct WindowUs(pub Box<[F; H]>); +impl From<&[[u8; 32]; H]> for WindowUs { + fn from(window_us: &[[u8; 32]; H]) -> Self { + Self( + window_us + .iter() + .map(|u| F::from_bytes(&u).unwrap()) + .collect::>() + .into_boxed_slice() + .try_into() + .unwrap(), + ) + } +} + #[derive(Clone, Debug)] // 85 windows per base (with the exception of ValueCommitV) pub struct U(pub Box<[WindowUs; NUM_WINDOWS]>); -#[derive(Clone, Debug)] -// 22 windows for ValueCommitV -pub struct UShort(pub Box<[WindowUs; NUM_WINDOWS_SHORT]>); - -pub(super) fn commit_ivk_r() -> OrchardFixedBase { - let commit_ivk_r = constants::commit_ivk_r::generator(); - OrchardFixedBase { - base: OrchardFixedBases::CommitIvkR(commit_ivk_r), - lagrange_coeffs: load_lagrange_coeffs(commit_ivk_r.0.compute_lagrange_coeffs(NUM_WINDOWS)), - z: load_z(&constants::commit_ivk_r::Z), - u: process_u(&constants::commit_ivk_r::U), +impl From>> for U { + fn from(windows: Vec>) -> Self { + Self(windows.into_boxed_slice().try_into().unwrap()) } } -pub(super) fn note_commit_r() -> OrchardFixedBase { - let note_commit_r = constants::note_commit_r::generator(); - OrchardFixedBase { - base: OrchardFixedBases::NoteCommitR(note_commit_r), - lagrange_coeffs: load_lagrange_coeffs(note_commit_r.0.compute_lagrange_coeffs(NUM_WINDOWS)), - z: load_z(&constants::note_commit_r::Z), - u: process_u(&constants::note_commit_r::U), +impl From<[[[u8; 32]; H]; NUM_WINDOWS]> for U { + fn from(window_us: [[[u8; 32]; H]; NUM_WINDOWS]) -> Self { + let windows: Vec> = window_us.iter().map(|us| us.into()).collect(); + windows.into() } } -pub(super) fn nullifier_k() -> OrchardFixedBase { - let nullifier_k = constants::nullifier_k::generator(); - OrchardFixedBase { - base: OrchardFixedBases::NullifierK(nullifier_k), - lagrange_coeffs: load_lagrange_coeffs(nullifier_k.0.compute_lagrange_coeffs(NUM_WINDOWS)), - z: load_z(&constants::nullifier_k::Z), - u: process_u(&constants::nullifier_k::U), - } -} +#[derive(Clone, Debug)] +// 22 windows for ValueCommitV +pub struct UShort(pub Box<[WindowUs; NUM_WINDOWS_SHORT]>); -pub(super) fn value_commit_r() -> OrchardFixedBase { - let value_commit_r = constants::value_commit_r::generator(); - OrchardFixedBase { - base: OrchardFixedBases::ValueCommitR(value_commit_r), - lagrange_coeffs: load_lagrange_coeffs( - value_commit_r.0.compute_lagrange_coeffs(NUM_WINDOWS), - ), - z: load_z(&constants::value_commit_r::Z), - u: process_u(&constants::value_commit_r::U), +impl From>> for UShort { + fn from(windows: Vec>) -> Self { + Self(windows.into_boxed_slice().try_into().unwrap()) } } -pub(super) fn value_commit_v() -> OrchardFixedBaseShort { - let value_commit_v = constants::value_commit_v::generator(); - OrchardFixedBaseShort { - base: OrchardFixedBasesShort(value_commit_v), - lagrange_coeffs_short: load_lagrange_coeffs_short( - value_commit_v.0.compute_lagrange_coeffs(NUM_WINDOWS_SHORT), - ), - z_short: load_z_short(&constants::value_commit_v::Z_SHORT), - u_short: process_u_short(&constants::value_commit_v::U_SHORT), - } -} - -fn load_lagrange_coeffs(coeffs: Vec<[F; H]>) -> LagrangeCoeffs { - LagrangeCoeffs( - coeffs - .iter() - .map(|window| { - WindowLagrangeCoeffs( - window - .iter() - .copied() - .collect::>() - .into_boxed_slice() - .try_into() - .unwrap(), - ) - }) - .collect::>() - .into_boxed_slice() - .try_into() - .unwrap(), - ) -} - -fn load_lagrange_coeffs_short(coeffs: Vec<[F; H]>) -> LagrangeCoeffsShort { - LagrangeCoeffsShort( - coeffs - .iter() - .map(|window| { - WindowLagrangeCoeffs( - window - .iter() - .copied() - .collect::>() - .into_boxed_slice() - .try_into() - .unwrap(), - ) - }) - .collect::>() - .into_boxed_slice() - .try_into() - .unwrap(), - ) -} - -fn load_z(zs: &[u64]) -> Z { - Z(zs.iter() - .map(|z| F::from_u64(*z)) - .collect::>() - .into_boxed_slice() - .try_into() - .unwrap()) -} - -fn load_z_short(zs: &[u64]) -> ZShort { - ZShort( - zs.iter() - .map(|z| F::from_u64(*z)) - .collect::>() - .into_boxed_slice() - .try_into() - .unwrap(), - ) -} - -fn process_u(us: &[[[u8; 32]; H]]) -> U { - U(us.iter() - .map(|window_us| { - WindowUs( - window_us - .iter() - .map(|u| F::from_bytes(&u).unwrap()) - .collect::>() - .into_boxed_slice() - .try_into() - .unwrap(), - ) - }) - .collect::>() - .into_boxed_slice() - .try_into() - .unwrap()) -} - -fn process_u_short(us: &[[[u8; 32]; H]]) -> UShort { - UShort( - us.iter() - .map(|window_us| { - WindowUs( - window_us - .iter() - .map(|u| F::from_bytes(&u).unwrap()) - .collect::>() - .into_boxed_slice() - .try_into() - .unwrap(), - ) - }) - .collect::>() - .into_boxed_slice() - .try_into() - .unwrap(), - ) +impl From<[[[u8; 32]; H]; NUM_WINDOWS_SHORT]> for UShort { + fn from(window_us: [[[u8; 32]; H]; NUM_WINDOWS_SHORT]) -> Self { + let windows: Vec> = window_us.iter().map(|us| us.into()).collect(); + windows.into() + } } From 9a46d1fea15cfeb0ec7b1ee97b49ff8464dcced3 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 25 May 2021 17:23:10 +0800 Subject: [PATCH 27/49] Move double API to #[cfg(test] --- src/circuit/gadget/ecc.rs | 1 + src/circuit/gadget/ecc/chip.rs | 5 +++++ src/circuit/gadget/ecc/chip/mul.rs | 14 +++++++------ src/circuit/gadget/ecc/chip/mul/complete.rs | 5 +---- src/circuit/gadget/ecc/chip/mul/incomplete.rs | 20 +++++++++++++++++++ 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index b5b81fb51..b0d87b1a3 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -113,6 +113,7 @@ pub trait EccInstructions: Chip { b: &Self::Point, ) -> Result; + #[cfg(test)] /// Performs point doubling, returning `[2] a`. fn double( &self, diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 8c01d2af8..9e361879e 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -8,6 +8,7 @@ use halo2::{ mod add; mod add_incomplete; +#[cfg(test)] mod double; mod load; mod mul; @@ -76,6 +77,7 @@ pub struct EccConfig { /// Fixed column used in scalar decomposition for variable-base scalar mul pub mul_decompose: Column, + #[cfg(test)] /// Point doubling pub q_double: Selector, /// Incomplete addition @@ -185,6 +187,7 @@ impl EccChip { ], fixed_z: meta.fixed_column(), mul_decompose: meta.fixed_column(), + #[cfg(test)] q_double: meta.selector(), q_add_incomplete: meta.selector(), q_add: meta.selector(), @@ -222,6 +225,7 @@ impl EccChip { } // Create point doubling gate + #[cfg(test)] { let config: double::Config = (&config).into(); config.create_gate(meta); @@ -400,6 +404,7 @@ impl EccInstructions for EccChip { ) } + #[cfg(test)] fn double( &self, layouter: &mut impl Layouter, diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 32c0bf843..30c34afa2 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -1,4 +1,4 @@ -use super::{add, double, util, CellValue, EccConfig, EccPoint}; +use super::{add, util, CellValue, EccConfig, EccPoint}; use crate::constants::NUM_COMPLETE_BITS; use std::ops::{Deref, Range}; @@ -24,8 +24,6 @@ pub struct Config { perm: Permutation, // Configuration used in complete addition add_config: add::Config, - // Configuration used in point doubling - double_config: double::Config, // Configuration used for `hi` bits of the scalar hi_config: incomplete::Config, // Configuration used for `lo` bits of the scalar @@ -40,7 +38,6 @@ impl From<&EccConfig> for Config { z_complete: ecc_config.bits, perm: ecc_config.perm.clone(), add_config: ecc_config.into(), - double_config: ecc_config.into(), hi_config: incomplete::Config::hi_config(ecc_config), lo_config: incomplete::Config::lo_config(ecc_config), } @@ -108,8 +105,13 @@ impl Config { offset: usize, region: &mut Region<'_, C::Base>, ) -> Result, Error> { - // Initialize the accumulator a [2]base - let acc = self.double_config.assign_region(&base, offset, region)?; + // Initialize the accumulator `acc = [2]base` + let acc = self + .add_config + .assign_region(&base, &base, offset, region)?; + + // Increase the offset by 1 after complete addition. + let offset = offset + 1; // Decompose the scalar bitwise (big-endian bit order). let k_bits = scalar.value.map(|scalar| decompose_scalar::(scalar)); diff --git a/src/circuit/gadget/ecc/chip/mul/complete.rs b/src/circuit/gadget/ecc/chip/mul/complete.rs index d7395906e..2fac2de7d 100644 --- a/src/circuit/gadget/ecc/chip/mul/complete.rs +++ b/src/circuit/gadget/ecc/chip/mul/complete.rs @@ -1,4 +1,4 @@ -use super::super::{add, double, util, CellValue, EccPoint}; +use super::super::{add, util, CellValue, EccPoint}; use super::Mul; use ff::Field; @@ -19,8 +19,6 @@ pub struct Config { perm: Permutation, // Configuration used in complete addition add_config: add::Config, - // Configuration used in point doubling - double_config: double::Config, _marker: PhantomData, } @@ -31,7 +29,6 @@ impl From<&super::Config> for Config { z_complete: config.z_complete, perm: config.perm.clone(), add_config: config.add_config.clone(), - double_config: config.double_config.clone(), _marker: PhantomData, } } diff --git a/src/circuit/gadget/ecc/chip/mul/incomplete.rs b/src/circuit/gadget/ecc/chip/mul/incomplete.rs index 11cba7188..2eac14c00 100644 --- a/src/circuit/gadget/ecc/chip/mul/incomplete.rs +++ b/src/circuit/gadget/ecc/chip/mul/incomplete.rs @@ -167,6 +167,26 @@ impl Config { bits: Option>, acc: (X, Y, Z), ) -> Result<(X, Y, Z), Error> { + // Handle exceptional cases + let (x_p, y_p) = (base.x.value, base.y.value); + let (x_a, y_a) = (acc.0.value, acc.1 .0); + x_p.zip(y_p) + .zip(x_a) + .zip(y_a) + .map(|(((x_p, y_p), x_a), y_a)| { + // A is point at infinity + if (x_p == F::zero() && y_p == F::zero()) + // Q is point at infinity + || (x_a == F::zero() && y_a == F::zero()) + // x_p = x_a + || (x_p == x_a) + { + return Err(Error::SynthesisError); + } + Ok(()) + }) + .unwrap_or(Err(Error::SynthesisError))?; + // Enable `q_mul` on all but the last row of the incomplete range. for row in 1..(self.num_bits - 1) { self.q_mul.enable(region, offset + row)?; From a399085ccd0887db54867fe5cd24a06b1517f753 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 25 May 2021 18:51:44 +0800 Subject: [PATCH 28/49] Documentation and clippy fixes --- src/circuit/gadget/ecc.rs | 1 + src/circuit/gadget/ecc/chip.rs | 6 ++-- src/circuit/gadget/ecc/chip/add.rs | 4 +-- src/circuit/gadget/ecc/chip/add_incomplete.rs | 6 ++-- src/circuit/gadget/ecc/chip/double.rs | 1 - src/circuit/gadget/ecc/chip/mul.rs | 12 ++++--- src/circuit/gadget/ecc/chip/mul/complete.rs | 3 +- src/circuit/gadget/ecc/chip/mul/incomplete.rs | 2 +- src/circuit/gadget/ecc/chip/mul_fixed.rs | 36 +++++++++++++++++-- .../gadget/ecc/chip/mul_fixed/full_width.rs | 1 - .../gadget/ecc/chip/mul_fixed/short.rs | 1 - src/circuit/gadget/ecc/chip/witness_point.rs | 4 +-- 12 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index b0d87b1a3..82ecc6c95 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -122,6 +122,7 @@ pub trait EccInstructions: Chip { ) -> Result; /// Performs variable-base scalar multiplication, returning `[scalar] base`. + /// Multiplication of the identity [a]𝒪 returns an error. fn mul( &self, layouter: &mut impl Layouter, diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 9e361879e..afec43e8c 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -78,8 +78,9 @@ pub struct EccConfig { pub mul_decompose: Column, #[cfg(test)] - /// Point doubling + /// Point doubling (not used in the Orchard circuit) pub q_double: Selector, + /// Incomplete addition pub q_add_incomplete: Selector, /// Complete addition @@ -161,8 +162,6 @@ impl EccChip { } #[allow(non_snake_case)] - #[allow(clippy::many_single_char_names)] - #[allow(clippy::too_many_arguments)] pub fn configure( meta: &mut ConstraintSystem, bits: Column, @@ -258,7 +257,6 @@ impl EccChip { config } - #[allow(clippy::type_complexity)] pub fn load() -> >::Loaded { let commit_ivk_r = load::commit_ivk_r(); let note_commit_r = load::note_commit_r(); diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 792b9b340..c274f94b8 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -50,7 +50,6 @@ impl From<&EccConfig> for Config { } impl Config { - #[allow(clippy::too_many_arguments)] pub(crate) fn create_gate(&self, meta: &mut ConstraintSystem) { let q_add = meta.query_selector(self.q_add, Rotation::cur()); let x_p = meta.query_advice(self.x_p, Rotation::cur()); @@ -195,8 +194,6 @@ impl Config { } } - #[allow(clippy::many_single_char_names)] - #[allow(non_snake_case)] pub(super) fn assign_region( &self, a: &EccPoint, @@ -260,6 +257,7 @@ impl Config { }; region.assign_advice(|| "δ", self.delta, offset, || Ok(delta))?; + #[allow(clippy::collapsible_else_if)] // Assign lambda let lambda = if x_q != x_p { // λ = (y_q - y_p)/(x_q - x_p) diff --git a/src/circuit/gadget/ecc/chip/add_incomplete.rs b/src/circuit/gadget/ecc/chip/add_incomplete.rs index e2a59b5af..b85b9a7ef 100644 --- a/src/circuit/gadget/ecc/chip/add_incomplete.rs +++ b/src/circuit/gadget/ecc/chip/add_incomplete.rs @@ -10,9 +10,9 @@ use halo2::{ pub struct Config { q_add_incomplete: Selector, // x-coordinate of P in P + Q = R - x_p: Column, + pub x_p: Column, // y-coordinate of P in P + Q = R - y_p: Column, + pub y_p: Column, // x-coordinate of Q or R in P + Q = R pub x_qr: Column, // y-coordinate of Q or R in P + Q = R @@ -35,7 +35,6 @@ impl From<&EccConfig> for Config { } impl Config { - #[allow(clippy::too_many_arguments)] pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { let q_add_incomplete = meta.query_selector(self.q_add_incomplete, Rotation::cur()); let x_p = meta.query_advice(self.x_p, Rotation::cur()); @@ -63,7 +62,6 @@ impl Config { }); } - #[allow(non_snake_case)] pub(super) fn assign_region( &self, p: &EccPoint, diff --git a/src/circuit/gadget/ecc/chip/double.rs b/src/circuit/gadget/ecc/chip/double.rs index 824388c0f..37e9d07ba 100644 --- a/src/circuit/gadget/ecc/chip/double.rs +++ b/src/circuit/gadget/ecc/chip/double.rs @@ -65,7 +65,6 @@ impl Config { }); } - #[allow(non_snake_case)] pub(super) fn assign_region( &self, p: &EccPoint, diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 30c34afa2..3a1c53089 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -90,8 +90,10 @@ impl Config { let z_cur = meta.query_advice(self.z_complete, Rotation::cur()); meta.create_gate("Decompose scalar", |_| { - // q = 2^254 + t_q is the scalar field of Pallas - let t_q = C::Base::from_u128(45560315531506369815346746415080538113); + // The scalar field `F_q = 2^254 + t_q`. + // -((2^127)^2) = -(2^254) = t_q (mod q) + let t_q = -(C::Scalar::from_u128(1u128 << 127).square()); + let t_q = C::Base::from_bytes(&t_q.to_bytes()).unwrap(); // Check that `k = scalar + t_q` scalar.clone() * (scalar + Expression::Constant(t_q) - z_cur) @@ -114,7 +116,7 @@ impl Config { let offset = offset + 1; // Decompose the scalar bitwise (big-endian bit order). - let k_bits = scalar.value.map(|scalar| decompose_scalar::(scalar)); + let k_bits = scalar.value.map(decompose_scalar::); // Bits used in incomplete addition. k_{254} to k_{4} inclusive let k_incomplete = k_bits @@ -185,10 +187,11 @@ impl Config { complete_config.assign_region(region, offset, k_complete, base, acc, z.value)?; // Process the least significant bit - let k_0 = k_bits.map(|k_bits| k_bits[C::Scalar::NUM_BITS as usize - 1].clone()); + let k_0 = k_bits.map(|k_bits| k_bits[C::Scalar::NUM_BITS as usize - 1]); self.process_lsb(region, offset, scalar, base, acc, k_0, z_val) } + #[allow(clippy::too_many_arguments)] fn process_lsb( &self, region: &mut Region<'_, C::Base>, @@ -321,6 +324,7 @@ fn decompose_scalar(scalar: C::Base) -> Vec { let scalar = C::Scalar::from_bytes(&scalar.to_bytes()).unwrap(); // The scalar field `F_q = 2^254 + t_q`. + // -((2^127)^2) = -(2^254) = t_q (mod q) let t_q = -(C::Scalar::from_u128(1u128 << 127).square()); // We will witness `k = scalar + t_q` diff --git a/src/circuit/gadget/ecc/chip/mul/complete.rs b/src/circuit/gadget/ecc/chip/mul/complete.rs index 2fac2de7d..f795eda64 100644 --- a/src/circuit/gadget/ecc/chip/mul/complete.rs +++ b/src/circuit/gadget/ecc/chip/mul/complete.rs @@ -56,6 +56,7 @@ impl Config { }); } + #[allow(clippy::type_complexity)] pub(super) fn assign_region( &self, region: &mut Region<'_, C::Base>, @@ -72,7 +73,7 @@ impl Config { // Convert Option> into Vec> let bits: Vec> = if let Some(b) = bits { - b.into_iter().map(|v| Some(v)).collect() + b.into_iter().map(Some).collect() } else { return Err(Error::SynthesisError); }; diff --git a/src/circuit/gadget/ecc/chip/mul/incomplete.rs b/src/circuit/gadget/ecc/chip/mul/incomplete.rs index 2eac14c00..a7fd68eca 100644 --- a/src/circuit/gadget/ecc/chip/mul/incomplete.rs +++ b/src/circuit/gadget/ecc/chip/mul/incomplete.rs @@ -222,7 +222,7 @@ impl Config { // Convert Option> into Vec> let bits: Vec> = if let Some(b) = bits { - b.into_iter().map(|v| Some(v)).collect() + b.into_iter().map(Some).collect() } else { return Err(Error::SynthesisError); }; diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index 7ec9921f6..c5ed38c4e 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -46,7 +46,7 @@ pub struct Config { impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { - Self { + let config = Self { q_mul_fixed: ecc_config.q_mul_fixed, q_mul_fixed_short: ecc_config.q_mul_fixed_short, lagrange_coeffs: ecc_config.lagrange_coeffs, @@ -60,7 +60,39 @@ impl From<&EccConfig> for Config { add_config: ecc_config.into(), add_incomplete_config: ecc_config.into(), witness_point_config: ecc_config.into(), - } + }; + + // Check relationships between this config and `add_config`. + assert_eq!( + config.x_p, config.add_config.x_p, + "add is used internally in mul_fixed." + ); + assert_eq!( + config.y_p, config.add_config.y_p, + "add is used internally in mul_fixed." + ); + + // Check relationships between this config and `add_incomplete_config`. + assert_eq!( + config.x_p, config.add_incomplete_config.x_p, + "add_incomplete is used internally in mul_fixed." + ); + assert_eq!( + config.y_p, config.add_incomplete_config.y_p, + "add_incomplete is used internally in mul_fixed." + ); + + // Check relationships between this config and `witness_point_config`. + assert_eq!( + config.x_p, config.witness_point_config.x, + "witness_point is used internally in mul_fixed." + ); + assert_eq!( + config.y_p, config.witness_point_config.y, + "witness_point is used internally in mul_fixed." + ); + + config } } 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 5b6dae891..089de3723 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -96,7 +96,6 @@ impl MulFixed for Config { } impl Config { - #[allow(non_snake_case)] pub(super) fn assign_region( &self, region: &mut Region<'_, C::Base>, diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index 15ad24e6c..b2110dcc2 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -124,7 +124,6 @@ impl Config { }); } - #[allow(non_snake_case)] pub(super) fn assign_region( &self, region: &mut Region<'_, C::Base>, diff --git a/src/circuit/gadget/ecc/chip/witness_point.rs b/src/circuit/gadget/ecc/chip/witness_point.rs index b0b6671ac..c420a6e3e 100644 --- a/src/circuit/gadget/ecc/chip/witness_point.rs +++ b/src/circuit/gadget/ecc/chip/witness_point.rs @@ -11,9 +11,9 @@ use halo2::{ pub struct Config { q_point: Selector, // x-coordinate - x: Column, + pub x: Column, // y-coordinate - y: Column, + pub y: Column, } impl From<&EccConfig> for Config { From acfc1bea8eece739c9ecad0f281dfedd5e3cb422 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 27 May 2021 01:24:30 +0800 Subject: [PATCH 29/49] Move all assignments out of Option.map() scopes Co-authored-by: Jack Grigg --- src/circuit/gadget/ecc/chip.rs | 33 ++- src/circuit/gadget/ecc/chip/add.rs | 201 ++++++++++----- src/circuit/gadget/ecc/chip/add_incomplete.rs | 46 ++-- src/circuit/gadget/ecc/chip/double.rs | 37 +-- src/circuit/gadget/ecc/chip/mul.rs | 238 ++++++++++-------- src/circuit/gadget/ecc/chip/mul/complete.rs | 29 ++- src/circuit/gadget/ecc/chip/mul/incomplete.rs | 37 ++- src/circuit/gadget/ecc/chip/mul_fixed.rs | 14 +- .../gadget/ecc/chip/mul_fixed/full_width.rs | 2 +- .../gadget/ecc/chip/mul_fixed/short.rs | 17 +- src/circuit/gadget/ecc/chip/witness_point.rs | 2 +- .../gadget/ecc/chip/witness_scalar_fixed.rs | 30 ++- 12 files changed, 395 insertions(+), 291 deletions(-) diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index afec43e8c..476114da9 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -1,7 +1,8 @@ use super::EccInstructions; use crate::constants; +use ff::Field; use halo2::{ - arithmetic::{CurveAffine, FieldExt}, + arithmetic::CurveAffine, circuit::{Cell, Chip, Layouter}, plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Permutation, Selector}, }; @@ -38,11 +39,27 @@ impl CellValue { /// A curve point represented in affine (x, y) coordinates. Each coordinate is /// assigned to a cell. #[derive(Clone, Debug)] -pub struct EccPoint { +pub struct EccPoint { /// x-coordinate - pub x: CellValue, + pub x: CellValue, /// y-coordinate - pub y: CellValue, + pub y: CellValue, +} + +impl EccPoint { + /// Returns the value of this curve point, if known. + pub fn point(&self) -> Option { + match (self.x.value, self.y.value) { + (Some(x), Some(y)) => { + if x == C::Base::zero() && y == C::Base::zero() { + Some(C::identity()) + } else { + Some(C::from_xy(x, y).unwrap()) + } + } + _ => None, + } + } } #[derive(Clone, Debug)] @@ -74,8 +91,6 @@ pub struct EccConfig { pub lagrange_coeffs: [Column; constants::H], /// Fixed z such that y + z = u^2 some square, and -y + z is a non-square. (Used in fixed-base scalar multiplication) pub fixed_z: Column, - /// Fixed column used in scalar decomposition for variable-base scalar mul - pub mul_decompose: Column, #[cfg(test)] /// Point doubling (not used in the Orchard circuit) @@ -89,6 +104,8 @@ pub struct EccConfig { pub q_mul_hi: Selector, /// Variable-base scalar multiplication (lo half) pub q_mul_lo: Selector, + /// Selector used in scalar decomposition for variable-base scalar mul + pub q_mul_decompose_var: Selector, /// Variable-base scalar multiplication (final scalar) pub q_mul_complete: Selector, /// Fixed-base full-width scalar multiplication @@ -185,13 +202,13 @@ impl EccChip { meta.fixed_column(), ], fixed_z: meta.fixed_column(), - mul_decompose: meta.fixed_column(), #[cfg(test)] q_double: meta.selector(), q_add_incomplete: meta.selector(), q_add: meta.selector(), q_mul_hi: meta.selector(), q_mul_lo: meta.selector(), + q_mul_decompose_var: meta.selector(), q_mul_complete: meta.selector(), q_mul_fixed: meta.selector(), q_mul_fixed_short: meta.selector(), @@ -299,7 +316,7 @@ impl EccInstructions for EccChip { type ScalarFixed = EccScalarFixed; type ScalarFixedShort = EccScalarFixedShort; type ScalarVar = CellValue; - type Point = EccPoint; + type Point = EccPoint; type X = CellValue; type FixedPoint = OrchardFixedBase; type FixedPointShort = OrchardFixedBaseShort; diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index c274f94b8..3c3b95c3d 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -1,6 +1,7 @@ use super::{util, CellValue, EccConfig, EccPoint}; +use ff::Field; use halo2::{ - arithmetic::FieldExt, + arithmetic::{CurveAffine, FieldExt}, circuit::Region, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector}, poly::Rotation, @@ -194,13 +195,13 @@ impl Config { } } - pub(super) fn assign_region( + pub(super) fn assign_region( &self, - a: &EccPoint, - b: &EccPoint, + a: &EccPoint, + b: &EccPoint, offset: usize, - region: &mut Region<'_, F>, - ) -> Result, Error> { + region: &mut Region<'_, C::Base>, + ) -> Result, Error> { // Enable `q_add` selector self.q_add.enable(region, offset)?; @@ -215,104 +216,170 @@ impl Config { let (x_p, y_p) = (a.x.value, a.y.value); let (x_q, y_q) = (b.x.value, b.y.value); - x_p.zip(x_q) - .zip(y_p) - .zip(y_q) - .map(|(((x_p, x_q), y_p), y_q)| -> Result, Error> { - // inv0(x) evaluates to 0 if x = 0, and 1/x otherwise. + // inv0(x) evaluates to 0 if x = 0, and 1/x otherwise. + + // Assign α = inv0(x_q - x_p) + region.assign_advice( + || "α", + self.alpha, + offset, + || { + let x_p = x_p.ok_or(Error::SynthesisError)?; + let x_q = x_q.ok_or(Error::SynthesisError)?; - // Assign α = inv0(x_q - x_p) let alpha = if x_q != x_p { (x_q - x_p).invert().unwrap() } else { - F::zero() + C::Base::zero() }; - region.assign_advice(|| "α", self.alpha, offset, || Ok(alpha))?; - - // Assign β = inv0(x_p) - let beta = if x_p != F::zero() { + Ok(alpha) + }, + )?; + + // Assign β = inv0(x_p) + region.assign_advice( + || "β", + self.beta, + offset, + || { + let x_p = x_p.ok_or(Error::SynthesisError)?; + + let beta = if x_p != C::Base::zero() { x_p.invert().unwrap() } else { - F::zero() + C::Base::zero() }; - region.assign_advice(|| "β", self.beta, offset, || Ok(beta))?; - - // Assign γ = inv0(x_q) - let gamma = if x_q != F::zero() { + Ok(beta) + }, + )?; + + // Assign γ = inv0(x_q) + region.assign_advice( + || "γ", + self.gamma, + offset, + || { + let x_q = x_q.ok_or(Error::SynthesisError)?; + + let gamma = if x_q != C::Base::zero() { x_q.invert().unwrap() } else { - F::zero() + C::Base::zero() }; - region.assign_advice(|| "γ", self.gamma, offset, || Ok(gamma))?; + Ok(gamma) + }, + )?; + + // Assign δ = inv0(y_q + y_p) if x_q = x_p, 0 otherwise + region.assign_advice( + || "δ", + self.delta, + offset, + || { + let x_p = x_p.ok_or(Error::SynthesisError)?; + let x_q = x_q.ok_or(Error::SynthesisError)?; + let y_p = y_p.ok_or(Error::SynthesisError)?; + let y_q = y_q.ok_or(Error::SynthesisError)?; - // Assign δ = inv0(y_q + y_p) if x_q = x_p, 0 otherwise let delta = if x_q == x_p { if y_q != -y_p { (y_q + y_p).invert().unwrap() } else { - F::zero() + C::Base::zero() } } else { - F::zero() + C::Base::zero() }; - region.assign_advice(|| "δ", self.delta, offset, || Ok(delta))?; + Ok(delta) + }, + )?; - #[allow(clippy::collapsible_else_if)] - // Assign lambda - let lambda = if x_q != x_p { + #[allow(clippy::collapsible_else_if)] + // Assign lambda + let lambda = x_p + .zip(y_p) + .zip(x_q) + .zip(y_q) + .map(|(((x_p, y_p), x_q), y_q)| { + if x_q != x_p { // λ = (y_q - y_p)/(x_q - x_p) (y_q - y_p) * (x_q - x_p).invert().unwrap() } else { - if y_p != F::zero() { + if y_p != C::Base::zero() { // 3(x_p)^2 - let three_x_p_sq = F::from_u64(3) * x_p * x_p; + let three_x_p_sq = C::Base::from_u64(3) * x_p * x_p; // 2(y_p) - let two_y_p = F::from_u64(2) * y_p; + let two_y_p = C::Base::from_u64(2) * y_p; // λ = 3(x_p)^2 / 2(y_p) three_x_p_sq * two_y_p.invert().unwrap() } else { - F::zero() + C::Base::zero() } - }; - region.assign_advice(|| "λ", self.lambda, offset, || Ok(lambda))?; - - // Assign x_r - let x_r = if x_p == F::zero() { - // 0 + Q = Q - x_q - } else if x_q == F::zero() { - // P + 0 = P - x_p - } else if (x_q == x_p) && (y_q == -y_p) { - // P + (-P) maps to (0,0) - F::zero() - } else { - // x_r = λ^2 - x_p - x_q - lambda * lambda - x_p - x_q - }; - let x_r_cell = region.assign_advice(|| "x_r", self.x_qr, offset + 1, || Ok(x_r))?; - - // Assign y_r - let y_r = if x_p == F::zero() { + } + }); + region.assign_advice( + || "λ", + self.lambda, + offset, + || lambda.ok_or(Error::SynthesisError), + )?; + + // Assign x_r + let x_r = + x_p.zip(y_p) + .zip(x_q) + .zip(y_q) + .zip(lambda) + .map(|((((x_p, y_p), x_q), y_q), lambda)| { + if x_p == C::Base::zero() { + // 0 + Q = Q + x_q + } else if x_q == C::Base::zero() { + // P + 0 = P + x_p + } else if (x_q == x_p) && (y_q == -y_p) { + // P + (-P) maps to (0,0) + C::Base::zero() + } else { + // x_r = λ^2 - x_p - x_q + lambda * lambda - x_p - x_q + } + }); + let x_r_cell = region.assign_advice( + || "x_r", + self.x_qr, + offset + 1, + || x_r.ok_or(Error::SynthesisError), + )?; + + // Assign y_r + let y_r = x_p.zip(y_p).zip(x_q).zip(y_q).zip(x_r).zip(lambda).map( + |(((((x_p, y_p), x_q), y_q), x_r), lambda)| { + if x_p == C::Base::zero() { // 0 + Q = Q y_q - } else if x_q == F::zero() { + } else if x_q == C::Base::zero() { // P + 0 = P y_p } else if (x_q == x_p) && (y_q == -y_p) { // P + (-P) maps to (0,0) - F::zero() + C::Base::zero() } else { // y_r = λ(x_p - x_r) - y_p lambda * (x_p - x_r) - y_p - }; - let y_r_cell = region.assign_advice(|| "y_r", self.y_qr, offset + 1, || Ok(y_r))?; - - Ok(EccPoint { - x: CellValue::::new(x_r_cell, Some(x_r)), - y: CellValue::::new(y_r_cell, Some(y_r)), - }) - }) - .unwrap_or(Err(Error::SynthesisError)) + } + }, + ); + let y_r_cell = region.assign_advice( + || "y_r", + self.y_qr, + offset + 1, + || y_r.ok_or(Error::SynthesisError), + )?; + + Ok(EccPoint { + x: CellValue::::new(x_r_cell, x_r), + y: CellValue::::new(y_r_cell, y_r), + }) } } diff --git a/src/circuit/gadget/ecc/chip/add_incomplete.rs b/src/circuit/gadget/ecc/chip/add_incomplete.rs index b85b9a7ef..dca63d414 100644 --- a/src/circuit/gadget/ecc/chip/add_incomplete.rs +++ b/src/circuit/gadget/ecc/chip/add_incomplete.rs @@ -1,6 +1,8 @@ use super::{util, CellValue, EccConfig, EccPoint}; +use ff::Field; +use group::Curve; use halo2::{ - arithmetic::FieldExt, + arithmetic::{CurveAffine, FieldExt}, circuit::Region, plonk::{Advice, Column, ConstraintSystem, Error, Permutation, Selector}, poly::Rotation, @@ -62,13 +64,13 @@ impl Config { }); } - pub(super) fn assign_region( + pub(super) fn assign_region( &self, - p: &EccPoint, - q: &EccPoint, + p: &EccPoint, + q: &EccPoint, offset: usize, - region: &mut Region<'_, F>, - ) -> Result, Error> { + region: &mut Region<'_, C::Base>, + ) -> Result, Error> { // Enable `q_add_incomplete` selector self.q_add_incomplete.enable(region, offset)?; @@ -80,17 +82,18 @@ impl Config { .zip(y_q) .map(|(((x_p, y_p), x_q), y_q)| { // P is point at infinity - if (x_p == F::zero() && y_p == F::zero()) + if (x_p == C::Base::zero() && y_p == C::Base::zero()) // Q is point at infinity - || (x_q == F::zero() && y_q == F::zero()) + || (x_q == C::Base::zero() && y_q == C::Base::zero()) // x_p = x_q || (x_p == x_q) { - return Err(Error::SynthesisError); + Err(Error::SynthesisError) + } else { + Ok(()) } - Ok(()) }) - .unwrap_or(Err(Error::SynthesisError))?; + .transpose()?; // Copy point `p` into `x_p`, `y_p` columns util::assign_and_constrain(region, || "x_p", self.x_p, offset, &p.x, &self.perm)?; @@ -101,17 +104,12 @@ impl Config { util::assign_and_constrain(region, || "y_q", self.y_qr, offset, &q.y, &self.perm)?; // Compute the sum `P + Q = R` - let r = x_p - .zip(y_p) - .zip(x_q) - .zip(y_q) - .map(|(((x_p, y_p), x_q), y_q)| { - // We can invert `(x_q - x_p)` because we rejected the `x_q == x_p` case. - let lambda = (y_q - y_p) * (x_q - x_p).invert().unwrap(); - let x_r = lambda * lambda - x_p - x_q; - let y_r = lambda * (x_p - x_r) - y_p; - (x_r, y_r) - }); + let p = p.point(); + let q = q.point(); + let r = p.zip(q).map(|(p, q)| { + let r = (p + q).to_affine().coordinates().unwrap(); + (*r.x(), *r.y()) + }); // Assign the sum to `x_qr`, `y_qr` columns in the next row let x_r = r.map(|r| r.0); @@ -131,8 +129,8 @@ impl Config { )?; Ok(EccPoint { - x: CellValue::::new(x_r_var, x_r), - y: CellValue::::new(y_r_var, y_r), + x: CellValue::::new(x_r_var, x_r), + y: CellValue::::new(y_r_var, y_r), }) } } diff --git a/src/circuit/gadget/ecc/chip/double.rs b/src/circuit/gadget/ecc/chip/double.rs index 37e9d07ba..1ed4c1be0 100644 --- a/src/circuit/gadget/ecc/chip/double.rs +++ b/src/circuit/gadget/ecc/chip/double.rs @@ -1,7 +1,9 @@ use super::{util, CellValue, EccConfig, EccPoint}; +use ff::Field; +use group::Curve; use halo2::{ - arithmetic::FieldExt, + arithmetic::{CurveAffine, FieldExt}, circuit::Region, plonk::{Advice, Column, ConstraintSystem, Error, Permutation, Selector}, poly::Rotation, @@ -65,12 +67,12 @@ impl Config { }); } - pub(super) fn assign_region( + pub(super) fn assign_region( &self, - p: &EccPoint, + p: &EccPoint, offset: usize, - region: &mut Region<'_, F>, - ) -> Result, Error> { + region: &mut Region<'_, C::Base>, + ) -> Result, Error> { // Enable `q_double` selector self.q_double.enable(region, offset)?; @@ -78,25 +80,24 @@ impl Config { let (x_p, y_p) = (p.x.value, p.y.value); x_p.zip(y_p) .map(|(x, y)| { - if x == F::zero() && y == F::zero() { - return Err(Error::SynthesisError); + if x == C::Base::zero() && y == C::Base::zero() { + Err(Error::SynthesisError) + } else { + Ok(()) } - Ok(()) }) - .unwrap_or(Err(Error::SynthesisError))?; + .transpose()?; // Copy the point `P` into `x_p`, `y_p` columns util::assign_and_constrain(region, || "x_p", self.x_p, offset, &p.x, &self.perm)?; util::assign_and_constrain(region, || "y_p", self.y_p, offset, &p.y, &self.perm)?; // Compute the doubled point - let r = x_p.zip(y_p).map(|(x_p, y_p)| { - // λ = 3(x_p)^2 / (2 * y_p) - // We can invert `y_p` since we already rejected the case where `y_p == 0`. - let lambda = F::from_u64(3) * x_p * x_p * F::TWO_INV * y_p.invert().unwrap(); - let x_r = lambda * lambda - x_p - x_p; - let y_r = lambda * (x_p - x_r) - y_p; - (x_r, y_r) + let r = p.point().map(|p| { + let r = p * C::Scalar::from_u64(2); + let r = r.to_affine().coordinates().unwrap(); + + (*r.x(), *r.y()) }); let x_r = r.map(|r| r.0); let y_r = r.map(|r| r.1); @@ -116,8 +117,8 @@ impl Config { )?; Ok(EccPoint { - x: CellValue::::new(x_r_var, x_r), - y: CellValue::::new(y_r_var, y_r), + x: CellValue::::new(x_r_var, x_r), + y: CellValue::::new(y_r_var, y_r), }) } } diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 3a1c53089..4c73ff4f4 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -6,7 +6,7 @@ use ff::PrimeField; use halo2::{ arithmetic::{CurveAffine, Field, FieldExt}, circuit::Region, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, Selector}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector}, poly::Rotation, }; @@ -16,10 +16,12 @@ mod incomplete; pub struct Config { // Selector used to constrain the cells used in complete addition. q_mul_complete: Selector, - // Fixed column used to check recovery of the original scalar after decomposition. - mul_decompose: Column, + // Selector used to check recovery of the original scalar after decomposition. + q_mul_decompose_var: Selector, // Advice column used to decompose scalar in complete addition. z_complete: Column, + // Advice column where the scalar is copied for use in the final recovery check. + scalar: Column, // Permutation perm: Permutation, // Configuration used in complete addition @@ -34,8 +36,9 @@ impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { Self { q_mul_complete: ecc_config.q_mul_complete, - mul_decompose: ecc_config.mul_decompose, + q_mul_decompose_var: ecc_config.q_mul_decompose_var, z_complete: ecc_config.bits, + scalar: ecc_config.extras[0], perm: ecc_config.perm.clone(), add_config: ecc_config.into(), hi_config: incomplete::Config::hi_config(ecc_config), @@ -46,27 +49,41 @@ impl From<&EccConfig> for Config { trait Mul { // Bits used in incomplete addition. k_{254} to k_{4} inclusive - fn incomplete_range(&self) -> Range { - 0..(C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS) + fn incomplete_len() -> usize { + C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS + } + + fn incomplete_range() -> Range { + 0..Self::incomplete_len() + } + + // Bits used in `lo` half of incomplete addition + fn incomplete_lo_range() -> Range { + (Self::incomplete_len() / 2)..Self::incomplete_len() + } + + // Bits used in `hi` half of incomplete addition + fn incomplete_hi_range() -> Range { + 0..(Self::incomplete_len() / 2) } // Bits k_{254} to k_{4} inclusive are used in incomplete addition. // The `lo` half is k_{129} to k_{4} inclusive (length 126 bits). fn incomplete_lo_len() -> usize { - (C::Scalar::NUM_BITS as usize - NUM_COMPLETE_BITS) / 2 + (Self::incomplete_len() + 1) / 2 } // Bits k_{254} to k_{4} inclusive are used in incomplete addition. // The `hi` half is k_{254} to k_{130} inclusive (length 125 bits). fn incomplete_hi_len() -> usize { - (C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS) / 2 + Self::incomplete_len() / 2 } - fn complete_range(&self) -> Range { - (C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS)..(C::Scalar::NUM_BITS as usize - 1) + fn complete_range() -> Range { + Self::incomplete_len()..(C::Scalar::NUM_BITS as usize - 1) } - fn complete_len(&self) -> usize { + fn complete_len() -> usize { NUM_COMPLETE_BITS as usize } } @@ -86,7 +103,8 @@ impl Config { /// Gate used to check final scalar is recovered. fn create_final_scalar_gate(&self, meta: &mut ConstraintSystem) { - let scalar = meta.query_fixed(self.mul_decompose, Rotation::cur()); + let q_mul_decompose_var = meta.query_selector(self.q_mul_decompose_var, Rotation::cur()); + let scalar = meta.query_advice(self.scalar, Rotation::cur()); let z_cur = meta.query_advice(self.z_complete, Rotation::cur()); meta.create_gate("Decompose scalar", |_| { @@ -96,17 +114,17 @@ impl Config { let t_q = C::Base::from_bytes(&t_q.to_bytes()).unwrap(); // Check that `k = scalar + t_q` - scalar.clone() * (scalar + Expression::Constant(t_q) - z_cur) + q_mul_decompose_var * (scalar + Expression::Constant(t_q) - z_cur) }); } pub(super) fn assign_region( &self, scalar: &CellValue, - base: &EccPoint, + base: &EccPoint, offset: usize, region: &mut Region<'_, C::Base>, - ) -> Result, Error> { + ) -> Result, Error> { // Initialize the accumulator `acc = [2]base` let acc = self .add_config @@ -116,12 +134,7 @@ impl Config { let offset = offset + 1; // Decompose the scalar bitwise (big-endian bit order). - let k_bits = scalar.value.map(decompose_scalar::); - - // Bits used in incomplete addition. k_{254} to k_{4} inclusive - let k_incomplete = k_bits - .clone() - .map(|k_bits| k_bits[self.incomplete_range()].to_vec()); + let k_bits = decompose_for_scalar_mul::(scalar.value); // Initialize the running sum for scalar decomposition to zero let z_val = C::Base::zero(); @@ -130,9 +143,7 @@ impl Config { let z = CellValue::new(z_cell, Some(z_val)); // Double-and-add (incomplete addition) for the `hi` half of the scalar decomposition - let k_incomplete_hi = k_incomplete - .clone() - .map(|k_incomplete| k_incomplete[..k_incomplete.len() / 2].to_vec()); + let k_incomplete_hi = &k_bits[Self::incomplete_hi_range()]; let (x, y_a, z) = self.hi_config.double_and_add( region, offset + 1, @@ -142,8 +153,7 @@ impl Config { )?; // Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition - let k_incomplete_lo = - k_incomplete.map(|k_incomplete| k_incomplete[k_incomplete.len() / 2..].to_vec()); + let k_incomplete_lo = &k_bits[Self::incomplete_lo_range()]; let (x, y_a, z) = self.lo_config.double_and_add( region, offset + 1, @@ -180,14 +190,12 @@ impl Config { let complete_config: complete::Config = self.into(); // Bits used in complete addition. k_{3} to k_{1} inclusive // The LSB k_{0} is handled separately. - let k_complete = k_bits - .clone() - .map(|k_bits| k_bits[self.complete_range()].to_vec()); + let k_complete = &k_bits[Self::complete_range()]; let (acc, z_val) = complete_config.assign_region(region, offset, k_complete, base, acc, z.value)?; // Process the least significant bit - let k_0 = k_bits.map(|k_bits| k_bits[C::Scalar::NUM_BITS as usize - 1]); + let k_0 = k_bits[C::Scalar::NUM_BITS as usize - 1]; self.process_lsb(region, offset, scalar, base, acc, k_0, z_val) } @@ -197,13 +205,13 @@ impl Config { region: &mut Region<'_, C::Base>, offset: usize, scalar: &CellValue, - base: &EccPoint, - acc: EccPoint, + base: &EccPoint, + acc: EccPoint, k_0: Option, mut z_val: Option, - ) -> Result, Error> { + ) -> Result, Error> { // Assign the final `z` value. - let k_0_row = Self::incomplete_lo_len() + self.complete_range().len() * 2 + 4; + let k_0_row = Self::incomplete_lo_len() + Self::complete_len() * 2 + 4; z_val = z_val .zip(k_0) .map(|(z_val, k_0)| C::Base::from_u64(2) * z_val + C::Base::from_u64(k_0 as u64)); @@ -224,65 +232,60 @@ impl Config { // is in deriving diversified addresses `[ivk] g_d`, and `ivk` is guaranteed // to be in the base field of the curve. (See non-normative notes in // https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents.) - let scalar = scalar - .value - .map(|scalar| C::Base::from_bytes(&scalar.to_bytes()).unwrap()); - region.assign_fixed( - || "original k", - self.mul_decompose, + util::assign_and_constrain( + region, + || "original scalar", + self.scalar, + k_0_row + offset, + &scalar, + &self.perm, + )?; + self.q_mul_decompose_var.enable(region, k_0_row + offset)?; + + // If `k_0` is 0, return `Acc + (-P)`. If `k_0` is 1, simply return `Acc + 0`. + let x_p = if let Some(k_0) = k_0 { + if !k_0 { + println!("!k_0"); + base.x.value + } else { + Some(C::Base::zero()) + } + } else { + None + }; + let y_p = if let Some(k_0) = k_0 { + if !k_0 { + println!("!k_0"); + base.y.value.map(|y_p| -y_p) + } else { + Some(C::Base::zero()) + } + } else { + None + }; + + let x_p_cell = region.assign_advice( + || "x_p", + self.add_config.x_p, k_0_row + offset, - || scalar.ok_or(Error::SynthesisError), + || x_p.ok_or(Error::SynthesisError), )?; - // If `k_0` is 0, return `Acc - P`. If `k_0` is 1, simply return `Acc`. - let p = k_0 - .map(|k_0| { - if !k_0 { - // If `k_0` is 0, return `Acc - P` - let (x_p, y_p) = (base.x.value, base.y.value.map(|y_p| -y_p)); - let x_p_cell = region.assign_advice( - || "x_p", - self.add_config.x_p, - k_0_row + offset, - || x_p.ok_or(Error::SynthesisError), - )?; - - let y_p_cell = region.assign_advice( - || "y_p", - self.add_config.y_p, - k_0_row + offset, - || y_p.ok_or(Error::SynthesisError), - )?; - Ok(EccPoint { - x: CellValue::::new(x_p_cell, x_p), - y: CellValue::::new(y_p_cell, y_p), - }) - } else { - // If `k_0` is 1, simply return `Acc` - let x_p_cell = region.assign_advice( - || "x_p", - self.add_config.x_p, - k_0_row + offset, - || Ok(C::Base::zero()), - )?; - - let y_p_cell = region.assign_advice( - || "y_p", - self.add_config.y_p, - k_0_row + offset, - || Ok(C::Base::zero()), - )?; - Ok(EccPoint { - x: CellValue::::new(x_p_cell, Some(C::Base::zero())), - y: CellValue::::new(y_p_cell, Some(C::Base::zero())), - }) - } - }) - .unwrap_or(Err(Error::SynthesisError))?; + let y_p_cell = region.assign_advice( + || "y_p", + self.add_config.y_p, + k_0_row + offset, + || y_p.ok_or(Error::SynthesisError), + )?; + + let p = EccPoint { + x: CellValue::::new(x_p_cell, x_p), + y: CellValue::::new(y_p_cell, y_p), + }; // Return the result of the final complete addition as `[scalar]B` self.add_config - .assign_region(&p, &acc, k_0_row + offset, region) + .assign_region(&p, &acc, k_0_row + offset + 1, region) } } @@ -319,27 +322,42 @@ impl Deref for Z { } } -fn decompose_scalar(scalar: C::Base) -> Vec { - // Cast from base field into scalar field. - let scalar = C::Scalar::from_bytes(&scalar.to_bytes()).unwrap(); - - // The scalar field `F_q = 2^254 + t_q`. - // -((2^127)^2) = -(2^254) = t_q (mod q) - let t_q = -(C::Scalar::from_u128(1u128 << 127).square()); - - // We will witness `k = scalar + t_q` - // `k` is decomposed bitwise in-circuit for our double-and-add algorithm. - let k = scalar + t_q; - - // `k` is decomposed bitwise (big-endian) into `[k_n, ..., k_0]`, where - // each `k_i` is a bit and `scalar = k_n * 2^n + ... + k_1 * 2 + k_0`. - let mut bits: Vec = k - .to_le_bits() - .into_iter() - .take(C::Scalar::NUM_BITS as usize) - .collect(); - bits.reverse(); - assert_eq!(bits.len(), C::Scalar::NUM_BITS as usize); - - bits +fn decompose_for_scalar_mul(scalar: Option) -> Vec> { + let bits = scalar.map(|scalar| { + // Cast from base field into scalar field. + // Assumptions: + // - The witnessed scalar field element fits into the base field. + // - The scalar field order is larger than the base field order. + let scalar = C::Scalar::from_bytes(&scalar.to_bytes()).unwrap(); + + // The scalar field `F_q = 2^254 + t_q`. + // -((2^127)^2) = -(2^254) = t_q (mod q) + // + // Assumptions: + // - The scalar field can be represented in 255 bits. + assert_eq!(C::Scalar::NUM_BITS, 255); + let t_q = -(C::Scalar::from_u128(1u128 << 127).square()); + + // We will witness `k = scalar + t_q` + // `k` is decomposed bitwise in-circuit for our double-and-add algorithm. + let k = scalar + t_q; + + // `k` is decomposed bitwise (big-endian) into `[k_n, ..., k_0]`, where + // each `k_i` is a bit and `scalar = k_n * 2^n + ... + k_1 * 2 + k_0`. + let mut bits: Vec = k + .to_le_bits() + .into_iter() + .take(C::Scalar::NUM_BITS as usize) + .collect(); + bits.reverse(); + assert_eq!(bits.len(), C::Scalar::NUM_BITS as usize); + + bits + }); + + if let Some(bits) = bits { + bits.into_iter().map(Some).collect() + } else { + vec![None; C::Scalar::NUM_BITS as usize] + } } diff --git a/src/circuit/gadget/ecc/chip/mul/complete.rs b/src/circuit/gadget/ecc/chip/mul/complete.rs index f795eda64..e41cab7b9 100644 --- a/src/circuit/gadget/ecc/chip/mul/complete.rs +++ b/src/circuit/gadget/ecc/chip/mul/complete.rs @@ -61,23 +61,20 @@ impl Config { &self, region: &mut Region<'_, C::Base>, offset: usize, - bits: Option>, - base: &EccPoint, - mut acc: EccPoint, + bits: &[Option], + base: &EccPoint, + mut acc: EccPoint, mut z_val: Option, - ) -> Result<(EccPoint, Option), Error> { + ) -> Result<(EccPoint, Option), Error> { + // Make sure we have the correct number of bits for the complete addition + // part of variable-base scalar mul. + assert_eq!(bits.len(), Self::complete_len()); + // Enable selectors for complete range - for row in self.complete_range() { + for row in Self::complete_range() { self.q_mul_complete.enable(region, row + offset)?; } - // Convert Option> into Vec> - let bits: Vec> = if let Some(b) = bits { - b.into_iter().map(Some).collect() - } else { - return Err(Error::SynthesisError); - }; - // Complete addition for (iter, k) in bits.into_iter().enumerate() { // Each iteration uses 2 rows (two complete additions) @@ -91,8 +88,8 @@ impl Config { || z_val.ok_or(Error::SynthesisError), )?; z_val = z_val - .zip(k) - .map(|(z_val, k)| C::Base::from_u64(2) * z_val + C::Base::from_u64(k as u64)); + .zip(k.as_ref()) + .map(|(z_val, k)| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64)); region.assign_advice( || "z", self.z_complete, @@ -110,7 +107,9 @@ impl Config { // If the bit is set, use `y`; if the bit is not set, use `-y` let y_p = base.y.value; - let y_p = y_p.zip(k).map(|(y_p, k)| if !k { -y_p } else { y_p }); + let y_p = y_p + .zip(k.as_ref()) + .map(|(y_p, k)| if !k { -y_p } else { y_p }); let y_p_cell = region.assign_advice( || "y_p", diff --git a/src/circuit/gadget/ecc/chip/mul/incomplete.rs b/src/circuit/gadget/ecc/chip/mul/incomplete.rs index a7fd68eca..b7d17f9cd 100644 --- a/src/circuit/gadget/ecc/chip/mul/incomplete.rs +++ b/src/circuit/gadget/ecc/chip/mul/incomplete.rs @@ -1,5 +1,6 @@ use super::super::{util, CellValue, EccConfig, EccPoint}; use super::{Mul, X, Y, Z}; +use ff::Field; use halo2::{ arithmetic::{CurveAffine, FieldExt}, circuit::Region, @@ -159,14 +160,17 @@ impl Config { // non-overlapping columns. // Returns (x, y, z). #[allow(clippy::type_complexity)] - pub(super) fn double_and_add( + pub(super) fn double_and_add( &self, - region: &mut Region<'_, F>, + region: &mut Region<'_, C::Base>, offset: usize, - base: &EccPoint, - bits: Option>, - acc: (X, Y, Z), - ) -> Result<(X, Y, Z), Error> { + base: &EccPoint, + bits: &[Option], + acc: (X, Y, Z), + ) -> Result<(X, Y, Z), Error> { + // Check that we have the correct number of bits for this double-and-add. + assert_eq!(bits.len(), self.num_bits); + // Handle exceptional cases let (x_p, y_p) = (base.x.value, base.y.value); let (x_a, y_a) = (acc.0.value, acc.1 .0); @@ -175,9 +179,9 @@ impl Config { .zip(y_a) .map(|(((x_p, y_p), x_a), y_a)| { // A is point at infinity - if (x_p == F::zero() && y_p == F::zero()) + if (x_p == C::Base::zero() && y_p == C::Base::zero()) // Q is point at infinity - || (x_a == F::zero() && y_a == F::zero()) + || (x_a == C::Base::zero() && y_a == C::Base::zero()) // x_p = x_a || (x_p == x_a) { @@ -220,20 +224,13 @@ impl Config { )?; let mut y_a = *acc.1; - // Convert Option> into Vec> - let bits: Vec> = if let Some(b) = bits { - b.into_iter().map(Some).collect() - } else { - return Err(Error::SynthesisError); - }; - // Incomplete addition for (row, k) in bits.into_iter().enumerate() { // z_{i} = 2 * z_{i+1} + k_i let z_val = z .value - .zip(k) - .map(|(z_val, k)| F::from_u64(2) * z_val + F::from_u64(k as u64)); + .zip(k.as_ref()) + .map(|(z_val, k)| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64)); let z_cell = region.assign_advice( || "z", self.z, @@ -257,7 +254,9 @@ impl Config { )?; // If the bit is set, use `y`; if the bit is not set, use `-y` - let y_p = y_p.zip(k).map(|(y_p, k)| if !k { -y_p } else { y_p }); + let y_p = y_p + .zip(k.as_ref()) + .map(|(y_p, k)| if !k { -y_p } else { y_p }); // Compute and assign λ1⋅(x_A − x_P) = y_A − y_P let lambda1 = y_a @@ -285,7 +284,7 @@ impl Config { .zip(x_a.value) .zip(x_r) .map(|(((lambda1, y_a), x_a), x_r)| { - F::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 + C::Base::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 }); region.assign_advice( || "lambda2", diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index c5ed38c4e..3a968e060 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -109,7 +109,7 @@ impl Config { base: &OrchardFixedBase, offset: usize, region: &mut Region<'_, C::Base>, - ) -> Result, Error> { + ) -> Result, Error> { let full_width_config: full_width::Config = self.into(); full_width_config.assign_region(region, offset, scalar, base) } @@ -120,7 +120,7 @@ impl Config { base: &OrchardFixedBaseShort, offset: usize, region: &mut Region<'_, C::Base>, - ) -> Result, Error> { + ) -> Result, Error> { let short_config: short::Config = self.into(); short_config.assign_region(region, offset, scalar, base) } @@ -266,7 +266,7 @@ trait MulFixed { offset: usize, scalar: &ScalarFixed, base: &FixedBase, - ) -> Result<(EccPoint, EccPoint), Error> { + ) -> Result<(EccPoint, EccPoint), Error> { // Assign fixed columns for given fixed base self.assign_fixed_constants(region, offset, base)?; @@ -361,7 +361,7 @@ trait MulFixed { offset: usize, base: &FixedBase, scalar: &ScalarFixed, - ) -> Result, Error> { + ) -> Result, Error> { // Witness `m0` in `x_p`, `y_p` cells on row 0 let k0 = scalar.k_field()[0]; let m0 = k0.map(|k0| base.value() * (k0 + C::Scalar::from_u64(2))); @@ -405,10 +405,10 @@ trait MulFixed { &self, region: &mut Region<'_, C::Base>, offset: usize, - mut acc: EccPoint, + mut acc: EccPoint, base: &FixedBase, scalar: &ScalarFixed, - ) -> Result, Error> { + ) -> Result, Error> { // This is 2^w, where w is the window width let h = C::Scalar::from_u64(constants::H as u64); @@ -456,7 +456,7 @@ trait MulFixed { offset: usize, base: &FixedBase, scalar: &ScalarFixed, - ) -> Result, Error> { + ) -> Result, Error> { // This is 2^w, where w is the window width let h = C::Scalar::from_u64(constants::H as u64); 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 089de3723..c1221603b 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -102,7 +102,7 @@ impl Config { offset: usize, scalar: &EccScalarFixed, base: &OrchardFixedBase, - ) -> Result, Error> { + ) -> Result, Error> { let (acc, mul_b) = self.assign_region_inner(region, offset, &scalar.into(), &base.into())?; diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index b2110dcc2..a61801b39 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -130,7 +130,7 @@ impl Config { offset: usize, scalar: &EccScalarFixedShort, base: &OrchardFixedBaseShort, - ) -> Result, Error> { + ) -> Result, Error> { let (acc, mul_b) = self.assign_region_inner(region, offset, &scalar.into(), &base.into())?; @@ -156,15 +156,14 @@ impl Config { )?; // Conditionally negate `y`-coordinate - let y_val = match sign.value { - Some(sign) => { - if sign == -C::Base::one() { - magnitude_mul.y.value.map(|y: C::Base| -y) - } else { - magnitude_mul.y.value - } + let y_val = if let Some(sign) = sign.value { + if sign == -C::Base::one() { + magnitude_mul.y.value.map(|y: C::Base| -y) + } else { + magnitude_mul.y.value } - None => None, + } else { + None }; // Enable mul_fixed_short selector on final row diff --git a/src/circuit/gadget/ecc/chip/witness_point.rs b/src/circuit/gadget/ecc/chip/witness_point.rs index c420a6e3e..f4242b6b7 100644 --- a/src/circuit/gadget/ecc/chip/witness_point.rs +++ b/src/circuit/gadget/ecc/chip/witness_point.rs @@ -43,7 +43,7 @@ impl Config { value: Option, offset: usize, region: &mut Region<'_, C::Base>, - ) -> Result, Error> { + ) -> Result, Error> { // Enable `q_point` selector self.q_point.enable(region, offset)?; diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs index d473a54b4..672674f59 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs @@ -98,7 +98,7 @@ trait WitnessScalarFixed { } // Decompose scalar into windows - let bits: Option> = value.map(|value| { + let windows: Option> = value.map(|value| { util::decompose_scalar_fixed::( value, Self::SCALAR_NUM_BITS, @@ -109,17 +109,23 @@ trait WitnessScalarFixed { // Store the scalar decomposition let mut k_bits: Vec> = Vec::new(); - if let Some(bits) = bits { - for (idx, window) in bits.iter().enumerate() { - let window = C::Base::from_u64(*window as u64); - let k_var = region.assign_advice( - || format!("k[{:?}]", offset + idx), - self.k(), - offset + idx, - || Ok(window), - )?; - k_bits.push(CellValue::new(k_var, Some(window))); - } + let windows: Vec> = if let Some(windows) = windows { + windows + .into_iter() + .map(|window| Some(C::Base::from_u64(window as u64))) + .collect() + } else { + vec![None; Self::NUM_WINDOWS] + }; + + for (idx, window) in windows.into_iter().enumerate() { + let k_var = region.assign_advice( + || format!("k[{:?}]", offset + idx), + self.k(), + offset + idx, + || window.ok_or(Error::SynthesisError), + )?; + k_bits.push(CellValue::new(k_var, window)); } Ok(k_bits) From 5497e152f1dd98be516a7f53b560f7118cda3c01 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 28 May 2021 12:33:03 +0800 Subject: [PATCH 30/49] Derive Eq for EccChip --- src/circuit/gadget/ecc/chip.rs | 21 ++++----------------- src/circuit/gadget/ecc/chip/load.rs | 24 ++++++++++++------------ src/constants.rs | 10 +++++----- 3 files changed, 21 insertions(+), 34 deletions(-) diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 476114da9..f6aa3179e 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -62,7 +62,7 @@ impl EccPoint { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] /// For each Orchard fixed base, we precompute: /// * coefficients for x-coordinate interpolation polynomials, and /// * z-values such that y + z = u^2 some square while -y + z is non-square. @@ -75,7 +75,7 @@ pub struct EccLoaded { } /// Configuration for the ECC chip -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] #[allow(non_snake_case)] pub struct EccConfig { /// Advice column for scalar decomposition into bits @@ -123,21 +123,12 @@ pub struct EccConfig { } /// A chip implementing EccInstructions -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct EccChip { - id: u64, config: EccConfig, loaded: EccLoaded, } -impl PartialEq for EccChip { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Eq for EccChip {} - impl EccLoaded { fn get(&self, point: OrchardFixedBases) -> OrchardFixedBase { match point { @@ -171,11 +162,7 @@ impl EccChip { config: >::Config, loaded: >::Loaded, ) -> Self { - Self { - id: rand::random::(), - config, - loaded, - } + Self { config, loaded } } #[allow(non_snake_case)] diff --git a/src/circuit/gadget/ecc/chip/load.rs b/src/circuit/gadget/ecc/chip/load.rs index dd117d149..a1bf58b73 100644 --- a/src/circuit/gadget/ecc/chip/load.rs +++ b/src/circuit/gadget/ecc/chip/load.rs @@ -3,7 +3,7 @@ use std::convert::TryInto; use crate::constants::{self, FixedBase, H, NUM_WINDOWS, NUM_WINDOWS_SHORT}; use halo2::arithmetic::{CurveAffine, FieldExt}; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum OrchardFixedBases { CommitIvkR(constants::CommitIvkR), NoteCommitR(constants::NoteCommitR), @@ -11,11 +11,11 @@ pub enum OrchardFixedBases { ValueCommitR(constants::ValueCommitR), } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct OrchardFixedBasesShort(pub constants::ValueCommitV); /// A fixed base to be used in scalar multiplication with a full-width scalar. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct OrchardFixedBase { pub base: OrchardFixedBases, pub lagrange_coeffs: LagrangeCoeffs, @@ -24,7 +24,7 @@ pub struct OrchardFixedBase { } /// A fixed base to be used in scalar multiplication with a short signed exponent. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct OrchardFixedBaseShort { pub base: OrchardFixedBasesShort, pub lagrange_coeffs_short: LagrangeCoeffsShort, @@ -85,7 +85,7 @@ pub(super) fn value_commit_v() -> OrchardFixedBaseShort { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] // 8 coefficients per window pub struct WindowLagrangeCoeffs(pub Box<[F; H]>); @@ -95,7 +95,7 @@ impl From<&[F; H]> for WindowLagrangeCoeffs { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] // 85 windows per base (with the exception of ValueCommitV) pub struct LagrangeCoeffs(pub Box<[WindowLagrangeCoeffs; constants::NUM_WINDOWS]>); @@ -113,7 +113,7 @@ impl From> for LagrangeCoeffs { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] // 22 windows for ValueCommitV pub struct LagrangeCoeffsShort(pub Box<[WindowLagrangeCoeffs; NUM_WINDOWS_SHORT]>); @@ -131,7 +131,7 @@ impl From> for LagrangeCoeffsShort { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] // 85 Z's per base (with the exception of ValueCommitV) pub struct Z(pub Box<[F; NUM_WINDOWS]>); @@ -148,7 +148,7 @@ impl From<[u64; NUM_WINDOWS]> for Z { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] // 22 Z's for ValueCommitV pub struct ZShort(pub Box<[F; NUM_WINDOWS_SHORT]>); @@ -165,7 +165,7 @@ impl From<[u64; NUM_WINDOWS_SHORT]> for ZShort { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] // 8 u's per window pub struct WindowUs(pub Box<[F; H]>); @@ -183,7 +183,7 @@ impl From<&[[u8; 32]; H]> for WindowUs { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] // 85 windows per base (with the exception of ValueCommitV) pub struct U(pub Box<[WindowUs; NUM_WINDOWS]>); @@ -200,7 +200,7 @@ impl From<[[[u8; 32]; H]; NUM_WINDOWS]> for U { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] // 22 windows for ValueCommitV pub struct UShort(pub Box<[WindowUs; NUM_WINDOWS_SHORT]>); diff --git a/src/constants.rs b/src/constants.rs index 22a556703..8773cfde8 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -58,19 +58,19 @@ pub const NUM_WINDOWS_SHORT: usize = /// scalar multiplication pub const NUM_COMPLETE_BITS: usize = 3; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct CommitIvkR(pub OrchardFixedBase); -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct NoteCommitR(pub OrchardFixedBase); -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct NullifierK(pub OrchardFixedBase); -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct ValueCommitR(pub OrchardFixedBase); -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct ValueCommitV(pub OrchardFixedBase); #[derive(Copy, Clone, Debug, Eq, PartialEq)] From 4948479c549d8bb32641ca565120511916bff94f Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 28 May 2021 12:56:19 +0800 Subject: [PATCH 31/49] Remove point doubling API. --- src/circuit/gadget/ecc.rs | 8 -- src/circuit/gadget/ecc/chip.rs | 28 ------ src/circuit/gadget/ecc/chip/double.rs | 124 -------------------------- 3 files changed, 160 deletions(-) delete mode 100644 src/circuit/gadget/ecc/chip/double.rs diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 82ecc6c95..0d8a83573 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -113,14 +113,6 @@ pub trait EccInstructions: Chip { b: &Self::Point, ) -> Result; - #[cfg(test)] - /// Performs point doubling, returning `[2] a`. - fn double( - &self, - layouter: &mut impl Layouter, - a: &Self::Point, - ) -> Result; - /// Performs variable-base scalar multiplication, returning `[scalar] base`. /// Multiplication of the identity [a]𝒪 returns an error. fn mul( diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index f6aa3179e..2faad0da3 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -9,8 +9,6 @@ use halo2::{ mod add; mod add_incomplete; -#[cfg(test)] -mod double; mod load; mod mul; mod mul_fixed; @@ -92,10 +90,6 @@ pub struct EccConfig { /// Fixed z such that y + z = u^2 some square, and -y + z is a non-square. (Used in fixed-base scalar multiplication) pub fixed_z: Column, - #[cfg(test)] - /// Point doubling (not used in the Orchard circuit) - pub q_double: Selector, - /// Incomplete addition pub q_add_incomplete: Selector, /// Complete addition @@ -189,8 +183,6 @@ impl EccChip { meta.fixed_column(), ], fixed_z: meta.fixed_column(), - #[cfg(test)] - q_double: meta.selector(), q_add_incomplete: meta.selector(), q_add: meta.selector(), q_mul_hi: meta.selector(), @@ -227,13 +219,6 @@ impl EccChip { config.create_gate::(meta); } - // Create point doubling gate - #[cfg(test)] - { - let config: double::Config = (&config).into(); - config.create_gate(meta); - } - // Create incomplete point addition gate { let config: add_incomplete::Config = (&config).into(); @@ -406,19 +391,6 @@ impl EccInstructions for EccChip { ) } - #[cfg(test)] - fn double( - &self, - layouter: &mut impl Layouter, - a: &Self::Point, - ) -> Result { - let config: double::Config = self.config().into(); - layouter.assign_region( - || "point doubling", - |mut region| config.assign_region(a, 0, &mut region), - ) - } - fn mul( &self, layouter: &mut impl Layouter, diff --git a/src/circuit/gadget/ecc/chip/double.rs b/src/circuit/gadget/ecc/chip/double.rs deleted file mode 100644 index 1ed4c1be0..000000000 --- a/src/circuit/gadget/ecc/chip/double.rs +++ /dev/null @@ -1,124 +0,0 @@ -use super::{util, CellValue, EccConfig, EccPoint}; - -use ff::Field; -use group::Curve; -use halo2::{ - arithmetic::{CurveAffine, FieldExt}, - circuit::Region, - plonk::{Advice, Column, ConstraintSystem, Error, Permutation, Selector}, - poly::Rotation, -}; - -#[derive(Clone, Debug)] -pub struct Config { - q_double: Selector, - // x-coordinate of P in [2]P = R - x_p: Column, - // y-coordinate of P in [2]P = R - y_p: Column, - // x-coordinate of R in [2]P = R - x_r: Column, - // y-coordinate of R in [2]P = R - y_r: Column, - // Permutation - perm: Permutation, -} - -impl From<&EccConfig> for Config { - fn from(ecc_config: &EccConfig) -> Self { - Self { - q_double: ecc_config.q_double, - x_p: ecc_config.P.0, - y_p: ecc_config.P.1, - x_r: ecc_config.extras[0], - y_r: ecc_config.extras[1], - perm: ecc_config.perm.clone(), - } - } -} - -impl Config { - pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { - let q_double = meta.query_selector(self.q_double, Rotation::cur()); - let x_p = meta.query_advice(self.x_p, Rotation::cur()); - let y_p = meta.query_advice(self.y_p, Rotation::cur()); - let x_r = meta.query_advice(self.x_r, Rotation::cur()); - let y_r = meta.query_advice(self.y_r, Rotation::cur()); - - let x_p_2 = x_p.clone() * x_p.clone(); - let x_p_4 = x_p_2.clone() * x_p_2.clone(); - - // 4⋅(y_p)^2⋅(x_r + 2⋅x_p) − 9⋅(x_p)^4 = 0 - meta.create_gate("point doubling expr1", |_| { - let expr1 = y_p.clone() - * y_p.clone() - * (x_r.clone() + x_p.clone() * F::from_u64(2)) - * F::from_u64(4) - - x_p_4 * F::from_u64(9); - q_double.clone() * expr1 - }); - - // 2⋅y_p⋅(y_r + y_p) − 3⋅(x_p)^2⋅(x_p − x_r) = 0 - meta.create_gate("point doubling expr2", |_| { - let expr2 = - y_p.clone() * (y_r + y_p) * F::from_u64(2) - x_p_2 * (x_p - x_r) * F::from_u64(3); - - q_double * expr2 - }); - } - - pub(super) fn assign_region( - &self, - p: &EccPoint, - offset: usize, - region: &mut Region<'_, C::Base>, - ) -> Result, Error> { - // Enable `q_double` selector - self.q_double.enable(region, offset)?; - - // Return error if `P` is point at infinity - let (x_p, y_p) = (p.x.value, p.y.value); - x_p.zip(y_p) - .map(|(x, y)| { - if x == C::Base::zero() && y == C::Base::zero() { - Err(Error::SynthesisError) - } else { - Ok(()) - } - }) - .transpose()?; - - // Copy the point `P` into `x_p`, `y_p` columns - util::assign_and_constrain(region, || "x_p", self.x_p, offset, &p.x, &self.perm)?; - util::assign_and_constrain(region, || "y_p", self.y_p, offset, &p.y, &self.perm)?; - - // Compute the doubled point - let r = p.point().map(|p| { - let r = p * C::Scalar::from_u64(2); - let r = r.to_affine().coordinates().unwrap(); - - (*r.x(), *r.y()) - }); - let x_r = r.map(|r| r.0); - let y_r = r.map(|r| r.1); - - // Assign the doubled point to `x_r`, `y_r` columns - let x_r_var = region.assign_advice( - || "x_r", - self.x_r, - offset, - || x_r.ok_or(Error::SynthesisError), - )?; - let y_r_var = region.assign_advice( - || "y_r", - self.y_r, - offset, - || y_r.ok_or(Error::SynthesisError), - )?; - - Ok(EccPoint { - x: CellValue::::new(x_r_var, x_r), - y: CellValue::::new(y_r_var, y_r), - }) - } -} From b0d04f78d0cadd5d3aa571d6d5f8117e21bb9322 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 28 May 2021 13:17:49 +0800 Subject: [PATCH 32/49] Define inv0() in add.rs --- src/circuit/gadget/ecc/chip/add.rs | 47 ++++++++++-------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 3c3b95c3d..ee49e1bb3 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -61,7 +61,6 @@ impl Config { let y_r = meta.query_advice(self.y_qr, Rotation::next()); let lambda = meta.query_advice(self.lambda, Rotation::cur()); - // inv0(x) is 0 if x = 0, 1/x otherwise. // α = inv0(x_q - x_p) let alpha = meta.query_advice(self.alpha, Rotation::cur()); // β = inv0(x_p) @@ -224,15 +223,8 @@ impl Config { self.alpha, offset, || { - let x_p = x_p.ok_or(Error::SynthesisError)?; - let x_q = x_q.ok_or(Error::SynthesisError)?; - - let alpha = if x_q != x_p { - (x_q - x_p).invert().unwrap() - } else { - C::Base::zero() - }; - Ok(alpha) + let alpha = x_p.zip(x_q).map(|(x_p, x_q)| inv0(x_q - x_p)); + alpha.ok_or(Error::SynthesisError) }, )?; @@ -242,14 +234,8 @@ impl Config { self.beta, offset, || { - let x_p = x_p.ok_or(Error::SynthesisError)?; - - let beta = if x_p != C::Base::zero() { - x_p.invert().unwrap() - } else { - C::Base::zero() - }; - Ok(beta) + let beta = x_p.map(|x_p| inv0(x_p)); + beta.ok_or(Error::SynthesisError) }, )?; @@ -259,14 +245,8 @@ impl Config { self.gamma, offset, || { - let x_q = x_q.ok_or(Error::SynthesisError)?; - - let gamma = if x_q != C::Base::zero() { - x_q.invert().unwrap() - } else { - C::Base::zero() - }; - Ok(gamma) + let gamma = x_q.map(|x_q| inv0(x_q)); + gamma.ok_or(Error::SynthesisError) }, )?; @@ -282,11 +262,7 @@ impl Config { let y_q = y_q.ok_or(Error::SynthesisError)?; let delta = if x_q == x_p { - if y_q != -y_p { - (y_q + y_p).invert().unwrap() - } else { - C::Base::zero() - } + inv0(y_q + y_p) } else { C::Base::zero() }; @@ -383,3 +359,12 @@ impl Config { }) } } + +// inv0(x) is 0 if x = 0, 1/x otherwise. +fn inv0(x: F) -> F { + if x == F::zero() { + F::zero() + } else { + x.invert().unwrap() + } +} From e5397205d5a4bd4132185c9868b08f110471440c Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 28 May 2021 13:53:23 +0800 Subject: [PATCH 33/49] mul.rs: Replace Mul trait with free-floating functions --- src/circuit/gadget/ecc/chip/mul.rs | 92 +++++++++---------- src/circuit/gadget/ecc/chip/mul/complete.rs | 10 +- src/circuit/gadget/ecc/chip/mul/incomplete.rs | 8 +- 3 files changed, 51 insertions(+), 59 deletions(-) diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 4c73ff4f4..46a3b6bf7 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -47,49 +47,6 @@ impl From<&EccConfig> for Config { } } -trait Mul { - // Bits used in incomplete addition. k_{254} to k_{4} inclusive - fn incomplete_len() -> usize { - C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS - } - - fn incomplete_range() -> Range { - 0..Self::incomplete_len() - } - - // Bits used in `lo` half of incomplete addition - fn incomplete_lo_range() -> Range { - (Self::incomplete_len() / 2)..Self::incomplete_len() - } - - // Bits used in `hi` half of incomplete addition - fn incomplete_hi_range() -> Range { - 0..(Self::incomplete_len() / 2) - } - - // Bits k_{254} to k_{4} inclusive are used in incomplete addition. - // The `lo` half is k_{129} to k_{4} inclusive (length 126 bits). - fn incomplete_lo_len() -> usize { - (Self::incomplete_len() + 1) / 2 - } - - // Bits k_{254} to k_{4} inclusive are used in incomplete addition. - // The `hi` half is k_{254} to k_{130} inclusive (length 125 bits). - fn incomplete_hi_len() -> usize { - Self::incomplete_len() / 2 - } - - fn complete_range() -> Range { - Self::incomplete_len()..(C::Scalar::NUM_BITS as usize - 1) - } - - fn complete_len() -> usize { - NUM_COMPLETE_BITS as usize - } -} - -impl Mul for Config {} - impl Config { pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { self.hi_config.create_gate(meta); @@ -143,7 +100,7 @@ impl Config { let z = CellValue::new(z_cell, Some(z_val)); // Double-and-add (incomplete addition) for the `hi` half of the scalar decomposition - let k_incomplete_hi = &k_bits[Self::incomplete_hi_range()]; + let k_incomplete_hi = &k_bits[incomplete_hi_range::()]; let (x, y_a, z) = self.hi_config.double_and_add( region, offset + 1, @@ -153,7 +110,7 @@ impl Config { )?; // Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition - let k_incomplete_lo = &k_bits[Self::incomplete_lo_range()]; + let k_incomplete_lo = &k_bits[incomplete_lo_range::()]; let (x, y_a, z) = self.lo_config.double_and_add( region, offset + 1, @@ -165,7 +122,7 @@ impl Config { // Move from incomplete addition to complete addition let acc = { let y_a_col = self.add_config.y_qr; - let row = Self::incomplete_lo_len() + 2; + let row = incomplete_lo_len::() + 2; let y_a_cell = region.assign_advice( || "y_a", @@ -190,7 +147,7 @@ impl Config { let complete_config: complete::Config = self.into(); // Bits used in complete addition. k_{3} to k_{1} inclusive // The LSB k_{0} is handled separately. - let k_complete = &k_bits[Self::complete_range()]; + let k_complete = &k_bits[complete_range::()]; let (acc, z_val) = complete_config.assign_region(region, offset, k_complete, base, acc, z.value)?; @@ -211,7 +168,7 @@ impl Config { mut z_val: Option, ) -> Result, Error> { // Assign the final `z` value. - let k_0_row = Self::incomplete_lo_len() + Self::complete_len() * 2 + 4; + let k_0_row = incomplete_lo_len::() + complete_len::() * 2 + 4; z_val = z_val .zip(k_0) .map(|(z_val, k_0)| C::Base::from_u64(2) * z_val + C::Base::from_u64(k_0 as u64)); @@ -361,3 +318,42 @@ fn decompose_for_scalar_mul(scalar: Option) -> Vec() -> usize { + C::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS +} + +fn incomplete_range() -> Range { + 0..incomplete_len::() +} + +// Bits used in `lo` half of incomplete addition +fn incomplete_lo_range() -> Range { + (incomplete_len::() / 2)..incomplete_len::() +} + +// Bits used in `hi` half of incomplete addition +fn incomplete_hi_range() -> Range { + 0..(incomplete_len::() / 2) +} + +// Bits k_{254} to k_{4} inclusive are used in incomplete addition. +// The `lo` half is k_{129} to k_{4} inclusive (length 126 bits). +fn incomplete_lo_len() -> usize { + (incomplete_len::() + 1) / 2 +} + +// Bits k_{254} to k_{4} inclusive are used in incomplete addition. +// The `hi` half is k_{254} to k_{130} inclusive (length 125 bits). +fn incomplete_hi_len() -> usize { + incomplete_len::() / 2 +} + +fn complete_range() -> Range { + incomplete_len::()..(C::Scalar::NUM_BITS as usize - 1) +} + +fn complete_len() -> usize { + NUM_COMPLETE_BITS as usize +} diff --git a/src/circuit/gadget/ecc/chip/mul/complete.rs b/src/circuit/gadget/ecc/chip/mul/complete.rs index e41cab7b9..190bad5b9 100644 --- a/src/circuit/gadget/ecc/chip/mul/complete.rs +++ b/src/circuit/gadget/ecc/chip/mul/complete.rs @@ -1,5 +1,5 @@ use super::super::{add, util, CellValue, EccPoint}; -use super::Mul; +use super::{complete_len, complete_range, incomplete_lo_len}; use ff::Field; use halo2::{ @@ -34,8 +34,6 @@ impl From<&super::Config> for Config { } } -impl Mul for Config {} - impl Config { /// Gate used to check scalar decomposition is correct. /// This is used to check the bits used in complete addition, since the incomplete @@ -68,17 +66,17 @@ impl Config { ) -> Result<(EccPoint, Option), Error> { // Make sure we have the correct number of bits for the complete addition // part of variable-base scalar mul. - assert_eq!(bits.len(), Self::complete_len()); + assert_eq!(bits.len(), complete_len::()); // Enable selectors for complete range - for row in Self::complete_range() { + for row in complete_range::() { self.q_mul_complete.enable(region, row + offset)?; } // Complete addition for (iter, k) in bits.into_iter().enumerate() { // Each iteration uses 2 rows (two complete additions) - let row = Self::incomplete_lo_len() + 2 * iter + 3; + let row = incomplete_lo_len::() + 2 * iter + 3; // Check scalar decomposition here region.assign_advice( diff --git a/src/circuit/gadget/ecc/chip/mul/incomplete.rs b/src/circuit/gadget/ecc/chip/mul/incomplete.rs index b7d17f9cd..a02fd2a47 100644 --- a/src/circuit/gadget/ecc/chip/mul/incomplete.rs +++ b/src/circuit/gadget/ecc/chip/mul/incomplete.rs @@ -1,5 +1,5 @@ use super::super::{util, CellValue, EccConfig, EccPoint}; -use super::{Mul, X, Y, Z}; +use super::{incomplete_hi_len, incomplete_lo_len, X, Y, Z}; use ff::Field; use halo2::{ arithmetic::{CurveAffine, FieldExt}, @@ -31,14 +31,12 @@ pub(super) struct Config { _marker: PhantomData, } -impl Mul for Config {} - impl Config { // Columns used in processing the `hi` bits of the scalar. // `x_p, y_p` are shared across the `hi` and `lo` halves. pub(super) fn hi_config(ecc_config: &EccConfig) -> Self { Self { - num_bits: Self::incomplete_hi_len(), + num_bits: incomplete_hi_len::(), q_mul: ecc_config.q_mul_hi, z: ecc_config.bits, x_a: ecc_config.extras[0], @@ -55,7 +53,7 @@ impl Config { // `x_p, y_p` are shared across the `hi` and `lo` halves. pub(super) fn lo_config(ecc_config: &EccConfig) -> Self { Self { - num_bits: Self::incomplete_lo_len(), + num_bits: incomplete_lo_len::(), q_mul: ecc_config.q_mul_lo, z: ecc_config.extras[1], x_a: ecc_config.extras[2], From 9beaf68093db4de1fb76286eabe78b936e6cbf1b Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 28 May 2021 17:20:36 +0800 Subject: [PATCH 34/49] Remove misleading column names at top-level config. The EccConfig only knows about 10 generic advice columns. These columns are renamed by individual components (e.g. AddConfig, MulConfig). The mapping for each individual config is captured in its impl From. --- src/circuit/gadget/ecc.rs | 12 +- src/circuit/gadget/ecc/chip.rs | 35 ++---- src/circuit/gadget/ecc/chip/add.rs | 33 ++++-- src/circuit/gadget/ecc/chip/add_incomplete.rs | 8 +- src/circuit/gadget/ecc/chip/mul.rs | 111 +++++++++++------- src/circuit/gadget/ecc/chip/mul/complete.rs | 6 +- src/circuit/gadget/ecc/chip/mul/incomplete.rs | 24 ++-- src/circuit/gadget/ecc/chip/mul_fixed.rs | 21 ++-- .../gadget/ecc/chip/mul_fixed/short.rs | 5 +- src/circuit/gadget/ecc/chip/witness_point.rs | 4 +- .../gadget/ecc/chip/witness_scalar_fixed.rs | 2 +- 11 files changed, 151 insertions(+), 110 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 0d8a83573..8a39c706f 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -397,10 +397,12 @@ mod tests { type Config = EccConfig; fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let bits = meta.advice_column(); - let P = (meta.advice_column(), meta.advice_column()); - let lambda = (meta.advice_column(), meta.advice_column()); - let extras = [ + 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(), @@ -408,7 +410,7 @@ mod tests { meta.advice_column(), ]; - EccChip::::configure(meta, bits, P, lambda, extras) + EccChip::::configure(meta, advices) } fn synthesize( diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 2faad0da3..d3aefc12a 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -76,14 +76,8 @@ pub struct EccLoaded { #[derive(Clone, Debug, Eq, PartialEq)] #[allow(non_snake_case)] pub struct EccConfig { - /// Advice column for scalar decomposition into bits - pub bits: Column, - /// Holds a point (x_p, y_p) - pub P: (Column, Column), - /// A pair (lambda1, lambda2) representing gradients - pub lambda: (Column, Column), /// Advice columns needed by instructions in the ECC chip. - pub extras: [Column; 5], + pub advices: [Column; 10], /// Coefficients of interpolation polynomials for x-coordinates (used in fixed-base scalar multiplication) pub lagrange_coeffs: [Column; constants::H], @@ -162,16 +156,10 @@ impl EccChip { #[allow(non_snake_case)] pub fn configure( meta: &mut ConstraintSystem, - bits: Column, - P: (Column, Column), - lambda: (Column, Column), - extras: [Column; 5], + advices: [Column; 10], ) -> >::Config { let config = EccConfig { - bits, - P, - lambda, - extras, + advices, lagrange_coeffs: [ meta.fixed_column(), meta.fixed_column(), @@ -197,12 +185,15 @@ impl EccChip { perm: Permutation::new( meta, &[ - P.0.into(), - P.1.into(), - bits.into(), - extras[0].into(), - extras[1].into(), - extras[2].into(), + advices[0].into(), + advices[1].into(), + advices[2].into(), + advices[3].into(), + advices[4].into(), + advices[6].into(), + advices[7].into(), + advices[8].into(), + advices[9].into(), ], ), }; @@ -305,7 +296,7 @@ impl EccInstructions for EccChip { |mut region| { let cell = region.assign_advice( || "Scalar var", - self.config().P.0, + self.config().advices[0], 0, || value.ok_or(Error::SynthesisError), )?; diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index ee49e1bb3..f9d9a6150 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -6,6 +6,7 @@ use halo2::{ plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector}, poly::Rotation, }; +use std::collections::HashSet; #[derive(Clone, Debug)] pub struct Config { @@ -36,21 +37,35 @@ impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { Self { q_add: ecc_config.q_add, - lambda: ecc_config.lambda.0, - x_p: ecc_config.P.0, - y_p: ecc_config.P.1, - x_qr: ecc_config.extras[0], - y_qr: ecc_config.extras[1], - alpha: ecc_config.extras[2], - beta: ecc_config.extras[3], - gamma: ecc_config.extras[4], - delta: ecc_config.lambda.1, + x_p: ecc_config.advices[0], + y_p: ecc_config.advices[1], + x_qr: ecc_config.advices[2], + y_qr: ecc_config.advices[3], + lambda: ecc_config.advices[4], + alpha: ecc_config.advices[5], + beta: ecc_config.advices[6], + gamma: ecc_config.advices[7], + delta: ecc_config.advices[8], perm: ecc_config.perm.clone(), } } } impl Config { + pub(crate) fn advice_columns(&self) -> HashSet> { + let mut advice_columns = HashSet::new(); + advice_columns.insert(self.x_p); + advice_columns.insert(self.y_p); + advice_columns.insert(self.x_qr); + advice_columns.insert(self.y_qr); + advice_columns.insert(self.lambda); + advice_columns.insert(self.alpha); + advice_columns.insert(self.beta); + advice_columns.insert(self.gamma); + advice_columns.insert(self.delta); + advice_columns + } + pub(crate) fn create_gate(&self, meta: &mut ConstraintSystem) { let q_add = meta.query_selector(self.q_add, Rotation::cur()); let x_p = meta.query_advice(self.x_p, Rotation::cur()); diff --git a/src/circuit/gadget/ecc/chip/add_incomplete.rs b/src/circuit/gadget/ecc/chip/add_incomplete.rs index dca63d414..4a375cd61 100644 --- a/src/circuit/gadget/ecc/chip/add_incomplete.rs +++ b/src/circuit/gadget/ecc/chip/add_incomplete.rs @@ -27,10 +27,10 @@ impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { Self { q_add_incomplete: ecc_config.q_add_incomplete, - x_p: ecc_config.P.0, - y_p: ecc_config.P.1, - x_qr: ecc_config.extras[0], - y_qr: ecc_config.extras[1], + x_p: ecc_config.advices[0], + y_p: ecc_config.advices[1], + x_qr: ecc_config.advices[2], + y_qr: ecc_config.advices[3], perm: ecc_config.perm.clone(), } } diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 46a3b6bf7..74192a119 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -34,16 +34,37 @@ pub struct Config { impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { - Self { + let config = Self { q_mul_complete: ecc_config.q_mul_complete, q_mul_decompose_var: ecc_config.q_mul_decompose_var, - z_complete: ecc_config.bits, - scalar: ecc_config.extras[0], + z_complete: ecc_config.advices[9], + scalar: ecc_config.advices[1], perm: ecc_config.perm.clone(), add_config: ecc_config.into(), hi_config: incomplete::Config::hi_config(ecc_config), lo_config: incomplete::Config::lo_config(ecc_config), - } + }; + + assert_eq!( + config.hi_config.x_p, config.lo_config.x_p, + "x_p is shared across hi and lo halves." + ); + assert_eq!( + config.hi_config.y_p, config.lo_config.y_p, + "y_p is shared across hi and lo halves." + ); + + let add_config_advices = config.add_config.advice_columns(); + assert!( + !add_config_advices.contains(&config.z_complete), + "z_complete cannot overlap with complete addition columns." + ); + assert!( + !add_config_advices.contains(&config.hi_config.z), + "hi_config z cannot overlap with complete addition columns." + ); + + config } } @@ -96,14 +117,17 @@ impl Config { // Initialize the running sum for scalar decomposition to zero let z_val = C::Base::zero(); let z_cell = - region.assign_advice(|| "initial z", self.hi_config.z, offset + 1, || Ok(z_val))?; + region.assign_advice(|| "initial z", self.hi_config.z, offset, || Ok(z_val))?; let z = CellValue::new(z_cell, Some(z_val)); + // Increase the offset by 1 after initializing `z`. + let offset = offset + 1; + // Double-and-add (incomplete addition) for the `hi` half of the scalar decomposition let k_incomplete_hi = &k_bits[incomplete_hi_range::()]; let (x, y_a, z) = self.hi_config.double_and_add( region, - offset + 1, + offset, &base, k_incomplete_hi, (X(acc.x.clone()), Y(acc.y.value), Z(z)), @@ -111,45 +135,52 @@ impl Config { // Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition let k_incomplete_lo = &k_bits[incomplete_lo_range::()]; - let (x, y_a, z) = self.lo_config.double_and_add( - region, - offset + 1, - &base, - k_incomplete_lo, - (x, y_a, z), - )?; + let (x, y_a, z) = + self.lo_config + .double_and_add(region, offset, &base, k_incomplete_lo, (x, y_a, z))?; // Move from incomplete addition to complete addition - let acc = { - let y_a_col = self.add_config.y_qr; - let row = incomplete_lo_len::() + 2; + let offset = offset + incomplete_lo_len::() + 1; + // Get value of acc after incomplete addition + let acc = { + // Assign final `y_a` output from incomplete addition let y_a_cell = region.assign_advice( || "y_a", - y_a_col, - row + offset, + self.add_config.y_qr, + offset, || y_a.ok_or(Error::SynthesisError), )?; - util::assign_and_constrain( - region, - || "Copy z from incomplete to complete", - self.z_complete, - row + offset, - &z, - &self.perm, - )?; + EccPoint { x: x.0, y: CellValue::::new(y_a_cell, *y_a), } }; - let complete_config: complete::Config = self.into(); - // Bits used in complete addition. k_{3} to k_{1} inclusive - // The LSB k_{0} is handled separately. - let k_complete = &k_bits[complete_range::()]; - let (acc, z_val) = - complete_config.assign_region(region, offset, k_complete, base, acc, z.value)?; + // Initialize `z` running sum for complete addition + util::assign_and_constrain( + region, + || "Initialize `z` running sum for complete addition", + self.z_complete, + offset, + &z, + &self.perm, + )?; + + // Increase the offset by 1 after complete addition. + let offset = offset + 1; + + // Complete addition + let (acc, z_val) = { + let complete_config: complete::Config = self.into(); + // Bits used in complete addition. k_{3} to k_{1} inclusive + // The LSB k_{0} is handled separately. + let k_complete = &k_bits[complete_range::()]; + complete_config.assign_region(region, offset, k_complete, base, acc, z.value)? + }; + + let offset = offset + complete_len::() * 2; // Process the least significant bit let k_0 = k_bits[C::Scalar::NUM_BITS as usize - 1]; @@ -168,14 +199,13 @@ impl Config { mut z_val: Option, ) -> Result, Error> { // Assign the final `z` value. - let k_0_row = incomplete_lo_len::() + complete_len::() * 2 + 4; z_val = z_val .zip(k_0) .map(|(z_val, k_0)| C::Base::from_u64(2) * z_val + C::Base::from_u64(k_0 as u64)); region.assign_advice( || "final z", self.z_complete, - k_0_row + offset, + offset, || z_val.ok_or(Error::SynthesisError), )?; @@ -193,16 +223,15 @@ impl Config { region, || "original scalar", self.scalar, - k_0_row + offset, + offset, &scalar, &self.perm, )?; - self.q_mul_decompose_var.enable(region, k_0_row + offset)?; + self.q_mul_decompose_var.enable(region, offset)?; // If `k_0` is 0, return `Acc + (-P)`. If `k_0` is 1, simply return `Acc + 0`. let x_p = if let Some(k_0) = k_0 { if !k_0 { - println!("!k_0"); base.x.value } else { Some(C::Base::zero()) @@ -212,7 +241,6 @@ impl Config { }; let y_p = if let Some(k_0) = k_0 { if !k_0 { - println!("!k_0"); base.y.value.map(|y_p| -y_p) } else { Some(C::Base::zero()) @@ -224,14 +252,14 @@ impl Config { let x_p_cell = region.assign_advice( || "x_p", self.add_config.x_p, - k_0_row + offset, + offset + 1, || x_p.ok_or(Error::SynthesisError), )?; let y_p_cell = region.assign_advice( || "y_p", self.add_config.y_p, - k_0_row + offset, + offset + 1, || y_p.ok_or(Error::SynthesisError), )?; @@ -241,8 +269,7 @@ impl Config { }; // Return the result of the final complete addition as `[scalar]B` - self.add_config - .assign_region(&p, &acc, k_0_row + offset + 1, region) + self.add_config.assign_region(&p, &acc, offset + 1, region) } } diff --git a/src/circuit/gadget/ecc/chip/mul/complete.rs b/src/circuit/gadget/ecc/chip/mul/complete.rs index 190bad5b9..f588e19e6 100644 --- a/src/circuit/gadget/ecc/chip/mul/complete.rs +++ b/src/circuit/gadget/ecc/chip/mul/complete.rs @@ -1,5 +1,5 @@ use super::super::{add, util, CellValue, EccPoint}; -use super::{complete_len, complete_range, incomplete_lo_len}; +use super::{complete_len, complete_range}; use ff::Field; use halo2::{ @@ -76,7 +76,7 @@ impl Config { // Complete addition for (iter, k) in bits.into_iter().enumerate() { // Each iteration uses 2 rows (two complete additions) - let row = incomplete_lo_len::() + 2 * iter + 3; + let row = 2 * iter; // Check scalar decomposition here region.assign_advice( @@ -95,6 +95,7 @@ impl Config { || z_val.ok_or(Error::SynthesisError), )?; + // Assign `x_p` for complete addition let x_p = base.x.value; let x_p_cell = region.assign_advice( || "x_p", @@ -103,6 +104,7 @@ impl Config { || x_p.ok_or(Error::SynthesisError), )?; + // Assign `y_p` for complete addition. // If the bit is set, use `y`; if the bit is not set, use `-y` let y_p = base.y.value; let y_p = y_p diff --git a/src/circuit/gadget/ecc/chip/mul/incomplete.rs b/src/circuit/gadget/ecc/chip/mul/incomplete.rs index a02fd2a47..30a1af488 100644 --- a/src/circuit/gadget/ecc/chip/mul/incomplete.rs +++ b/src/circuit/gadget/ecc/chip/mul/incomplete.rs @@ -38,12 +38,12 @@ impl Config { Self { num_bits: incomplete_hi_len::(), q_mul: ecc_config.q_mul_hi, - z: ecc_config.bits, - x_a: ecc_config.extras[0], - x_p: ecc_config.P.0, - y_p: ecc_config.P.1, - lambda1: ecc_config.lambda.0, - lambda2: ecc_config.lambda.1, + x_p: ecc_config.advices[0], + y_p: ecc_config.advices[1], + z: ecc_config.advices[9], + x_a: ecc_config.advices[3], + lambda1: ecc_config.advices[4], + lambda2: ecc_config.advices[5], perm: ecc_config.perm.clone(), _marker: PhantomData, } @@ -55,12 +55,12 @@ impl Config { Self { num_bits: incomplete_lo_len::(), q_mul: ecc_config.q_mul_lo, - z: ecc_config.extras[1], - x_a: ecc_config.extras[2], - x_p: ecc_config.P.0, - y_p: ecc_config.P.1, - lambda1: ecc_config.extras[3], - lambda2: ecc_config.extras[4], + x_p: ecc_config.advices[0], + y_p: ecc_config.advices[1], + z: ecc_config.advices[6], + x_a: ecc_config.advices[7], + lambda1: ecc_config.advices[8], + lambda2: ecc_config.advices[2], perm: ecc_config.perm.clone(), _marker: PhantomData, } diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index 3a968e060..6e632882c 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -31,8 +31,6 @@ pub struct Config { // y-coordinate of the multiple of the fixed base at the current window. y_p: Column, // y-coordinate of accumulator (only used in the final row). - y_a: Column, - // An integer `u` for the current window, s.t. `y + z = u^2`. u: Column, // Permutation perm: Permutation, @@ -51,11 +49,10 @@ impl From<&EccConfig> for Config { q_mul_fixed_short: ecc_config.q_mul_fixed_short, lagrange_coeffs: ecc_config.lagrange_coeffs, fixed_z: ecc_config.fixed_z, - k: ecc_config.bits, - x_p: ecc_config.P.0, - y_p: ecc_config.P.1, - y_a: ecc_config.extras[1], - u: ecc_config.extras[2], + x_p: ecc_config.advices[0], + y_p: ecc_config.advices[1], + k: ecc_config.advices[4], + u: ecc_config.advices[5], perm: ecc_config.perm.clone(), add_config: ecc_config.into(), add_incomplete_config: ecc_config.into(), @@ -81,6 +78,16 @@ impl From<&EccConfig> for Config { config.y_p, config.add_incomplete_config.y_p, "add_incomplete is used internally in mul_fixed." ); + for advice in [config.x_p, config.y_p, config.k, config.u].iter() { + assert_ne!( + *advice, config.add_config.x_qr, + "Do not overlap with output columns of add." + ); + assert_ne!( + *advice, config.add_config.y_qr, + "Do not overlap with output columns of add." + ); + } // Check relationships between this config and `witness_point_config`. assert_eq!( diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index a61801b39..d6e9186fe 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -27,8 +27,6 @@ pub struct Config { x_p: Column, // y-coordinate of the multiple of the fixed base at the current window. y_p: Column, - // y-coordinate of accumulator (only used in the final row). - y_a: Column, // An integer `u` for the current window, s.t. `y + z = u^2`. u: Column, // Permutation @@ -52,7 +50,6 @@ impl From<&super::Config> for Config { k_s: config.k, x_p: config.x_p, y_p: config.y_p, - y_a: config.y_a, u: config.u, perm: config.perm.clone(), add_config: config.add_config.clone(), @@ -107,7 +104,7 @@ impl Config { pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { let q_mul_fixed_short = meta.query_selector(self.q_mul_fixed_short, Rotation::cur()); let y_p = meta.query_advice(self.y_p, Rotation::cur()); - let y_a = meta.query_advice(self.y_a, Rotation::cur()); + let y_a = meta.query_advice(self.add_config().y_qr, Rotation::cur()); // `(x_a, y_a)` is the result of `[m]B`, where `m` is the magnitude. // We conditionally negate this result using `y_p = y_a * s`, where `s` is the sign. diff --git a/src/circuit/gadget/ecc/chip/witness_point.rs b/src/circuit/gadget/ecc/chip/witness_point.rs index f4242b6b7..280c14f2f 100644 --- a/src/circuit/gadget/ecc/chip/witness_point.rs +++ b/src/circuit/gadget/ecc/chip/witness_point.rs @@ -20,8 +20,8 @@ impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { Self { q_point: ecc_config.q_point, - x: ecc_config.P.0, - y: ecc_config.P.1, + x: ecc_config.advices[0], + y: ecc_config.advices[1], } } } diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs index 672674f59..9ade51a4f 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs @@ -22,7 +22,7 @@ impl From<&EccConfig> for Config { Self { q_scalar_fixed: ecc_config.q_scalar_fixed, q_scalar_fixed_short: ecc_config.q_scalar_fixed_short, - k_s: ecc_config.bits, + k_s: ecc_config.advices[0], } } } From 13ebe7857ea9bbb1b422ab3793dc2eb38f453f84 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Fri, 28 May 2021 16:17:47 +0100 Subject: [PATCH 35/49] Add check for a corner case of fixed-base scalar multiplication that requires a doubling. Signed-off-by: Daira Hopwood --- src/circuit/gadget/ecc.rs | 74 ++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 8a39c706f..1a1d22e8f 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -550,39 +550,71 @@ mod tests { // Check fixed-base scalar multiplication { - let scalar_fixed = C::Scalar::rand(); let nullifier_k = constants::nullifier_k::generator(); let base = nullifier_k.0.value(); - let real_mul_fixed = base * scalar_fixed; - - let scalar_fixed = super::ScalarFixed::new( - chip.clone(), - layouter.namespace(|| "ScalarFixed"), - Some(scalar_fixed), - )?; let nullifier_k = super::FixedPoint::get( chip.clone(), OrchardFixedBases::NullifierK(nullifier_k), )?; // [a]B - let mul_fixed = nullifier_k.mul(layouter.namespace(|| "mul"), &scalar_fixed)?; - if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { - assert_eq!(real_mul_fixed.to_affine(), C::from_xy(x, y).unwrap()); + { + let scalar_fixed = C::Scalar::rand(); + let real_mul_fixed = base * scalar_fixed; + + let scalar_fixed = super::ScalarFixed::new( + chip.clone(), + layouter.namespace(|| "ScalarFixed"), + Some(scalar_fixed), + )?; + + let mul_fixed = nullifier_k.mul(layouter.namespace(|| "mul"), &scalar_fixed)?; + if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { + assert_eq!(real_mul_fixed.to_affine(), C::from_xy(x, y).unwrap()); + } + } + + // 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 = C::ScalarExt::from_u64(constants::H as u64); + let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334" + .chars() + .fold(C::ScalarExt::zero(), |acc, c| { + acc * h + C::ScalarExt::from_u64(c.to_digit(8).unwrap().into()) + }); + let real_mul_fixed = base * scalar_fixed; + + let scalar_fixed = super::ScalarFixed::new( + chip.clone(), + layouter.namespace(|| "ScalarFixed"), + Some(scalar_fixed), + )?; + + let mul_fixed = + nullifier_k.mul(layouter.namespace(|| "mul with double"), &scalar_fixed)?; + if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { + assert_eq!(real_mul_fixed.to_affine(), C::from_xy(x, y).unwrap()); + } } // [0]B should return (0,0) since it uses complete addition // on the last step. - let scalar_fixed = C::Scalar::zero(); - let scalar_fixed = super::ScalarFixed::new( - chip.clone(), - layouter.namespace(|| "ScalarFixed"), - Some(scalar_fixed), - )?; - let mul_fixed = nullifier_k.mul(layouter.namespace(|| "mul"), &scalar_fixed)?; - if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { - assert_eq!(C::Base::zero(), x); - assert_eq!(C::Base::zero(), y); + { + let scalar_fixed = C::Scalar::zero(); + let scalar_fixed = super::ScalarFixed::new( + chip.clone(), + layouter.namespace(|| "ScalarFixed"), + Some(scalar_fixed), + )?; + let mul_fixed = + nullifier_k.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)?; + if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { + assert_eq!(C::Base::zero(), x); + assert_eq!(C::Base::zero(), y); + } } } From 381584f017b94845549470d2145466e5bd56163a Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Fri, 28 May 2021 17:21:31 +0100 Subject: [PATCH 36/49] Add check for a corner case of *short* fixed-base scalar multiplication that requires a doubling. Signed-off-by: Daira Hopwood --- src/circuit/gadget/ecc.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 1a1d22e8f..71bc10f8c 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -698,6 +698,24 @@ mod tests { checks.push((real_mul_fixed_short, mul_fixed_short)); } + // There is a single canonical sequence of window values for which a doubling occurs on the last step: + // 1333333333333333333334 in octal. + // [0xB6DB_6DB6_DB6D_B6DC] B + { + let scalar_fixed_short = C::Scalar::from_u64(0xB6DB_6DB6_DB6D_B6DCu64); + let real_mul_fixed_short = value_commit_v_inner.0.value() * scalar_fixed_short; + + let scalar_fixed_short = super::ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "ScalarFixedShort"), + Some(scalar_fixed_short), + )?; + let mul_fixed_short = value_commit_v + .mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; + + checks.push((real_mul_fixed_short, mul_fixed_short)); + } + for check in checks.into_iter() { let real_mul_fixed_short = check.0; let mul_fixed_short = check.1; From dbb6120380adca8350abf6e295b8ef954bdf388b Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 29 May 2021 12:43:13 +0800 Subject: [PATCH 37/49] Reduce test boilerplate Inline equality assertions on values to individual components (flagged off with #[cfg(test)]. Move module-specific tests into respective modules. --- src/circuit/gadget/ecc.rs | 348 +++--------------- src/circuit/gadget/ecc/chip.rs | 16 +- src/circuit/gadget/ecc/chip/add.rs | 103 +++++- src/circuit/gadget/ecc/chip/add_incomplete.rs | 81 +++- src/circuit/gadget/ecc/chip/mul.rs | 62 +++- src/circuit/gadget/ecc/chip/mul_fixed.rs | 4 +- .../gadget/ecc/chip/mul_fixed/full_width.rs | 99 ++++- .../gadget/ecc/chip/mul_fixed/short.rs | 121 +++++- 8 files changed, 490 insertions(+), 344 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 71bc10f8c..9db08d33c 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -227,7 +227,7 @@ impl + Clone + Debug + Eq> } /// An elliptic curve point over the given curve. -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] pub struct Point + Clone + Debug + Eq> { chip: EccChip, inner: EccChip::Point, @@ -376,10 +376,9 @@ impl + Clone + Debug + Eq> FixedPoin #[cfg(test)] mod tests { use crate::constants; - use ff::Field; use group::{Curve, Group}; use halo2::{ - arithmetic::{CurveAffine, CurveExt, FieldExt}, + arithmetic::CurveAffine, circuit::{layouter::SingleChipLayouter, Layouter}, dev::MockProver, pasta::pallas, @@ -436,333 +435,68 @@ mod tests { // Make sure P and Q are not the same point. assert_ne!(p_val, q_val); - // Check complete addition P + (-P) - let zero = { - let zero = p.add(layouter.namespace(|| "P + (-P)"), &p_neg)?; - if let (Some(x), Some(y)) = (zero.inner.x.value, zero.inner.y.value) { - assert_eq!(C::Base::zero(), x); - assert_eq!(C::Base::zero(), y); - } - zero - }; - - // Check complete addition 𝒪 + 𝒪 - { - let zero = zero.add(layouter.namespace(|| "𝒪 + 𝒪"), &zero)?; - if let (Some(x), Some(y)) = (zero.inner.x.value, zero.inner.y.value) { - assert_eq!(C::Base::zero(), x); - assert_eq!(C::Base::zero(), y); - } - } + // Generate a (0,0) point to be used in other tests. + let zero = p.add(layouter.namespace(|| "P + (-P)"), &p_neg)?; - // Check complete addition (other cases) + // Test complete addition { - let mut checks = Vec::<(C::CurveExt, super::Point>)>::new(); - // P + Q - let real_added = p_val + q_val; - let added_complete = p.add(layouter.namespace(|| "P + Q"), &q)?; - checks.push((real_added, added_complete)); - - // P + P - let real_added = p_val + p_val; - let added_complete = p.add(layouter.namespace(|| "P + P"), &p)?; - checks.push((real_added, added_complete)); - - // 𝒪 + P - let real_added = p_val.to_curve(); - let added_complete = p.add(layouter.namespace(|| "𝒪 + P"), &zero)?; - checks.push((real_added, added_complete)); - - // P + 𝒪 - let real_added = p_val.to_curve(); - let added_complete = zero.add(layouter.namespace(|| "P + 𝒪"), &p)?; - checks.push((real_added, added_complete)); - - // (x, y) + (ζx, -y) should behave like normal P + Q. - let endo_p = p_val.to_curve().endo(); - let real_added = p_val.to_curve() + endo_p; - let endo_p = super::Point::new( + super::chip::add::tests::test_add( chip.clone(), - layouter.namespace(|| "point"), - Some(endo_p.to_affine()), + layouter.namespace(|| "complete addition"), + &zero, + p_val, + &p, + q_val, + &q, + &p_neg, )?; - let added_complete = p.add(layouter.namespace(|| "P + endo(P)"), &endo_p)?; - checks.push((real_added, added_complete)); - - // (x, y) + ((ζ^2)x, -y) - let endo_p = p_val.to_curve().endo().endo(); - let real_added = p_val.to_curve() + endo_p; - let endo_p = super::Point::new( - chip.clone(), - layouter.namespace(|| "point"), - Some(endo_p.to_affine()), - )?; - let added_complete = p.add(layouter.namespace(|| "P + endo(P)"), &endo_p)?; - checks.push((real_added, added_complete)); - - for check in checks.into_iter() { - let real_added = check.0; - let added_complete = check.1; - if let (Some(x), Some(y)) = - (added_complete.inner.x.value, added_complete.inner.y.value) - { - if C::from_xy(x, y).is_some().into() { - assert_eq!(real_added.to_affine(), C::from_xy(x, y).unwrap()); - } - } - } } - // Check incomplete addition + // Test incomplete addition { - // P + Q - let real_added = p_val + q_val; - let added_incomplete = p.add_incomplete(layouter.namespace(|| "P + Q"), &q)?; - if let (Some(x), Some(y)) = ( - added_incomplete.inner.x.value, - added_incomplete.inner.y.value, - ) { - if C::from_xy(x, y).is_some().into() { - assert_eq!(real_added.to_affine(), C::from_xy(x, y).unwrap()); - } - } - - // P + P should return an error - p.add_incomplete(layouter.namespace(|| "P + P"), &p) - .expect_err("P + P should return an error"); - - // P + (-P) should return an error - p.add_incomplete(layouter.namespace(|| "P + (-P)"), &p_neg) - .expect_err("P + (-P) should return an error"); - - // P + 𝒪 should return an error - p.add_incomplete(layouter.namespace(|| "P + 𝒪"), &zero) - .expect_err("P + 0 should return an error"); - - // 𝒪 + P should return an error - zero.add_incomplete(layouter.namespace(|| "𝒪 + P"), &p) - .expect_err("0 + P should return an error"); - - // 𝒪 + 𝒪 should return an error - zero.add_incomplete(layouter.namespace(|| "𝒪 + 𝒪"), &zero) - .expect_err("𝒪 + 𝒪 should return an error"); + super::chip::add_incomplete::tests::test_add_incomplete( + layouter.namespace(|| "incomplete addition"), + &zero, + &p, + &q, + &p_neg, + )?; } - // Check fixed-base scalar multiplication + // Test variable-base scalar multiplication { - let nullifier_k = constants::nullifier_k::generator(); - let base = nullifier_k.0.value(); - let nullifier_k = super::FixedPoint::get( + super::chip::mul::tests::test_mul( chip.clone(), - OrchardFixedBases::NullifierK(nullifier_k), + layouter.namespace(|| "variable-base scalar mul"), + &zero, + &p, )?; - - // [a]B - { - let scalar_fixed = C::Scalar::rand(); - let real_mul_fixed = base * scalar_fixed; - - let scalar_fixed = super::ScalarFixed::new( - chip.clone(), - layouter.namespace(|| "ScalarFixed"), - Some(scalar_fixed), - )?; - - let mul_fixed = nullifier_k.mul(layouter.namespace(|| "mul"), &scalar_fixed)?; - if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { - assert_eq!(real_mul_fixed.to_affine(), C::from_xy(x, y).unwrap()); - } - } - - // 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 = C::ScalarExt::from_u64(constants::H as u64); - let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334" - .chars() - .fold(C::ScalarExt::zero(), |acc, c| { - acc * h + C::ScalarExt::from_u64(c.to_digit(8).unwrap().into()) - }); - let real_mul_fixed = base * scalar_fixed; - - let scalar_fixed = super::ScalarFixed::new( - chip.clone(), - layouter.namespace(|| "ScalarFixed"), - Some(scalar_fixed), - )?; - - let mul_fixed = - nullifier_k.mul(layouter.namespace(|| "mul with double"), &scalar_fixed)?; - if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { - assert_eq!(real_mul_fixed.to_affine(), C::from_xy(x, y).unwrap()); - } - } - - // [0]B should return (0,0) since it uses complete addition - // on the last step. - { - let scalar_fixed = C::Scalar::zero(); - let scalar_fixed = super::ScalarFixed::new( - chip.clone(), - layouter.namespace(|| "ScalarFixed"), - Some(scalar_fixed), - )?; - let mul_fixed = - nullifier_k.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)?; - if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { - assert_eq!(C::Base::zero(), x); - assert_eq!(C::Base::zero(), y); - } - } } - // Check short signed fixed-base scalar multiplication + // Test full-width fixed-base scalar multiplication { - let value_commit_v_inner = constants::value_commit_v::generator(); - let value_commit_v = super::FixedPointShort::get( + let nullifier_k = super::FixedPoint::get( chip.clone(), - OrchardFixedBasesShort(value_commit_v_inner), + OrchardFixedBases::NullifierK(constants::nullifier_k::generator()), + )?; + super::chip::mul_fixed::full_width::tests::test_mul_fixed( + chip.clone(), + layouter.namespace(|| "full-width fixed-base scalar mul"), + nullifier_k, )?; - - // [0]B should return (0,0) since it uses complete addition - // on the last step. - { - let scalar_fixed = C::Scalar::zero(); - let scalar_fixed = super::ScalarFixedShort::new( - chip.clone(), - layouter.namespace(|| "ScalarFixedShort"), - Some(scalar_fixed), - )?; - let mul_fixed = - value_commit_v.mul(layouter.namespace(|| "mul"), &scalar_fixed)?; - if let (Some(x), Some(y)) = (mul_fixed.inner.x.value, mul_fixed.inner.y.value) { - assert_eq!(C::Base::zero(), x); - assert_eq!(C::Base::zero(), y); - } - } - - let mut checks = Vec::<(C::CurveExt, super::Point>)>::new(); - - // Random [a]B - { - let scalar_fixed_short = C::Scalar::from_u64(rand::random::()); - let mut sign = C::Scalar::one(); - if rand::random::() { - sign = -sign; - } - let scalar_fixed_short = sign * scalar_fixed_short; - let real_mul_fixed_short = value_commit_v_inner.0.value() * scalar_fixed_short; - - let scalar_fixed_short = super::ScalarFixedShort::new( - chip.clone(), - layouter.namespace(|| "ScalarFixedShort"), - Some(scalar_fixed_short), - )?; - let mul_fixed_short = value_commit_v - .mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; - - checks.push((real_mul_fixed_short, mul_fixed_short)); - } - - // [2^64 - 1]B - { - let scalar_fixed_short = C::Scalar::from_u64(0xFFFF_FFFF_FFFF_FFFFu64); - let real_mul_fixed_short = value_commit_v_inner.0.value() * scalar_fixed_short; - - let scalar_fixed_short = super::ScalarFixedShort::new( - chip.clone(), - layouter.namespace(|| "ScalarFixedShort"), - Some(scalar_fixed_short), - )?; - let mul_fixed_short = value_commit_v - .mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; - - checks.push((real_mul_fixed_short, mul_fixed_short)); - } - - // [-(2^64 - 1)]B - { - let scalar_fixed_short = -C::Scalar::from_u64(0xFFFF_FFFF_FFFF_FFFFu64); - let real_mul_fixed_short = value_commit_v_inner.0.value() * scalar_fixed_short; - - let scalar_fixed_short = super::ScalarFixedShort::new( - chip.clone(), - layouter.namespace(|| "ScalarFixedShort"), - Some(scalar_fixed_short), - )?; - let mul_fixed_short = value_commit_v - .mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; - - checks.push((real_mul_fixed_short, mul_fixed_short)); - } - - // There is a single canonical sequence of window values for which a doubling occurs on the last step: - // 1333333333333333333334 in octal. - // [0xB6DB_6DB6_DB6D_B6DC] B - { - let scalar_fixed_short = C::Scalar::from_u64(0xB6DB_6DB6_DB6D_B6DCu64); - let real_mul_fixed_short = value_commit_v_inner.0.value() * scalar_fixed_short; - - let scalar_fixed_short = super::ScalarFixedShort::new( - chip.clone(), - layouter.namespace(|| "ScalarFixedShort"), - Some(scalar_fixed_short), - )?; - let mul_fixed_short = value_commit_v - .mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; - - checks.push((real_mul_fixed_short, mul_fixed_short)); - } - - for check in checks.into_iter() { - let real_mul_fixed_short = check.0; - let mul_fixed_short = check.1; - if let (Some(x), Some(y)) = - (mul_fixed_short.inner.x.value, mul_fixed_short.inner.y.value) - { - assert_eq!(real_mul_fixed_short.to_affine(), C::from_xy(x, y).unwrap()); - } - } } - // Check variable-base scalar multiplication + // Test signed short fixed-base scalar multiplication { - let scalar_val = C::Scalar::rand(); - let real_mul = p_val * scalar_val; - - let scalar_val = C::Base::from_bytes(&scalar_val.to_bytes()).unwrap(); - let scalar = super::ScalarVar::new( + let value_commit_v = super::FixedPointShort::get( chip.clone(), - layouter.namespace(|| "ScalarVar"), - Some(scalar_val), + OrchardFixedBasesShort(constants::value_commit_v::generator()), )?; - - // [a]B - let mul = p.mul(layouter.namespace(|| "mul"), &scalar)?; - if let (Some(x), Some(y)) = (mul.inner.x.value, mul.inner.y.value) { - assert_eq!(real_mul.to_affine(), C::from_xy(x, y).unwrap()); - } - - // [a]𝒪 should return an error since variable-base scalar multiplication - // uses incomplete addition at the beginning of its double-and-add. - zero.mul(layouter.namespace(|| "mul"), &scalar) - .expect_err("[a]𝒪 should return an error"); - - // [0]B should return (0,0) since variable-base scalar multiplication - // uses complete addition for the final bits of the scalar. - let scalar_val = C::Base::zero(); - let scalar = super::ScalarVar::new( - chip, - layouter.namespace(|| "ScalarVar"), - Some(scalar_val), + super::chip::mul_fixed::short::tests::test_mul_fixed_short( + chip.clone(), + layouter.namespace(|| "full-width fixed-base scalar mul"), + value_commit_v, )?; - let mul = p.mul(layouter.namespace(|| "mul"), &scalar)?; - if let (Some(x), Some(y)) = (mul.inner.x.value, mul.inner.y.value) { - assert_eq!(C::Base::zero(), x); - assert_eq!(C::Base::zero(), y); - } } Ok(()) diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index d3aefc12a..b44e94c6f 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -7,14 +7,14 @@ use halo2::{ plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Permutation, Selector}, }; -mod add; -mod add_incomplete; -mod load; -mod mul; -mod mul_fixed; -mod util; -mod witness_point; -mod witness_scalar_fixed; +pub(super) mod add; +pub(super) mod add_incomplete; +pub(super) mod load; +pub(super) mod mul; +pub(super) mod mul_fixed; +pub(super) mod util; +pub(super) mod witness_point; +pub(super) mod witness_scalar_fixed; pub use load::*; diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index f9d9a6150..8c6a0fb19 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -211,24 +211,24 @@ impl Config { pub(super) fn assign_region( &self, - a: &EccPoint, - b: &EccPoint, + p: &EccPoint, + q: &EccPoint, offset: usize, region: &mut Region<'_, C::Base>, ) -> Result, Error> { // Enable `q_add` selector self.q_add.enable(region, offset)?; - // Copy point `a` into `x_p`, `y_p` columns - util::assign_and_constrain(region, || "x_p", self.x_p, offset, &a.x, &self.perm)?; - util::assign_and_constrain(region, || "y_p", self.y_p, offset, &a.y, &self.perm)?; + // Copy point `p` into `x_p`, `y_p` columns + util::assign_and_constrain(region, || "x_p", self.x_p, offset, &p.x, &self.perm)?; + util::assign_and_constrain(region, || "y_p", self.y_p, offset, &p.y, &self.perm)?; - // Copy point `b` into `x_qr`, `y_qr` columns - util::assign_and_constrain(region, || "x_q", self.x_qr, offset, &b.x, &self.perm)?; - util::assign_and_constrain(region, || "y_q", self.y_qr, offset, &b.y, &self.perm)?; + // Copy point `q` into `x_qr`, `y_qr` columns + util::assign_and_constrain(region, || "x_q", self.x_qr, offset, &q.x, &self.perm)?; + util::assign_and_constrain(region, || "y_q", self.y_qr, offset, &q.y, &self.perm)?; - let (x_p, y_p) = (a.x.value, a.y.value); - let (x_q, y_q) = (b.x.value, b.y.value); + let (x_p, y_p) = (p.x.value, p.y.value); + let (x_q, y_q) = (q.x.value, q.y.value); // inv0(x) evaluates to 0 if x = 0, and 1/x otherwise. @@ -368,10 +368,25 @@ impl Config { || y_r.ok_or(Error::SynthesisError), )?; - Ok(EccPoint { + let result = EccPoint:: { x: CellValue::::new(x_r_cell, x_r), y: CellValue::::new(y_r_cell, y_r), - }) + }; + + #[cfg(test)] + // Check that the correct sum is obtained. + { + use group::Curve; + + let p = p.point(); + let q = q.point(); + let real_sum = p.zip(q).map(|(p, q)| p + q); + let result = result.point(); + + assert_eq!(real_sum.unwrap().to_affine(), result.unwrap()); + } + + Ok(result) } } @@ -383,3 +398,67 @@ fn inv0(x: F) -> F { x.invert().unwrap() } } + +#[cfg(test)] +pub mod tests { + use group::Curve; + use halo2::{ + arithmetic::{CurveAffine, CurveExt}, + circuit::Layouter, + plonk::Error, + }; + + use crate::circuit::gadget::ecc::{EccInstructions, Point}; + + pub fn test_add + Clone + Eq + std::fmt::Debug>( + chip: EccChip, + mut layouter: impl Layouter, + zero: &Point, + p_val: C, + p: &Point, + q_val: C, + q: &Point, + p_neg: &Point, + ) -> Result<(), Error> { + // Make sure P and Q are not the same point. + assert_ne!(p_val, q_val); + + // Check complete addition P + (-P) + p.add(layouter.namespace(|| "P + (-P)"), &p_neg)?; + + // Check complete addition 𝒪 + 𝒪 + zero.add(layouter.namespace(|| "𝒪 + 𝒪"), &zero)?; + + // Check P + Q + p.add(layouter.namespace(|| "P + Q"), &q)?; + + // P + P + p.add(layouter.namespace(|| "P + P"), &p)?; + + // P + 𝒪 + p.add(layouter.namespace(|| "P + 𝒪"), &zero)?; + + // 𝒪 + P + zero.add(layouter.namespace(|| "𝒪 + P"), &p)?; + + // (x, y) + (ζx, -y) should behave like normal P + Q. + let endo_p = p_val.to_curve().endo(); + let endo_p = Point::new( + chip.clone(), + layouter.namespace(|| "point"), + Some(endo_p.to_affine()), + )?; + p.add(layouter.namespace(|| "P + endo(P)"), &endo_p)?; + + // (x, y) + ((ζ^2)x, -y) + let endo_p = p_val.to_curve().endo().endo(); + let endo_p = Point::new( + chip.clone(), + layouter.namespace(|| "point"), + Some(endo_p.to_affine()), + )?; + p.add(layouter.namespace(|| "P + endo(P)"), &endo_p)?; + + Ok(()) + } +} diff --git a/src/circuit/gadget/ecc/chip/add_incomplete.rs b/src/circuit/gadget/ecc/chip/add_incomplete.rs index 4a375cd61..ab1632e44 100644 --- a/src/circuit/gadget/ecc/chip/add_incomplete.rs +++ b/src/circuit/gadget/ecc/chip/add_incomplete.rs @@ -104,15 +104,20 @@ impl Config { util::assign_and_constrain(region, || "y_q", self.y_qr, offset, &q.y, &self.perm)?; // Compute the sum `P + Q = R` - let p = p.point(); - let q = q.point(); - let r = p.zip(q).map(|(p, q)| { - let r = (p + q).to_affine().coordinates().unwrap(); - (*r.x(), *r.y()) - }); + let r = { + let p = p.point(); + let q = q.point(); + let r = p + .zip(q) + .map(|(p, q)| (p + q).to_affine().coordinates().unwrap()); + let r_x = r.map(|r| *r.x()); + let r_y = r.map(|r| *r.y()); + + (r_x, r_y) + }; // Assign the sum to `x_qr`, `y_qr` columns in the next row - let x_r = r.map(|r| r.0); + let x_r = r.0; let x_r_var = region.assign_advice( || "x_r", self.x_qr, @@ -120,7 +125,7 @@ impl Config { || x_r.ok_or(Error::SynthesisError), )?; - let y_r = r.map(|r| r.1); + let y_r = r.1; let y_r_var = region.assign_advice( || "y_r", self.y_qr, @@ -128,9 +133,65 @@ impl Config { || y_r.ok_or(Error::SynthesisError), )?; - Ok(EccPoint { + let result = EccPoint:: { x: CellValue::::new(x_r_var, x_r), y: CellValue::::new(y_r_var, y_r), - }) + }; + + #[cfg(test)] + // Check that the correct sum is obtained. + { + let p = p.point(); + let q = q.point(); + let real_sum = p.zip(q).map(|(p, q)| p + q); + let result = result.point(); + + assert_eq!(real_sum.unwrap().to_affine(), result.unwrap()); + } + + Ok(result) + } +} + +#[cfg(test)] +pub mod tests { + use halo2::{arithmetic::CurveAffine, circuit::Layouter, plonk::Error}; + + use crate::circuit::gadget::ecc::{EccInstructions, Point}; + + pub fn test_add_incomplete< + C: CurveAffine, + EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, + >( + mut layouter: impl Layouter, + zero: &Point, + p: &Point, + q: &Point, + p_neg: &Point, + ) -> Result<(), Error> { + // P + Q + p.add_incomplete(layouter.namespace(|| "P + Q"), &q)?; + + // P + P should return an error + p.add_incomplete(layouter.namespace(|| "P + P"), &p) + .expect_err("P + P should return an error"); + + // P + (-P) should return an error + p.add_incomplete(layouter.namespace(|| "P + (-P)"), &p_neg) + .expect_err("P + (-P) should return an error"); + + // P + 𝒪 should return an error + p.add_incomplete(layouter.namespace(|| "P + 𝒪"), &zero) + .expect_err("P + 0 should return an error"); + + // 𝒪 + P should return an error + zero.add_incomplete(layouter.namespace(|| "𝒪 + P"), &p) + .expect_err("0 + P should return an error"); + + // 𝒪 + 𝒪 should return an error + zero.add_incomplete(layouter.namespace(|| "𝒪 + 𝒪"), &zero) + .expect_err("𝒪 + 𝒪 should return an error"); + + Ok(()) } } diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 74192a119..425999a6b 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -184,7 +184,24 @@ impl Config { // Process the least significant bit let k_0 = k_bits[C::Scalar::NUM_BITS as usize - 1]; - self.process_lsb(region, offset, scalar, base, acc, k_0, z_val) + let result = self.process_lsb(region, offset, scalar, base, acc, k_0, z_val)?; + + #[cfg(test)] + // Check that the correct multiple is obtained. + { + use group::Curve; + + let base = base.point(); + let scalar = scalar + .value + .map(|scalar| C::Scalar::from_bytes(&scalar.to_bytes()).unwrap()); + let real_mul = base.zip(scalar).map(|(base, scalar)| base * scalar); + let result = result.point(); + + assert_eq!(real_mul.unwrap().to_affine(), result.unwrap()); + } + + Ok(result) } #[allow(clippy::too_many_arguments)] @@ -384,3 +401,46 @@ fn complete_range() -> Range { fn complete_len() -> usize { NUM_COMPLETE_BITS as usize } + +#[cfg(test)] +pub mod tests { + use ff::Field; + use halo2::{ + arithmetic::{CurveAffine, FieldExt}, + circuit::Layouter, + plonk::Error, + }; + + use crate::circuit::gadget::ecc::{EccInstructions, Point, ScalarVar}; + + pub fn test_mul + Clone + Eq + std::fmt::Debug>( + chip: EccChip, + mut layouter: impl Layouter, + zero: &Point, + p: &Point, + ) -> Result<(), Error> { + let scalar_val = C::Scalar::rand(); + let scalar_val = C::Base::from_bytes(&scalar_val.to_bytes()).unwrap(); + let scalar = ScalarVar::new( + chip.clone(), + layouter.namespace(|| "ScalarVar"), + Some(scalar_val), + )?; + + // [a]B + p.mul(layouter.namespace(|| "mul"), &scalar)?; + + // [a]𝒪 should return an error since variable-base scalar multiplication + // uses incomplete addition at the beginning of its double-and-add. + zero.mul(layouter.namespace(|| "mul"), &scalar) + .expect_err("[a]𝒪 should return an error"); + + // [0]B should return (0,0) since variable-base scalar multiplication + // uses complete addition for the final bits of the scalar. + let scalar_val = C::Base::zero(); + let scalar = ScalarVar::new(chip, layouter.namespace(|| "ScalarVar"), Some(scalar_val))?; + p.mul(layouter.namespace(|| "mul"), &scalar)?; + + Ok(()) + } +} diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index 6e632882c..8a8d3de44 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -13,8 +13,8 @@ use halo2::{ poly::Rotation, }; -mod full_width; -mod short; +pub mod full_width; +pub mod short; pub struct Config { q_mul_fixed: Selector, 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 c1221603b..84b8423b3 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -107,7 +107,102 @@ impl Config { self.assign_region_inner(region, offset, &scalar.into(), &base.into())?; // Add to the accumulator and return the final result as `[scalar]B`. - self.add_config - .assign_region(&mul_b, &acc, offset + constants::NUM_WINDOWS, region) + let result = + self.add_config + .assign_region(&mul_b, &acc, offset + constants::NUM_WINDOWS, region)?; + + #[cfg(test)] + // Check that the correct multiple is obtained. + { + use super::super::OrchardFixedBases; + use group::Curve; + use halo2::arithmetic::FieldExt; + + let base = match base.base { + OrchardFixedBases::CommitIvkR(base) => base.0.value(), + OrchardFixedBases::NoteCommitR(base) => base.0.value(), + OrchardFixedBases::NullifierK(base) => base.0.value(), + OrchardFixedBases::ValueCommitR(base) => base.0.value(), + }; + let scalar = scalar + .value + .map(|scalar| C::Scalar::from_bytes(&scalar.to_bytes()).unwrap()); + let real_mul = scalar.map(|scalar| base * scalar); + let result = result.point(); + + assert_eq!(real_mul.unwrap().to_affine(), result.unwrap()); + } + + Ok(result) + } +} + +#[cfg(test)] +pub mod tests { + use ff::Field; + use halo2::{ + arithmetic::{CurveAffine, FieldExt}, + circuit::Layouter, + plonk::Error, + }; + + use crate::circuit::gadget::ecc::{EccInstructions, FixedPoint, ScalarFixed}; + use crate::constants; + + pub fn test_mul_fixed< + C: CurveAffine, + EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, + >( + chip: EccChip, + mut layouter: impl Layouter, + nullifier_k: FixedPoint, + ) -> Result<(), Error> { + // [a]B + { + let scalar_fixed = C::Scalar::rand(); + + let scalar_fixed = ScalarFixed::new( + chip.clone(), + layouter.namespace(|| "ScalarFixed"), + Some(scalar_fixed), + )?; + + nullifier_k.mul(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 = C::ScalarExt::from_u64(constants::H as u64); + let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334" + .chars() + .fold(C::ScalarExt::zero(), |acc, c| { + acc * h + C::ScalarExt::from_u64(c.to_digit(8).unwrap().into()) + }); + + let scalar_fixed = ScalarFixed::new( + chip.clone(), + layouter.namespace(|| "ScalarFixed"), + Some(scalar_fixed), + )?; + + nullifier_k.mul(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 = C::Scalar::zero(); + let scalar_fixed = ScalarFixed::new( + chip.clone(), + layouter.namespace(|| "ScalarFixed"), + Some(scalar_fixed), + )?; + nullifier_k.mul(layouter.namespace(|| "mul by zero"), &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 d6e9186fe..077e05663 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -182,9 +182,126 @@ impl Config { || y_val.ok_or(Error::SynthesisError), )?; - Ok(EccPoint { + let result = EccPoint:: { x: CellValue::new(x_var, x_val), y: CellValue::new(y_var, y_val), - }) + }; + + #[cfg(test)] + // Check that the correct multiple is obtained. + { + use group::Curve; + + let base = base.base.0 .0.value(); + let scalar = scalar + .magnitude + .zip(scalar.sign.value) + .map(|(magnitude, sign)| { + let sign = if sign == C::Base::one() { + C::Scalar::one() + } else if sign == -C::Base::one() { + -C::Scalar::one() + } else { + panic!("Sign should be 1 or -1.") + }; + magnitude * sign + }); + let real_mul = scalar.map(|scalar| base * scalar); + let result = result.point(); + + assert_eq!(real_mul.unwrap().to_affine(), result.unwrap()); + } + + Ok(result) + } +} + +#[cfg(test)] +pub mod tests { + use ff::Field; + use halo2::{ + arithmetic::{CurveAffine, FieldExt}, + circuit::Layouter, + plonk::Error, + }; + + use crate::circuit::gadget::ecc::{EccInstructions, FixedPointShort, ScalarFixedShort}; + + pub fn test_mul_fixed_short< + C: CurveAffine, + EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, + >( + chip: EccChip, + mut layouter: impl Layouter, + value_commit_v: FixedPointShort, + ) -> Result<(), Error> { + // [0]B should return (0,0) since it uses complete addition + // on the last step. + { + let scalar_fixed = C::Scalar::zero(); + let scalar_fixed = ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "ScalarFixedShort"), + Some(scalar_fixed), + )?; + value_commit_v.mul(layouter.namespace(|| "mul"), &scalar_fixed)?; + } + + // Random [a]B + { + let scalar_fixed_short = C::Scalar::from_u64(rand::random::()); + let mut sign = C::Scalar::one(); + if rand::random::() { + sign = -sign; + } + let scalar_fixed_short = sign * scalar_fixed_short; + + let scalar_fixed_short = ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "ScalarFixedShort"), + Some(scalar_fixed_short), + )?; + value_commit_v.mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; + } + + // [2^64 - 1]B + { + let scalar_fixed_short = C::Scalar::from_u64(0xFFFF_FFFF_FFFF_FFFFu64); + + let scalar_fixed_short = ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "ScalarFixedShort"), + Some(scalar_fixed_short), + )?; + value_commit_v.mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; + } + + // [-(2^64 - 1)]B + { + let scalar_fixed_short = -C::Scalar::from_u64(0xFFFF_FFFF_FFFF_FFFFu64); + + let scalar_fixed_short = ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "ScalarFixedShort"), + Some(scalar_fixed_short), + )?; + value_commit_v.mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; + } + + // There is a single canonical sequence of window values for which a doubling occurs on the last step: + // 1333333333333333333334 in octal. + // [0xB6DB_6DB6_DB6D_B6DC] B + { + let scalar_fixed_short = C::Scalar::from_u64(0xB6DB_6DB6_DB6D_B6DCu64); + + let scalar_fixed_short = ScalarFixedShort::new( + chip.clone(), + layouter.namespace(|| "ScalarFixedShort"), + Some(scalar_fixed_short), + )?; + value_commit_v.mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?; + } + + Ok(()) } } From ffa3d6e7b3b334092aebfbd49a2358974921d0f9 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 29 May 2021 12:54:49 +0800 Subject: [PATCH 38/49] Check endo_p_neg, endo_p_2_neg in complete addition tests --- src/circuit/gadget/ecc.rs | 2 +- src/circuit/gadget/ecc/chip/add.rs | 32 +++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 9db08d33c..231793178 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -114,7 +114,7 @@ pub trait EccInstructions: Chip { ) -> Result; /// Performs variable-base scalar multiplication, returning `[scalar] base`. - /// Multiplication of the identity [a]𝒪 returns an error. + /// Multiplication of the identity [a] 𝒪 returns an error. fn mul( &self, layouter: &mut impl Layouter, diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 8c6a0fb19..74483de15 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -249,7 +249,7 @@ impl Config { self.beta, offset, || { - let beta = x_p.map(|x_p| inv0(x_p)); + let beta = x_p.map(inv0); beta.ok_or(Error::SynthesisError) }, )?; @@ -260,7 +260,7 @@ impl Config { self.gamma, offset, || { - let gamma = x_q.map(|x_q| inv0(x_q)); + let gamma = x_q.map(inv0); gamma.ok_or(Error::SynthesisError) }, )?; @@ -441,7 +441,7 @@ pub mod tests { // 𝒪 + P zero.add(layouter.namespace(|| "𝒪 + P"), &p)?; - // (x, y) + (ζx, -y) should behave like normal P + Q. + // (x, y) + (ζx, y) should behave like normal P + Q. let endo_p = p_val.to_curve().endo(); let endo_p = Point::new( chip.clone(), @@ -450,14 +450,32 @@ pub mod tests { )?; p.add(layouter.namespace(|| "P + endo(P)"), &endo_p)?; + // (x, y) + (ζx, -y) should also behave like normal P + Q. + let endo_p_neg = (-p_val).to_curve().endo(); + let endo_p_neg = Point::new( + chip.clone(), + layouter.namespace(|| "point"), + Some(endo_p_neg.to_affine()), + )?; + p.add(layouter.namespace(|| "P + endo(-P)"), &endo_p_neg)?; + + // (x, y) + ((ζ^2)x, y) + let endo_2_p = p_val.to_curve().endo().endo(); + let endo_2_p = Point::new( + chip.clone(), + layouter.namespace(|| "point"), + Some(endo_2_p.to_affine()), + )?; + p.add(layouter.namespace(|| "P + endo(P)"), &endo_2_p)?; + // (x, y) + ((ζ^2)x, -y) - let endo_p = p_val.to_curve().endo().endo(); - let endo_p = Point::new( + let endo_2_p_neg = (-p_val).to_curve().endo().endo(); + let endo_2_p_neg = Point::new( chip.clone(), layouter.namespace(|| "point"), - Some(endo_p.to_affine()), + Some(endo_2_p_neg.to_affine()), )?; - p.add(layouter.namespace(|| "P + endo(P)"), &endo_p)?; + p.add(layouter.namespace(|| "P + endo(P)"), &endo_2_p_neg)?; Ok(()) } From db95b5b19f7f1b52d34bf0bb9268f3bd8251db63 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 29 May 2021 16:40:25 +0800 Subject: [PATCH 39/49] Test all fixed bases --- src/circuit/gadget/ecc.rs | 27 ++++---- src/circuit/gadget/ecc/chip/load.rs | 24 ++++++- .../gadget/ecc/chip/mul_fixed/full_width.rs | 65 ++++++++++++++++--- .../gadget/ecc/chip/mul_fixed/short.rs | 18 +++-- 4 files changed, 102 insertions(+), 32 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 231793178..2ffebe801 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -340,6 +340,11 @@ impl + Clone + Debug + Eq> FixedPoin inner, }) } + + /// Wraps the given fixed base (obtained directly from an instruction) in a gadget. + pub fn from_inner(chip: EccChip, inner: EccChip::FixedPoint) -> Self { + FixedPoint { chip, inner } + } } /// A constant elliptic curve point over the given curve, used in scalar multiplication @@ -371,11 +376,15 @@ impl + Clone + Debug + Eq> FixedPoin inner, }) } + + /// Wraps the given fixed base (obtained directly from an instruction) in a gadget. + pub fn from_inner(chip: EccChip, inner: EccChip::FixedPointShort) -> Self { + FixedPointShort { chip, inner } + } } #[cfg(test)] mod tests { - use crate::constants; use group::{Curve, Group}; use halo2::{ arithmetic::CurveAffine, @@ -385,7 +394,7 @@ mod tests { plonk::{Assignment, Circuit, ConstraintSystem, Error}, }; - use super::chip::{EccChip, EccConfig, OrchardFixedBases, OrchardFixedBasesShort}; + use super::chip::{EccChip, EccConfig}; struct MyCircuit { _marker: std::marker::PhantomData, @@ -475,27 +484,17 @@ mod tests { // Test full-width fixed-base scalar multiplication { - let nullifier_k = super::FixedPoint::get( - chip.clone(), - OrchardFixedBases::NullifierK(constants::nullifier_k::generator()), - )?; super::chip::mul_fixed::full_width::tests::test_mul_fixed( chip.clone(), layouter.namespace(|| "full-width fixed-base scalar mul"), - nullifier_k, )?; } // Test signed short fixed-base scalar multiplication { - let value_commit_v = super::FixedPointShort::get( - chip.clone(), - OrchardFixedBasesShort(constants::value_commit_v::generator()), - )?; super::chip::mul_fixed::short::tests::test_mul_fixed_short( chip.clone(), - layouter.namespace(|| "full-width fixed-base scalar mul"), - value_commit_v, + layouter.namespace(|| "signed short fixed-base scalar mul"), )?; } @@ -505,7 +504,7 @@ mod tests { #[test] fn ecc() { - let k = 11; + let k = 12; let circuit = MyCircuit:: { _marker: std::marker::PhantomData, }; diff --git a/src/circuit/gadget/ecc/chip/load.rs b/src/circuit/gadget/ecc/chip/load.rs index a1bf58b73..6845b9a3d 100644 --- a/src/circuit/gadget/ecc/chip/load.rs +++ b/src/circuit/gadget/ecc/chip/load.rs @@ -1,7 +1,12 @@ use std::convert::TryInto; +use super::EccChip; +use crate::circuit::gadget::ecc::{EccInstructions, FixedPoint, FixedPointShort}; use crate::constants::{self, FixedBase, H, NUM_WINDOWS, NUM_WINDOWS_SHORT}; -use halo2::arithmetic::{CurveAffine, FieldExt}; +use halo2::{ + arithmetic::{CurveAffine, FieldExt}, + plonk::Error, +}; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum OrchardFixedBases { @@ -11,9 +16,26 @@ pub enum OrchardFixedBases { ValueCommitR(constants::ValueCommitR), } +impl OrchardFixedBases { + pub fn into_fixed_point(self, chip: EccChip) -> Result>, Error> { + let base = chip.get_fixed(self)?; + Ok(FixedPoint::>::from_inner(chip, base)) + } +} + #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct OrchardFixedBasesShort(pub constants::ValueCommitV); +impl OrchardFixedBasesShort { + pub fn into_fixed_point_short( + self, + chip: EccChip, + ) -> Result>, Error> { + let base = chip.get_fixed_short(self)?; + Ok(FixedPointShort::>::from_inner(chip, base)) + } +} + /// A fixed base to be used in scalar multiplication with a full-width scalar. #[derive(Clone, Debug, Eq, PartialEq)] pub struct OrchardFixedBase { 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 84b8423b3..53783c4e4 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -146,16 +146,61 @@ pub mod tests { plonk::Error, }; - use crate::circuit::gadget::ecc::{EccInstructions, FixedPoint, ScalarFixed}; + use crate::circuit::gadget::ecc::{ + chip::{EccChip, OrchardFixedBases}, + FixedPoint, ScalarFixed, + }; use crate::constants; - pub fn test_mul_fixed< - C: CurveAffine, - EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, - >( - chip: EccChip, + pub fn test_mul_fixed( + chip: EccChip, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // commit_ivk_r + let commit_ivk_r = OrchardFixedBases::::CommitIvkR(constants::commit_ivk_r::generator()) + .into_fixed_point(chip.clone())?; + test_single_base( + chip.clone(), + layouter.namespace(|| "commit_ivk_r"), + commit_ivk_r, + )?; + + // note_commit_r + let note_commit_r = + OrchardFixedBases::::NoteCommitR(constants::note_commit_r::generator()) + .into_fixed_point(chip.clone())?; + test_single_base( + chip.clone(), + layouter.namespace(|| "note_commit_r"), + note_commit_r, + )?; + + // nullifier_k + let nullifier_k = OrchardFixedBases::::NullifierK(constants::nullifier_k::generator()) + .into_fixed_point(chip.clone())?; + test_single_base( + chip.clone(), + layouter.namespace(|| "nullifier_k"), + nullifier_k, + )?; + + // value_commit_r + let value_commit_r = + OrchardFixedBases::::ValueCommitR(constants::value_commit_r::generator()) + .into_fixed_point(chip.clone())?; + test_single_base( + chip.clone(), + layouter.namespace(|| "value_commit_r"), + value_commit_r, + )?; + + Ok(()) + } + + fn test_single_base( + chip: EccChip, mut layouter: impl Layouter, - nullifier_k: FixedPoint, + base: FixedPoint>, ) -> Result<(), Error> { // [a]B { @@ -167,7 +212,7 @@ pub mod tests { Some(scalar_fixed), )?; - nullifier_k.mul(layouter.namespace(|| "mul"), &scalar_fixed)?; + base.mul(layouter.namespace(|| "mul"), &scalar_fixed)?; } // There is a single canonical sequence of window values for which a doubling occurs on the last step: @@ -188,7 +233,7 @@ pub mod tests { Some(scalar_fixed), )?; - nullifier_k.mul(layouter.namespace(|| "mul with double"), &scalar_fixed)?; + base.mul(layouter.namespace(|| "mul with double"), &scalar_fixed)?; } // [0]B should return (0,0) since it uses complete addition @@ -200,7 +245,7 @@ pub mod tests { layouter.namespace(|| "ScalarFixed"), Some(scalar_fixed), )?; - nullifier_k.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)?; + base.mul(layouter.namespace(|| "mul by zero"), &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 077e05663..266333651 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -225,16 +225,20 @@ pub mod tests { plonk::Error, }; - use crate::circuit::gadget::ecc::{EccInstructions, FixedPointShort, ScalarFixedShort}; + use crate::circuit::gadget::ecc::{ + chip::{EccChip, OrchardFixedBasesShort}, + ScalarFixedShort, + }; + use crate::constants; - pub fn test_mul_fixed_short< - C: CurveAffine, - EccChip: EccInstructions + Clone + Eq + std::fmt::Debug, - >( - chip: EccChip, + pub fn test_mul_fixed_short( + chip: EccChip, mut layouter: impl Layouter, - value_commit_v: FixedPointShort, ) -> Result<(), Error> { + // value_commit_v + let value_commit_v = OrchardFixedBasesShort::(constants::value_commit_v::generator()) + .into_fixed_point_short(chip.clone())?; + // [0]B should return (0,0) since it uses complete addition // on the last step. { From a90b7f0bf475191f2d350300a8eafb563c367f2b Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 31 May 2021 13:42:31 +0800 Subject: [PATCH 40/49] Remove EccLoaded struct and FixedPoint, FixedPointShort associated types. We do not load the fixed bases into a single location in the circuit; rather, they are assigned to fixed columns at the offsets where they need to be used. The EccLoaded struct was not storing any references to circuit variables. It was holding constants in memory which the caller can instead directly access. Similarly, there is no need to have the FixedPoint or FixedPointShort associated types in the EccInstructions trait, since these types do not contain references to circuit variables. The corresponding get_fixed() and get_fixed_short() instructions are also removed. --- src/circuit/gadget/ecc.rs | 53 +-- src/circuit/gadget/ecc/chip.rs | 99 ++--- src/circuit/gadget/ecc/chip/mul_fixed.rs | 96 ++--- .../gadget/ecc/chip/mul_fixed/full_width.rs | 35 +- .../gadget/ecc/chip/mul_fixed/short.rs | 23 +- src/constants.rs | 350 ++++++++---------- src/constants/commit_ivk_r.rs | 23 +- .../gadget/ecc/chip => constants}/load.rs | 163 ++++---- src/constants/note_commit_r.rs | 23 +- src/constants/nullifier_k.rs | 23 +- src/constants/value_commit_r.rs | 23 +- src/constants/value_commit_v.rs | 23 +- 12 files changed, 380 insertions(+), 554 deletions(-) rename src/{circuit/gadget/ecc/chip => constants}/load.rs (60%) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 2ffebe801..46bb1742e 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -39,15 +39,7 @@ pub trait EccInstructions: Chip { /// elliptic curve point. type X: Clone + Debug; /// Variable representing the set of fixed bases in the circuit. - type FixedPoints: Clone + Debug; - /// Variable representing the set of fixed bases to be used in scalar - /// multiplication with a short signed exponent. - type FixedPointsShort: Clone + Debug; - /// Variable representing a fixed elliptic curve point (constant in the circuit). - type FixedPoint: Clone + Debug; - /// Variable representing a fixed elliptic curve point (constant in the circuit) - /// to be used in scalar multiplication with a short signed exponent. - type FixedPointShort: Clone + Debug; + type FixedPoints: Copy + Clone + Debug; /// Witnesses the given base field element as a private input to the circuit /// for variable-base scalar mul. @@ -83,18 +75,6 @@ pub trait EccInstructions: Chip { /// Extracts the x-coordinate of a point. fn extract_p(point: &Self::Point) -> &Self::X; - /// Returns a fixed point that had been previously loaded into the circuit. - /// The pre-loaded cells are used to set up equality constraints in other - /// parts of the circuit where the fixed base is used. - fn get_fixed(&self, fixed_points: Self::FixedPoints) -> Result; - - /// Returns a fixed point to be used in scalar multiplication with a signed - /// short exponent. - fn get_fixed_short( - &self, - fixed_points: Self::FixedPointsShort, - ) -> Result; - /// Performs incomplete point addition, returning `a + b`. /// /// This returns an error in exceptional cases. @@ -127,7 +107,7 @@ pub trait EccInstructions: Chip { &self, layouter: &mut impl Layouter, scalar: &Self::ScalarFixed, - base: &Self::FixedPoint, + base: Self::FixedPoints, ) -> Result; /// Performs fixed-base scalar multiplication using a short signed scalar, returning `[scalar] base`. @@ -135,7 +115,7 @@ pub trait EccInstructions: Chip { &self, layouter: &mut impl Layouter, scalar: &Self::ScalarFixedShort, - base: &Self::FixedPointShort, + base: Self::FixedPoints, ) -> Result; } @@ -316,16 +296,10 @@ impl + Clone + Debug + Eq> X + Clone + Debug + Eq> { chip: EccChip, - inner: EccChip::FixedPoint, + inner: EccChip::FixedPoints, } impl + Clone + Debug + Eq> FixedPoint { - /// Gets a reference to the specified fixed point in the circuit. - pub fn get(chip: EccChip, point: EccChip::FixedPoints) -> Result { - chip.get_fixed(point) - .map(|inner| FixedPoint { chip, inner }) - } - /// Returns `[by] self`. pub fn mul( &self, @@ -334,7 +308,7 @@ impl + Clone + Debug + Eq> FixedPoin ) -> Result, Error> { assert_eq!(self.chip, by.chip); self.chip - .mul_fixed(&mut layouter, &by.inner, &self.inner) + .mul_fixed(&mut layouter, &by.inner, self.inner) .map(|inner| Point { chip: self.chip.clone(), inner, @@ -342,7 +316,7 @@ impl + Clone + Debug + Eq> FixedPoin } /// Wraps the given fixed base (obtained directly from an instruction) in a gadget. - pub fn from_inner(chip: EccChip, inner: EccChip::FixedPoint) -> Self { + pub fn from_inner(chip: EccChip, inner: EccChip::FixedPoints) -> Self { FixedPoint { chip, inner } } } @@ -352,16 +326,10 @@ impl + Clone + Debug + Eq> FixedPoin #[derive(Clone, Debug)] pub struct FixedPointShort + Clone + Debug + Eq> { chip: EccChip, - inner: EccChip::FixedPointShort, + inner: EccChip::FixedPoints, } impl + Clone + Debug + Eq> FixedPointShort { - /// Gets a reference to the specified fixed point in the circuit. - pub fn get(chip: EccChip, point: EccChip::FixedPointsShort) -> Result { - chip.get_fixed_short(point) - .map(|inner| FixedPointShort { chip, inner }) - } - /// Returns `[by] self`. pub fn mul( &self, @@ -370,7 +338,7 @@ impl + Clone + Debug + Eq> FixedPoin ) -> Result, Error> { assert_eq!(self.chip, by.chip); self.chip - .mul_fixed_short(&mut layouter, &by.inner, &self.inner) + .mul_fixed_short(&mut layouter, &by.inner, self.inner) .map(|inner| Point { chip: self.chip.clone(), inner, @@ -378,7 +346,7 @@ impl + Clone + Debug + Eq> FixedPoin } /// Wraps the given fixed base (obtained directly from an instruction) in a gadget. - pub fn from_inner(chip: EccChip, inner: EccChip::FixedPointShort) -> Self { + pub fn from_inner(chip: EccChip, inner: EccChip::FixedPoints) -> Self { FixedPointShort { chip, inner } } } @@ -427,8 +395,7 @@ mod tests { config: Self::Config, ) -> Result<(), Error> { let mut layouter = SingleChipLayouter::new(cs)?; - let loaded = EccChip::::load(); - let chip = EccChip::construct(config, loaded); + let chip = EccChip::construct(config); // Generate a random point P let p_val = C::CurveExt::random(rand::rngs::OsRng).to_affine(); // P diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index b44e94c6f..6d436050a 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -1,23 +1,21 @@ use super::EccInstructions; -use crate::constants; +use crate::constants::{self, load::OrchardFixedBases}; use ff::Field; use halo2::{ arithmetic::CurveAffine, circuit::{Cell, Chip, Layouter}, plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Permutation, Selector}, }; +use std::marker::PhantomData; pub(super) mod add; pub(super) mod add_incomplete; -pub(super) mod load; pub(super) mod mul; pub(super) mod mul_fixed; pub(super) mod util; pub(super) mod witness_point; pub(super) mod witness_scalar_fixed; -pub use load::*; - /// A structure containing a cell and its assigned value. #[derive(Clone, Debug)] pub struct CellValue { @@ -60,18 +58,6 @@ impl EccPoint { } } -#[derive(Clone, Debug, Eq, PartialEq)] -/// For each Orchard fixed base, we precompute: -/// * coefficients for x-coordinate interpolation polynomials, and -/// * z-values such that y + z = u^2 some square while -y + z is non-square. -pub struct EccLoaded { - commit_ivk_r: OrchardFixedBase, - note_commit_r: OrchardFixedBase, - nullifier_k: OrchardFixedBase, - value_commit_r: OrchardFixedBase, - value_commit_v: OrchardFixedBaseShort, -} - /// Configuration for the ECC chip #[derive(Clone, Debug, Eq, PartialEq)] #[allow(non_snake_case)] @@ -114,43 +100,28 @@ pub struct EccConfig { #[derive(Clone, Debug, Eq, PartialEq)] pub struct EccChip { config: EccConfig, - loaded: EccLoaded, -} - -impl EccLoaded { - fn get(&self, point: OrchardFixedBases) -> OrchardFixedBase { - match point { - OrchardFixedBases::CommitIvkR(_) => self.commit_ivk_r.clone(), - OrchardFixedBases::NoteCommitR(_) => self.note_commit_r.clone(), - OrchardFixedBases::NullifierK(_) => self.nullifier_k.clone(), - OrchardFixedBases::ValueCommitR(_) => self.value_commit_r.clone(), - } - } - - fn get_short(&self, _point: OrchardFixedBasesShort) -> OrchardFixedBaseShort { - self.value_commit_v.clone() - } + _marker: PhantomData, } impl Chip for EccChip { type Config = EccConfig; - type Loaded = EccLoaded; + type Loaded = (); fn config(&self) -> &Self::Config { &self.config } fn loaded(&self) -> &Self::Loaded { - &self.loaded + &() } } impl EccChip { - pub fn construct( - config: >::Config, - loaded: >::Loaded, - ) -> Self { - Self { config, loaded } + pub fn construct(config: >::Config) -> Self { + Self { + config, + _marker: PhantomData, + } } #[allow(non_snake_case)] @@ -236,22 +207,6 @@ impl EccChip { config } - - pub fn load() -> >::Loaded { - let commit_ivk_r = load::commit_ivk_r(); - let note_commit_r = load::note_commit_r(); - let nullifier_k = load::nullifier_k(); - let value_commit_r = load::value_commit_r(); - let value_commit_v = load::value_commit_v(); - - EccLoaded { - commit_ivk_r, - note_commit_r, - nullifier_k, - value_commit_r, - value_commit_v, - } - } } /// A full-width scalar used for variable-base scalar multiplication. @@ -281,10 +236,7 @@ impl EccInstructions for EccChip { type ScalarVar = CellValue; type Point = EccPoint; type X = CellValue; - type FixedPoint = OrchardFixedBase; - type FixedPointShort = OrchardFixedBaseShort; type FixedPoints = OrchardFixedBases; - type FixedPointsShort = OrchardFixedBasesShort; fn witness_scalar_var( &self, @@ -345,17 +297,6 @@ impl EccInstructions for EccChip { &point.x } - fn get_fixed(&self, fixed_point: Self::FixedPoints) -> Result { - Ok(self.loaded().get(fixed_point)) - } - - fn get_fixed_short( - &self, - fixed_point: Self::FixedPointsShort, - ) -> Result { - Ok(self.loaded().get_short(fixed_point)) - } - fn add_incomplete( &self, layouter: &mut impl Layouter, @@ -399,11 +340,17 @@ impl EccInstructions for EccChip { &self, layouter: &mut impl Layouter, scalar: &Self::ScalarFixed, - base: &Self::FixedPoint, + base: Self::FixedPoints, ) -> Result { + // Full-width fixed-base scalar mul cannot be used with ValueCommitV. + match base { + OrchardFixedBases::ValueCommitV(_) => return Err(Error::SynthesisError), + _ => (), + }; + let config: mul_fixed::Config = self.config().into(); layouter.assign_region( - || format!("Multiply {:?}", base.base), + || format!("Multiply {:?}", base), |mut region| config.assign_region_full::(scalar, base, 0, &mut region), ) } @@ -412,11 +359,17 @@ impl EccInstructions for EccChip { &self, layouter: &mut impl Layouter, scalar: &Self::ScalarFixedShort, - base: &Self::FixedPointShort, + base: Self::FixedPoints, ) -> Result { + // Short fixed-base scalar mul is only used with ValueCommitV. + match base { + OrchardFixedBases::ValueCommitV(_) => (), + _ => return Err(Error::SynthesisError), + }; + let config: mul_fixed::Config = self.config().into(); layouter.assign_region( - || format!("Multiply {:?}", base.base), + || format!("Multiply {:?}", base), |mut region| config.assign_region_short::(scalar, 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 8a8d3de44..554b09380 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -1,9 +1,11 @@ use super::{ - add, add_incomplete, load::WindowUs, util, witness_point, CellValue, EccConfig, EccPoint, - EccScalarFixed, EccScalarFixedShort, OrchardFixedBase, OrchardFixedBaseShort, - OrchardFixedBases, + add, add_incomplete, util, witness_point, CellValue, EccConfig, EccPoint, EccScalarFixed, + EccScalarFixedShort, +}; +use crate::constants::{ + self, + load::{OrchardFixedBase, OrchardFixedBaseShort, OrchardFixedBases}, }; -use crate::constants; use group::Curve; use halo2::{ @@ -113,7 +115,7 @@ impl Config { pub(super) fn assign_region_full( &self, scalar: &EccScalarFixed, - base: &OrchardFixedBase, + base: OrchardFixedBases, offset: usize, region: &mut Region<'_, C::Base>, ) -> Result, Error> { @@ -124,7 +126,7 @@ impl Config { pub(super) fn assign_region_short( &self, scalar: &EccScalarFixedShort, - base: &OrchardFixedBaseShort, + base: OrchardFixedBases, offset: usize, region: &mut Region<'_, C::Base>, ) -> Result, Error> { @@ -165,44 +167,6 @@ impl Config { } } -enum FixedBase { - FullWidth(OrchardFixedBase), - Short(OrchardFixedBaseShort), -} - -impl From<&OrchardFixedBase> for FixedBase { - fn from(fixed_base: &OrchardFixedBase) -> Self { - Self::FullWidth(fixed_base.clone()) - } -} - -impl From<&OrchardFixedBaseShort> for FixedBase { - fn from(fixed_base: &OrchardFixedBaseShort) -> Self { - Self::Short(fixed_base.clone()) - } -} - -impl FixedBase { - fn value(&self) -> C { - match self { - FixedBase::FullWidth(base) => match base.base { - OrchardFixedBases::CommitIvkR(inner) => inner.0.value(), - OrchardFixedBases::NoteCommitR(inner) => inner.0.value(), - OrchardFixedBases::NullifierK(inner) => inner.0.value(), - OrchardFixedBases::ValueCommitR(inner) => inner.0.value(), - }, - FixedBase::Short(base) => base.base.0 .0.value(), - } - } - - fn u(&self) -> Vec> { - match self { - FixedBase::FullWidth(base) => base.u.0.as_ref().to_vec(), - FixedBase::Short(base) => base.u_short.0.as_ref().to_vec(), - } - } -} - enum ScalarFixed { FullWidth(EccScalarFixed), Short(EccScalarFixedShort), @@ -272,7 +236,7 @@ trait MulFixed { region: &mut Region<'_, C::Base>, offset: usize, scalar: &ScalarFixed, - base: &FixedBase, + base: OrchardFixedBases, ) -> Result<(EccPoint, EccPoint), Error> { // Assign fixed columns for given fixed base self.assign_fixed_constants(region, offset, base)?; @@ -296,17 +260,23 @@ trait MulFixed { &self, region: &mut Region<'_, C::Base>, offset: usize, - base: &FixedBase, + base: OrchardFixedBases, ) -> Result<(), Error> { - let (base_lagrange_coeffs, base_z) = match base { - FixedBase::FullWidth(base) => ( - base.lagrange_coeffs.0.as_ref().to_vec(), - base.z.0.as_ref().to_vec(), - ), - FixedBase::Short(base) => ( - base.lagrange_coeffs_short.0.as_ref().to_vec(), - base.z_short.0.as_ref().to_vec(), - ), + let (lagrange_coeffs, z) = match base { + OrchardFixedBases::ValueCommitV(_) => { + let base: OrchardFixedBaseShort = base.into(); + ( + base.lagrange_coeffs_short.0.as_ref().to_vec(), + base.z_short.0.as_ref().to_vec(), + ) + } + _ => { + let base: OrchardFixedBase = base.into(); + ( + base.lagrange_coeffs.0.as_ref().to_vec(), + base.z.0.as_ref().to_vec(), + ) + } }; // Assign fixed columns for given fixed base @@ -325,7 +295,7 @@ trait MulFixed { }, self.lagrange_coeffs()[k], window + offset, - || Ok(base_lagrange_coeffs[window].0[k]), + || Ok(lagrange_coeffs[window].0[k]), )?; } @@ -334,7 +304,7 @@ trait MulFixed { || format!("z-value for window: {:?}", window), self.fixed_z(), window + offset, - || Ok(base_z[window]), + || Ok(z[window]), )?; } @@ -366,12 +336,12 @@ trait MulFixed { &self, region: &mut Region<'_, C::Base>, offset: usize, - base: &FixedBase, + base: OrchardFixedBases, scalar: &ScalarFixed, ) -> Result, Error> { // Witness `m0` in `x_p`, `y_p` cells on row 0 let k0 = scalar.k_field()[0]; - let m0 = k0.map(|k0| base.value() * (k0 + C::Scalar::from_u64(2))); + let m0 = k0.map(|k0| base.generator() * (k0 + C::Scalar::from_u64(2))); let m0 = self.witness_point_config().assign_region( m0.map(|point| point.to_affine()), offset, @@ -413,13 +383,13 @@ trait MulFixed { region: &mut Region<'_, C::Base>, offset: usize, mut acc: EccPoint, - base: &FixedBase, + base: OrchardFixedBases, scalar: &ScalarFixed, ) -> Result, Error> { // This is 2^w, where w is the window width let h = C::Scalar::from_u64(constants::H as u64); - let base_value = base.value(); + let base_value = base.generator(); let base_u = base.u(); let scalar_k_field = scalar.k_field(); let scalar_k_usize = scalar.k_usize(); @@ -461,7 +431,7 @@ trait MulFixed { &self, region: &mut Region<'_, C::Base>, offset: usize, - base: &FixedBase, + base: OrchardFixedBases, scalar: &ScalarFixed, ) -> Result, Error> { // This is 2^w, where w is the window width @@ -493,7 +463,7 @@ trait MulFixed { let scalar = scalar.k_field()[scalar.k_field().len() - 1] .map(|k| k * h.pow(&[(Self::NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_acc); - let mul_b = scalar.map(|scalar| base.value() * scalar); + let mul_b = scalar.map(|scalar| base.generator() * scalar); self.witness_point_config().assign_region( mul_b.map(|point| point.to_affine()), offset + Self::NUM_WINDOWS - 1, 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 53783c4e4..45bd9efab 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -1,5 +1,5 @@ use super::super::{ - add, add_incomplete, witness_point, EccPoint, EccScalarFixed, OrchardFixedBase, + add, add_incomplete, witness_point, EccPoint, EccScalarFixed, OrchardFixedBases, }; use super::MulFixed; @@ -101,10 +101,9 @@ impl Config { region: &mut Region<'_, C::Base>, offset: usize, scalar: &EccScalarFixed, - base: &OrchardFixedBase, + base: OrchardFixedBases, ) -> Result, Error> { - let (acc, mul_b) = - self.assign_region_inner(region, offset, &scalar.into(), &base.into())?; + let (acc, mul_b) = self.assign_region_inner(region, offset, &scalar.into(), base.into())?; // Add to the accumulator and return the final result as `[scalar]B`. let result = @@ -114,20 +113,13 @@ impl Config { #[cfg(test)] // Check that the correct multiple is obtained. { - use super::super::OrchardFixedBases; use group::Curve; use halo2::arithmetic::FieldExt; - let base = match base.base { - OrchardFixedBases::CommitIvkR(base) => base.0.value(), - OrchardFixedBases::NoteCommitR(base) => base.0.value(), - OrchardFixedBases::NullifierK(base) => base.0.value(), - OrchardFixedBases::ValueCommitR(base) => base.0.value(), - }; let scalar = scalar .value .map(|scalar| C::Scalar::from_bytes(&scalar.to_bytes()).unwrap()); - let real_mul = scalar.map(|scalar| base * scalar); + let real_mul = scalar.map(|scalar| base.generator() * scalar); let result = result.point(); assert_eq!(real_mul.unwrap().to_affine(), result.unwrap()); @@ -151,14 +143,15 @@ pub mod tests { FixedPoint, ScalarFixed, }; use crate::constants; + use std::marker::PhantomData; pub fn test_mul_fixed( chip: EccChip, mut layouter: impl Layouter, ) -> Result<(), Error> { // commit_ivk_r - let commit_ivk_r = OrchardFixedBases::::CommitIvkR(constants::commit_ivk_r::generator()) - .into_fixed_point(chip.clone())?; + let commit_ivk_r = OrchardFixedBases::CommitIvkR(PhantomData); + let commit_ivk_r = FixedPoint::from_inner(chip.clone(), commit_ivk_r); test_single_base( chip.clone(), layouter.namespace(|| "commit_ivk_r"), @@ -166,9 +159,8 @@ pub mod tests { )?; // note_commit_r - let note_commit_r = - OrchardFixedBases::::NoteCommitR(constants::note_commit_r::generator()) - .into_fixed_point(chip.clone())?; + let note_commit_r = OrchardFixedBases::NoteCommitR(PhantomData); + let note_commit_r = FixedPoint::from_inner(chip.clone(), note_commit_r); test_single_base( chip.clone(), layouter.namespace(|| "note_commit_r"), @@ -176,8 +168,8 @@ pub mod tests { )?; // nullifier_k - let nullifier_k = OrchardFixedBases::::NullifierK(constants::nullifier_k::generator()) - .into_fixed_point(chip.clone())?; + let nullifier_k = OrchardFixedBases::NullifierK(PhantomData); + let nullifier_k = FixedPoint::from_inner(chip.clone(), nullifier_k); test_single_base( chip.clone(), layouter.namespace(|| "nullifier_k"), @@ -185,9 +177,8 @@ pub mod tests { )?; // value_commit_r - let value_commit_r = - OrchardFixedBases::::ValueCommitR(constants::value_commit_r::generator()) - .into_fixed_point(chip.clone())?; + let value_commit_r = OrchardFixedBases::ValueCommitR(PhantomData); + let value_commit_r = FixedPoint::from_inner(chip.clone(), value_commit_r); test_single_base( chip.clone(), layouter.namespace(|| "value_commit_r"), diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index 266333651..b1c9f3bfc 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -1,9 +1,8 @@ use super::super::{ add, add_incomplete, util, witness_point, CellValue, EccPoint, EccScalarFixedShort, - OrchardFixedBaseShort, }; use super::MulFixed; -use crate::constants; +use crate::constants::{self, load::OrchardFixedBases}; use halo2::{ arithmetic::{CurveAffine, Field}, @@ -126,10 +125,9 @@ impl Config { region: &mut Region<'_, C::Base>, offset: usize, scalar: &EccScalarFixedShort, - base: &OrchardFixedBaseShort, + base: OrchardFixedBases, ) -> Result, Error> { - let (acc, mul_b) = - self.assign_region_inner(region, offset, &scalar.into(), &base.into())?; + let (acc, mul_b) = self.assign_region_inner(region, offset, &scalar.into(), base.into())?; // Add to the cumulative sum to get `[magnitude]B`. let magnitude_mul = self.add_config.assign_region( @@ -192,7 +190,6 @@ impl Config { { use group::Curve; - let base = base.base.0 .0.value(); let scalar = scalar .magnitude .zip(scalar.sign.value) @@ -206,7 +203,7 @@ impl Config { }; magnitude * sign }); - let real_mul = scalar.map(|scalar| base * scalar); + let real_mul = scalar.map(|scalar| base.generator() * scalar); let result = result.point(); assert_eq!(real_mul.unwrap().to_affine(), result.unwrap()); @@ -225,19 +222,17 @@ pub mod tests { plonk::Error, }; - use crate::circuit::gadget::ecc::{ - chip::{EccChip, OrchardFixedBasesShort}, - ScalarFixedShort, - }; - use crate::constants; + use crate::circuit::gadget::ecc::{chip::EccChip, FixedPointShort, ScalarFixedShort}; + use crate::constants::load::OrchardFixedBases; + use std::marker::PhantomData; pub fn test_mul_fixed_short( chip: EccChip, mut layouter: impl Layouter, ) -> Result<(), Error> { // value_commit_v - let value_commit_v = OrchardFixedBasesShort::(constants::value_commit_v::generator()) - .into_fixed_point_short(chip.clone())?; + let value_commit_v = OrchardFixedBases::ValueCommitV(PhantomData); + let value_commit_v = FixedPointShort::from_inner(chip.clone(), value_commit_v); // [0]B should return (0,0) since it uses complete addition // on the last step. diff --git a/src/constants.rs b/src/constants.rs index 8773cfde8..803ceeb28 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -13,8 +13,11 @@ pub mod nullifier_k; pub mod value_commit_r; pub mod value_commit_v; +pub mod load; pub mod util; +pub use load::{OrchardFixedBase, OrchardFixedBaseShort, OrchardFixedBases}; + /// $\ell^\mathsf{Orchard}_\mathsf{base}$ pub(crate) const L_ORCHARD_BASE: usize = 255; @@ -58,234 +61,181 @@ pub const NUM_WINDOWS_SHORT: usize = /// scalar multiplication pub const NUM_COMPLETE_BITS: usize = 3; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct CommitIvkR(pub OrchardFixedBase); - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct NoteCommitR(pub OrchardFixedBase); - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct NullifierK(pub OrchardFixedBase); - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct ValueCommitR(pub OrchardFixedBase); - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct ValueCommitV(pub OrchardFixedBase); - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct OrchardFixedBase(C); - -impl OrchardFixedBase { - pub fn new(generator: C) -> Self { - OrchardFixedBase(generator) - } - - pub fn value(&self) -> C { - self.0 - } -} - -pub trait FixedBase { - /// For each fixed base, we calculate its scalar multiples in three-bit windows. - /// Each window will have $2^3 = 8$ points. - fn compute_window_table(&self, num_windows: usize) -> Vec<[C; H]>; +/// For each fixed base, we calculate its scalar multiples in three-bit windows. +/// Each window will have $2^3 = 8$ points. +fn compute_window_table(base: C, num_windows: usize) -> Vec<[C; H]> { + let mut window_table: Vec<[C; H]> = Vec::with_capacity(num_windows); - /// For each window, we interpolate the $x$-coordinate. - /// Here, we pre-compute and store the coefficients of the interpolation polynomial. - fn compute_lagrange_coeffs(&self, num_windows: usize) -> Vec<[C::Base; H]>; - - /// For each window, $z$ is a field element such that for each point $(x, y)$ in the window: - /// - $z + y = u^2$ (some square in the field); and - /// - $z - y$ is not a square. - /// If successful, return a vector of `(z: u64, us: [C::Base; H])` for each window. - fn find_zs_and_us(&self, num_windows: usize) -> Option>; -} - -impl FixedBase for OrchardFixedBase { - fn compute_window_table(&self, num_windows: usize) -> Vec<[C; H]> { - let mut window_table: Vec<[C; H]> = Vec::with_capacity(num_windows); - - // Generate window table entries for all windows but the last. - // For these first `num_windows - 1` windows, we compute the multiple [(k+2)*(2^3)^w]B. - // Here, w ranges from [0..`num_windows - 1`) - for w in 0..(num_windows - 1) { - window_table.push( - (0..H) - .map(|k| { - // scalar = (k+2)*(8^w) - let scalar = C::ScalarExt::from_u64(k as u64 + 2) - * C::ScalarExt::from_u64(H as u64).pow(&[w as u64, 0, 0, 0]); - (self.0 * scalar).to_affine() - }) - .collect::>() - .into_inner() - .unwrap(), - ); - } - - // Generate window table entries for the last window, w = `num_windows - 1`. - // For the last window, we compute [k * (2^3)^w - sum]B, where sum is defined - // as sum = \sum_{j = 0}^{`num_windows - 2`} 2^{3j+1} - let sum = (0..(num_windows - 1)).fold(C::ScalarExt::zero(), |acc, j| { - acc + C::ScalarExt::from_u64(2).pow(&[ - FIXED_BASE_WINDOW_SIZE as u64 * j as u64 + 1, - 0, - 0, - 0, - ]) - }); + // Generate window table entries for all windows but the last. + // For these first `num_windows - 1` windows, we compute the multiple [(k+2)*(2^3)^w]B. + // Here, w ranges from [0..`num_windows - 1`) + for w in 0..(num_windows - 1) { window_table.push( (0..H) .map(|k| { - // scalar = k * (2^3)^w - sum, where w = `num_windows - 1` - let scalar = C::ScalarExt::from_u64(k as u64) - * C::ScalarExt::from_u64(H as u64).pow(&[ - (num_windows - 1) as u64, - 0, - 0, - 0, - ]) - - sum; - (self.0 * scalar).to_affine() + // scalar = (k+2)*(8^w) + let scalar = C::ScalarExt::from_u64(k as u64 + 2) + * C::ScalarExt::from_u64(H as u64).pow(&[w as u64, 0, 0, 0]); + (base * scalar).to_affine() }) .collect::>() .into_inner() .unwrap(), ); - - window_table } - fn compute_lagrange_coeffs(&self, num_windows: usize) -> Vec<[C::Base; H]> { - // We are interpolating over the 3-bit window, k \in [0..8) - let points: Vec<_> = (0..H).map(|i| C::Base::from_u64(i as u64)).collect(); + // Generate window table entries for the last window, w = `num_windows - 1`. + // For the last window, we compute [k * (2^3)^w - sum]B, where sum is defined + // as sum = \sum_{j = 0}^{`num_windows - 2`} 2^{3j+1} + let sum = (0..(num_windows - 1)).fold(C::ScalarExt::zero(), |acc, j| { + acc + C::ScalarExt::from_u64(2).pow(&[ + FIXED_BASE_WINDOW_SIZE as u64 * j as u64 + 1, + 0, + 0, + 0, + ]) + }); + window_table.push( + (0..H) + .map(|k| { + // scalar = k * (2^3)^w - sum, where w = `num_windows - 1` + let scalar = C::ScalarExt::from_u64(k as u64) + * C::ScalarExt::from_u64(H as u64).pow(&[(num_windows - 1) as u64, 0, 0, 0]) + - sum; + (base * scalar).to_affine() + }) + .collect::>() + .into_inner() + .unwrap(), + ); - let window_table = self.compute_window_table(num_windows); + window_table +} - window_table - .iter() - .map(|window_points| { - let x_window_points: Vec<_> = window_points - .iter() - .map(|point| *point.coordinates().unwrap().x()) - .collect(); - lagrange_interpolate(&points, &x_window_points) - .into_iter() - .collect::>() - .into_inner() - .unwrap() - }) - .collect() - } +/// For each window, we interpolate the $x$-coordinate. +/// Here, we pre-compute and store the coefficients of the interpolation polynomial. +fn compute_lagrange_coeffs(base: C, num_windows: usize) -> Vec<[C::Base; H]> { + // We are interpolating over the 3-bit window, k \in [0..8) + let points: Vec<_> = (0..H).map(|i| C::Base::from_u64(i as u64)).collect(); - /// For each window, z is a field element such that for each point (x, y) in the window: - /// - z + y = u^2 (some square in the field); and - /// - z - y is not a square. - /// If successful, return a vector of `(z: u64, us: [C::Base; H])` for each window. - fn find_zs_and_us(&self, num_windows: usize) -> Option> { - // Closure to find z and u's for one window - let find_z_and_us = |window_points: &[C]| { - assert_eq!(H, window_points.len()); + let window_table = compute_window_table(base, num_windows); - let ys: Vec<_> = window_points + window_table + .iter() + .map(|window_points| { + let x_window_points: Vec<_> = window_points .iter() - .map(|point| *point.coordinates().unwrap().y()) + .map(|point| *point.coordinates().unwrap().x()) .collect(); - (0..(1000 * (1 << (2 * H)))).find_map(|z| { - ys.iter() - .map(|&y| { - if (-y + C::Base::from_u64(z)).sqrt().is_none().into() { - (y + C::Base::from_u64(z)).sqrt().into() - } else { - None - } - }) - .collect::>>() - .map(|us| (z, us.into_inner().unwrap())) - }) - }; - - let window_table = self.compute_window_table(num_windows); - window_table - .iter() - .map(|window_points| find_z_and_us(window_points)) - .collect() - } + lagrange_interpolate(&points, &x_window_points) + .into_iter() + .collect::>() + .into_inner() + .unwrap() + }) + .collect() } -trait TestFixedBase { - // Test that Lagrange interpolation coefficients reproduce the correct x-coordinate - // for each fixed-base multiple in each window. - fn test_lagrange_coeffs(&self, num_windows: usize); +/// For each window, $z$ is a field element such that for each point $(x, y)$ in the window: +/// - $z + y = u^2$ (some square in the field); and +/// - $z - y$ is not a square. +/// If successful, return a vector of `(z: u64, us: [C::Base; H])` for each window. +fn find_zs_and_us(base: C, num_windows: usize) -> Option> { + // Closure to find z and u's for one window + let find_z_and_us = |window_points: &[C]| { + assert_eq!(H, window_points.len()); - // Test that the z-values and u-values satisfy the conditions: - // 1. z + y = u^2, - // 2. z - y is not a square - // for the y-coordinate of each fixed-base multiple in each window. - fn test_zs_and_us(&self, z: &[u64], u: &[[[u8; 32]; H]], num_windows: usize); + let ys: Vec<_> = window_points + .iter() + .map(|point| *point.coordinates().unwrap().y()) + .collect(); + (0..(1000 * (1 << (2 * H)))).find_map(|z| { + ys.iter() + .map(|&y| { + if (-y + C::Base::from_u64(z)).sqrt().is_none().into() { + (y + C::Base::from_u64(z)).sqrt().into() + } else { + None + } + }) + .collect::>>() + .map(|us| (z, us.into_inner().unwrap())) + }) + }; + + let window_table = compute_window_table(base, num_windows); + window_table + .iter() + .map(|window_points| find_z_and_us(window_points)) + .collect() } -impl TestFixedBase for OrchardFixedBase { - fn test_lagrange_coeffs(&self, num_windows: usize) { - let lagrange_coeffs = self.compute_lagrange_coeffs(num_windows); - - // Check first 84 windows, i.e. `k_0, k_1, ..., k_83` - for (idx, coeffs) in lagrange_coeffs[0..(num_windows - 1)].iter().enumerate() { - // Test each three-bit chunk in this window. - for bits in 0..(1 << FIXED_BASE_WINDOW_SIZE) { - { - // Interpolate the x-coordinate using this window's coefficients - let interpolated_x = util::evaluate::(bits, coeffs); - - // Compute the actual x-coordinate of the multiple [(k+2)*(8^w)]B. - let point = self.0 - * C::Scalar::from_u64(bits as u64 + 2) - * C::Scalar::from_u64(H as u64).pow(&[idx as u64, 0, 0, 0]); - let x = *point.to_affine().coordinates().unwrap().x(); - - // Check that the interpolated x-coordinate matches the actual one. - assert_eq!(x, interpolated_x); - } - } - } +#[cfg(test)] +// Test that Lagrange interpolation coefficients reproduce the correct x-coordinate +// for each fixed-base multiple in each window. +fn test_lagrange_coeffs(base: C, num_windows: usize) { + let lagrange_coeffs = compute_lagrange_coeffs(base, num_windows); - // Check last window. + // Check first 84 windows, i.e. `k_0, k_1, ..., k_83` + for (idx, coeffs) in lagrange_coeffs[0..(num_windows - 1)].iter().enumerate() { + // Test each three-bit chunk in this window. for bits in 0..(1 << FIXED_BASE_WINDOW_SIZE) { - // Interpolate the x-coordinate using the last window's coefficients - let interpolated_x = util::evaluate::(bits, &lagrange_coeffs[num_windows - 1]); - - // Compute the actual x-coordinate of the multiple [k * (8^84) - offset]B, - // where offset = \sum_{j = 0}^{83} 2^{3j+1} - let offset = (0..(num_windows - 1)).fold(C::Scalar::zero(), |acc, w| { - acc + C::Scalar::from_u64(2).pow(&[ - FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, - 0, - 0, - 0, - ]) - }); - let scalar = C::Scalar::from_u64(bits as u64) - * C::Scalar::from_u64(H as u64).pow(&[(num_windows - 1) as u64, 0, 0, 0]) - - offset; - let point = self.0 * scalar; - let x = *point.to_affine().coordinates().unwrap().x(); - - // Check that the interpolated x-coordinate matches the actual one. - assert_eq!(x, interpolated_x); + { + // Interpolate the x-coordinate using this window's coefficients + let interpolated_x = util::evaluate::(bits, coeffs); + + // Compute the actual x-coordinate of the multiple [(k+2)*(8^w)]B. + let point = base + * C::Scalar::from_u64(bits as u64 + 2) + * C::Scalar::from_u64(H as u64).pow(&[idx as u64, 0, 0, 0]); + let x = *point.to_affine().coordinates().unwrap().x(); + + // Check that the interpolated x-coordinate matches the actual one. + assert_eq!(x, interpolated_x); + } } } - fn test_zs_and_us(&self, z: &[u64], u: &[[[u8; 32]; H]], num_windows: usize) { - let window_table = self.compute_window_table(num_windows); + // Check last window. + for bits in 0..(1 << FIXED_BASE_WINDOW_SIZE) { + // Interpolate the x-coordinate using the last window's coefficients + let interpolated_x = util::evaluate::(bits, &lagrange_coeffs[num_windows - 1]); - for ((u, z), window_points) in u.iter().zip(z.iter()).zip(window_table) { - for (u, point) in u.iter().zip(window_points.iter()) { - let y = *point.coordinates().unwrap().y(); - let u = C::Base::from_bytes(&u).unwrap(); - assert_eq!(C::Base::from_u64(*z) + y, u * u); // allow either square root - assert!(bool::from((C::Base::from_u64(*z) - y).sqrt().is_none())); - } + // Compute the actual x-coordinate of the multiple [k * (8^84) - offset]B, + // where offset = \sum_{j = 0}^{83} 2^{3j+1} + let offset = (0..(num_windows - 1)).fold(C::Scalar::zero(), |acc, w| { + acc + C::Scalar::from_u64(2).pow(&[ + FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, + 0, + 0, + 0, + ]) + }); + let scalar = C::Scalar::from_u64(bits as u64) + * C::Scalar::from_u64(H as u64).pow(&[(num_windows - 1) as u64, 0, 0, 0]) + - offset; + let point = base * scalar; + let x = *point.to_affine().coordinates().unwrap().x(); + + // Check that the interpolated x-coordinate matches the actual one. + assert_eq!(x, interpolated_x); + } +} + +#[cfg(test)] +// Test that the z-values and u-values satisfy the conditions: +// 1. z + y = u^2, +// 2. z - y is not a square +// for the y-coordinate of each fixed-base multiple in each window. +fn test_zs_and_us(base: C, z: &[u64], u: &[[[u8; 32]; H]], num_windows: usize) { + let window_table = compute_window_table(base, num_windows); + + for ((u, z), window_points) in u.iter().zip(z.iter()).zip(window_table) { + for (u, point) in u.iter().zip(window_points.iter()) { + let y = *point.coordinates().unwrap().y(); + let u = C::Base::from_bytes(&u).unwrap(); + assert_eq!(C::Base::from_u64(*z) + y, u * u); // allow either square root + assert!(bool::from((C::Base::from_u64(*z) - y).sqrt().is_none())); } } } diff --git a/src/constants/commit_ivk_r.rs b/src/constants/commit_ivk_r.rs index cc539008e..90c5f16c5 100644 --- a/src/constants/commit_ivk_r.rs +++ b/src/constants/commit_ivk_r.rs @@ -1,4 +1,3 @@ -use super::{CommitIvkR, OrchardFixedBase}; use halo2::arithmetic::{CurveAffine, FieldExt}; /// Generator used in SinsemillaCommit randomness for IVK commitment @@ -2918,19 +2917,19 @@ pub const U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [ ], ]; -pub fn generator() -> CommitIvkR { - CommitIvkR(OrchardFixedBase::::new( - C::from_xy( - C::Base::from_bytes(&GENERATOR.0).unwrap(), - C::Base::from_bytes(&GENERATOR.1).unwrap(), - ) - .unwrap(), - )) +pub fn generator() -> C { + C::from_xy( + C::Base::from_bytes(&GENERATOR.0).unwrap(), + C::Base::from_bytes(&GENERATOR.1).unwrap(), + ) + .unwrap() } #[cfg(test)] mod tests { - use super::super::{TestFixedBase, COMMIT_IVK_PERSONALIZATION, NUM_WINDOWS}; + use super::super::{ + test_lagrange_coeffs, test_zs_and_us, COMMIT_IVK_PERSONALIZATION, NUM_WINDOWS, + }; use super::*; use crate::primitives::sinsemilla::CommitDomain; use group::Curve; @@ -2952,12 +2951,12 @@ mod tests { #[test] fn lagrange_coeffs() { let base = super::generator::(); - base.0.test_lagrange_coeffs(NUM_WINDOWS); + test_lagrange_coeffs(base, NUM_WINDOWS); } #[test] fn z() { let base = super::generator::(); - base.0.test_zs_and_us(&Z, &U, NUM_WINDOWS); + test_zs_and_us(base, &Z, &U, NUM_WINDOWS); } } diff --git a/src/circuit/gadget/ecc/chip/load.rs b/src/constants/load.rs similarity index 60% rename from src/circuit/gadget/ecc/chip/load.rs rename to src/constants/load.rs index 6845b9a3d..663c96d58 100644 --- a/src/circuit/gadget/ecc/chip/load.rs +++ b/src/constants/load.rs @@ -1,109 +1,114 @@ use std::convert::TryInto; -use super::EccChip; -use crate::circuit::gadget::ecc::{EccInstructions, FixedPoint, FixedPointShort}; -use crate::constants::{self, FixedBase, H, NUM_WINDOWS, NUM_WINDOWS_SHORT}; -use halo2::{ - arithmetic::{CurveAffine, FieldExt}, - plonk::Error, -}; +use crate::constants::{self, compute_lagrange_coeffs, H, NUM_WINDOWS, NUM_WINDOWS_SHORT}; +use halo2::arithmetic::{CurveAffine, FieldExt}; +use std::marker::PhantomData; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum OrchardFixedBases { - CommitIvkR(constants::CommitIvkR), - NoteCommitR(constants::NoteCommitR), - NullifierK(constants::NullifierK), - ValueCommitR(constants::ValueCommitR), + CommitIvkR(PhantomData), + NoteCommitR(PhantomData), + NullifierK(PhantomData), + ValueCommitR(PhantomData), + ValueCommitV(PhantomData), } impl OrchardFixedBases { - pub fn into_fixed_point(self, chip: EccChip) -> Result>, Error> { - let base = chip.get_fixed(self)?; - Ok(FixedPoint::>::from_inner(chip, base)) - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct OrchardFixedBasesShort(pub constants::ValueCommitV); - -impl OrchardFixedBasesShort { - pub fn into_fixed_point_short( - self, - chip: EccChip, - ) -> Result>, Error> { - let base = chip.get_fixed_short(self)?; - Ok(FixedPointShort::>::from_inner(chip, base)) + pub fn generator(&self) -> C { + match self { + Self::ValueCommitV(_) => { + let base: OrchardFixedBaseShort = (*self).into(); + base.generator + } + _ => { + let base: OrchardFixedBase = (*self).into(); + base.generator + } + } + } + + pub fn u(&self) -> Vec> { + match self { + Self::ValueCommitV(_) => { + let base: OrchardFixedBaseShort = (*self).into(); + base.u_short.0.as_ref().to_vec() + } + _ => { + let base: OrchardFixedBase = (*self).into(); + base.u.0.as_ref().to_vec() + } + } } } /// A fixed base to be used in scalar multiplication with a full-width scalar. #[derive(Clone, Debug, Eq, PartialEq)] pub struct OrchardFixedBase { - pub base: OrchardFixedBases, + pub generator: C, pub lagrange_coeffs: LagrangeCoeffs, pub z: Z, pub u: U, } +impl From> for OrchardFixedBase { + fn from(base: OrchardFixedBases) -> Self { + let (generator, z, u) = match base { + OrchardFixedBases::CommitIvkR(_) => ( + super::commit_ivk_r::generator(), + super::commit_ivk_r::Z.into(), + super::commit_ivk_r::U.into(), + ), + OrchardFixedBases::NoteCommitR(_) => ( + super::note_commit_r::generator(), + super::note_commit_r::Z.into(), + super::note_commit_r::U.into(), + ), + OrchardFixedBases::NullifierK(_) => ( + super::nullifier_k::generator(), + super::nullifier_k::Z.into(), + super::nullifier_k::U.into(), + ), + OrchardFixedBases::ValueCommitR(_) => ( + super::value_commit_r::generator(), + super::value_commit_r::Z.into(), + super::value_commit_r::U.into(), + ), + _ => unreachable!("ValueCommitV cannot be used with full-width scalar mul."), + }; + + Self { + generator, + lagrange_coeffs: compute_lagrange_coeffs(generator, NUM_WINDOWS).into(), + z, + u, + } + } +} + /// A fixed base to be used in scalar multiplication with a short signed exponent. #[derive(Clone, Debug, Eq, PartialEq)] pub struct OrchardFixedBaseShort { - pub base: OrchardFixedBasesShort, + pub generator: C, pub lagrange_coeffs_short: LagrangeCoeffsShort, pub z_short: ZShort, pub u_short: UShort, } -pub(super) fn commit_ivk_r() -> OrchardFixedBase { - let commit_ivk_r = constants::commit_ivk_r::generator(); - OrchardFixedBase { - base: OrchardFixedBases::CommitIvkR(commit_ivk_r), - lagrange_coeffs: commit_ivk_r.0.compute_lagrange_coeffs(NUM_WINDOWS).into(), - z: constants::commit_ivk_r::Z.into(), - u: constants::commit_ivk_r::U.into(), - } -} - -pub(super) fn note_commit_r() -> OrchardFixedBase { - let note_commit_r = constants::note_commit_r::generator(); - OrchardFixedBase { - base: OrchardFixedBases::NoteCommitR(note_commit_r), - lagrange_coeffs: note_commit_r.0.compute_lagrange_coeffs(NUM_WINDOWS).into(), - z: constants::note_commit_r::Z.into(), - u: constants::note_commit_r::U.into(), - } -} - -pub(super) fn nullifier_k() -> OrchardFixedBase { - let nullifier_k = constants::nullifier_k::generator(); - OrchardFixedBase { - base: OrchardFixedBases::NullifierK(nullifier_k), - lagrange_coeffs: nullifier_k.0.compute_lagrange_coeffs(NUM_WINDOWS).into(), - z: constants::nullifier_k::Z.into(), - u: constants::nullifier_k::U.into(), - } -} - -pub(super) fn value_commit_r() -> OrchardFixedBase { - let value_commit_r = constants::value_commit_r::generator(); - OrchardFixedBase { - base: OrchardFixedBases::ValueCommitR(value_commit_r), - lagrange_coeffs: value_commit_r.0.compute_lagrange_coeffs(NUM_WINDOWS).into(), - z: constants::value_commit_r::Z.into(), - u: constants::value_commit_r::U.into(), - } -} - -pub(super) fn value_commit_v() -> OrchardFixedBaseShort { - let value_commit_v = constants::value_commit_v::generator(); - OrchardFixedBaseShort { - base: OrchardFixedBasesShort(value_commit_v), - lagrange_coeffs_short: value_commit_v - .0 - .compute_lagrange_coeffs(NUM_WINDOWS_SHORT) - .into(), - z_short: constants::value_commit_v::Z_SHORT.into(), - u_short: constants::value_commit_v::U_SHORT.into(), +impl From> for OrchardFixedBaseShort { + fn from(base: OrchardFixedBases) -> Self { + match base { + OrchardFixedBases::ValueCommitV(_) => { + let generator = super::value_commit_v::generator(); + Self { + generator, + lagrange_coeffs_short: compute_lagrange_coeffs(generator, NUM_WINDOWS_SHORT) + .into(), + z_short: super::value_commit_v::Z_SHORT.into(), + u_short: super::value_commit_v::U_SHORT.into(), + } + } + _ => unreachable!("Only ValueCommitV can be used with short signed scalar mul."), + } } } diff --git a/src/constants/note_commit_r.rs b/src/constants/note_commit_r.rs index 3e9c59ebc..ecae83d42 100644 --- a/src/constants/note_commit_r.rs +++ b/src/constants/note_commit_r.rs @@ -1,4 +1,3 @@ -use super::{NoteCommitR, OrchardFixedBase}; use halo2::arithmetic::{CurveAffine, FieldExt}; /// Generator used in SinsemillaCommit randomness for note commitment @@ -2918,19 +2917,19 @@ pub const U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [ ], ]; -pub fn generator() -> NoteCommitR { - NoteCommitR(OrchardFixedBase::::new( - C::from_xy( - C::Base::from_bytes(&GENERATOR.0).unwrap(), - C::Base::from_bytes(&GENERATOR.1).unwrap(), - ) - .unwrap(), - )) +pub fn generator() -> C { + C::from_xy( + C::Base::from_bytes(&GENERATOR.0).unwrap(), + C::Base::from_bytes(&GENERATOR.1).unwrap(), + ) + .unwrap() } #[cfg(test)] mod tests { - use super::super::{TestFixedBase, NOTE_COMMITMENT_PERSONALIZATION, NUM_WINDOWS}; + use super::super::{ + test_lagrange_coeffs, test_zs_and_us, NOTE_COMMITMENT_PERSONALIZATION, NUM_WINDOWS, + }; use super::*; use crate::primitives::sinsemilla::CommitDomain; use group::Curve; @@ -2952,12 +2951,12 @@ mod tests { #[test] fn lagrange_coeffs() { let base = super::generator::(); - base.0.test_lagrange_coeffs(NUM_WINDOWS); + test_lagrange_coeffs(base, NUM_WINDOWS); } #[test] fn z() { let base = super::generator::(); - base.0.test_zs_and_us(&Z, &U, NUM_WINDOWS); + test_zs_and_us(base, &Z, &U, NUM_WINDOWS); } } diff --git a/src/constants/nullifier_k.rs b/src/constants/nullifier_k.rs index 15971dfb2..dff07bcc8 100644 --- a/src/constants/nullifier_k.rs +++ b/src/constants/nullifier_k.rs @@ -1,4 +1,3 @@ -use crate::constants::{NullifierK, OrchardFixedBase}; use halo2::arithmetic::{CurveAffine, FieldExt}; pub const GENERATOR: ([u8; 32], [u8; 32]) = ( @@ -2917,19 +2916,19 @@ pub const U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [ ], ]; -pub fn generator() -> NullifierK { - NullifierK(OrchardFixedBase::::new( - C::from_xy( - C::Base::from_bytes(&GENERATOR.0).unwrap(), - C::Base::from_bytes(&GENERATOR.1).unwrap(), - ) - .unwrap(), - )) +pub fn generator() -> C { + C::from_xy( + C::Base::from_bytes(&GENERATOR.0).unwrap(), + C::Base::from_bytes(&GENERATOR.1).unwrap(), + ) + .unwrap() } #[cfg(test)] mod tests { - use super::super::{TestFixedBase, NUM_WINDOWS, ORCHARD_PERSONALIZATION}; + use super::super::{ + test_lagrange_coeffs, test_zs_and_us, NUM_WINDOWS, ORCHARD_PERSONALIZATION, + }; use super::*; use group::Curve; use halo2::{ @@ -2950,12 +2949,12 @@ mod tests { #[test] fn lagrange_coeffs() { let base = super::generator::(); - base.0.test_lagrange_coeffs(NUM_WINDOWS); + test_lagrange_coeffs(base, NUM_WINDOWS); } #[test] fn z() { let base = super::generator::(); - base.0.test_zs_and_us(&Z, &U, NUM_WINDOWS); + test_zs_and_us(base, &Z, &U, NUM_WINDOWS); } } diff --git a/src/constants/value_commit_r.rs b/src/constants/value_commit_r.rs index 9278a11a7..bff418772 100644 --- a/src/constants/value_commit_r.rs +++ b/src/constants/value_commit_r.rs @@ -1,4 +1,3 @@ -use super::{OrchardFixedBase, ValueCommitR}; use halo2::arithmetic::{CurveAffine, FieldExt}; /// The value commitment is used to check balance between inputs and outputs. The value is @@ -2919,19 +2918,19 @@ pub const U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [ ], ]; -pub fn generator() -> ValueCommitR { - ValueCommitR(OrchardFixedBase::::new( - C::from_xy( - C::Base::from_bytes(&GENERATOR.0).unwrap(), - C::Base::from_bytes(&GENERATOR.1).unwrap(), - ) - .unwrap(), - )) +pub fn generator() -> C { + C::from_xy( + C::Base::from_bytes(&GENERATOR.0).unwrap(), + C::Base::from_bytes(&GENERATOR.1).unwrap(), + ) + .unwrap() } #[cfg(test)] mod tests { - use super::super::{TestFixedBase, NUM_WINDOWS, VALUE_COMMITMENT_PERSONALIZATION}; + use super::super::{ + test_lagrange_coeffs, test_zs_and_us, NUM_WINDOWS, VALUE_COMMITMENT_PERSONALIZATION, + }; use super::*; use group::Curve; use halo2::{ @@ -2952,12 +2951,12 @@ mod tests { #[test] fn lagrange_coeffs() { let base = super::generator::(); - base.0.test_lagrange_coeffs(NUM_WINDOWS); + test_lagrange_coeffs(base, NUM_WINDOWS); } #[test] fn z() { let base = super::generator::(); - base.0.test_zs_and_us(&Z, &U, NUM_WINDOWS); + test_zs_and_us(base, &Z, &U, NUM_WINDOWS); } } diff --git a/src/constants/value_commit_v.rs b/src/constants/value_commit_v.rs index 2e5d31a61..0ad8661b7 100644 --- a/src/constants/value_commit_v.rs +++ b/src/constants/value_commit_v.rs @@ -1,4 +1,3 @@ -use super::{OrchardFixedBase, ValueCommitV}; use halo2::arithmetic::{CurveAffine, FieldExt}; /// The value commitment is used to check balance between inputs and outputs. The value is @@ -772,19 +771,19 @@ pub const U_SHORT: [[[u8; 32]; super::H]; super::NUM_WINDOWS_SHORT] = [ ], ]; -pub fn generator() -> ValueCommitV { - ValueCommitV(OrchardFixedBase::::new( - C::from_xy( - C::Base::from_bytes(&GENERATOR.0).unwrap(), - C::Base::from_bytes(&GENERATOR.1).unwrap(), - ) - .unwrap(), - )) +pub fn generator() -> C { + C::from_xy( + C::Base::from_bytes(&GENERATOR.0).unwrap(), + C::Base::from_bytes(&GENERATOR.1).unwrap(), + ) + .unwrap() } #[cfg(test)] mod tests { - use super::super::{TestFixedBase, NUM_WINDOWS_SHORT, VALUE_COMMITMENT_PERSONALIZATION}; + use super::super::{ + test_lagrange_coeffs, test_zs_and_us, NUM_WINDOWS_SHORT, VALUE_COMMITMENT_PERSONALIZATION, + }; use super::*; use group::Curve; use halo2::{ @@ -805,12 +804,12 @@ mod tests { #[test] fn lagrange_coeffs_short() { let base = super::generator::(); - base.0.test_lagrange_coeffs(NUM_WINDOWS_SHORT); + test_lagrange_coeffs(base, NUM_WINDOWS_SHORT); } #[test] fn z_short() { let base = super::generator::(); - base.0.test_zs_and_us(&Z_SHORT, &U_SHORT, NUM_WINDOWS_SHORT); + test_zs_and_us(base, &Z_SHORT, &U_SHORT, NUM_WINDOWS_SHORT); } } From 9a8a81b9850355564e7ab7a0bd59dde2d5c98ddf Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 31 May 2021 14:29:57 +0800 Subject: [PATCH 41/49] mul_fixed: Remove MulFixed trait This was defining shared behaviour between the mul_fixed::short and mul_fixed::full_width modules. But the behaviour can be captured by simply sharing the same mul_fixed::Config. --- src/circuit/gadget/ecc/chip.rs | 12 +- src/circuit/gadget/ecc/chip/mul_fixed.rs | 198 +++++++++--------- .../gadget/ecc/chip/mul_fixed/full_width.rs | 102 ++------- .../gadget/ecc/chip/mul_fixed/short.rs | 104 ++------- 4 files changed, 132 insertions(+), 284 deletions(-) diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 6d436050a..aa3e78442 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -195,8 +195,8 @@ impl EccChip { // Create fixed-base scalar mul gates { - let mul_fixed_config: mul_fixed::Config = (&config).into(); - mul_fixed_config.create_gate::(meta); + let mul_fixed_config: mul_fixed::Config = (&config).into(); + mul_fixed_config.create_gate(meta); } // Create variable-base scalar mul gates @@ -348,10 +348,10 @@ impl EccInstructions for EccChip { _ => (), }; - let config: mul_fixed::Config = self.config().into(); + let config: mul_fixed::Config = self.config().into(); layouter.assign_region( || format!("Multiply {:?}", base), - |mut region| config.assign_region_full::(scalar, base, 0, &mut region), + |mut region| config.assign_region_full(scalar, base, 0, &mut region), ) } @@ -367,10 +367,10 @@ impl EccInstructions for EccChip { _ => return Err(Error::SynthesisError), }; - let config: mul_fixed::Config = self.config().into(); + let config: mul_fixed::Config = self.config().into(); layouter.assign_region( || format!("Multiply {:?}", base), - |mut region| config.assign_region_short::(scalar, base, 0, &mut region), + |mut region| config.assign_region_short(scalar, 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 554b09380..8db83064d 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -6,6 +6,7 @@ use crate::constants::{ self, load::{OrchardFixedBase, OrchardFixedBaseShort, OrchardFixedBases}, }; +use std::marker::PhantomData; use group::Curve; use halo2::{ @@ -18,9 +19,13 @@ use halo2::{ pub mod full_width; pub mod short; -pub struct Config { +#[derive(Clone, Debug)] +pub struct Config { + // Selector used for full-width fixed-base scalar mul. q_mul_fixed: Selector, + // Selector used for fixed-base scalar mul with short signed exponent. q_mul_fixed_short: 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`. @@ -42,9 +47,10 @@ pub struct Config { add_incomplete_config: add_incomplete::Config, // Configuration for `witness_point` witness_point_config: witness_point::Config, + _marker: PhantomData, } -impl From<&EccConfig> for Config { +impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { let config = Self { q_mul_fixed: ecc_config.q_mul_fixed, @@ -59,6 +65,7 @@ impl From<&EccConfig> for Config { add_config: ecc_config.into(), add_incomplete_config: ecc_config.into(), witness_point_config: ecc_config.into(), + _marker: PhantomData, }; // Check relationships between this config and `add_config`. @@ -105,14 +112,14 @@ impl From<&EccConfig> for Config { } } -impl Config { - pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { +impl Config { + pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { self.create_gate_inner(meta); let short_config: short::Config = self.into(); short_config.create_gate(meta); } - pub(super) fn assign_region_full( + pub(super) fn assign_region_full( &self, scalar: &EccScalarFixed, base: OrchardFixedBases, @@ -123,7 +130,7 @@ impl Config { full_width_config.assign_region(region, offset, scalar, base) } - pub(super) fn assign_region_short( + pub(super) fn assign_region_short( &self, scalar: &EccScalarFixedShort, base: OrchardFixedBases, @@ -134,7 +141,7 @@ impl Config { short_config.assign_region(region, offset, scalar, base) } - fn create_gate_inner(&self, meta: &mut ConstraintSystem) { + fn create_gate_inner(&self, meta: &mut ConstraintSystem) { let q_mul_fixed = meta.query_selector(self.q_mul_fixed, Rotation::cur()); let y_p = meta.query_advice(self.y_p, Rotation::cur()); @@ -143,12 +150,16 @@ impl Config { let k = meta.query_advice(self.k, Rotation::cur()); let x_p = meta.query_advice(self.x_p, Rotation::cur()); - let k_pow: Vec> = (0..constants::H) - .map(|pow| (0..pow).fold(Expression::Constant(F::one()), |acc, _| acc * k.clone())) + let k_pow: Vec> = (0..constants::H) + .map(|pow| { + (0..pow).fold(Expression::Constant(C::Base::one()), |acc, _| { + acc * k.clone() + }) + }) .collect(); let interpolated_x = k_pow.iter().zip(self.lagrange_coeffs.iter()).fold( - Expression::Constant(F::zero()), + Expression::Constant(C::Base::zero()), |acc, (k_pow, coeff)| { acc + (k_pow.clone() * meta.query_fixed(*coeff, Rotation::cur())) }, @@ -165,73 +176,9 @@ impl Config { q_mul_fixed * (u.clone() * u - y_p - z) }); } -} - -enum ScalarFixed { - FullWidth(EccScalarFixed), - Short(EccScalarFixedShort), -} - -impl From<&EccScalarFixed> for ScalarFixed { - fn from(scalar_fixed: &EccScalarFixed) -> Self { - Self::FullWidth(scalar_fixed.clone()) - } -} - -impl From<&EccScalarFixedShort> for ScalarFixed { - fn from(scalar_fixed: &EccScalarFixedShort) -> Self { - Self::Short(scalar_fixed.clone()) - } -} - -impl ScalarFixed { - fn k_bits(&self) -> &[CellValue] { - match self { - ScalarFixed::FullWidth(scalar) => &scalar.k_bits, - ScalarFixed::Short(scalar) => &scalar.k_bits, - } - } - - // The scalar decomposition was done in the base field. For computation - // outside the circuit, we now convert them back into the scalar field. - fn k_field(&self) -> Vec> { - self.k_bits() - .iter() - .map(|bits| { - bits.value - .map(|value| C::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 k_usize(&self) -> Vec> { - self.k_bits() - .iter() - .map(|bits| bits.value.map(|value| value.to_bytes()[0] as usize)) - .collect::>() - } -} - -trait MulFixed { - const NUM_WINDOWS: usize; - - fn q_mul_fixed(&self) -> Selector; - fn lagrange_coeffs(&self) -> [Column; constants::H]; - fn fixed_z(&self) -> Column; - fn k(&self) -> Column; - fn u(&self) -> Column; - fn x_p(&self) -> Column; - fn y_p(&self) -> Column; - fn perm(&self) -> &Permutation; - fn witness_point_config(&self) -> &witness_point::Config; - fn add_config(&self) -> &add::Config; - fn add_incomplete_config(&self) -> &add_incomplete::Config; #[allow(clippy::type_complexity)] - fn assign_region_inner( + fn assign_region_inner( &self, region: &mut Region<'_, C::Base>, offset: usize, @@ -239,7 +186,7 @@ trait MulFixed { base: OrchardFixedBases, ) -> Result<(EccPoint, EccPoint), Error> { // Assign fixed columns for given fixed base - self.assign_fixed_constants(region, offset, base)?; + self.assign_fixed_constants::(region, offset, base)?; // Copy the scalar decomposition self.copy_scalar(region, offset, scalar)?; @@ -251,12 +198,12 @@ trait MulFixed { let acc = self.add_incomplete(region, offset, acc, base, scalar)?; // Process most significant window using complete addition - let mul_b = self.process_msb(region, offset, base, scalar)?; + let mul_b = self.process_msb::(region, offset, base, scalar)?; Ok((acc, mul_b)) } - fn assign_fixed_constants( + fn assign_fixed_constants( &self, region: &mut Region<'_, C::Base>, offset: usize, @@ -280,9 +227,9 @@ trait MulFixed { }; // Assign fixed columns for given fixed base - for window in 0..Self::NUM_WINDOWS { + for window in 0..NUM_WINDOWS { // Enable `q_mul_fixed` selector - self.q_mul_fixed().enable(region, window + offset)?; + self.q_mul_fixed.enable(region, window + offset)?; // Assign x-coordinate Lagrange interpolation coefficients for k in 0..(constants::H) { @@ -293,7 +240,7 @@ trait MulFixed { window, k ) }, - self.lagrange_coeffs()[k], + self.lagrange_coeffs[k], window + offset, || Ok(lagrange_coeffs[window].0[k]), )?; @@ -302,7 +249,7 @@ trait MulFixed { // Assign z-values for each window region.assign_fixed( || format!("z-value for window: {:?}", window), - self.fixed_z(), + self.fixed_z, window + offset, || Ok(z[window]), )?; @@ -322,10 +269,10 @@ trait MulFixed { util::assign_and_constrain( region, || format!("k[{:?}]", window), - self.k(), + self.k, window + offset, k, - self.perm(), + &self.perm, )?; } @@ -342,7 +289,7 @@ trait MulFixed { // Witness `m0` in `x_p`, `y_p` cells on row 0 let k0 = scalar.k_field()[0]; let m0 = k0.map(|k0| base.generator() * (k0 + C::Scalar::from_u64(2))); - let m0 = self.witness_point_config().assign_region( + let m0 = self.witness_point_config.assign_region( m0.map(|point| point.to_affine()), offset, region, @@ -354,25 +301,25 @@ trait MulFixed { let u0 = &base.u()[0]; let u0 = k0.map(|k0| u0.0[k0]); - region.assign_advice(|| "u", self.u(), offset, || u0.ok_or(Error::SynthesisError))?; + region.assign_advice(|| "u", self.u, offset, || u0.ok_or(Error::SynthesisError))?; } // Copy `m0` into `x_qr`, `y_qr` cells on row 1 let x = util::assign_and_constrain( region, || "initialize acc x", - self.add_incomplete_config().x_qr, + self.add_incomplete_config.x_qr, offset + 1, &m0.x, - self.perm(), + &self.perm, )?; let y = util::assign_and_constrain( region, || "initialize acc y", - self.add_incomplete_config().y_qr, + self.add_incomplete_config.y_qr, offset + 1, &m0.y, - self.perm(), + &self.perm, )?; Ok(EccPoint { x, y }) @@ -404,7 +351,7 @@ trait MulFixed { // Compute [(k_w + 2) ⋅ 8^w]B let mul_b = k.map(|k| base_value * (k + C::Scalar::from_u64(2)) * h.pow(&[w as u64, 0, 0, 0])); - let mul_b = self.witness_point_config().assign_region( + let mul_b = self.witness_point_config.assign_region( mul_b.map(|point| point.to_affine()), offset + w, region, @@ -414,20 +361,20 @@ trait MulFixed { let u_val = scalar_k_usize[w].map(|k| base_u[w].0[k]); region.assign_advice( || "u", - self.u(), + self.u, offset + w, || u_val.ok_or(Error::SynthesisError), )?; // Add to the accumulator acc = self - .add_incomplete_config() + .add_incomplete_config .assign_region(&mul_b, &acc, offset + w, region)?; } Ok(acc) } - fn process_msb( + fn process_msb( &self, region: &mut Region<'_, C::Base>, offset: usize, @@ -439,18 +386,17 @@ trait MulFixed { // Assign u = (y_p + z_w).sqrt() for the most significant window { - let u_val = scalar.k_usize()[Self::NUM_WINDOWS - 1] - .map(|k| base.u()[Self::NUM_WINDOWS - 1].0[k]); + let u_val = scalar.k_usize()[NUM_WINDOWS - 1].map(|k| base.u()[NUM_WINDOWS - 1].0[k]); region.assign_advice( || "u", - self.u(), - offset + Self::NUM_WINDOWS - 1, + self.u, + offset + NUM_WINDOWS - 1, || u_val.ok_or(Error::SynthesisError), )?; } // offset_acc = \sum_{j = 0}^{NUM_WINDOWS - 2} 2^{FIXED_BASE_WINDOW_SIZE * j+1} - let offset_acc = (0..(Self::NUM_WINDOWS - 1)).fold(C::ScalarExt::zero(), |acc, w| { + let offset_acc = (0..(NUM_WINDOWS - 1)).fold(C::ScalarExt::zero(), |acc, w| { acc + C::Scalar::from_u64(2).pow(&[ constants::FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, 0, @@ -461,13 +407,61 @@ trait MulFixed { // `scalar = [k * 8^84 - offset_acc]`, where `offset_acc = \sum_{j = 0}^{83} 2^{FIXED_BASE_WINDOW_SIZE * j + 1}`. let scalar = scalar.k_field()[scalar.k_field().len() - 1] - .map(|k| k * h.pow(&[(Self::NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_acc); + .map(|k| k * h.pow(&[(NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_acc); let mul_b = scalar.map(|scalar| base.generator() * scalar); - self.witness_point_config().assign_region( + self.witness_point_config.assign_region( mul_b.map(|point| point.to_affine()), - offset + Self::NUM_WINDOWS - 1, + offset + NUM_WINDOWS - 1, region, ) } } + +enum ScalarFixed { + FullWidth(EccScalarFixed), + Short(EccScalarFixedShort), +} + +impl From<&EccScalarFixed> for ScalarFixed { + fn from(scalar_fixed: &EccScalarFixed) -> Self { + Self::FullWidth(scalar_fixed.clone()) + } +} + +impl From<&EccScalarFixedShort> for ScalarFixed { + fn from(scalar_fixed: &EccScalarFixedShort) -> Self { + Self::Short(scalar_fixed.clone()) + } +} + +impl ScalarFixed { + fn k_bits(&self) -> &[CellValue] { + match self { + ScalarFixed::FullWidth(scalar) => &scalar.k_bits, + ScalarFixed::Short(scalar) => &scalar.k_bits, + } + } + + // The scalar decomposition was done in the base field. For computation + // outside the circuit, we now convert them back into the scalar field. + fn k_field(&self) -> Vec> { + self.k_bits() + .iter() + .map(|bits| { + bits.value + .map(|value| C::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 k_usize(&self) -> Vec> { + self.k_bits() + .iter() + .map(|bits| bits.value.map(|value| value.to_bytes()[0] as usize)) + .collect::>() + } +} 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 45bd9efab..fe30a62e1 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -1,97 +1,22 @@ -use super::super::{ - add, add_incomplete, witness_point, EccPoint, EccScalarFixed, OrchardFixedBases, -}; -use super::MulFixed; +use super::super::{EccPoint, EccScalarFixed, OrchardFixedBases}; use crate::constants; -use std::marker::PhantomData; - -use halo2::{ - arithmetic::CurveAffine, - circuit::Region, - plonk::{Advice, Column, Error, Fixed, Permutation, Selector}, -}; - -pub struct Config { - q_mul_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`. - fixed_z: Column, - // k-bit decomposition of an `n-1`-bit scalar: - // a = a_0 + 2^k(a_1) + 2^{2k}(a_2) + ... + 2^{(n-1)k}(a_{n-1}) - k: Column, - // x-coordinate of the multiple of the fixed base at the current window. - x_p: Column, - // y-coordinate of the multiple of the fixed base at the current window. - y_p: Column, - // An integer `u` for the current window, s.t. `y + z = u^2`. - u: Column, - // Permutation - perm: Permutation, - // Configuration for `add` - add_config: add::Config, - // Configuration for `add_incomplete` - add_incomplete_config: add_incomplete::Config, - // Configuration for `witness_point` - witness_point_config: witness_point::Config, - _marker: PhantomData, -} -impl From<&super::Config> for Config { - fn from(config: &super::Config) -> Self { - Self { - q_mul_fixed: config.q_mul_fixed, - lagrange_coeffs: config.lagrange_coeffs, - fixed_z: config.fixed_z, - k: config.k, - x_p: config.x_p, - y_p: config.y_p, - u: config.u, - perm: config.perm.clone(), - add_config: config.add_config.clone(), - add_incomplete_config: config.add_incomplete_config.clone(), - witness_point_config: config.witness_point_config.clone(), - _marker: PhantomData, - } +use halo2::{arithmetic::CurveAffine, circuit::Region, plonk::Error}; + +pub struct Config(super::Config); + +impl From<&super::Config> for Config { + fn from(config: &super::Config) -> Self { + Self(config.clone()) } } -impl MulFixed for Config { - const NUM_WINDOWS: usize = constants::NUM_WINDOWS; +impl std::ops::Deref for Config { + type Target = super::Config; - fn q_mul_fixed(&self) -> Selector { - self.q_mul_fixed - } - fn lagrange_coeffs(&self) -> [Column; constants::H] { - self.lagrange_coeffs - } - fn fixed_z(&self) -> Column { - self.fixed_z - } - fn k(&self) -> Column { - self.k - } - fn x_p(&self) -> Column { - self.x_p - } - fn y_p(&self) -> Column { - self.y_p - } - fn u(&self) -> Column { - self.u - } - fn perm(&self) -> &Permutation { - &self.perm - } - fn witness_point_config(&self) -> &witness_point::Config { - &self.witness_point_config - } - fn add_config(&self) -> &add::Config { - &self.add_config - } - fn add_incomplete_config(&self) -> &add_incomplete::Config { - &self.add_incomplete_config + fn deref(&self) -> &super::Config { + &self.0 } } @@ -103,7 +28,8 @@ impl Config { scalar: &EccScalarFixed, base: OrchardFixedBases, ) -> Result, Error> { - let (acc, mul_b) = self.assign_region_inner(region, offset, &scalar.into(), base.into())?; + let (acc, mul_b) = + (*self).assign_region_inner::<{constants::NUM_WINDOWS}>(region, offset, &scalar.into(), base.into())?; // Add to the accumulator and return the final result as `[scalar]B`. let result = diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index b1c9f3bfc..b2c18b141 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -1,99 +1,26 @@ -use super::super::{ - add, add_incomplete, util, witness_point, CellValue, EccPoint, EccScalarFixedShort, -}; -use super::MulFixed; +use super::super::{util, CellValue, EccPoint, EccScalarFixedShort}; use crate::constants::{self, load::OrchardFixedBases}; use halo2::{ arithmetic::{CurveAffine, Field}, circuit::Region, - plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Permutation, Selector}, + plonk::{ConstraintSystem, Error}, poly::Rotation, }; -use std::marker::PhantomData; - -pub struct Config { - q_mul_fixed: Selector, - q_mul_fixed_short: 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`. - fixed_z: Column, - // k-bit decomposition of an `n-1`-bit scalar: - // a = a_0 + 2^k(a_1) + 2^{2k}(a_2) + ... + 2^{(n-1)k}(a_{n-1}) - k_s: Column, - // x-coordinate of the multiple of the fixed base at the current window. - x_p: Column, - // y-coordinate of the multiple of the fixed base at the current window. - y_p: Column, - // An integer `u` for the current window, s.t. `y + z = u^2`. - u: Column, - // Permutation - perm: Permutation, - // Configuration for `add` - add_config: add::Config, - // Configuration for `add_incomplete` - add_incomplete_config: add_incomplete::Config, - // Configuration for `witness_point` - witness_point_config: witness_point::Config, - _marker: PhantomData, -} -impl From<&super::Config> for Config { - fn from(config: &super::Config) -> Self { - Self { - q_mul_fixed: config.q_mul_fixed, - q_mul_fixed_short: config.q_mul_fixed_short, - lagrange_coeffs: config.lagrange_coeffs, - fixed_z: config.fixed_z, - k_s: config.k, - x_p: config.x_p, - y_p: config.y_p, - u: config.u, - perm: config.perm.clone(), - add_config: config.add_config.clone(), - add_incomplete_config: config.add_incomplete_config.clone(), - witness_point_config: config.witness_point_config.clone(), - _marker: PhantomData, - } +pub struct Config(super::Config); + +impl From<&super::Config> for Config { + fn from(config: &super::Config) -> Self { + Self(config.clone()) } } -impl MulFixed for Config { - const NUM_WINDOWS: usize = constants::NUM_WINDOWS_SHORT; +impl std::ops::Deref for Config { + type Target = super::Config; - fn q_mul_fixed(&self) -> Selector { - self.q_mul_fixed - } - fn lagrange_coeffs(&self) -> [Column; constants::H] { - self.lagrange_coeffs - } - fn fixed_z(&self) -> Column { - self.fixed_z - } - fn k(&self) -> Column { - self.k_s - } - fn x_p(&self) -> Column { - self.x_p - } - fn y_p(&self) -> Column { - self.y_p - } - fn u(&self) -> Column { - self.u - } - fn perm(&self) -> &Permutation { - &self.perm - } - fn witness_point_config(&self) -> &witness_point::Config { - &self.witness_point_config - } - fn add_config(&self) -> &add::Config { - &self.add_config - } - fn add_incomplete_config(&self) -> &add_incomplete::Config { - &self.add_incomplete_config + fn deref(&self) -> &super::Config { + &self.0 } } @@ -103,7 +30,7 @@ impl Config { pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { let q_mul_fixed_short = meta.query_selector(self.q_mul_fixed_short, Rotation::cur()); let y_p = meta.query_advice(self.y_p, Rotation::cur()); - let y_a = meta.query_advice(self.add_config().y_qr, Rotation::cur()); + let y_a = meta.query_advice(self.add_config.y_qr, Rotation::cur()); // `(x_a, y_a)` is the result of `[m]B`, where `m` is the magnitude. // We conditionally negate this result using `y_p = y_a * s`, where `s` is the sign. @@ -115,7 +42,7 @@ impl Config { // Check that s * y_p = y_a meta.create_gate("check negation", |meta| { - let s = meta.query_advice(self.k_s, Rotation::cur()); + let s = meta.query_advice(self.k, Rotation::cur()); q_mul_fixed_short * (s * y_p - y_a) }); } @@ -127,7 +54,8 @@ impl Config { scalar: &EccScalarFixedShort, base: OrchardFixedBases, ) -> Result, Error> { - let (acc, mul_b) = self.assign_region_inner(region, offset, &scalar.into(), base.into())?; + let (acc, mul_b) = + self.assign_region_inner::<{constants::NUM_WINDOWS_SHORT}>(region, offset, &scalar.into(), base.into())?; // Add to the cumulative sum to get `[magnitude]B`. let magnitude_mul = self.add_config.assign_region( @@ -144,7 +72,7 @@ impl Config { let sign = util::assign_and_constrain( region, || "sign", - self.k_s, + self.k, offset + constants::NUM_WINDOWS_SHORT, &scalar.sign, &self.perm, From a4f71280f0929f94721169ecc51984d02d33fd17 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 31 May 2021 16:17:55 +0800 Subject: [PATCH 42/49] chip: Directly use ValueCommitV as FixedPointsShort associated type There is no need to enumerate FixedPointsShort or to make a newtype, since ValueCommitV is the only fixed base used with short signed scalars. --- src/circuit/gadget/ecc.rs | 18 ++--- src/circuit/gadget/ecc/chip.rs | 23 ++----- src/circuit/gadget/ecc/chip/mul_fixed.rs | 54 +++++++++++++-- .../gadget/ecc/chip/mul_fixed/full_width.rs | 23 ++++--- .../gadget/ecc/chip/mul_fixed/short.rs | 19 ++++-- src/constants.rs | 2 +- src/constants/load.rs | 68 +++++-------------- 7 files changed, 107 insertions(+), 100 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 46bb1742e..a34d01cc2 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -38,8 +38,10 @@ pub trait EccInstructions: Chip { /// Variable representing the affine short Weierstrass x-coordinate of an /// elliptic curve point. type X: Clone + Debug; - /// Variable representing the set of fixed bases in the circuit. - type FixedPoints: Copy + Clone + Debug; + /// Enumeration of the set of fixed bases to be used in full-width scalar mul. + type FixedPoints: Clone + Debug; + /// Enumeration of the set of fixed bases to be used in short signed scalar mul. + type FixedPointsShort: Clone + Debug; /// Witnesses the given base field element as a private input to the circuit /// for variable-base scalar mul. @@ -107,7 +109,7 @@ pub trait EccInstructions: Chip { &self, layouter: &mut impl Layouter, scalar: &Self::ScalarFixed, - base: Self::FixedPoints, + base: &Self::FixedPoints, ) -> Result; /// Performs fixed-base scalar multiplication using a short signed scalar, returning `[scalar] base`. @@ -115,7 +117,7 @@ pub trait EccInstructions: Chip { &self, layouter: &mut impl Layouter, scalar: &Self::ScalarFixedShort, - base: Self::FixedPoints, + base: &Self::FixedPointsShort, ) -> Result; } @@ -308,7 +310,7 @@ impl + Clone + Debug + Eq> FixedPoin ) -> Result, Error> { assert_eq!(self.chip, by.chip); self.chip - .mul_fixed(&mut layouter, &by.inner, self.inner) + .mul_fixed(&mut layouter, &by.inner, &self.inner) .map(|inner| Point { chip: self.chip.clone(), inner, @@ -326,7 +328,7 @@ impl + Clone + Debug + Eq> FixedPoin #[derive(Clone, Debug)] pub struct FixedPointShort + Clone + Debug + Eq> { chip: EccChip, - inner: EccChip::FixedPoints, + inner: EccChip::FixedPointsShort, } impl + Clone + Debug + Eq> FixedPointShort { @@ -338,7 +340,7 @@ impl + Clone + Debug + Eq> FixedPoin ) -> Result, Error> { assert_eq!(self.chip, by.chip); self.chip - .mul_fixed_short(&mut layouter, &by.inner, self.inner) + .mul_fixed_short(&mut layouter, &by.inner, &self.inner) .map(|inner| Point { chip: self.chip.clone(), inner, @@ -346,7 +348,7 @@ impl + Clone + Debug + Eq> FixedPoin } /// Wraps the given fixed base (obtained directly from an instruction) in a gadget. - pub fn from_inner(chip: EccChip, inner: EccChip::FixedPoints) -> Self { + pub fn from_inner(chip: EccChip, inner: EccChip::FixedPointsShort) -> Self { FixedPointShort { chip, inner } } } diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index aa3e78442..571c675d1 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -1,5 +1,5 @@ use super::EccInstructions; -use crate::constants::{self, load::OrchardFixedBases}; +use crate::constants::{self, OrchardFixedBasesFull, ValueCommitV}; use ff::Field; use halo2::{ arithmetic::CurveAffine, @@ -236,7 +236,8 @@ impl EccInstructions for EccChip { type ScalarVar = CellValue; type Point = EccPoint; type X = CellValue; - type FixedPoints = OrchardFixedBases; + type FixedPoints = OrchardFixedBasesFull; + type FixedPointsShort = ValueCommitV; fn witness_scalar_var( &self, @@ -340,18 +341,12 @@ impl EccInstructions for EccChip { &self, layouter: &mut impl Layouter, scalar: &Self::ScalarFixed, - base: Self::FixedPoints, + base: &Self::FixedPoints, ) -> Result { - // Full-width fixed-base scalar mul cannot be used with ValueCommitV. - match base { - OrchardFixedBases::ValueCommitV(_) => return Err(Error::SynthesisError), - _ => (), - }; - let config: mul_fixed::Config = self.config().into(); layouter.assign_region( || format!("Multiply {:?}", base), - |mut region| config.assign_region_full(scalar, base, 0, &mut region), + |mut region| config.assign_region_full(scalar, *base, 0, &mut region), ) } @@ -359,14 +354,8 @@ impl EccInstructions for EccChip { &self, layouter: &mut impl Layouter, scalar: &Self::ScalarFixedShort, - base: Self::FixedPoints, + base: &Self::FixedPointsShort, ) -> Result { - // Short fixed-base scalar mul is only used with ValueCommitV. - match base { - OrchardFixedBases::ValueCommitV(_) => (), - _ => return Err(Error::SynthesisError), - }; - let config: mul_fixed::Config = self.config().into(); layouter.assign_region( || format!("Multiply {:?}", base), diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index 8db83064d..c8ba8f540 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::constants::{ self, - load::{OrchardFixedBase, OrchardFixedBaseShort, OrchardFixedBases}, + load::{OrchardFixedBase, OrchardFixedBasesFull, ValueCommitV, WindowUs}, }; use std::marker::PhantomData; @@ -19,6 +19,48 @@ use halo2::{ pub mod full_width; pub mod short; +// A sum type for both full-width and short bases. This enables us to use the +// shared functionality of full-width and short fixed-base scalar multiplication. +#[derive(Copy, Clone, Debug)] +enum OrchardFixedBases { + Full(OrchardFixedBasesFull), + ValueCommitV, +} + +impl From> for OrchardFixedBases { + fn from(full_width_base: OrchardFixedBasesFull) -> Self { + Self::Full(full_width_base) + } +} + +impl From> for OrchardFixedBases { + fn from(_value_commit_v: ValueCommitV) -> Self { + Self::ValueCommitV + } +} + +impl OrchardFixedBases { + pub fn generator(self) -> C { + match self { + Self::ValueCommitV => constants::value_commit_v::generator(), + Self::Full(base) => { + let base: OrchardFixedBase = base.into(); + base.generator + } + } + } + + pub fn u(self) -> Vec> { + match self { + Self::ValueCommitV => ValueCommitV::::get().u_short.0.as_ref().to_vec(), + Self::Full(base) => { + let base: OrchardFixedBase = base.into(); + base.u.0.as_ref().to_vec() + } + } + } +} + #[derive(Clone, Debug)] pub struct Config { // Selector used for full-width fixed-base scalar mul. @@ -122,7 +164,7 @@ impl Config { pub(super) fn assign_region_full( &self, scalar: &EccScalarFixed, - base: OrchardFixedBases, + base: OrchardFixedBasesFull, offset: usize, region: &mut Region<'_, C::Base>, ) -> Result, Error> { @@ -133,7 +175,7 @@ impl Config { pub(super) fn assign_region_short( &self, scalar: &EccScalarFixedShort, - base: OrchardFixedBases, + base: &ValueCommitV, offset: usize, region: &mut Region<'_, C::Base>, ) -> Result, Error> { @@ -210,14 +252,14 @@ impl Config { base: OrchardFixedBases, ) -> Result<(), Error> { let (lagrange_coeffs, z) = match base { - OrchardFixedBases::ValueCommitV(_) => { - let base: OrchardFixedBaseShort = base.into(); + OrchardFixedBases::ValueCommitV => { + let base = ValueCommitV::::get(); ( base.lagrange_coeffs_short.0.as_ref().to_vec(), base.z_short.0.as_ref().to_vec(), ) } - _ => { + OrchardFixedBases::Full(base) => { let base: OrchardFixedBase = base.into(); ( base.lagrange_coeffs.0.as_ref().to_vec(), 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 fe30a62e1..b33379160 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -1,4 +1,4 @@ -use super::super::{EccPoint, EccScalarFixed, OrchardFixedBases}; +use super::super::{EccPoint, EccScalarFixed, OrchardFixedBasesFull}; use crate::constants; @@ -26,10 +26,14 @@ impl Config { region: &mut Region<'_, C::Base>, offset: usize, scalar: &EccScalarFixed, - base: OrchardFixedBases, + base: OrchardFixedBasesFull, ) -> Result, Error> { - let (acc, mul_b) = - (*self).assign_region_inner::<{constants::NUM_WINDOWS}>(region, offset, &scalar.into(), base.into())?; + let (acc, mul_b) = (*self).assign_region_inner::<{ constants::NUM_WINDOWS }>( + region, + offset, + &scalar.into(), + base.into(), + )?; // Add to the accumulator and return the final result as `[scalar]B`. let result = @@ -42,6 +46,7 @@ impl Config { use group::Curve; use halo2::arithmetic::FieldExt; + let base: super::OrchardFixedBases = base.into(); let scalar = scalar .value .map(|scalar| C::Scalar::from_bytes(&scalar.to_bytes()).unwrap()); @@ -65,7 +70,7 @@ pub mod tests { }; use crate::circuit::gadget::ecc::{ - chip::{EccChip, OrchardFixedBases}, + chip::{EccChip, OrchardFixedBasesFull}, FixedPoint, ScalarFixed, }; use crate::constants; @@ -76,7 +81,7 @@ pub mod tests { mut layouter: impl Layouter, ) -> Result<(), Error> { // commit_ivk_r - let commit_ivk_r = OrchardFixedBases::CommitIvkR(PhantomData); + let commit_ivk_r = OrchardFixedBasesFull::CommitIvkR(PhantomData); let commit_ivk_r = FixedPoint::from_inner(chip.clone(), commit_ivk_r); test_single_base( chip.clone(), @@ -85,7 +90,7 @@ pub mod tests { )?; // note_commit_r - let note_commit_r = OrchardFixedBases::NoteCommitR(PhantomData); + let note_commit_r = OrchardFixedBasesFull::NoteCommitR(PhantomData); let note_commit_r = FixedPoint::from_inner(chip.clone(), note_commit_r); test_single_base( chip.clone(), @@ -94,7 +99,7 @@ pub mod tests { )?; // nullifier_k - let nullifier_k = OrchardFixedBases::NullifierK(PhantomData); + let nullifier_k = OrchardFixedBasesFull::NullifierK(PhantomData); let nullifier_k = FixedPoint::from_inner(chip.clone(), nullifier_k); test_single_base( chip.clone(), @@ -103,7 +108,7 @@ pub mod tests { )?; // value_commit_r - let value_commit_r = OrchardFixedBases::ValueCommitR(PhantomData); + let value_commit_r = OrchardFixedBasesFull::ValueCommitR(PhantomData); let value_commit_r = FixedPoint::from_inner(chip.clone(), value_commit_r); test_single_base( chip.clone(), diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index b2c18b141..1f5cf6c4d 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -1,5 +1,5 @@ use super::super::{util, CellValue, EccPoint, EccScalarFixedShort}; -use crate::constants::{self, load::OrchardFixedBases}; +use crate::constants::{self, ValueCommitV}; use halo2::{ arithmetic::{CurveAffine, Field}, @@ -52,10 +52,14 @@ impl Config { region: &mut Region<'_, C::Base>, offset: usize, scalar: &EccScalarFixedShort, - base: OrchardFixedBases, + base: &ValueCommitV, ) -> Result, Error> { - let (acc, mul_b) = - self.assign_region_inner::<{constants::NUM_WINDOWS_SHORT}>(region, offset, &scalar.into(), base.into())?; + let (acc, mul_b) = self.assign_region_inner::<{ constants::NUM_WINDOWS_SHORT }>( + region, + offset, + &scalar.into(), + base.clone().into(), + )?; // Add to the cumulative sum to get `[magnitude]B`. let magnitude_mul = self.add_config.assign_region( @@ -118,6 +122,8 @@ impl Config { { use group::Curve; + let base: super::OrchardFixedBases = base.clone().into(); + let scalar = scalar .magnitude .zip(scalar.sign.value) @@ -151,15 +157,14 @@ pub mod tests { }; use crate::circuit::gadget::ecc::{chip::EccChip, FixedPointShort, ScalarFixedShort}; - use crate::constants::load::OrchardFixedBases; - use std::marker::PhantomData; + use crate::constants::load::ValueCommitV; pub fn test_mul_fixed_short( chip: EccChip, mut layouter: impl Layouter, ) -> Result<(), Error> { // value_commit_v - let value_commit_v = OrchardFixedBases::ValueCommitV(PhantomData); + let value_commit_v = ValueCommitV::::get(); let value_commit_v = FixedPointShort::from_inner(chip.clone(), value_commit_v); // [0]B should return (0,0) since it uses complete addition diff --git a/src/constants.rs b/src/constants.rs index 803ceeb28..c94630a8b 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -16,7 +16,7 @@ pub mod value_commit_v; pub mod load; pub mod util; -pub use load::{OrchardFixedBase, OrchardFixedBaseShort, OrchardFixedBases}; +pub use load::{OrchardFixedBase, OrchardFixedBasesFull, ValueCommitV}; /// $\ell^\mathsf{Orchard}_\mathsf{base}$ pub(crate) const L_ORCHARD_BASE: usize = 255; diff --git a/src/constants/load.rs b/src/constants/load.rs index 663c96d58..6f50724d8 100644 --- a/src/constants/load.rs +++ b/src/constants/load.rs @@ -5,40 +5,11 @@ use halo2::arithmetic::{CurveAffine, FieldExt}; use std::marker::PhantomData; #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum OrchardFixedBases { +pub enum OrchardFixedBasesFull { CommitIvkR(PhantomData), NoteCommitR(PhantomData), NullifierK(PhantomData), ValueCommitR(PhantomData), - ValueCommitV(PhantomData), -} - -impl OrchardFixedBases { - pub fn generator(&self) -> C { - match self { - Self::ValueCommitV(_) => { - let base: OrchardFixedBaseShort = (*self).into(); - base.generator - } - _ => { - let base: OrchardFixedBase = (*self).into(); - base.generator - } - } - } - - pub fn u(&self) -> Vec> { - match self { - Self::ValueCommitV(_) => { - let base: OrchardFixedBaseShort = (*self).into(); - base.u_short.0.as_ref().to_vec() - } - _ => { - let base: OrchardFixedBase = (*self).into(); - base.u.0.as_ref().to_vec() - } - } - } } /// A fixed base to be used in scalar multiplication with a full-width scalar. @@ -50,30 +21,29 @@ pub struct OrchardFixedBase { pub u: U, } -impl From> for OrchardFixedBase { - fn from(base: OrchardFixedBases) -> Self { +impl From> for OrchardFixedBase { + fn from(base: OrchardFixedBasesFull) -> Self { let (generator, z, u) = match base { - OrchardFixedBases::CommitIvkR(_) => ( + OrchardFixedBasesFull::CommitIvkR(_) => ( super::commit_ivk_r::generator(), super::commit_ivk_r::Z.into(), super::commit_ivk_r::U.into(), ), - OrchardFixedBases::NoteCommitR(_) => ( + OrchardFixedBasesFull::NoteCommitR(_) => ( super::note_commit_r::generator(), super::note_commit_r::Z.into(), super::note_commit_r::U.into(), ), - OrchardFixedBases::NullifierK(_) => ( + OrchardFixedBasesFull::NullifierK(_) => ( super::nullifier_k::generator(), super::nullifier_k::Z.into(), super::nullifier_k::U.into(), ), - OrchardFixedBases::ValueCommitR(_) => ( + OrchardFixedBasesFull::ValueCommitR(_) => ( super::value_commit_r::generator(), super::value_commit_r::Z.into(), super::value_commit_r::U.into(), ), - _ => unreachable!("ValueCommitV cannot be used with full-width scalar mul."), }; Self { @@ -87,27 +57,21 @@ impl From> for OrchardFixedBase { /// A fixed base to be used in scalar multiplication with a short signed exponent. #[derive(Clone, Debug, Eq, PartialEq)] -pub struct OrchardFixedBaseShort { +pub struct ValueCommitV { pub generator: C, pub lagrange_coeffs_short: LagrangeCoeffsShort, pub z_short: ZShort, pub u_short: UShort, } -impl From> for OrchardFixedBaseShort { - fn from(base: OrchardFixedBases) -> Self { - match base { - OrchardFixedBases::ValueCommitV(_) => { - let generator = super::value_commit_v::generator(); - Self { - generator, - lagrange_coeffs_short: compute_lagrange_coeffs(generator, NUM_WINDOWS_SHORT) - .into(), - z_short: super::value_commit_v::Z_SHORT.into(), - u_short: super::value_commit_v::U_SHORT.into(), - } - } - _ => unreachable!("Only ValueCommitV can be used with short signed scalar mul."), +impl ValueCommitV { + pub fn get() -> Self { + let generator = super::value_commit_v::generator(); + Self { + generator, + lagrange_coeffs_short: compute_lagrange_coeffs(generator, NUM_WINDOWS_SHORT).into(), + z_short: super::value_commit_v::Z_SHORT.into(), + u_short: super::value_commit_v::U_SHORT.into(), } } } From 218efa200e6e7b2c98de94de23f19d7596c533c0 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 31 May 2021 17:00:40 +0800 Subject: [PATCH 43/49] mul_fixed: Use const generics to differentiate full-width vs short Configs --- src/circuit/gadget/ecc/chip.rs | 20 +++++-- src/circuit/gadget/ecc/chip/mul_fixed.rs | 33 +----------- .../gadget/ecc/chip/mul_fixed/full_width.rs | 30 +++++------ .../gadget/ecc/chip/mul_fixed/short.rs | 52 ++++++++++--------- 4 files changed, 58 insertions(+), 77 deletions(-) diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 571c675d1..40700c88c 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -193,12 +193,20 @@ impl EccChip { add_config.create_gate(meta); } - // Create fixed-base scalar mul gates + // Create fixed-base scalar mul gates that are used in both full-width + // and short multiplication. { let mul_fixed_config: mul_fixed::Config = (&config).into(); mul_fixed_config.create_gate(meta); } + // Create gates that are only used in short fixed-base scalar mul. + { + let short_config: mul_fixed::short::Config = + (&config).into(); + short_config.create_gate(meta); + } + // Create variable-base scalar mul gates { let mul_config: mul::Config = (&config).into(); @@ -343,10 +351,11 @@ impl EccInstructions for EccChip { scalar: &Self::ScalarFixed, base: &Self::FixedPoints, ) -> Result { - let config: mul_fixed::Config = self.config().into(); + let config: mul_fixed::full_width::Config = + self.config().into(); layouter.assign_region( || format!("Multiply {:?}", base), - |mut region| config.assign_region_full(scalar, *base, 0, &mut region), + |mut region| config.assign_region(scalar, *base, 0, &mut region), ) } @@ -356,10 +365,11 @@ impl EccInstructions for EccChip { scalar: &Self::ScalarFixedShort, base: &Self::FixedPointsShort, ) -> Result { - let config: mul_fixed::Config = self.config().into(); + let config: mul_fixed::short::Config = + self.config().into(); layouter.assign_region( || format!("Multiply {:?}", base), - |mut region| config.assign_region_short(scalar, base, 0, &mut region), + |mut region| config.assign_region(scalar, 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 c8ba8f540..e010e4b9b 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -63,10 +63,8 @@ impl OrchardFixedBases { #[derive(Clone, Debug)] pub struct Config { - // Selector used for full-width fixed-base scalar mul. + // Selector used in both short and full-width fixed-base scalar mul. q_mul_fixed: Selector, - // Selector used for fixed-base scalar mul with short signed exponent. - q_mul_fixed_short: Selector, // The fixed Lagrange interpolation coefficients for `x_p`. lagrange_coeffs: [Column; constants::H], @@ -96,7 +94,6 @@ impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { let config = Self { q_mul_fixed: ecc_config.q_mul_fixed, - q_mul_fixed_short: ecc_config.q_mul_fixed_short, lagrange_coeffs: ecc_config.lagrange_coeffs, fixed_z: ecc_config.fixed_z, x_p: ecc_config.advices[0], @@ -156,34 +153,6 @@ impl From<&EccConfig> for Config { impl Config { pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { - self.create_gate_inner(meta); - let short_config: short::Config = self.into(); - short_config.create_gate(meta); - } - - pub(super) fn assign_region_full( - &self, - scalar: &EccScalarFixed, - base: OrchardFixedBasesFull, - offset: usize, - region: &mut Region<'_, C::Base>, - ) -> Result, Error> { - let full_width_config: full_width::Config = self.into(); - full_width_config.assign_region(region, offset, scalar, base) - } - - pub(super) fn assign_region_short( - &self, - scalar: &EccScalarFixedShort, - base: &ValueCommitV, - offset: usize, - region: &mut Region<'_, C::Base>, - ) -> Result, Error> { - let short_config: short::Config = self.into(); - short_config.assign_region(region, offset, scalar, base) - } - - fn create_gate_inner(&self, meta: &mut ConstraintSystem) { let q_mul_fixed = meta.query_selector(self.q_mul_fixed, Rotation::cur()); let y_p = meta.query_advice(self.y_p, Rotation::cur()); 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 b33379160..d326f17d8 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -1,18 +1,16 @@ -use super::super::{EccPoint, EccScalarFixed, OrchardFixedBasesFull}; - -use crate::constants; +use super::super::{EccConfig, EccPoint, EccScalarFixed, OrchardFixedBasesFull}; use halo2::{arithmetic::CurveAffine, circuit::Region, plonk::Error}; -pub struct Config(super::Config); +pub struct Config(super::Config); -impl From<&super::Config> for Config { - fn from(config: &super::Config) -> Self { - Self(config.clone()) +impl From<&EccConfig> for Config { + fn from(config: &EccConfig) -> Self { + Self(config.into()) } } -impl std::ops::Deref for Config { +impl std::ops::Deref for Config { type Target = super::Config; fn deref(&self) -> &super::Config { @@ -20,15 +18,15 @@ impl std::ops::Deref for Config { } } -impl Config { - pub(super) fn assign_region( +impl Config { + pub fn assign_region( &self, - region: &mut Region<'_, C::Base>, - offset: usize, scalar: &EccScalarFixed, base: OrchardFixedBasesFull, + offset: usize, + region: &mut Region<'_, C::Base>, ) -> Result, Error> { - let (acc, mul_b) = (*self).assign_region_inner::<{ constants::NUM_WINDOWS }>( + let (acc, mul_b) = (*self).assign_region_inner::( region, offset, &scalar.into(), @@ -36,9 +34,9 @@ impl Config { )?; // Add to the accumulator and return the final result as `[scalar]B`. - let result = - self.add_config - .assign_region(&mul_b, &acc, offset + constants::NUM_WINDOWS, region)?; + let result = self + .add_config + .assign_region(&mul_b, &acc, offset + NUM_WINDOWS, region)?; #[cfg(test)] // Check that the correct multiple is obtained. diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index 1f5cf6c4d..12a6db362 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -1,30 +1,37 @@ -use super::super::{util, CellValue, EccPoint, EccScalarFixedShort}; -use crate::constants::{self, ValueCommitV}; +use super::super::{util, CellValue, EccConfig, EccPoint, EccScalarFixedShort}; +use crate::constants::ValueCommitV; use halo2::{ arithmetic::{CurveAffine, Field}, circuit::Region, - plonk::{ConstraintSystem, Error}, + plonk::{ConstraintSystem, Error, Selector}, poly::Rotation, }; -pub struct Config(super::Config); +pub struct Config { + // Selector used for fixed-base scalar mul with short signed exponent. + q_mul_fixed_short: Selector, + mul_fixed_config: super::Config, +} -impl From<&super::Config> for Config { - fn from(config: &super::Config) -> Self { - Self(config.clone()) +impl From<&EccConfig> for Config { + fn from(config: &EccConfig) -> Self { + Self { + q_mul_fixed_short: config.q_mul_fixed_short, + mul_fixed_config: config.into(), + } } } -impl std::ops::Deref for Config { +impl std::ops::Deref for Config { type Target = super::Config; fn deref(&self) -> &super::Config { - &self.0 + &self.mul_fixed_config } } -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(super) fn create_gate(&self, meta: &mut ConstraintSystem) { @@ -47,14 +54,14 @@ impl Config { }); } - pub(super) fn assign_region( + pub fn assign_region( &self, - region: &mut Region<'_, C::Base>, - offset: usize, scalar: &EccScalarFixedShort, base: &ValueCommitV, + offset: usize, + region: &mut Region<'_, C::Base>, ) -> Result, Error> { - let (acc, mul_b) = self.assign_region_inner::<{ constants::NUM_WINDOWS_SHORT }>( + let (acc, mul_b) = self.assign_region_inner::( region, offset, &scalar.into(), @@ -62,12 +69,9 @@ impl Config { )?; // Add to the cumulative sum to get `[magnitude]B`. - let magnitude_mul = self.add_config.assign_region( - &mul_b, - &acc, - offset + constants::NUM_WINDOWS_SHORT, - region, - )?; + let magnitude_mul = + self.add_config + .assign_region(&mul_b, &acc, offset + NUM_WINDOWS, region)?; // Increase offset by 1 after complete addition let offset = offset + 1; @@ -77,7 +81,7 @@ impl Config { region, || "sign", self.k, - offset + constants::NUM_WINDOWS_SHORT, + offset + NUM_WINDOWS, &scalar.sign, &self.perm, )?; @@ -95,20 +99,20 @@ impl Config { // Enable mul_fixed_short selector on final row self.q_mul_fixed_short - .enable(region, offset + constants::NUM_WINDOWS_SHORT)?; + .enable(region, offset + NUM_WINDOWS)?; // Assign final `x, y` to `x_p, y_p` columns and return final point let x_val = magnitude_mul.x.value; let x_var = region.assign_advice( || "x_var", self.x_p, - offset + constants::NUM_WINDOWS_SHORT, + offset + NUM_WINDOWS, || x_val.ok_or(Error::SynthesisError), )?; let y_var = region.assign_advice( || "y_var", self.y_p, - offset + constants::NUM_WINDOWS_SHORT, + offset + NUM_WINDOWS, || y_val.ok_or(Error::SynthesisError), )?; From 9303f8b67bca5ae92be6b1f98d0be500f0ccb1e9 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 31 May 2021 19:56:36 +0800 Subject: [PATCH 44/49] witness_scalar_fixed: Use const generics to differentiate full-width vs. short scalars --- src/circuit/gadget/ecc.rs | 40 ++++-- src/circuit/gadget/ecc/chip.rs | 39 +++--- src/circuit/gadget/ecc/chip/mul_fixed.rs | 22 ++-- .../gadget/ecc/chip/mul_fixed/full_width.rs | 14 +-- .../gadget/ecc/chip/mul_fixed/short.rs | 14 +-- .../gadget/ecc/chip/witness_scalar_fixed.rs | 118 ++++++------------ .../chip/witness_scalar_fixed/full_width.rs | 50 +++----- .../ecc/chip/witness_scalar_fixed/short.rs | 71 +++++------ src/constants.rs | 3 + 9 files changed, 170 insertions(+), 201 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index a34d01cc2..dfc66a88c 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -152,12 +152,18 @@ impl + Clone + Debug + Eq> ScalarVar /// 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 + Clone + Debug + Eq> { +pub struct ScalarFixed +where + EccChip: EccInstructions + Clone + Debug + Eq, +{ chip: EccChip, inner: EccChip::ScalarFixed, } -impl + Clone + Debug + Eq> ScalarFixed { +impl ScalarFixed +where + EccChip: EccInstructions + Clone + Debug + Eq, +{ /// Constructs a new ScalarFixed with the given value. pub fn new( chip: EccChip, @@ -171,13 +177,17 @@ impl + Clone + Debug + Eq> ScalarFix /// 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 + Clone + Debug + Eq> { +pub struct ScalarFixedShort +where + EccChip: EccInstructions + Clone + Debug + Eq, +{ chip: EccChip, inner: EccChip::ScalarFixedShort, } -impl + Clone + Debug + Eq> - ScalarFixedShort +impl ScalarFixedShort +where + EccChip: EccInstructions + Clone + Debug + Eq, { /// Constructs a new ScalarFixedShort with the given value. /// @@ -296,12 +306,18 @@ impl + Clone + Debug + Eq> X + Clone + Debug + Eq> { +pub struct FixedPoint +where + EccChip: EccInstructions + Clone + Debug + Eq, +{ chip: EccChip, inner: EccChip::FixedPoints, } -impl + Clone + Debug + Eq> FixedPoint { +impl FixedPoint +where + EccChip: EccInstructions + Clone + Debug + Eq, +{ /// Returns `[by] self`. pub fn mul( &self, @@ -326,12 +342,18 @@ impl + Clone + Debug + Eq> FixedPoin /// A constant elliptic curve point over the given curve, used in scalar multiplication /// with a short signed exponent #[derive(Clone, Debug)] -pub struct FixedPointShort + Clone + Debug + Eq> { +pub struct FixedPointShort +where + EccChip: EccInstructions + Clone + Debug + Eq, +{ chip: EccChip, inner: EccChip::FixedPointsShort, } -impl + Clone + Debug + Eq> FixedPointShort { +impl FixedPointShort +where + EccChip: EccInstructions + Clone + Debug + Eq, +{ /// Returns `[by] self`. pub fn mul( &self, diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 40700c88c..1bc282b07 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -1,5 +1,6 @@ use super::EccInstructions; use crate::constants::{self, OrchardFixedBasesFull, ValueCommitV}; +use arrayvec::ArrayVec; use ff::Field; use halo2::{ arithmetic::CurveAffine, @@ -175,10 +176,17 @@ impl EccChip { config.create_gate::(meta); } - // Create witness scalar_fixed gates (both full-width and short) + // Create witness scalar_fixed gates that apply to both full-width and + // short scalars { - let config: witness_scalar_fixed::Config = (&config).into(); - config.create_gate::(meta); + let config: witness_scalar_fixed::Config = (&config).into(); + config.create_gate(meta); + } + + // Create witness scalar_fixed gates that only apply to short scalars + { + let config: witness_scalar_fixed::short::Config = (&config).into(); + config.create_gate(meta); } // Create incomplete point addition gate @@ -196,7 +204,8 @@ impl EccChip { // Create fixed-base scalar mul gates that are used in both full-width // and short multiplication. { - let mul_fixed_config: mul_fixed::Config = (&config).into(); + let mul_fixed_config: mul_fixed::Config = + (&config).into(); mul_fixed_config.create_gate(meta); } @@ -217,25 +226,27 @@ impl EccChip { } } -/// A full-width scalar used for variable-base scalar multiplication. +/// 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`. +/// where `scalar = k_0 + k_1 * (2^3) + ... + k_n * (2^3)^n` and each `k_i` is +/// in the range [0..2^3). #[derive(Clone, Debug)] pub struct EccScalarFixed { value: Option, - k_bits: Vec>, + windows: ArrayVec, { constants::NUM_WINDOWS }>, } -/// A signed short scalar used for variable-base scalar multiplication. +/// 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`. +/// where `scalar = k_0 + k_1 * (2^3) + ... + k_n * (2^3)^n` and each `k_i` is +/// in the range [0..2^3). #[derive(Clone, Debug)] pub struct EccScalarFixedShort { magnitude: Option, sign: CellValue, - k_bits: Vec>, + windows: ArrayVec, { constants::NUM_WINDOWS_SHORT }>, } impl EccInstructions for EccChip { @@ -271,10 +282,10 @@ impl EccInstructions for EccChip { layouter: &mut impl Layouter, value: Option, ) -> Result { - let config: witness_scalar_fixed::Config = self.config().into(); + 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_full(value, 0, &mut region), + |mut region| config.assign_region(value, 0, &mut region), ) } @@ -283,10 +294,10 @@ impl EccInstructions for EccChip { layouter: &mut impl Layouter, value: Option, ) -> Result { - let config: witness_scalar_fixed::Config = self.config().into(); + let config: witness_scalar_fixed::short::Config = self.config().into(); layouter.assign_region( || "witness scalar for fixed-base mul", - |mut region| config.assign_region_short(value, 0, &mut region), + |mut region| config.assign_region(value, 0, &mut region), ) } diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index e010e4b9b..a23e35257 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -62,7 +62,7 @@ impl OrchardFixedBases { } #[derive(Clone, Debug)] -pub struct Config { +pub struct Config { // Selector used in both short and full-width fixed-base scalar mul. q_mul_fixed: Selector, @@ -90,7 +90,7 @@ pub struct Config { _marker: PhantomData, } -impl From<&EccConfig> for Config { +impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { let config = Self { q_mul_fixed: ecc_config.q_mul_fixed, @@ -151,7 +151,7 @@ impl From<&EccConfig> for Config { } } -impl Config { +impl Config { pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { let q_mul_fixed = meta.query_selector(self.q_mul_fixed, Rotation::cur()); let y_p = meta.query_advice(self.y_p, Rotation::cur()); @@ -189,7 +189,7 @@ impl Config { } #[allow(clippy::type_complexity)] - fn assign_region_inner( + fn assign_region_inner( &self, region: &mut Region<'_, C::Base>, offset: usize, @@ -197,7 +197,7 @@ impl Config { base: OrchardFixedBases, ) -> Result<(EccPoint, EccPoint), Error> { // Assign fixed columns for given fixed base - self.assign_fixed_constants::(region, offset, base)?; + self.assign_fixed_constants(region, offset, base)?; // Copy the scalar decomposition self.copy_scalar(region, offset, scalar)?; @@ -209,12 +209,12 @@ impl Config { let acc = self.add_incomplete(region, offset, acc, base, scalar)?; // Process most significant window using complete addition - let mul_b = self.process_msb::(region, offset, base, scalar)?; + let mul_b = self.process_msb(region, offset, base, scalar)?; Ok((acc, mul_b)) } - fn assign_fixed_constants( + fn assign_fixed_constants( &self, region: &mut Region<'_, C::Base>, offset: usize, @@ -222,6 +222,7 @@ impl Config { ) -> Result<(), Error> { let (lagrange_coeffs, z) = match base { OrchardFixedBases::ValueCommitV => { + assert_eq!(NUM_WINDOWS, constants::NUM_WINDOWS_SHORT); let base = ValueCommitV::::get(); ( base.lagrange_coeffs_short.0.as_ref().to_vec(), @@ -229,6 +230,7 @@ impl Config { ) } OrchardFixedBases::Full(base) => { + assert_eq!(NUM_WINDOWS, constants::NUM_WINDOWS); let base: OrchardFixedBase = base.into(); ( base.lagrange_coeffs.0.as_ref().to_vec(), @@ -385,7 +387,7 @@ impl Config { Ok(acc) } - fn process_msb( + fn process_msb( &self, region: &mut Region<'_, C::Base>, offset: usize, @@ -449,8 +451,8 @@ impl From<&EccScalarFixedShort> for ScalarFixed { impl ScalarFixed { fn k_bits(&self) -> &[CellValue] { match self { - ScalarFixed::FullWidth(scalar) => &scalar.k_bits, - ScalarFixed::Short(scalar) => &scalar.k_bits, + ScalarFixed::FullWidth(scalar) => &scalar.windows, + ScalarFixed::Short(scalar) => &scalar.windows, } } 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 d326f17d8..a75d2417e 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -2,7 +2,7 @@ use super::super::{EccConfig, EccPoint, EccScalarFixed, OrchardFixedBasesFull}; use halo2::{arithmetic::CurveAffine, circuit::Region, plonk::Error}; -pub struct Config(super::Config); +pub struct Config(super::Config); impl From<&EccConfig> for Config { fn from(config: &EccConfig) -> Self { @@ -11,9 +11,9 @@ impl From<&EccConfig> for Config std::ops::Deref for Config { - type Target = super::Config; + type Target = super::Config; - fn deref(&self) -> &super::Config { + fn deref(&self) -> &super::Config { &self.0 } } @@ -26,12 +26,8 @@ impl Config { offset: usize, region: &mut Region<'_, C::Base>, ) -> Result, Error> { - let (acc, mul_b) = (*self).assign_region_inner::( - region, - offset, - &scalar.into(), - base.into(), - )?; + let (acc, mul_b) = + (*self).assign_region_inner(region, offset, &scalar.into(), base.into())?; // Add to the accumulator and return the final result as `[scalar]B`. let result = self diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index 12a6db362..b14a651ad 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -11,7 +11,7 @@ use halo2::{ pub struct Config { // Selector used for fixed-base scalar mul with short signed exponent. q_mul_fixed_short: Selector, - mul_fixed_config: super::Config, + mul_fixed_config: super::Config, } impl From<&EccConfig> for Config { @@ -24,9 +24,9 @@ impl From<&EccConfig> for Config std::ops::Deref for Config { - type Target = super::Config; + type Target = super::Config; - fn deref(&self) -> &super::Config { + fn deref(&self) -> &super::Config { &self.mul_fixed_config } } @@ -61,12 +61,8 @@ impl Config { offset: usize, region: &mut Region<'_, C::Base>, ) -> Result, Error> { - let (acc, mul_b) = self.assign_region_inner::( - region, - offset, - &scalar.into(), - base.clone().into(), - )?; + let (acc, mul_b) = + self.assign_region_inner(region, offset, &scalar.into(), base.clone().into())?; // Add to the cumulative sum to get `[magnitude]B`. let magnitude_mul = diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs index 9ade51a4f..a0e2334fa 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs @@ -1,133 +1,95 @@ -use super::{CellValue, EccConfig, EccScalarFixed, EccScalarFixedShort}; +use super::{CellValue, EccConfig}; use crate::constants::{self, util}; +use arrayvec::ArrayVec; +use ff::Field; use halo2::{ arithmetic::{CurveAffine, FieldExt}, circuit::Region, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, poly::Rotation, }; +use std::marker::PhantomData; -mod full_width; -mod short; +pub mod full_width; +pub mod short; -pub(super) struct Config { +pub struct Config { q_scalar_fixed: Selector, - q_scalar_fixed_short: Selector, - // k-bit decomposition of scalar. - k_s: Column, + // Decomposition of scalar into `k`-bit windows. + window: Column, + _marker: PhantomData, } -impl From<&EccConfig> for Config { +impl From<&EccConfig> for Config { fn from(ecc_config: &EccConfig) -> Self { Self { q_scalar_fixed: ecc_config.q_scalar_fixed, - q_scalar_fixed_short: ecc_config.q_scalar_fixed_short, - k_s: ecc_config.advices[0], + window: ecc_config.advices[0], + _marker: PhantomData, } } } -impl Config { - pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { - // Range check gate applies to both full-width and short scalars - self.range_check_gate(meta); - - // Gate for short scalar - let short_config: short::Config = self.into(); - short_config.sign_check_gate(meta); - } - - pub(super) fn assign_region_full( - &self, - value: Option, - offset: usize, - region: &mut Region<'_, C::Base>, - ) -> Result, Error> { - let full_width_config: full_width::Config = self.into(); - full_width_config.assign_region(value, offset, region) - } - - pub(super) fn assign_region_short( - &self, - value: Option, - offset: usize, - region: &mut Region<'_, C::Base>, - ) -> Result, Error> { - let short_config: short::Config = self.into(); - short_config.assign_region(value, offset, region) - } - - fn range_check_gate(&self, meta: &mut ConstraintSystem) { +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, Rotation::cur()); - let k = meta.query_advice(self.k_s, Rotation::cur()); + let window = meta.query_advice(self.window, Rotation::cur()); - let range_check = (0..constants::H).fold(Expression::Constant(F::one()), |acc, i| { - acc * (k.clone() - Expression::Constant(F::from_u64(i as u64))) - }); + let range_check = (0..constants::H) + .fold(Expression::Constant(C::Base::one()), |acc, i| { + acc * (window.clone() - Expression::Constant(C::Base::from_u64(i as u64))) + }); q_scalar_fixed * range_check }); } -} - -trait WitnessScalarFixed { - const SCALAR_NUM_BITS: usize; - const NUM_WINDOWS: usize; - type Scalar: Clone + std::fmt::Debug; - - fn q_scalar_fixed(&self) -> Selector; - fn k(&self) -> Column; - - fn assign_region( - &self, - value: Option, - offset: usize, - region: &mut Region<'_, C::Base>, - ) -> Result; - fn decompose_scalar_fixed( + fn decompose_scalar_fixed( &self, - value: Option, + scalar: Option, offset: usize, region: &mut Region<'_, C::Base>, - ) -> Result>, Error> { + ) -> Result, NUM_WINDOWS>, Error> { // Enable `q_scalar_fixed` selector - for idx in 0..Self::NUM_WINDOWS { - self.q_scalar_fixed().enable(region, offset + idx)?; + for idx in 0..NUM_WINDOWS { + self.q_scalar_fixed.enable(region, offset + idx)?; } - // Decompose scalar into windows - let windows: Option> = value.map(|value| { + // Decompose scalar into `k-bit` windows + let scalar_windows: Option> = scalar.map(|scalar| { util::decompose_scalar_fixed::( - value, - Self::SCALAR_NUM_BITS, + scalar, + SCALAR_NUM_BITS, constants::FIXED_BASE_WINDOW_SIZE, ) }); // Store the scalar decomposition - let mut k_bits: Vec> = Vec::new(); + let mut windows: ArrayVec, NUM_WINDOWS> = ArrayVec::new(); - let windows: Vec> = if let Some(windows) = windows { + let scalar_windows: Vec> = if let Some(windows) = scalar_windows { + println!("windows: {:?}", windows); + assert_eq!(windows.len(), NUM_WINDOWS); windows .into_iter() .map(|window| Some(C::Base::from_u64(window as u64))) .collect() } else { - vec![None; Self::NUM_WINDOWS] + vec![None; NUM_WINDOWS] }; - for (idx, window) in windows.into_iter().enumerate() { - let k_var = region.assign_advice( + for (idx, window) in scalar_windows.into_iter().enumerate() { + let window_cell = region.assign_advice( || format!("k[{:?}]", offset + idx), - self.k(), + self.window, offset + idx, || window.ok_or(Error::SynthesisError), )?; - k_bits.push(CellValue::new(k_var, window)); + windows.push(CellValue::new(window_cell, window)); } - Ok(k_bits) + 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 index fa57f344d..984e949e5 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/full_width.rs @@ -1,49 +1,33 @@ -use super::super::EccScalarFixed; -use ff::PrimeField; -use halo2::{ - arithmetic::CurveAffine, - circuit::Region, - plonk::{Advice, Column, Error, Selector}, -}; -use std::marker::PhantomData; +use super::super::{EccConfig, EccScalarFixed}; +use crate::constants::{L_ORCHARD_SCALAR, NUM_WINDOWS}; +use halo2::{arithmetic::CurveAffine, circuit::Region, plonk::Error}; -pub(super) struct Config { - pub q_scalar_fixed: Selector, - // k-bit decomposition of scalar - pub k: Column, - _marker: PhantomData, -} +pub struct Config(super::Config); -impl From<&super::Config> for Config { - fn from(config: &super::Config) -> Self { - Self { - q_scalar_fixed: config.q_scalar_fixed, - k: config.k_s, - _marker: PhantomData, - } +impl From<&EccConfig> for Config { + fn from(ecc_config: &EccConfig) -> Self { + Self(ecc_config.into()) } } -impl super::WitnessScalarFixed for Config { - const SCALAR_NUM_BITS: usize = C::Scalar::NUM_BITS as usize; - const NUM_WINDOWS: usize = crate::constants::NUM_WINDOWS as usize; - type Scalar = EccScalarFixed; +impl std::ops::Deref for Config { + type Target = super::Config; - fn q_scalar_fixed(&self) -> Selector { - self.q_scalar_fixed - } - fn k(&self) -> Column { - self.k + fn deref(&self) -> &super::Config { + &self.0 } +} - fn assign_region( +impl Config { + pub fn assign_region( &self, value: Option, offset: usize, region: &mut Region<'_, C::Base>, ) -> Result, Error> { - let k_bits = self.decompose_scalar_fixed(value, offset, region)?; + let windows = + self.decompose_scalar_fixed::(value, offset, region)?; - Ok(EccScalarFixed { value, k_bits }) + 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 index a167d7ed3..042e21dec 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs @@ -1,61 +1,51 @@ -use super::super::{CellValue, EccScalarFixedShort}; -use crate::constants; +use super::super::{CellValue, EccConfig, EccScalarFixedShort}; +use crate::constants::{L_VALUE, NUM_WINDOWS_SHORT}; use halo2::{ arithmetic::{CurveAffine, Field, FieldExt}, circuit::Region, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, + plonk::{ConstraintSystem, Error, Expression, Selector}, poly::Rotation, }; -use std::marker::PhantomData; -pub(super) struct Config { - q_scalar_fixed: Selector, +pub struct Config { q_scalar_fixed_short: Selector, - // k-bit decomposition of scalar. Also used to witness the sign `s` - // in the last row. - k_s: Column, - _marker: PhantomData, + witness_scalar_fixed_config: super::Config, } -impl From<&super::Config> for Config { - fn from(config: &super::Config) -> Self { +impl From<&EccConfig> for Config { + fn from(ecc_config: &EccConfig) -> Self { Self { - q_scalar_fixed: config.q_scalar_fixed, - q_scalar_fixed_short: config.q_scalar_fixed_short, - k_s: config.k_s, - _marker: PhantomData, + q_scalar_fixed_short: ecc_config.q_scalar_fixed_short, + witness_scalar_fixed_config: ecc_config.into(), } } } +impl std::ops::Deref for Config { + type Target = super::Config; + + fn deref(&self) -> &super::Config { + &self.witness_scalar_fixed_config + } +} + impl Config { - pub(super) fn sign_check_gate(&self, meta: &mut ConstraintSystem) { - // Check that sign s \in {1, -1} + 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, Rotation::cur()); - let s = meta.query_advice(self.k_s, Rotation::cur()); + let sign = meta.query_advice(self.window, Rotation::cur()); q_scalar_fixed_short - * (s.clone() + Expression::Constant(C::Base::one())) - * (s - Expression::Constant(C::Base::one())) + * (sign.clone() + Expression::Constant(C::Base::one())) + * (sign - Expression::Constant(C::Base::one())) }); } } -impl super::WitnessScalarFixed for Config { - const SCALAR_NUM_BITS: usize = constants::L_VALUE as usize; - const NUM_WINDOWS: usize = crate::constants::NUM_WINDOWS_SHORT as usize; - type Scalar = EccScalarFixedShort; - - fn q_scalar_fixed(&self) -> Selector { - self.q_scalar_fixed - } - fn k(&self) -> Column { - self.k_s - } - - fn assign_region( +impl Config { + pub fn assign_region( &self, value: Option, offset: usize, @@ -63,7 +53,7 @@ impl super::WitnessScalarFixed for Config { ) -> Result, Error> { // Enable `q_scalar_fixed_short` self.q_scalar_fixed_short - .enable(region, offset + Self::NUM_WINDOWS)?; + .enable(region, offset + NUM_WINDOWS_SHORT)?; // Compute the scalar's sign and magnitude let sign = value.map(|value| { @@ -78,8 +68,11 @@ impl super::WitnessScalarFixed for Config { let magnitude = sign.zip(value).map(|(sign, value)| sign * value); + println!("before decompose"); // Decompose magnitude into `k`-bit windows - let k_bits = self.decompose_scalar_fixed(magnitude, offset, region)?; + let windows = + self.decompose_scalar_fixed::(magnitude, offset, region)?; + println!("after decompose"); // Assign the sign and enable `q_scalar_fixed_short` let sign = sign.map(|sign| { @@ -92,15 +85,15 @@ impl super::WitnessScalarFixed for Config { }); let sign_cell = region.assign_advice( || "sign", - self.k_s, - offset + k_bits.len(), + self.window, + NUM_WINDOWS_SHORT, || sign.ok_or(Error::SynthesisError), )?; Ok(EccScalarFixedShort { magnitude, sign: CellValue::::new(sign_cell, sign), - k_bits, + windows, }) } } diff --git a/src/constants.rs b/src/constants.rs index c94630a8b..79165d688 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -21,6 +21,9 @@ pub use load::{OrchardFixedBase, OrchardFixedBasesFull, ValueCommitV}; /// $\ell^\mathsf{Orchard}_\mathsf{base}$ pub(crate) const L_ORCHARD_BASE: usize = 255; +/// $\ell^\mathsf{Orchard}_\mathsf{scalar}$ +pub(crate) const L_ORCHARD_SCALAR: usize = 255; + /// $\ell_\mathsf{value}$ pub(crate) const L_VALUE: usize = 64; From aeee55a2dc17ebfd37233fa1ef3b87bbb3b2a77a Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Mon, 31 May 2021 20:04:51 +0800 Subject: [PATCH 45/49] Documentation + variable naming cleanups --- src/circuit/gadget/ecc.rs | 2 +- src/circuit/gadget/ecc/chip/add.rs | 56 +++++++++--------- src/circuit/gadget/ecc/chip/mul.rs | 46 ++++++++------- src/circuit/gadget/ecc/chip/mul_fixed.rs | 57 ++++++++++--------- .../gadget/ecc/chip/mul_fixed/short.rs | 10 ++-- 5 files changed, 86 insertions(+), 85 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index dfc66a88c..cab8afa2f 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -96,7 +96,7 @@ pub trait EccInstructions: Chip { ) -> Result; /// Performs variable-base scalar multiplication, returning `[scalar] base`. - /// Multiplication of the identity [a] 𝒪 returns an error. + /// Multiplication of the identity `[scalar] 𝒪 ` returns an error. fn mul( &self, layouter: &mut impl Layouter, diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 74483de15..2a592f1bc 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -53,17 +53,18 @@ impl From<&EccConfig> for Config { impl Config { pub(crate) fn advice_columns(&self) -> HashSet> { - let mut advice_columns = HashSet::new(); - advice_columns.insert(self.x_p); - advice_columns.insert(self.y_p); - advice_columns.insert(self.x_qr); - advice_columns.insert(self.y_qr); - advice_columns.insert(self.lambda); - advice_columns.insert(self.alpha); - advice_columns.insert(self.beta); - advice_columns.insert(self.gamma); - advice_columns.insert(self.delta); - advice_columns + core::array::IntoIter::new([ + self.x_p, + self.y_p, + self.x_qr, + self.y_qr, + self.lambda, + self.alpha, + self.beta, + self.gamma, + self.delta, + ]) + .collect() } pub(crate) fn create_gate(&self, meta: &mut ConstraintSystem) { @@ -102,7 +103,6 @@ impl Config { // Handle cases in incomplete addition { - // (x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p))=0 meta.create_gate( "(x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p))=0", |_| { @@ -116,30 +116,27 @@ impl Config { }, ); - // (1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2) = 0 meta.create_gate("(1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2) = 0", |_| { let three_x_p_sq = three * x_p.clone() * x_p.clone(); // 3x_p^2 let two_y_p = two.clone() * y_p.clone(); // 2y_p - let derivative = two_y_p * lambda.clone() - three_x_p_sq; // (2y_p ⋅λ - 3x_p^2) + let tangent_line = two_y_p * lambda.clone() - three_x_p_sq; // (2y_p ⋅λ - 3x_p^2) // q_add ⋅(1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2) - q_add.clone() * (one.clone() - if_alpha.clone()) * derivative + q_add.clone() * (one.clone() - if_alpha.clone()) * tangent_line }); - // x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r) = 0 meta.create_gate( "x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r) = 0", |_| { let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q - x_p) - let incomplete = + let secant_line = lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); // (λ^2 - x_p - x_q - x_r) // q_add ⋅ x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r) - q_add.clone() * x_p.clone() * x_q.clone() * x_q_minus_x_p * incomplete + q_add.clone() * x_p.clone() * x_q.clone() * x_q_minus_x_p * secant_line }, ); - // x_p⋅x_q⋅(x_q - x_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0 meta.create_gate( "x_p⋅x_q⋅(x_q - x_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0", |_| { @@ -155,17 +152,18 @@ impl Config { }, ); - // x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) = 0 - meta.create_gate("y_r check", |_| { - let y_q_plus_y_p = y_q.clone() + y_p.clone(); // (y_q + y_p) - let incomplete = - lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); // (λ^2 - x_p - x_q - x_r) + meta.create_gate( + "x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) = 0", + |_| { + let y_q_plus_y_p = y_q.clone() + y_p.clone(); // (y_q + y_p) + let incomplete = + lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); // (λ^2 - x_p - x_q - x_r) - // q_add ⋅ x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) - q_add.clone() * x_p.clone() * x_q.clone() * y_q_plus_y_p * incomplete - }); + // q_add ⋅ x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) + q_add.clone() * x_p.clone() * x_q.clone() * y_q_plus_y_p * incomplete + }, + ); - // x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0 meta.create_gate( "x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0", |_| { @@ -197,12 +195,10 @@ impl Config { q_add.clone() * (one.clone() - if_gamma) * (y_r.clone() - y_p.clone()) }); - // ((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * x_r meta.create_gate("((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * x_r", |_| { q_add.clone() * (one.clone() - if_alpha.clone() - if_delta.clone()) * x_r.clone() }); - // ((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * y_r meta.create_gate("((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * y_r", |_| { q_add * (one - if_alpha - if_delta) * y_r }); diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 425999a6b..54c33267e 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -112,7 +112,7 @@ impl Config { let offset = offset + 1; // Decompose the scalar bitwise (big-endian bit order). - let k_bits = decompose_for_scalar_mul::(scalar.value); + let bits = decompose_for_scalar_mul::(scalar.value); // Initialize the running sum for scalar decomposition to zero let z_val = C::Base::zero(); @@ -124,20 +124,24 @@ impl Config { let offset = offset + 1; // Double-and-add (incomplete addition) for the `hi` half of the scalar decomposition - let k_incomplete_hi = &k_bits[incomplete_hi_range::()]; + let bits_incomplete_hi = &bits[incomplete_hi_range::()]; let (x, y_a, z) = self.hi_config.double_and_add( region, offset, &base, - k_incomplete_hi, + bits_incomplete_hi, (X(acc.x.clone()), Y(acc.y.value), Z(z)), )?; // Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition - let k_incomplete_lo = &k_bits[incomplete_lo_range::()]; - let (x, y_a, z) = - self.lo_config - .double_and_add(region, offset, &base, k_incomplete_lo, (x, y_a, z))?; + let bits_incomplete_lo = &bits[incomplete_lo_range::()]; + let (x, y_a, z) = self.lo_config.double_and_add( + region, + offset, + &base, + bits_incomplete_lo, + (x, y_a, z), + )?; // Move from incomplete addition to complete addition let offset = offset + incomplete_lo_len::() + 1; @@ -176,15 +180,15 @@ impl Config { let complete_config: complete::Config = self.into(); // Bits used in complete addition. k_{3} to k_{1} inclusive // The LSB k_{0} is handled separately. - let k_complete = &k_bits[complete_range::()]; - complete_config.assign_region(region, offset, k_complete, base, acc, z.value)? + let bits_complete = &bits[complete_range::()]; + complete_config.assign_region(region, offset, bits_complete, base, acc, z.value)? }; let offset = offset + complete_len::() * 2; // Process the least significant bit - let k_0 = k_bits[C::Scalar::NUM_BITS as usize - 1]; - let result = self.process_lsb(region, offset, scalar, base, acc, k_0, z_val)?; + let lsb = bits[C::Scalar::NUM_BITS as usize - 1]; + let result = self.process_lsb(region, offset, scalar, base, acc, lsb, z_val)?; #[cfg(test)] // Check that the correct multiple is obtained. @@ -212,13 +216,13 @@ impl Config { scalar: &CellValue, base: &EccPoint, acc: EccPoint, - k_0: Option, + lsb: Option, mut z_val: Option, ) -> Result, Error> { // Assign the final `z` value. z_val = z_val - .zip(k_0) - .map(|(z_val, k_0)| C::Base::from_u64(2) * z_val + C::Base::from_u64(k_0 as u64)); + .zip(lsb) + .map(|(z_val, lsb)| C::Base::from_u64(2) * z_val + C::Base::from_u64(lsb as u64)); region.assign_advice( || "final z", self.z_complete, @@ -246,9 +250,9 @@ impl Config { )?; self.q_mul_decompose_var.enable(region, offset)?; - // If `k_0` is 0, return `Acc + (-P)`. If `k_0` is 1, simply return `Acc + 0`. - let x_p = if let Some(k_0) = k_0 { - if !k_0 { + // If `lsb` is 0, return `Acc + (-P)`. If `lsb` is 1, simply return `Acc + 0`. + let x_p = if let Some(lsb) = lsb { + if !lsb { base.x.value } else { Some(C::Base::zero()) @@ -256,8 +260,8 @@ impl Config { } else { None }; - let y_p = if let Some(k_0) = k_0 { - if !k_0 { + let y_p = if let Some(lsb) = lsb { + if !lsb { base.y.value.map(|y_p| -y_p) } else { Some(C::Base::zero()) @@ -343,8 +347,8 @@ fn decompose_for_scalar_mul(scalar: Option) -> Vec = k .to_le_bits() .into_iter() diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index a23e35257..c94ecfb60 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -70,9 +70,9 @@ pub struct Config { lagrange_coeffs: [Column; constants::H], // The fixed `z` for each window such that `y + z = u^2`. fixed_z: Column, - // k-bit decomposition of an `n-1`-bit scalar: + // Decomposition of an `n-1`-bit scalar into `k`-bit windows: // a = a_0 + 2^k(a_1) + 2^{2k}(a_2) + ... + 2^{(n-1)k}(a_{n-1}) - k: Column, + window: Column, // x-coordinate of the multiple of the fixed base at the current window. x_p: Column, // y-coordinate of the multiple of the fixed base at the current window. @@ -98,7 +98,7 @@ impl From<&EccConfig> for Config From<&EccConfig> for Config Config { // Check interpolation of x-coordinate meta.create_gate("fixed-base scalar mul (x)", |meta| { - let k = meta.query_advice(self.k, Rotation::cur()); + let window = meta.query_advice(self.window, Rotation::cur()); let x_p = meta.query_advice(self.x_p, Rotation::cur()); - let k_pow: Vec> = (0..constants::H) + let window_pow: Vec> = (0..constants::H) .map(|pow| { (0..pow).fold(Expression::Constant(C::Base::one()), |acc, _| { - acc * k.clone() + acc * window.clone() }) }) .collect(); - let interpolated_x = k_pow.iter().zip(self.lagrange_coeffs.iter()).fold( + let interpolated_x = window_pow.iter().zip(self.lagrange_coeffs.iter()).fold( Expression::Constant(C::Base::zero()), - |acc, (k_pow, coeff)| { - acc + (k_pow.clone() * meta.query_fixed(*coeff, Rotation::cur())) + |acc, (window_pow, coeff)| { + acc + (window_pow.clone() * meta.query_fixed(*coeff, Rotation::cur())) }, ); @@ -277,14 +277,14 @@ impl Config { offset: usize, scalar: &ScalarFixed, ) -> Result<(), Error> { - // Copy the scalar decomposition - for (window, k) in scalar.k_bits().iter().enumerate() { + // Copy the scalar decomposition (`k`-bit windows) + for (window_idx, window) in scalar.windows().iter().enumerate() { util::assign_and_constrain( region, || format!("k[{:?}]", window), - self.k, - window + offset, - k, + self.window, + window_idx + offset, + window, &self.perm, )?; } @@ -300,7 +300,7 @@ impl Config { scalar: &ScalarFixed, ) -> Result, Error> { // Witness `m0` in `x_p`, `y_p` cells on row 0 - let k0 = scalar.k_field()[0]; + let k0 = scalar.windows_field()[0]; let m0 = k0.map(|k0| base.generator() * (k0 + C::Scalar::from_u64(2))); let m0 = self.witness_point_config.assign_region( m0.map(|point| point.to_affine()), @@ -310,7 +310,7 @@ impl Config { // Assign u = (y_p + z_w).sqrt() for `m0` { - let k0 = scalar.k_usize()[0]; + let k0 = scalar.windows_usize()[0]; let u0 = &base.u()[0]; let u0 = k0.map(|k0| u0.0[k0]); @@ -351,10 +351,10 @@ impl Config { let base_value = base.generator(); let base_u = base.u(); - let scalar_k_field = scalar.k_field(); - let scalar_k_usize = scalar.k_usize(); + let scalar_windows_field = scalar.windows_field(); + let scalar_windows_usize = scalar.windows_usize(); - for (w, k) in scalar_k_field[1..(scalar_k_field.len() - 1)] + for (w, k) in scalar_windows_field[1..(scalar_windows_field.len() - 1)] .iter() .enumerate() { @@ -371,7 +371,7 @@ impl Config { )?; // Assign u = (y_p + z_w).sqrt() - let u_val = scalar_k_usize[w].map(|k| base_u[w].0[k]); + let u_val = scalar_windows_usize[w].map(|k| base_u[w].0[k]); region.assign_advice( || "u", self.u, @@ -399,7 +399,8 @@ impl Config { // Assign u = (y_p + z_w).sqrt() for the most significant window { - let u_val = scalar.k_usize()[NUM_WINDOWS - 1].map(|k| base.u()[NUM_WINDOWS - 1].0[k]); + let u_val = + scalar.windows_usize()[NUM_WINDOWS - 1].map(|k| base.u()[NUM_WINDOWS - 1].0[k]); region.assign_advice( || "u", self.u, @@ -419,7 +420,7 @@ impl Config { }); // `scalar = [k * 8^84 - offset_acc]`, where `offset_acc = \sum_{j = 0}^{83} 2^{FIXED_BASE_WINDOW_SIZE * j + 1}`. - let scalar = scalar.k_field()[scalar.k_field().len() - 1] + let scalar = scalar.windows_field()[scalar.windows_field().len() - 1] .map(|k| k * h.pow(&[(NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_acc); let mul_b = scalar.map(|scalar| base.generator() * scalar); @@ -449,7 +450,7 @@ impl From<&EccScalarFixedShort> for ScalarFixed { } impl ScalarFixed { - fn k_bits(&self) -> &[CellValue] { + fn windows(&self) -> &[CellValue] { match self { ScalarFixed::FullWidth(scalar) => &scalar.windows, ScalarFixed::Short(scalar) => &scalar.windows, @@ -458,8 +459,8 @@ impl ScalarFixed { // The scalar decomposition was done in the base field. For computation // outside the circuit, we now convert them back into the scalar field. - fn k_field(&self) -> Vec> { - self.k_bits() + fn windows_field(&self) -> Vec> { + self.windows() .iter() .map(|bits| { bits.value @@ -471,8 +472,8 @@ impl ScalarFixed { // 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 k_usize(&self) -> Vec> { - self.k_bits() + fn windows_usize(&self) -> Vec> { + self.windows() .iter() .map(|bits| bits.value.map(|value| value.to_bytes()[0] as usize)) .collect::>() diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index b14a651ad..e4033ce31 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -47,10 +47,10 @@ impl Config { q_mul_fixed_short.clone() * (y_p.clone() - y_a.clone()) * (y_p.clone() + y_a.clone()) }); - // Check that s * y_p = y_a + // Check that sign * y_p = y_a meta.create_gate("check negation", |meta| { - let s = meta.query_advice(self.k, Rotation::cur()); - q_mul_fixed_short * (s * y_p - y_a) + let sign = meta.query_advice(self.window, Rotation::cur()); + q_mul_fixed_short * (sign * y_p - y_a) }); } @@ -72,11 +72,11 @@ impl Config { // Increase offset by 1 after complete addition let offset = offset + 1; - // Assign sign to `bits` column + // Assign sign to `window` column let sign = util::assign_and_constrain( region, || "sign", - self.k, + self.window, offset + NUM_WINDOWS, &scalar.sign, &self.perm, From 65ac81640c8af026ada5b12af3cd56173398d827 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 2 Jun 2021 09:16:55 +0800 Subject: [PATCH 46/49] Update to use multi-polynomial create_gate() API --- src/circuit/gadget/ecc.rs | 2 +- src/circuit/gadget/ecc/chip/add.rs | 255 +++++++++--------- src/circuit/gadget/ecc/chip/add_incomplete.rs | 47 ++-- src/circuit/gadget/ecc/chip/mul.rs | 11 +- src/circuit/gadget/ecc/chip/mul/complete.rs | 36 +-- src/circuit/gadget/ecc/chip/mul/incomplete.rs | 127 ++++----- src/circuit/gadget/ecc/chip/mul_fixed.rs | 26 +- .../gadget/ecc/chip/mul_fixed/full_width.rs | 4 +- .../gadget/ecc/chip/mul_fixed/short.rs | 33 ++- src/circuit/gadget/ecc/chip/witness_point.rs | 5 +- .../gadget/ecc/chip/witness_scalar_fixed.rs | 3 +- .../ecc/chip/witness_scalar_fixed/short.rs | 10 +- 12 files changed, 279 insertions(+), 280 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index cab8afa2f..986a9621d 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -484,7 +484,7 @@ mod tests { // Test signed short fixed-base scalar multiplication { super::chip::mul_fixed::short::tests::test_mul_fixed_short( - chip.clone(), + chip, layouter.namespace(|| "signed short fixed-base scalar mul"), )?; } diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 2a592f1bc..11f38d5e2 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -68,141 +68,131 @@ impl Config { } pub(crate) fn create_gate(&self, meta: &mut ConstraintSystem) { - let q_add = meta.query_selector(self.q_add, Rotation::cur()); - let x_p = meta.query_advice(self.x_p, Rotation::cur()); - let y_p = meta.query_advice(self.y_p, Rotation::cur()); - let x_q = meta.query_advice(self.x_qr, Rotation::cur()); - let y_q = meta.query_advice(self.y_qr, Rotation::cur()); - let x_r = meta.query_advice(self.x_qr, Rotation::next()); - let y_r = meta.query_advice(self.y_qr, Rotation::next()); - let lambda = meta.query_advice(self.lambda, Rotation::cur()); - - // α = inv0(x_q - x_p) - let alpha = meta.query_advice(self.alpha, Rotation::cur()); - // β = inv0(x_p) - let beta = meta.query_advice(self.beta, Rotation::cur()); - // γ = inv0(x_q) - let gamma = meta.query_advice(self.gamma, Rotation::cur()); - // δ = inv0(y_p + y_q) if x_q = x_p, 0 otherwise - let delta = meta.query_advice(self.delta, Rotation::cur()); - - // Useful composite expressions - // α ⋅(x_q - x_p) - let if_alpha = (x_q.clone() - x_p.clone()) * alpha; - // β ⋅ x_p - let if_beta = x_p.clone() * beta; - // γ ⋅ x_q - let if_gamma = x_q.clone() * gamma; - // δ ⋅(y_p + y_q) - let if_delta = (y_q.clone() + y_p.clone()) * delta; - - // Useful constants - let one = Expression::Constant(F::one()); - let two = Expression::Constant(F::from_u64(2)); - let three = Expression::Constant(F::from_u64(3)); - - // Handle cases in incomplete addition - { - meta.create_gate( - "(x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p))=0", - |_| { - let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q − x_p) - - let y_q_minus_y_p = y_q.clone() - y_p.clone(); // (y_q − y_p) - let incomplete = x_q_minus_x_p.clone() * lambda.clone() - y_q_minus_y_p; // (x_q − x_p)⋅λ − (y_q−y_p) - - // q_add ⋅(x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p)) - q_add.clone() * x_q_minus_x_p * incomplete - }, - ); - - meta.create_gate("(1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2) = 0", |_| { + meta.create_gate("complete addition gates", |meta| { + let q_add = meta.query_selector(self.q_add, Rotation::cur()); + let x_p = meta.query_advice(self.x_p, Rotation::cur()); + let y_p = meta.query_advice(self.y_p, Rotation::cur()); + let x_q = meta.query_advice(self.x_qr, Rotation::cur()); + let y_q = meta.query_advice(self.y_qr, Rotation::cur()); + let x_r = meta.query_advice(self.x_qr, Rotation::next()); + let y_r = meta.query_advice(self.y_qr, Rotation::next()); + let lambda = meta.query_advice(self.lambda, Rotation::cur()); + + // α = inv0(x_q - x_p) + let alpha = meta.query_advice(self.alpha, Rotation::cur()); + // β = inv0(x_p) + let beta = meta.query_advice(self.beta, Rotation::cur()); + // γ = inv0(x_q) + let gamma = meta.query_advice(self.gamma, Rotation::cur()); + // δ = inv0(y_p + y_q) if x_q = x_p, 0 otherwise + let delta = meta.query_advice(self.delta, Rotation::cur()); + + // Useful composite expressions + // α ⋅(x_q - x_p) + let if_alpha = (x_q.clone() - x_p.clone()) * alpha; + // β ⋅ x_p + let if_beta = x_p.clone() * beta; + // γ ⋅ x_q + let if_gamma = x_q.clone() * gamma; + // δ ⋅(y_p + y_q) + let if_delta = (y_q.clone() + y_p.clone()) * delta; + + // Useful constants + let one = Expression::Constant(F::one()); + let two = Expression::Constant(F::from_u64(2)); + let three = Expression::Constant(F::from_u64(3)); + + // (x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p)) = 0 + let poly1 = { + let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q − x_p) + + let y_q_minus_y_p = y_q.clone() - y_p.clone(); // (y_q − y_p) + let incomplete = x_q_minus_x_p.clone() * lambda.clone() - y_q_minus_y_p; // (x_q − x_p)⋅λ − (y_q−y_p) + + // q_add ⋅(x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_p)) + x_q_minus_x_p * incomplete + }; + + // (1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2) = 0 + let poly2 = { let three_x_p_sq = three * x_p.clone() * x_p.clone(); // 3x_p^2 - let two_y_p = two.clone() * y_p.clone(); // 2y_p + let two_y_p = two * y_p.clone(); // 2y_p let tangent_line = two_y_p * lambda.clone() - three_x_p_sq; // (2y_p ⋅λ - 3x_p^2) // q_add ⋅(1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2) - q_add.clone() * (one.clone() - if_alpha.clone()) * tangent_line - }); - - meta.create_gate( - "x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r) = 0", - |_| { - let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q - x_p) - let secant_line = - lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); // (λ^2 - x_p - x_q - x_r) - - // q_add ⋅ x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r) - q_add.clone() * x_p.clone() * x_q.clone() * x_q_minus_x_p * secant_line - }, - ); - - meta.create_gate( - "x_p⋅x_q⋅(x_q - x_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0", - |_| { - let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q - x_p) - let x_p_minus_x_r = x_p.clone() - x_r.clone(); // (x_p - x_r) - - // q_add ⋅ x_p⋅x_q⋅(x_q - x_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) - q_add.clone() - * x_p.clone() - * x_q.clone() - * x_q_minus_x_p - * (lambda.clone() * x_p_minus_x_r - y_p.clone() - y_r.clone()) - }, - ); - - meta.create_gate( - "x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) = 0", - |_| { - let y_q_plus_y_p = y_q.clone() + y_p.clone(); // (y_q + y_p) - let incomplete = - lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); // (λ^2 - x_p - x_q - x_r) - - // q_add ⋅ x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) - q_add.clone() * x_p.clone() * x_q.clone() * y_q_plus_y_p * incomplete - }, - ); - - meta.create_gate( - "x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0", - |_| { - let y_q_plus_y_p = y_q.clone() + y_p.clone(); // (y_q + y_p) - let x_p_minus_x_r = x_p.clone() - x_r.clone(); // (x_p - x_r) - - // q_add ⋅ x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) - q_add.clone() - * x_p.clone() - * x_q.clone() - * y_q_plus_y_p - * (lambda.clone() * x_p_minus_x_r - y_p.clone() - y_r.clone()) - }, - ); - - meta.create_gate("(1 - x_p * β) * (x_r - x_q) = 0", |_| { - q_add.clone() * (one.clone() - if_beta.clone()) * (x_r.clone() - x_q.clone()) - }); - - meta.create_gate("(1 - x_p * β) * (y_r - y_q) = 0", |_| { - q_add.clone() * (one.clone() - if_beta) * (y_r.clone() - y_q.clone()) - }); - - meta.create_gate("(1 - x_q * γ) * (x_r - x_p) = 0", |_| { - q_add.clone() * (one.clone() - if_gamma.clone()) * (x_r.clone() - x_p.clone()) - }); - - meta.create_gate("(1 - x_q * γ) * (y_r - y_p) = 0", |_| { - q_add.clone() * (one.clone() - if_gamma) * (y_r.clone() - y_p.clone()) - }); - - meta.create_gate("((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * x_r", |_| { - q_add.clone() * (one.clone() - if_alpha.clone() - if_delta.clone()) * x_r.clone() - }); - - meta.create_gate("((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * y_r", |_| { - q_add * (one - if_alpha - if_delta) * y_r - }); - } + (one.clone() - if_alpha.clone()) * tangent_line + }; + + // x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r) = 0 + let poly3 = { + let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q - x_p) + let secant_line = + lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); // (λ^2 - x_p - x_q - x_r) + + // x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r) + x_p.clone() * x_q.clone() * x_q_minus_x_p * secant_line + }; + + // x_p⋅x_q⋅(x_q - x_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0 + let poly4 = { + let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q - x_p) + let x_p_minus_x_r = x_p.clone() - x_r.clone(); // (x_p - x_r) + + // x_p⋅x_q⋅(x_q - x_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) + x_p.clone() + * x_q.clone() + * x_q_minus_x_p + * (lambda.clone() * x_p_minus_x_r - y_p.clone() - y_r.clone()) + }; + + // x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) = 0 + let poly5 = { + let y_q_plus_y_p = y_q.clone() + y_p.clone(); // (y_q + y_p) + let incomplete = + lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); // (λ^2 - x_p - x_q - x_r) + + // x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) + x_p.clone() * x_q.clone() * y_q_plus_y_p * incomplete + }; + + // x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0 + let poly6 = { + let y_q_plus_y_p = y_q.clone() + y_p.clone(); // (y_q + y_p) + let x_p_minus_x_r = x_p.clone() - x_r.clone(); // (x_p - x_r) + + // x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) + x_p.clone() + * x_q.clone() + * y_q_plus_y_p + * (lambda * x_p_minus_x_r - y_p.clone() - y_r.clone()) + }; + + // (1 - x_p * β) * (x_r - x_q) = 0 + let poly7 = (one.clone() - if_beta.clone()) * (x_r.clone() - x_q); + + // (1 - x_p * β) * (y_r - y_q) = 0 + let poly8 = (one.clone() - if_beta) * (y_r.clone() - y_q); + + // (1 - x_q * γ) * (x_r - x_p) = 0 + let poly9 = (one.clone() - if_gamma.clone()) * (x_r.clone() - x_p); + + // (1 - x_q * γ) * (y_r - y_p) = 0 + let poly10 = (one.clone() - if_gamma) * (y_r.clone() - y_p); + + // ((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * x_r + let poly11 = (one.clone() - if_alpha.clone() - if_delta.clone()) * x_r; + + // ((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * y_r + let poly12 = (one - if_alpha - if_delta) * y_r; + + [ + poly1, poly2, poly3, poly4, poly5, poly6, poly7, poly8, poly9, poly10, poly11, + poly12, + ] + .iter() + .map(|poly| q_add.clone() * poly.clone()) + .collect() + }); } pub(super) fn assign_region( @@ -406,6 +396,7 @@ pub mod tests { use crate::circuit::gadget::ecc::{EccInstructions, Point}; + #[allow(clippy::too_many_arguments)] pub fn test_add + Clone + Eq + std::fmt::Debug>( chip: EccChip, mut layouter: impl Layouter, @@ -467,7 +458,7 @@ pub mod tests { // (x, y) + ((ζ^2)x, -y) let endo_2_p_neg = (-p_val).to_curve().endo().endo(); let endo_2_p_neg = Point::new( - chip.clone(), + chip, layouter.namespace(|| "point"), Some(endo_2_p_neg.to_affine()), )?; diff --git a/src/circuit/gadget/ecc/chip/add_incomplete.rs b/src/circuit/gadget/ecc/chip/add_incomplete.rs index ab1632e44..34720a485 100644 --- a/src/circuit/gadget/ecc/chip/add_incomplete.rs +++ b/src/circuit/gadget/ecc/chip/add_incomplete.rs @@ -38,29 +38,30 @@ impl From<&EccConfig> for Config { impl Config { pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { - let q_add_incomplete = meta.query_selector(self.q_add_incomplete, Rotation::cur()); - let x_p = meta.query_advice(self.x_p, Rotation::cur()); - let y_p = meta.query_advice(self.y_p, Rotation::cur()); - let x_q = meta.query_advice(self.x_qr, Rotation::cur()); - let y_q = meta.query_advice(self.y_qr, Rotation::cur()); - let x_r = meta.query_advice(self.x_qr, Rotation::next()); - let y_r = meta.query_advice(self.y_qr, Rotation::next()); - - // (x_r + x_q + x_p)⋅(x_p − x_q)^2 − (y_p − y_q)^2 = 0 - meta.create_gate("point addition expr1", |_| { - let expr1 = (x_r.clone() + x_q.clone() + x_p.clone()) - * (x_p.clone() - x_q.clone()) - * (x_p.clone() - x_q.clone()) - - (y_p.clone() - y_q.clone()) * (y_p.clone() - y_q.clone()); - - q_add_incomplete.clone() * expr1 - }); - - // (y_r + y_q)(x_p − x_q) − (y_p − y_q)(x_q − x_r) = 0 - meta.create_gate("point addition expr2", |_| { - let expr2 = (y_r + y_q.clone()) * (x_p - x_q.clone()) - (y_p - y_q) * (x_q - x_r); - - q_add_incomplete * expr2 + meta.create_gate("incomplete addition gates", |meta| { + let q_add_incomplete = meta.query_selector(self.q_add_incomplete, Rotation::cur()); + let x_p = meta.query_advice(self.x_p, Rotation::cur()); + let y_p = meta.query_advice(self.y_p, Rotation::cur()); + let x_q = meta.query_advice(self.x_qr, Rotation::cur()); + let y_q = meta.query_advice(self.y_qr, Rotation::cur()); + let x_r = meta.query_advice(self.x_qr, Rotation::next()); + let y_r = meta.query_advice(self.y_qr, Rotation::next()); + + // (x_r + x_q + x_p)⋅(x_p − x_q)^2 − (y_p − y_q)^2 = 0 + let poly1 = { + (x_r.clone() + x_q.clone() + x_p.clone()) + * (x_p.clone() - x_q.clone()) + * (x_p.clone() - x_q.clone()) + - (y_p.clone() - y_q.clone()) * (y_p.clone() - y_q.clone()) + }; + + // (y_r + y_q)(x_p − x_q) − (y_p − y_q)(x_q − x_r) = 0 + let poly2 = (y_r + y_q.clone()) * (x_p - x_q.clone()) - (y_p - y_q) * (x_q - x_r); + + [poly1, poly2] + .iter() + .map(|poly| q_add_incomplete.clone() * poly.clone()) + .collect() }); } diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 54c33267e..33c638f9a 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -81,18 +81,19 @@ impl Config { /// Gate used to check final scalar is recovered. fn create_final_scalar_gate(&self, meta: &mut ConstraintSystem) { - let q_mul_decompose_var = meta.query_selector(self.q_mul_decompose_var, Rotation::cur()); - let scalar = meta.query_advice(self.scalar, Rotation::cur()); - let z_cur = meta.query_advice(self.z_complete, Rotation::cur()); + meta.create_gate("Decompose scalar for variable-base mul", |meta| { + let q_mul_decompose_var = + meta.query_selector(self.q_mul_decompose_var, Rotation::cur()); + let scalar = meta.query_advice(self.scalar, Rotation::cur()); + let z_cur = meta.query_advice(self.z_complete, Rotation::cur()); - meta.create_gate("Decompose scalar", |_| { // The scalar field `F_q = 2^254 + t_q`. // -((2^127)^2) = -(2^254) = t_q (mod q) let t_q = -(C::Scalar::from_u128(1u128 << 127).square()); let t_q = C::Base::from_bytes(&t_q.to_bytes()).unwrap(); // Check that `k = scalar + t_q` - q_mul_decompose_var * (scalar + Expression::Constant(t_q) - z_cur) + vec![q_mul_decompose_var * (scalar + Expression::Constant(t_q) - z_cur)] }); } diff --git a/src/circuit/gadget/ecc/chip/mul/complete.rs b/src/circuit/gadget/ecc/chip/mul/complete.rs index f588e19e6..ad1547e9e 100644 --- a/src/circuit/gadget/ecc/chip/mul/complete.rs +++ b/src/circuit/gadget/ecc/chip/mul/complete.rs @@ -1,5 +1,5 @@ use super::super::{add, util, CellValue, EccPoint}; -use super::{complete_len, complete_range}; +use super::complete_len; use ff::Field; use halo2::{ @@ -40,18 +40,21 @@ impl Config { /// addition gate (controlled by `q_mul`) already checks scalar decomposition for /// the other bits. pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { - let q_mul_complete = meta.query_selector(self.q_mul_complete, Rotation::cur()); - let z_cur = meta.query_advice(self.z_complete, Rotation::cur()); - let z_prev = meta.query_advice(self.z_complete, Rotation::prev()); - - meta.create_gate("Decompose scalar ", |_| { - // k_{i} = z_{i} - 2⋅z_{i+1} - let k = z_cur.clone() - Expression::Constant(C::Base::from_u64(2)) * z_prev; - // (k_i) ⋅ (k_i - 1) = 0 - let bool_check = k.clone() * (k + Expression::Constant(-C::Base::one())); - - q_mul_complete.clone() * bool_check - }); + meta.create_gate( + "Decompose scalar for complete bits of variable-base mul", + |meta| { + let q_mul_complete = meta.query_selector(self.q_mul_complete, Rotation::cur()); + let z_cur = meta.query_advice(self.z_complete, Rotation::cur()); + let z_prev = meta.query_advice(self.z_complete, Rotation::prev()); + + // k_{i} = z_{i} - 2⋅z_{i+1} + let k = z_cur - Expression::Constant(C::Base::from_u64(2)) * z_prev; + // (k_i) ⋅ (k_i - 1) = 0 + let bool_check = k.clone() * (k + Expression::Constant(-C::Base::one())); + + vec![q_mul_complete * bool_check] + }, + ); } #[allow(clippy::type_complexity)] @@ -69,12 +72,15 @@ impl Config { assert_eq!(bits.len(), complete_len::()); // Enable selectors for complete range - for row in complete_range::() { + for row in 0..complete_len::() { + // Each iteration uses 2 rows (two complete additions) + let row = 2 * row; + self.q_mul_complete.enable(region, row + offset)?; } // Complete addition - for (iter, k) in bits.into_iter().enumerate() { + for (iter, k) in bits.iter().enumerate() { // Each iteration uses 2 rows (two complete additions) let row = 2 * iter; diff --git a/src/circuit/gadget/ecc/chip/mul/incomplete.rs b/src/circuit/gadget/ecc/chip/mul/incomplete.rs index 30a1af488..1e56f181c 100644 --- a/src/circuit/gadget/ecc/chip/mul/incomplete.rs +++ b/src/circuit/gadget/ecc/chip/mul/incomplete.rs @@ -68,86 +68,77 @@ impl Config { // Gate for incomplete addition part of variable-base scalar multiplication. pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { - let q_mul = meta.query_selector(self.q_mul, Rotation::cur()); - let z_cur = meta.query_advice(self.z, Rotation::cur()); - let z_prev = meta.query_advice(self.z, Rotation::prev()); - let x_a_cur = meta.query_advice(self.x_a, Rotation::cur()); - let x_a_next = meta.query_advice(self.x_a, Rotation::next()); - let x_p_cur = meta.query_advice(self.x_p, Rotation::cur()); - let x_p_next = meta.query_advice(self.x_p, Rotation::next()); - let y_p_cur = meta.query_advice(self.y_p, Rotation::cur()); - let y_p_next = meta.query_advice(self.y_p, Rotation::next()); - let lambda1_cur = meta.query_advice(self.lambda1, Rotation::cur()); - let lambda2_cur = meta.query_advice(self.lambda2, Rotation::cur()); - let lambda1_next = meta.query_advice(self.lambda1, Rotation::next()); - let lambda2_next = meta.query_advice(self.lambda2, Rotation::next()); + // (k_i) ⋅ (k_i - 1) = 0 + meta.create_gate("Scalar boolean decomposition", |meta| { + let q_mul = meta.query_selector(self.q_mul, Rotation::cur()); + let z_cur = meta.query_advice(self.z, Rotation::cur()); + let z_prev = meta.query_advice(self.z, Rotation::prev()); + let x_a_cur = meta.query_advice(self.x_a, Rotation::cur()); + let x_a_next = meta.query_advice(self.x_a, Rotation::next()); + let x_p_cur = meta.query_advice(self.x_p, Rotation::cur()); + let x_p_next = meta.query_advice(self.x_p, Rotation::next()); + let y_p_cur = meta.query_advice(self.y_p, Rotation::cur()); + let y_p_next = meta.query_advice(self.y_p, Rotation::next()); + let lambda1_cur = meta.query_advice(self.lambda1, Rotation::cur()); + let lambda2_cur = meta.query_advice(self.lambda2, Rotation::cur()); + let lambda1_next = meta.query_advice(self.lambda1, Rotation::next()); + let lambda2_next = meta.query_advice(self.lambda2, Rotation::next()); - // The current bit in the scalar decomposition, k_i = z_i - 2⋅z_{i+1}. - // Recall that we assigned the cumulative variable `z_i` in descending order, - // i from n down to 0. So z_{i+1} corresponds to the `z_prev` query. - let k = z_cur - Expression::Constant(F::from_u64(2)) * z_prev; + // The current bit in the scalar decomposition, k_i = z_i - 2⋅z_{i+1}. + // Recall that we assigned the cumulative variable `z_i` in descending order, + // i from n down to 0. So z_{i+1} corresponds to the `z_prev` query. + let k = z_cur - Expression::Constant(F::from_u64(2)) * z_prev; - // (k_i) ⋅ (k_i - 1) = 0 - meta.create_gate("Scalar boolean decomposition", |_| { - let bool_check = k.clone() * (k.clone() + Expression::Constant(-F::one())); - q_mul.clone() * bool_check - }); + // y_{A,i} = (λ_{1,i} + λ_{2,i}) + // * (x_{A,i} - (λ_{1,i}^2 - x_{A,i} - x_{P,i})) / 2 + let y_a_cur = (lambda1_cur.clone() + lambda2_cur.clone()) + * (x_a_cur.clone() + - (lambda1_cur.clone() * lambda1_cur.clone() + - x_a_cur.clone() + - x_p_cur.clone())) + * F::TWO_INV; - // The base used in double-and-add remains constant. We check that its - // x- and y- coordinates are the same throughout. - meta.create_gate("x_p equality", |_| { - q_mul.clone() * (x_p_cur.clone() - x_p_next.clone()) - }); - meta.create_gate("y_p equality", |_| { - q_mul.clone() * (y_p_cur.clone() - y_p_next.clone()) - }); + // y_{A,i+1} = (λ_{1,i+1} + λ_{2,i+1}) + // * (x_{A,i+1} - (λ_{1,i+1}^2 - x_{A,i+1} - x_{P,i+1})) / 2 + let y_a_next = (lambda1_next.clone() + lambda2_next) + * (x_a_next.clone() + - (lambda1_next.clone() * lambda1_next - x_a_next.clone() - x_p_next.clone())) + * F::TWO_INV; - // y_{A,i} = (λ_{1,i} + λ_{2,i}) - // * (x_{A,i} - (λ_{1,i}^2 - x_{A,i} - x_{P,i})) / 2 - let y_a_cur = (lambda1_cur.clone() + lambda2_cur.clone()) - * (x_a_cur.clone() - - (lambda1_cur.clone() * lambda1_cur.clone() - x_a_cur.clone() - x_p_cur.clone())) - * F::TWO_INV; + // Check booleanity of decomposition. + let bool_check = k.clone() * (k.clone() + Expression::Constant(-F::one())); - // y_{A,i+1} = (λ_{1,i+1} + λ_{2,i+1}) - // * (x_{A,i+1} - (λ_{1,i+1}^2 - x_{A,i+1} - x_{P,i+1})) / 2 - let y_a_next = (lambda1_next.clone() + lambda2_next) - * (x_a_next.clone() - - (lambda1_next.clone() * lambda1_next - x_a_next.clone() - x_p_next)) - * F::TWO_INV; + // The base used in double-and-add remains constant. We check that its + // x- and y- coordinates are the same throughout. + let x_p_check = x_p_cur.clone() - x_p_next; + let y_p_check = y_p_cur.clone() - y_p_next; - // λ_{1,i}⋅(x_{A,i} − x_{P,i}) − y_{A,i} + (2k_i - 1) y_{P,i} = 0 - meta.create_gate("Double-and-add lambda1", |_| { - let expr = lambda1_cur.clone() * (x_a_cur.clone() - x_p_cur.clone()) - y_a_cur.clone() - + (k * F::from_u64(2) + Expression::Constant(-F::one())) * y_p_cur.clone(); - q_mul.clone() * expr - }); + // λ_{1,i}⋅(x_{A,i} − x_{P,i}) − y_{A,i} + (2k_i - 1) y_{P,i} = 0 + let poly1 = lambda1_cur.clone() * (x_a_cur.clone() - x_p_cur.clone()) - y_a_cur.clone() + + (k * F::from_u64(2) + Expression::Constant(-F::one())) * y_p_cur; - // (λ_{1,i} + λ_{2,i})⋅(x_{A,i} − (λ_{1,i}^2 − x_{A,i} − x_{P,i})) − 2y_{A,i}) = 0 - meta.create_gate("Double-and-add expr0", |_| { - let lambda_neg = lambda1_cur.clone() + lambda2_cur.clone(); - let lambda1_expr = - lambda1_cur.clone() * lambda1_cur.clone() - x_a_cur.clone() - x_p_cur.clone(); - let expr = lambda_neg * (x_a_cur.clone() - lambda1_expr) - - Expression::Constant(F::from_u64(2)) * y_a_cur.clone(); - q_mul.clone() * expr - }); + // (λ_{1,i} + λ_{2,i})⋅(x_{A,i} − (λ_{1,i}^2 − x_{A,i} − x_{P,i})) − 2y_{A,i}) = 0 + let poly2 = { + let lambda_neg = lambda1_cur.clone() + lambda2_cur.clone(); + let lambda1_expr = + lambda1_cur.clone() * lambda1_cur.clone() - x_a_cur.clone() - x_p_cur.clone(); + lambda_neg * (x_a_cur.clone() - lambda1_expr) + - Expression::Constant(F::from_u64(2)) * y_a_cur.clone() + }; - // λ_{2,i}^2 − x_{A,i+1} −(λ_{1,i}^2 − x_{A,i} − x_{P,i}) − x_{A,i} = 0 - meta.create_gate("Double-and-add expr1", |_| { - let expr1 = lambda2_cur.clone() * lambda2_cur.clone() + // λ_{2,i}^2 − x_{A,i+1} −(λ_{1,i}^2 − x_{A,i} − x_{P,i}) − x_{A,i} = 0 + let poly3 = lambda2_cur.clone() * lambda2_cur.clone() - x_a_next.clone() - (lambda1_cur.clone() * lambda1_cur) + x_p_cur; - q_mul.clone() * expr1 - }); - - // λ_{2,i}⋅(x_{A,i} − x_{A,i+1}) − y_{A,i} − y_{A,i+1} = 0 - meta.create_gate("Double-and-add expr2", |_| { - let expr2 = lambda2_cur * (x_a_cur - x_a_next) - y_a_cur - y_a_next; + // λ_{2,i}⋅(x_{A,i} − x_{A,i+1}) − y_{A,i} − y_{A,i+1} = 0 + let poly4 = lambda2_cur * (x_a_cur - x_a_next) - y_a_cur - y_a_next; - q_mul.clone() * expr2 + [bool_check, x_p_check, y_p_check, poly1, poly2, poly3, poly4] + .iter() + .map(|poly| q_mul.clone() * poly.clone()) + .collect() }); } @@ -223,7 +214,7 @@ impl Config { let mut y_a = *acc.1; // Incomplete addition - for (row, k) in bits.into_iter().enumerate() { + for (row, k) in bits.iter().enumerate() { // z_{i} = 2 * z_{i+1} + k_i let z_val = z .value diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index c94ecfb60..3eebb4a6b 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -153,14 +153,16 @@ impl From<&EccConfig> for Config Config { pub(super) fn create_gate(&self, meta: &mut ConstraintSystem) { - let q_mul_fixed = meta.query_selector(self.q_mul_fixed, Rotation::cur()); - let y_p = meta.query_advice(self.y_p, Rotation::cur()); + meta.create_gate("Fixed-base scalar mul gate", |meta| { + let q_mul_fixed = meta.query_selector(self.q_mul_fixed, Rotation::cur()); + let y_p = meta.query_advice(self.y_p, Rotation::cur()); - // Check interpolation of x-coordinate - meta.create_gate("fixed-base scalar mul (x)", |meta| { 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(C::Base::one()), |acc, _| { @@ -176,15 +178,15 @@ impl Config { }, ); - q_mul_fixed.clone() * (interpolated_x - x_p) - }); - - // Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed - meta.create_gate("fixed-base scalar mul (y)", |meta| { - let z = meta.query_fixed(self.fixed_z, Rotation::cur()); - let u = meta.query_advice(self.u, 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; - q_mul_fixed * (u.clone() * u - y_p - z) + [x_check, y_check] + .iter() + .map(|poly| q_mul_fixed.clone() * poly.clone()) + .collect() }); } 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 a75d2417e..61c380808 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -105,7 +105,7 @@ pub mod tests { let value_commit_r = OrchardFixedBasesFull::ValueCommitR(PhantomData); let value_commit_r = FixedPoint::from_inner(chip.clone(), value_commit_r); test_single_base( - chip.clone(), + chip, layouter.namespace(|| "value_commit_r"), value_commit_r, )?; @@ -157,7 +157,7 @@ pub mod tests { { let scalar_fixed = C::Scalar::zero(); let scalar_fixed = ScalarFixed::new( - chip.clone(), + chip, layouter.namespace(|| "ScalarFixed"), Some(scalar_fixed), )?; diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index e4033ce31..1213d9a64 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -35,22 +35,27 @@ 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(super) fn create_gate(&self, meta: &mut ConstraintSystem) { - let q_mul_fixed_short = meta.query_selector(self.q_mul_fixed_short, Rotation::cur()); - let y_p = meta.query_advice(self.y_p, Rotation::cur()); - let y_a = meta.query_advice(self.add_config.y_qr, Rotation::cur()); + meta.create_gate("Short fixed-base mul gate", |meta| { + let q_mul_fixed_short = meta.query_selector(self.q_mul_fixed_short, Rotation::cur()); + let y_p = meta.query_advice(self.y_p, Rotation::cur()); + let y_a = meta.query_advice(self.add_config.y_qr, Rotation::cur()); + let sign = meta.query_advice(self.window, Rotation::cur()); - // `(x_a, y_a)` is the result of `[m]B`, where `m` is the magnitude. - // We conditionally negate this result using `y_p = y_a * s`, where `s` is the sign. + // `(x_a, y_a)` is the result of `[m]B`, where `m` is the magnitude. + // We conditionally negate this result using `y_p = y_a * s`, where `s` is the sign. - // Check that the final `y_p = y_a` or `y_p = -y_a` - meta.create_gate("check y", |_| { - q_mul_fixed_short.clone() * (y_p.clone() - y_a.clone()) * (y_p.clone() + y_a.clone()) - }); + // Check that the final `y_p = y_a` or `y_p = -y_a` + let y_check = q_mul_fixed_short.clone() + * (y_p.clone() - y_a.clone()) + * (y_p.clone() + y_a.clone()); - // Check that sign * y_p = y_a - meta.create_gate("check negation", |meta| { - let sign = meta.query_advice(self.window, Rotation::cur()); - q_mul_fixed_short * (sign * y_p - y_a) + // Check that the correct sign is witnessed s.t. sign * y_p = y_a + let negation_check = sign * y_p - y_a; + + [y_check, negation_check] + .iter() + .map(|poly| q_mul_fixed_short.clone() * poly.clone()) + .collect() }); } @@ -227,7 +232,7 @@ pub mod tests { let scalar_fixed_short = C::Scalar::from_u64(0xB6DB_6DB6_DB6D_B6DCu64); let scalar_fixed_short = ScalarFixedShort::new( - chip.clone(), + chip, layouter.namespace(|| "ScalarFixedShort"), Some(scalar_fixed_short), )?; diff --git a/src/circuit/gadget/ecc/chip/witness_point.rs b/src/circuit/gadget/ecc/chip/witness_point.rs index 280c14f2f..d45ab2fb5 100644 --- a/src/circuit/gadget/ecc/chip/witness_point.rs +++ b/src/circuit/gadget/ecc/chip/witness_point.rs @@ -34,7 +34,10 @@ impl Config { let y = meta.query_advice(self.y, Rotation::cur()); // Check that y^2 = x^3 + b, where b = 5 in the Pallas equation - q_point * (y.clone() * y - (x.clone() * x.clone() * x) - Expression::Constant(C::b())) + vec![ + q_point + * (y.clone() * y - (x.clone() * x.clone() * x) - Expression::Constant(C::b())), + ] }); } diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs index a0e2334fa..34704a17d 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs @@ -42,7 +42,7 @@ impl Config { .fold(Expression::Constant(C::Base::one()), |acc, i| { acc * (window.clone() - Expression::Constant(C::Base::from_u64(i as u64))) }); - q_scalar_fixed * range_check + vec![q_scalar_fixed * range_check] }); } @@ -70,7 +70,6 @@ impl Config { let mut windows: ArrayVec, NUM_WINDOWS> = ArrayVec::new(); let scalar_windows: Vec> = if let Some(windows) = scalar_windows { - println!("windows: {:?}", windows); assert_eq!(windows.len(), NUM_WINDOWS); windows .into_iter() diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs index 042e21dec..fbe4c9dda 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs @@ -37,9 +37,11 @@ impl Config { meta.query_selector(self.q_scalar_fixed_short, Rotation::cur()); let sign = meta.query_advice(self.window, Rotation::cur()); - q_scalar_fixed_short - * (sign.clone() + Expression::Constant(C::Base::one())) - * (sign - Expression::Constant(C::Base::one())) + vec![ + q_scalar_fixed_short + * (sign.clone() + Expression::Constant(C::Base::one())) + * (sign - Expression::Constant(C::Base::one())), + ] }); } } @@ -68,11 +70,9 @@ impl Config { let magnitude = sign.zip(value).map(|(sign, value)| sign * value); - println!("before decompose"); // Decompose magnitude into `k`-bit windows let windows = self.decompose_scalar_fixed::(magnitude, offset, region)?; - println!("after decompose"); // Assign the sign and enable `q_scalar_fixed_short` let sign = sign.map(|sign| { From 9c3046327fd9e4ed84de92dadaa034052860be75 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 5 Jun 2021 00:25:56 +0800 Subject: [PATCH 47/49] Rebase on Utilities chip --- src/circuit/gadget/ecc/chip.rs | 22 ++----------- src/circuit/gadget/ecc/chip/add.rs | 14 ++++---- src/circuit/gadget/ecc/chip/add_incomplete.rs | 14 ++++---- src/circuit/gadget/ecc/chip/mul.rs | 18 +++++----- src/circuit/gadget/ecc/chip/mul/complete.rs | 10 +++--- src/circuit/gadget/ecc/chip/mul/incomplete.rs | 33 ++++++++----------- src/circuit/gadget/ecc/chip/mul_fixed.rs | 14 ++++---- .../gadget/ecc/chip/mul_fixed/short.rs | 14 ++++---- src/circuit/gadget/ecc/chip/util.rs | 28 ---------------- src/circuit/gadget/ecc/chip/witness_point.rs | 2 +- .../gadget/ecc/chip/witness_scalar_fixed.rs | 2 +- .../ecc/chip/witness_scalar_fixed/short.rs | 2 +- 12 files changed, 61 insertions(+), 112 deletions(-) delete mode 100644 src/circuit/gadget/ecc/chip/util.rs diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 1bc282b07..951bb975d 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -1,10 +1,11 @@ use super::EccInstructions; +use crate::circuit::gadget::utilities::{copy, CellValue, Var}; use crate::constants::{self, OrchardFixedBasesFull, ValueCommitV}; use arrayvec::ArrayVec; use ff::Field; use halo2::{ arithmetic::CurveAffine, - circuit::{Cell, Chip, Layouter}, + circuit::{Chip, Layouter}, plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Permutation, Selector}, }; use std::marker::PhantomData; @@ -13,26 +14,9 @@ pub(super) mod add; pub(super) mod add_incomplete; pub(super) mod mul; pub(super) mod mul_fixed; -pub(super) mod util; pub(super) mod witness_point; pub(super) mod witness_scalar_fixed; -/// A structure containing a cell and its assigned value. -#[derive(Clone, Debug)] -pub struct CellValue { - /// The cell of this `CellValue` - pub cell: Cell, - /// The value assigned to this `CellValue` - pub value: Option, -} - -impl CellValue { - /// Construct a `CellValue`. - pub fn new(cell: Cell, value: Option) -> Self { - CellValue { cell, value } - } -} - /// A curve point represented in affine (x, y) coordinates. Each coordinate is /// assigned to a cell. #[derive(Clone, Debug)] @@ -46,7 +30,7 @@ pub struct EccPoint { impl EccPoint { /// Returns the value of this curve point, if known. pub fn point(&self) -> Option { - match (self.x.value, self.y.value) { + match (self.x.value(), self.y.value()) { (Some(x), Some(y)) => { if x == C::Base::zero() && y == C::Base::zero() { Some(C::identity()) diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 11f38d5e2..79b68f0bc 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -1,4 +1,4 @@ -use super::{util, CellValue, EccConfig, EccPoint}; +use super::{copy, CellValue, EccConfig, EccPoint, Var}; use ff::Field; use halo2::{ arithmetic::{CurveAffine, FieldExt}, @@ -206,15 +206,15 @@ impl Config { self.q_add.enable(region, offset)?; // Copy point `p` into `x_p`, `y_p` columns - util::assign_and_constrain(region, || "x_p", self.x_p, offset, &p.x, &self.perm)?; - util::assign_and_constrain(region, || "y_p", self.y_p, offset, &p.y, &self.perm)?; + copy(region, || "x_p", self.x_p, offset, &p.x, &self.perm)?; + copy(region, || "y_p", self.y_p, offset, &p.y, &self.perm)?; // Copy point `q` into `x_qr`, `y_qr` columns - util::assign_and_constrain(region, || "x_q", self.x_qr, offset, &q.x, &self.perm)?; - util::assign_and_constrain(region, || "y_q", self.y_qr, offset, &q.y, &self.perm)?; + copy(region, || "x_q", self.x_qr, offset, &q.x, &self.perm)?; + copy(region, || "y_q", self.y_qr, offset, &q.y, &self.perm)?; - let (x_p, y_p) = (p.x.value, p.y.value); - let (x_q, y_q) = (q.x.value, q.y.value); + let (x_p, y_p) = (p.x.value(), p.y.value()); + let (x_q, y_q) = (q.x.value(), q.y.value()); // inv0(x) evaluates to 0 if x = 0, and 1/x otherwise. diff --git a/src/circuit/gadget/ecc/chip/add_incomplete.rs b/src/circuit/gadget/ecc/chip/add_incomplete.rs index 34720a485..4c84c0cda 100644 --- a/src/circuit/gadget/ecc/chip/add_incomplete.rs +++ b/src/circuit/gadget/ecc/chip/add_incomplete.rs @@ -1,4 +1,4 @@ -use super::{util, CellValue, EccConfig, EccPoint}; +use super::{copy, CellValue, EccConfig, EccPoint, Var}; use ff::Field; use group::Curve; use halo2::{ @@ -76,8 +76,8 @@ impl Config { self.q_add_incomplete.enable(region, offset)?; // Handle exceptional cases - let (x_p, y_p) = (p.x.value, p.y.value); - let (x_q, y_q) = (q.x.value, q.y.value); + let (x_p, y_p) = (p.x.value(), p.y.value()); + let (x_q, y_q) = (q.x.value(), q.y.value()); x_p.zip(y_p) .zip(x_q) .zip(y_q) @@ -97,12 +97,12 @@ impl Config { .transpose()?; // Copy point `p` into `x_p`, `y_p` columns - util::assign_and_constrain(region, || "x_p", self.x_p, offset, &p.x, &self.perm)?; - util::assign_and_constrain(region, || "y_p", self.y_p, offset, &p.y, &self.perm)?; + copy(region, || "x_p", self.x_p, offset, &p.x, &self.perm)?; + copy(region, || "y_p", self.y_p, offset, &p.y, &self.perm)?; // Copy point `q` into `x_qr`, `y_qr` columns - util::assign_and_constrain(region, || "x_q", self.x_qr, offset, &q.x, &self.perm)?; - util::assign_and_constrain(region, || "y_q", self.y_qr, offset, &q.y, &self.perm)?; + copy(region, || "x_q", self.x_qr, offset, &q.x, &self.perm)?; + copy(region, || "y_q", self.y_qr, offset, &q.y, &self.perm)?; // Compute the sum `P + Q = R` let r = { diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 33c638f9a..161864b29 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -1,4 +1,4 @@ -use super::{add, util, CellValue, EccConfig, EccPoint}; +use super::{add, copy, CellValue, EccConfig, EccPoint, Var}; use crate::constants::NUM_COMPLETE_BITS; use std::ops::{Deref, Range}; @@ -113,7 +113,7 @@ impl Config { let offset = offset + 1; // Decompose the scalar bitwise (big-endian bit order). - let bits = decompose_for_scalar_mul::(scalar.value); + let bits = decompose_for_scalar_mul::(scalar.value()); // Initialize the running sum for scalar decomposition to zero let z_val = C::Base::zero(); @@ -131,7 +131,7 @@ impl Config { offset, &base, bits_incomplete_hi, - (X(acc.x.clone()), Y(acc.y.value), Z(z)), + (X(acc.x.clone()), Y(acc.y.value()), Z(z)), )?; // Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition @@ -164,7 +164,7 @@ impl Config { }; // Initialize `z` running sum for complete addition - util::assign_and_constrain( + copy( region, || "Initialize `z` running sum for complete addition", self.z_complete, @@ -182,7 +182,7 @@ impl Config { // Bits used in complete addition. k_{3} to k_{1} inclusive // The LSB k_{0} is handled separately. let bits_complete = &bits[complete_range::()]; - complete_config.assign_region(region, offset, bits_complete, base, acc, z.value)? + complete_config.assign_region(region, offset, bits_complete, base, acc, z.value())? }; let offset = offset + complete_len::() * 2; @@ -198,7 +198,7 @@ impl Config { let base = base.point(); let scalar = scalar - .value + .value() .map(|scalar| C::Scalar::from_bytes(&scalar.to_bytes()).unwrap()); let real_mul = base.zip(scalar).map(|(base, scalar)| base * scalar); let result = result.point(); @@ -241,7 +241,7 @@ impl Config { // is in deriving diversified addresses `[ivk] g_d`, and `ivk` is guaranteed // to be in the base field of the curve. (See non-normative notes in // https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents.) - util::assign_and_constrain( + copy( region, || "original scalar", self.scalar, @@ -254,7 +254,7 @@ impl Config { // If `lsb` is 0, return `Acc + (-P)`. If `lsb` is 1, simply return `Acc + 0`. let x_p = if let Some(lsb) = lsb { if !lsb { - base.x.value + base.x.value() } else { Some(C::Base::zero()) } @@ -263,7 +263,7 @@ impl Config { }; let y_p = if let Some(lsb) = lsb { if !lsb { - base.y.value.map(|y_p| -y_p) + base.y.value().map(|y_p| -y_p) } else { Some(C::Base::zero()) } diff --git a/src/circuit/gadget/ecc/chip/mul/complete.rs b/src/circuit/gadget/ecc/chip/mul/complete.rs index ad1547e9e..c14a41aa2 100644 --- a/src/circuit/gadget/ecc/chip/mul/complete.rs +++ b/src/circuit/gadget/ecc/chip/mul/complete.rs @@ -1,4 +1,4 @@ -use super::super::{add, util, CellValue, EccPoint}; +use super::super::{add, copy, CellValue, EccPoint, Var}; use super::complete_len; use ff::Field; @@ -102,7 +102,7 @@ impl Config { )?; // Assign `x_p` for complete addition - let x_p = base.x.value; + let x_p = base.x.value(); let x_p_cell = region.assign_advice( || "x_p", self.add_config.x_p, @@ -112,7 +112,7 @@ impl Config { // Assign `y_p` for complete addition. // If the bit is set, use `y`; if the bit is not set, use `-y` - let y_p = base.y.value; + let y_p = base.y.value(); let y_p = y_p .zip(k.as_ref()) .map(|(y_p, k)| if !k { -y_p } else { y_p }); @@ -134,7 +134,7 @@ impl Config { .assign_region(&p, &acc, row + offset, region)?; // Copy acc from `x_a`, `y_a` over to `x_p`, `y_p` on the next row - let acc_x = util::assign_and_constrain( + let acc_x = copy( region, || "copy acc x_a", self.add_config.x_p, @@ -142,7 +142,7 @@ impl Config { &acc.x, &self.perm, )?; - let acc_y = util::assign_and_constrain( + let acc_y = copy( region, || "copy acc y_a", self.add_config.y_p, diff --git a/src/circuit/gadget/ecc/chip/mul/incomplete.rs b/src/circuit/gadget/ecc/chip/mul/incomplete.rs index 1e56f181c..0aed00e28 100644 --- a/src/circuit/gadget/ecc/chip/mul/incomplete.rs +++ b/src/circuit/gadget/ecc/chip/mul/incomplete.rs @@ -1,4 +1,4 @@ -use super::super::{util, CellValue, EccConfig, EccPoint}; +use super::super::{copy, CellValue, EccConfig, EccPoint, Var}; use super::{incomplete_hi_len, incomplete_lo_len, X, Y, Z}; use ff::Field; use halo2::{ @@ -161,8 +161,8 @@ impl Config { assert_eq!(bits.len(), self.num_bits); // Handle exceptional cases - let (x_p, y_p) = (base.x.value, base.y.value); - let (x_a, y_a) = (acc.0.value, acc.1 .0); + let (x_p, y_p) = (base.x.value(), base.y.value()); + let (x_a, y_a) = (acc.0.value(), acc.1 .0); x_p.zip(y_p) .zip(x_a) .zip(y_a) @@ -186,24 +186,17 @@ impl Config { } // Initialise the running `z` sum for the scalar bits. - let mut z = util::assign_and_constrain( - region, - || "starting z", - self.z, - offset, - &acc.2, - &self.perm, - )?; + let mut z = copy(region, || "starting z", self.z, offset, &acc.2, &self.perm)?; // Increase offset by 1; we used row 0 for initializing `z`. let offset = offset + 1; // Define `x_p`, `y_p` - let x_p = base.x.value; - let y_p = base.y.value; + let x_p = base.x.value(); + let y_p = base.y.value(); // Initialise acc - let mut x_a = util::assign_and_constrain( + let mut x_a = copy( region, || "starting x_a", self.x_a, @@ -217,7 +210,7 @@ impl Config { for (row, k) in bits.iter().enumerate() { // z_{i} = 2 * z_{i+1} + k_i let z_val = z - .value + .value() .zip(k.as_ref()) .map(|(z_val, k)| C::Base::from_u64(2) * z_val + C::Base::from_u64(*k as u64)); let z_cell = region.assign_advice( @@ -250,7 +243,7 @@ impl Config { // Compute and assign λ1⋅(x_A − x_P) = y_A − y_P let lambda1 = y_a .zip(y_p) - .zip(x_a.value) + .zip(x_a.value()) .zip(x_p) .map(|(((y_a, y_p), x_a), x_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap()); region.assign_advice( @@ -262,7 +255,7 @@ impl Config { // x_R = λ1^2 - x_A - x_P let x_r = lambda1 - .zip(x_a.value) + .zip(x_a.value()) .zip(x_p) .map(|((lambda1, x_a), x_p)| lambda1 * lambda1 - x_a - x_p); @@ -270,7 +263,7 @@ impl Config { let lambda2 = lambda1 .zip(y_a) - .zip(x_a.value) + .zip(x_a.value()) .zip(x_r) .map(|(((lambda1, y_a), x_a), x_r)| { C::Base::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 @@ -284,11 +277,11 @@ impl Config { // Compute and assign `x_a` for the next row let x_a_new = lambda2 - .zip(x_a.value) + .zip(x_a.value()) .zip(x_r) .map(|((lambda2, x_a), x_r)| lambda2 * lambda2 - x_a - x_r); y_a = lambda2 - .zip(x_a.value) + .zip(x_a.value()) .zip(x_a_new) .zip(y_a) .map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a); diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index 3eebb4a6b..670552661 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -1,6 +1,6 @@ use super::{ - add, add_incomplete, util, witness_point, CellValue, EccConfig, EccPoint, EccScalarFixed, - EccScalarFixedShort, + add, add_incomplete, copy, witness_point, CellValue, EccConfig, EccPoint, EccScalarFixed, + EccScalarFixedShort, Var, }; use crate::constants::{ self, @@ -281,7 +281,7 @@ impl Config { ) -> Result<(), Error> { // Copy the scalar decomposition (`k`-bit windows) for (window_idx, window) in scalar.windows().iter().enumerate() { - util::assign_and_constrain( + copy( region, || format!("k[{:?}]", window), self.window, @@ -320,7 +320,7 @@ impl Config { } // Copy `m0` into `x_qr`, `y_qr` cells on row 1 - let x = util::assign_and_constrain( + let x = copy( region, || "initialize acc x", self.add_incomplete_config.x_qr, @@ -328,7 +328,7 @@ impl Config { &m0.x, &self.perm, )?; - let y = util::assign_and_constrain( + let y = copy( region, || "initialize acc y", self.add_incomplete_config.y_qr, @@ -465,7 +465,7 @@ impl ScalarFixed { self.windows() .iter() .map(|bits| { - bits.value + bits.value() .map(|value| C::Scalar::from_bytes(&value.to_bytes()).unwrap()) }) .collect::>() @@ -477,7 +477,7 @@ impl ScalarFixed { fn windows_usize(&self) -> Vec> { self.windows() .iter() - .map(|bits| bits.value.map(|value| value.to_bytes()[0] as usize)) + .map(|bits| bits.value().map(|value| value.to_bytes()[0] as usize)) .collect::>() } } diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index 1213d9a64..7bd57fd59 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -1,4 +1,4 @@ -use super::super::{util, CellValue, EccConfig, EccPoint, EccScalarFixedShort}; +use super::super::{copy, CellValue, EccConfig, EccPoint, EccScalarFixedShort, Var}; use crate::constants::ValueCommitV; use halo2::{ @@ -78,7 +78,7 @@ impl Config { let offset = offset + 1; // Assign sign to `window` column - let sign = util::assign_and_constrain( + let sign = copy( region, || "sign", self.window, @@ -88,11 +88,11 @@ impl Config { )?; // Conditionally negate `y`-coordinate - let y_val = if let Some(sign) = sign.value { + let y_val = if let Some(sign) = sign.value() { if sign == -C::Base::one() { - magnitude_mul.y.value.map(|y: C::Base| -y) + magnitude_mul.y.value().map(|y: C::Base| -y) } else { - magnitude_mul.y.value + magnitude_mul.y.value() } } else { None @@ -103,7 +103,7 @@ impl Config { .enable(region, offset + NUM_WINDOWS)?; // Assign final `x, y` to `x_p, y_p` columns and return final point - let x_val = magnitude_mul.x.value; + let x_val = magnitude_mul.x.value(); let x_var = region.assign_advice( || "x_var", self.x_p, @@ -131,7 +131,7 @@ impl Config { let scalar = scalar .magnitude - .zip(scalar.sign.value) + .zip(scalar.sign.value()) .map(|(magnitude, sign)| { let sign = if sign == C::Base::one() { C::Scalar::one() diff --git a/src/circuit/gadget/ecc/chip/util.rs b/src/circuit/gadget/ecc/chip/util.rs deleted file mode 100644 index af2fe6d73..000000000 --- a/src/circuit/gadget/ecc/chip/util.rs +++ /dev/null @@ -1,28 +0,0 @@ -use super::CellValue; -use halo2::{ - arithmetic::FieldExt, - circuit::Region, - plonk::{Advice, Column, Error, Permutation}, -}; - -/// Assign a cell the same value as another cell and set up a copy constraint between them. -pub(super) fn assign_and_constrain( - region: &mut Region<'_, F>, - annotation: A, - column: Column, - row: usize, - copy: &CellValue, - perm: &Permutation, -) -> Result, Error> -where - A: Fn() -> AR, - AR: Into, -{ - let cell = region.assign_advice(annotation, column, row, || { - copy.value.ok_or(Error::SynthesisError) - })?; - - region.constrain_equal(perm, cell, copy.cell)?; - - Ok(CellValue::new(cell, copy.value)) -} diff --git a/src/circuit/gadget/ecc/chip/witness_point.rs b/src/circuit/gadget/ecc/chip/witness_point.rs index d45ab2fb5..acbb05058 100644 --- a/src/circuit/gadget/ecc/chip/witness_point.rs +++ b/src/circuit/gadget/ecc/chip/witness_point.rs @@ -1,4 +1,4 @@ -use super::{CellValue, EccConfig, EccPoint}; +use super::{CellValue, EccConfig, EccPoint, Var}; use halo2::{ arithmetic::CurveAffine, diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs index 34704a17d..458d66e11 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed.rs @@ -1,4 +1,4 @@ -use super::{CellValue, EccConfig}; +use super::{CellValue, EccConfig, Var}; use crate::constants::{self, util}; use arrayvec::ArrayVec; use ff::Field; diff --git a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs index fbe4c9dda..1ace112ae 100644 --- a/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/witness_scalar_fixed/short.rs @@ -1,4 +1,4 @@ -use super::super::{CellValue, EccConfig, EccScalarFixedShort}; +use super::super::{CellValue, EccConfig, EccScalarFixedShort, Var}; use crate::constants::{L_VALUE, NUM_WINDOWS_SHORT}; use halo2::{ arithmetic::{CurveAffine, Field, FieldExt}, From 6d1059be4cf191aec9422f3f75571b3e9d5d7bf5 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 5 Jun 2021 12:29:43 +0800 Subject: [PATCH 48/49] Remove unwrap() in tests Tests do not always use the MockProver. --- src/circuit/gadget/ecc/chip/add.rs | 4 +++- src/circuit/gadget/ecc/chip/add_incomplete.rs | 4 +++- src/circuit/gadget/ecc/chip/mul.rs | 4 +++- src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs | 4 +++- src/circuit/gadget/ecc/chip/mul_fixed/short.rs | 4 +++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index 79b68f0bc..a316526ef 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -369,7 +369,9 @@ impl Config { let real_sum = p.zip(q).map(|(p, q)| p + q); let result = result.point(); - assert_eq!(real_sum.unwrap().to_affine(), result.unwrap()); + if let (Some(real_sum), Some(result)) = (real_sum, result) { + assert_eq!(real_sum.to_affine(), result); + } } Ok(result) diff --git a/src/circuit/gadget/ecc/chip/add_incomplete.rs b/src/circuit/gadget/ecc/chip/add_incomplete.rs index 4c84c0cda..4205ef997 100644 --- a/src/circuit/gadget/ecc/chip/add_incomplete.rs +++ b/src/circuit/gadget/ecc/chip/add_incomplete.rs @@ -147,7 +147,9 @@ impl Config { let real_sum = p.zip(q).map(|(p, q)| p + q); let result = result.point(); - assert_eq!(real_sum.unwrap().to_affine(), result.unwrap()); + if let (Some(real_sum), Some(result)) = (real_sum, result) { + assert_eq!(real_sum.to_affine(), result); + } } Ok(result) diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 161864b29..610b89b3a 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -203,7 +203,9 @@ impl Config { let real_mul = base.zip(scalar).map(|(base, scalar)| base * scalar); let result = result.point(); - assert_eq!(real_mul.unwrap().to_affine(), result.unwrap()); + if let (Some(real_mul), Some(result)) = (real_mul, result) { + assert_eq!(real_mul.to_affine(), result); + } } Ok(result) 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 61c380808..f7a030429 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -47,7 +47,9 @@ impl Config { let real_mul = scalar.map(|scalar| base.generator() * scalar); let result = result.point(); - assert_eq!(real_mul.unwrap().to_affine(), result.unwrap()); + if let (Some(real_mul), Some(result)) = (real_mul, result) { + assert_eq!(real_mul.to_affine(), result); + } } Ok(result) diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index 7bd57fd59..97b7731b9 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -145,7 +145,9 @@ impl Config { let real_mul = scalar.map(|scalar| base.generator() * scalar); let result = result.point(); - assert_eq!(real_mul.unwrap().to_affine(), result.unwrap()); + if let (Some(real_mul), Some(result)) = (real_mul, result) { + assert_eq!(real_mul.to_affine(), result); + } } Ok(result) From 63751fad1c5380ed5431ec3a100b1f2449b658c2 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Sat, 5 Jun 2021 13:47:57 +0800 Subject: [PATCH 49/49] Address review comments --- src/circuit/gadget/ecc.rs | 9 ++++++++- src/circuit/gadget/ecc/chip.rs | 28 ++++++++-------------------- src/circuit/gadget/ecc/chip/add.rs | 16 +++++++--------- src/circuit/gadget/ecc/chip/mul.rs | 2 +- 4 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/circuit/gadget/ecc.rs b/src/circuit/gadget/ecc.rs index 986a9621d..86abd9b55 100644 --- a/src/circuit/gadget/ecc.rs +++ b/src/circuit/gadget/ecc.rs @@ -410,7 +410,14 @@ mod tests { meta.advice_column(), ]; - EccChip::::configure(meta, advices) + let perm = meta.permutation( + &advices + .iter() + .map(|advice| (*advice).into()) + .collect::>(), + ); + + EccChip::::configure(meta, advices, perm) } fn synthesize( diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index 951bb975d..cf99c33e3 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -113,6 +113,7 @@ impl EccChip { pub fn configure( meta: &mut ConstraintSystem, advices: [Column; 10], + perm: Permutation, ) -> >::Config { let config = EccConfig { advices, @@ -138,20 +139,7 @@ impl EccChip { q_point: meta.selector(), q_scalar_fixed: meta.selector(), q_scalar_fixed_short: meta.selector(), - perm: Permutation::new( - meta, - &[ - advices[0].into(), - advices[1].into(), - advices[2].into(), - advices[3].into(), - advices[4].into(), - advices[6].into(), - advices[7].into(), - advices[8].into(), - advices[9].into(), - ], - ), + perm, }; // Create witness point gate @@ -248,7 +236,7 @@ impl EccInstructions for EccChip { value: Option, ) -> Result { layouter.assign_region( - || "Witness scalar var", + || "Witness scalar for variable-base mul", |mut region| { let cell = region.assign_advice( || "Scalar var", @@ -280,7 +268,7 @@ impl EccInstructions for EccChip { ) -> Result { let config: witness_scalar_fixed::short::Config = self.config().into(); layouter.assign_region( - || "witness scalar for fixed-base mul", + || "witness short scalar for fixed-base mul", |mut region| config.assign_region(value, 0, &mut region), ) } @@ -309,7 +297,7 @@ impl EccInstructions for EccChip { ) -> Result { let config: add_incomplete::Config = self.config().into(); layouter.assign_region( - || "point addition", + || "incomplete point addition", |mut region| config.assign_region(a, b, 0, &mut region), ) } @@ -322,7 +310,7 @@ impl EccInstructions for EccChip { ) -> Result { let config: add::Config = self.config().into(); layouter.assign_region( - || "point addition", + || "complete point addition", |mut region| config.assign_region(a, b, 0, &mut region), ) } @@ -349,7 +337,7 @@ impl EccInstructions for EccChip { let config: mul_fixed::full_width::Config = self.config().into(); layouter.assign_region( - || format!("Multiply {:?}", base), + || format!("fixed-base mul of {:?}", base), |mut region| config.assign_region(scalar, *base, 0, &mut region), ) } @@ -363,7 +351,7 @@ impl EccInstructions for EccChip { let config: mul_fixed::short::Config = self.config().into(); layouter.assign_region( - || format!("Multiply {:?}", base), + || format!("short fixed-base mul of {:?}", base), |mut region| config.assign_region(scalar, base, 0, &mut region), ) } diff --git a/src/circuit/gadget/ecc/chip/add.rs b/src/circuit/gadget/ecc/chip/add.rs index a316526ef..7ffc9e5f5 100644 --- a/src/circuit/gadget/ecc/chip/add.rs +++ b/src/circuit/gadget/ecc/chip/add.rs @@ -148,11 +148,11 @@ impl Config { // x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) = 0 let poly5 = { let y_q_plus_y_p = y_q.clone() + y_p.clone(); // (y_q + y_p) - let incomplete = + let output_line_x = lambda.clone() * lambda.clone() - x_p.clone() - x_q.clone() - x_r.clone(); // (λ^2 - x_p - x_q - x_r) // x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) - x_p.clone() * x_q.clone() * y_q_plus_y_p * incomplete + x_p.clone() * x_q.clone() * y_q_plus_y_p * output_line_x }; // x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0 @@ -216,17 +216,13 @@ impl Config { let (x_p, y_p) = (p.x.value(), p.y.value()); let (x_q, y_q) = (q.x.value(), q.y.value()); - // inv0(x) evaluates to 0 if x = 0, and 1/x otherwise. - // Assign α = inv0(x_q - x_p) + let alpha = x_p.zip(x_q).map(|(x_p, x_q)| inv0(x_q - x_p)); region.assign_advice( || "α", self.alpha, offset, - || { - let alpha = x_p.zip(x_q).map(|(x_p, x_q)| inv0(x_q - x_p)); - alpha.ok_or(Error::SynthesisError) - }, + || alpha.ok_or(Error::SynthesisError), )?; // Assign β = inv0(x_p) @@ -280,7 +276,9 @@ impl Config { .map(|(((x_p, y_p), x_q), y_q)| { if x_q != x_p { // λ = (y_q - y_p)/(x_q - x_p) - (y_q - y_p) * (x_q - x_p).invert().unwrap() + // Here, alpha = inv0(x_q - x_p), which suffices since we + // know that x_q != x_p in this branch. + (y_q - y_p) * alpha.unwrap() } else { if y_p != C::Base::zero() { // 3(x_p)^2 diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index 610b89b3a..d6e59b9a2 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -131,7 +131,7 @@ impl Config { offset, &base, bits_incomplete_hi, - (X(acc.x.clone()), Y(acc.y.value()), Z(z)), + (X(acc.x), Y(acc.y.value()), Z(z)), )?; // Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition