gadget::ecc.rs: Inline witness_scalar_* APIs.

Witness a scalar in the region where it is used for multiplication,
instead of witnessing it separately and then copying it in.
This commit is contained in:
therealyingtong 2021-07-10 13:19:42 +08:00
parent 32f28ed4b0
commit 426f954b1d
11 changed files with 329 additions and 589 deletions

View File

@ -1,10 +1,9 @@
//! Gadgets for elliptic curve operations.
use ff::Field;
use std::fmt::Debug;
use halo2::{
arithmetic::{CurveAffine, FieldExt},
arithmetic::CurveAffine,
circuit::{Chip, Layouter},
plonk::Error,
};
@ -53,30 +52,6 @@ pub trait EccInstructions<C: CurveAffine>: Chip<C::Base> + UtilitiesInstructions
b: &Self::Point,
) -> Result<(), Error>;
/// Witnesses the given base field element as a private input to the circuit
/// for variable-base scalar mul.
fn witness_scalar_var(
&self,
layouter: &mut impl Layouter<C::Base>,
value: Option<C::Base>,
) -> Result<Self::ScalarVar, Error>;
/// Witnesses the given full-width scalar as a private input to the circuit
/// for fixed-base scalar mul.
fn witness_scalar_fixed(
&self,
layouter: &mut impl Layouter<C::Base>,
value: Option<C::Scalar>,
) -> Result<Self::ScalarFixed, Error>;
/// Witnesses the given signed short scalar as a private input to the circuit
/// for fixed-base scalar mul.
fn witness_scalar_fixed_short(
&self,
layouter: &mut impl Layouter<C::Base>,
value: Option<C::Scalar>,
) -> Result<Self::ScalarFixedShort, Error>;
/// Witnesses the given point as a private input to the circuit.
/// This maps the identity to (0, 0) in affine coordinates.
fn witness_point(
@ -111,25 +86,25 @@ pub trait EccInstructions<C: CurveAffine>: Chip<C::Base> + UtilitiesInstructions
fn mul(
&self,
layouter: &mut impl Layouter<C::Base>,
scalar: &Self::ScalarVar,
scalar: &Self::Var,
base: &Self::Point,
) -> Result<Self::Point, Error>;
) -> Result<(Self::Point, Self::ScalarVar), Error>;
/// Performs fixed-base scalar multiplication using a full-width scalar, returning `[scalar] base`.
fn mul_fixed(
&self,
layouter: &mut impl Layouter<C::Base>,
scalar: &Self::ScalarFixed,
scalar: Option<C::Scalar>,
base: &Self::FixedPoints,
) -> Result<Self::Point, Error>;
) -> Result<(Self::Point, Self::ScalarFixed), Error>;
/// Performs fixed-base scalar multiplication using a short signed scalar, returning `[scalar] base`.
fn mul_fixed_short(
&self,
layouter: &mut impl Layouter<C::Base>,
scalar: &Self::ScalarFixedShort,
scalar: Option<C::Scalar>,
base: &Self::FixedPointsShort,
) -> Result<Self::Point, Error>;
) -> Result<(Self::Point, Self::ScalarFixedShort), Error>;
/// Performs fixed-base scalar multiplication using a base field element as the scalar.
/// In the current implementation, this base field element must be output from another
@ -159,18 +134,6 @@ pub struct ScalarVar<C: CurveAffine, EccChip: EccInstructions<C> + Clone + Debug
inner: EccChip::ScalarVar,
}
impl<C: CurveAffine, EccChip: EccInstructions<C> + Clone + Debug + Eq> ScalarVar<C, EccChip> {
/// Constructs a new ScalarVar with the given value.
pub fn new(
chip: EccChip,
mut layouter: impl Layouter<C::Base>,
value: Option<C::Base>,
) -> Result<Self, Error> {
chip.witness_scalar_var(&mut layouter, value)
.map(|inner| ScalarVar { chip, inner })
}
}
/// A full-width element of the given elliptic curve's scalar field, to be used for fixed-base scalar mul.
#[derive(Debug)]
pub struct ScalarFixed<C: CurveAffine, EccChip>
@ -181,21 +144,6 @@ where
inner: EccChip::ScalarFixed,
}
impl<C: CurveAffine, EccChip> ScalarFixed<C, EccChip>
where
EccChip: EccInstructions<C> + Clone + Debug + Eq,
{
/// Constructs a new ScalarFixed with the given value.
pub fn new(
chip: EccChip,
mut layouter: impl Layouter<C::Base>,
value: Option<C::Scalar>,
) -> Result<Self, Error> {
chip.witness_scalar_fixed(&mut layouter, value)
.map(|inner| ScalarFixed { chip, inner })
}
}
/// A signed short element of the given elliptic curve's scalar field, to be used for fixed-base scalar mul.
#[derive(Debug)]
pub struct ScalarFixedShort<C: CurveAffine, EccChip>
@ -206,39 +154,6 @@ where
inner: EccChip::ScalarFixedShort,
}
impl<C: CurveAffine, EccChip> ScalarFixedShort<C, EccChip>
where
EccChip: EccInstructions<C> + Clone + Debug + Eq,
{
/// Constructs a new ScalarFixedShort with the given value.
///
/// # Panics
///
/// The short scalar must be in the range [-(2^64 - 1), (2^64 - 1)].
pub fn new(
chip: EccChip,
mut layouter: impl Layouter<C::Base>,
value: Option<C::Scalar>,
) -> Result<Self, Error> {
// Check that the scalar is in the range [-(2^64 - 1), (2^64 - 1)]
if let Some(value) = value {
let mut sign = C::Scalar::one();
// T = (p-1) / 2
let t = (C::Scalar::zero() - C::Scalar::one()) * C::Scalar::TWO_INV;
if value > t {
sign = -sign;
}
let magnitude = value * sign;
assert!(magnitude < C::Scalar::from_u128(1 << 64));
}
chip.witness_scalar_fixed_short(&mut layouter, value)
.map(|inner| ScalarFixedShort { chip, inner })
}
}
/// An elliptic curve point over the given curve.
#[derive(Copy, Clone, Debug)]
pub struct Point<C: CurveAffine, EccChip: EccInstructions<C> + Clone + Debug + Eq> {
@ -307,14 +222,21 @@ impl<C: CurveAffine, EccChip: EccInstructions<C> + Clone + Debug + Eq> Point<C,
pub fn mul(
&self,
mut layouter: impl Layouter<C::Base>,
by: &ScalarVar<C, EccChip>,
) -> Result<Self, Error> {
assert_eq!(self.chip, by.chip);
by: &EccChip::Var,
) -> Result<(Self, ScalarVar<C, EccChip>), Error> {
self.chip
.mul(&mut layouter, &by.inner, &self.inner)
.map(|inner| Point {
chip: self.chip.clone(),
inner,
.mul(&mut layouter, by, &self.inner)
.map(|(point, scalar)| {
(
Point {
chip: self.chip.clone(),
inner: point,
},
ScalarVar {
chip: self.chip.clone(),
inner: scalar,
},
)
})
}
}
@ -353,14 +275,21 @@ where
pub fn mul(
&self,
mut layouter: impl Layouter<C::Base>,
by: &ScalarFixed<C, EccChip>,
) -> Result<Point<C, EccChip>, Error> {
assert_eq!(self.chip, by.chip);
by: Option<C::Scalar>,
) -> Result<(Point<C, EccChip>, ScalarFixed<C, EccChip>), Error> {
self.chip
.mul_fixed(&mut layouter, &by.inner, &self.inner)
.map(|inner| Point {
chip: self.chip.clone(),
inner,
.mul_fixed(&mut layouter, by, &self.inner)
.map(|(point, scalar)| {
(
Point {
chip: self.chip.clone(),
inner: point,
},
ScalarFixed {
chip: self.chip.clone(),
inner: scalar,
},
)
})
}
@ -404,14 +333,21 @@ where
pub fn mul(
&self,
mut layouter: impl Layouter<C::Base>,
by: &ScalarFixedShort<C, EccChip>,
) -> Result<Point<C, EccChip>, Error> {
assert_eq!(self.chip, by.chip);
by: Option<C::Scalar>,
) -> Result<(Point<C, EccChip>, ScalarFixedShort<C, EccChip>), Error> {
self.chip
.mul_fixed_short(&mut layouter, &by.inner, &self.inner)
.map(|inner| Point {
chip: self.chip.clone(),
inner,
.mul_fixed_short(&mut layouter, by, &self.inner)
.map(|(point, scalar)| {
(
Point {
chip: self.chip.clone(),
inner: point,
},
ScalarFixedShort {
chip: self.chip.clone(),
inner: scalar,
},
)
})
}

View File

@ -21,7 +21,6 @@ pub(super) mod add_incomplete;
pub(super) mod mul;
pub(super) mod mul_fixed;
pub(super) mod witness_point;
pub(super) mod witness_scalar_fixed;
/// A curve point represented in affine (x, y) coordinates. Each coordinate is
/// assigned to a cell.
@ -248,30 +247,15 @@ impl EccChip {
mul_config.create_gate(meta);
}
// Create witness scalar_fixed gate that applies to both full-width and
// short scalars
// Create gate that is only used in full-width fixed-base scalar mul.
{
let config: witness_scalar_fixed::Config = (&config).into();
config.create_gate(meta);
}
// Create witness scalar_fixed gate that only applies to short scalars
{
let config: witness_scalar_fixed::short::Config = (&config).into();
config.create_gate(meta);
}
// Create fixed-base scalar mul gate that is used in both full-width
// and short multiplication.
{
let mul_fixed_config: mul_fixed::Config<{ constants::NUM_WINDOWS }> = (&config).into();
mul_fixed_config.create_gate_scalar(meta);
let mul_fixed_full_config: mul_fixed::full_width::Config = (&config).into();
mul_fixed_full_config.create_gate(meta);
}
// Create gate that is only used in short fixed-base scalar mul.
{
let short_config: mul_fixed::short::Config<{ constants::NUM_WINDOWS_SHORT }> =
(&config).into();
let short_config: mul_fixed::short::Config = (&config).into();
short_config.create_gate(meta);
}
@ -285,17 +269,6 @@ impl EccChip {
}
}
/// A base-field element used as the scalar in variable-base scalar multiplication.
#[derive(Copy, Clone, Debug)]
pub struct EccScalarVar(CellValue<pallas::Base>);
impl std::ops::Deref for EccScalarVar {
type Target = CellValue<pallas::Base>;
fn deref(&self) -> &CellValue<pallas::Base> {
&self.0
}
}
/// A full-width scalar used for fixed-base scalar multiplication.
/// This is decomposed into 85 3-bit windows in little-endian order,
/// i.e. `windows` = [k_0, k_1, ..., k_84] (for a 255-bit scalar)
@ -350,7 +323,7 @@ impl EccBaseFieldElemFixed {
impl EccInstructions<pallas::Affine> for EccChip {
type ScalarFixed = EccScalarFixed;
type ScalarFixedShort = EccScalarFixedShort;
type ScalarVar = EccScalarVar;
type ScalarVar = CellValue<pallas::Base>;
type Point = EccPoint;
type X = CellValue<pallas::Base>;
type FixedPoints = OrchardFixedBasesFull;
@ -374,50 +347,6 @@ impl EccInstructions<pallas::Affine> for EccChip {
)
}
fn witness_scalar_var(
&self,
layouter: &mut impl Layouter<pallas::Base>,
value: Option<pallas::Base>,
) -> Result<Self::ScalarVar, Error> {
let config = self.config().clone();
layouter.assign_region(
|| "Witness scalar for variable-base mul",
|mut region| {
let cell = region.assign_advice(
|| "witness scalar var",
config.advices[0],
0,
|| value.ok_or(Error::SynthesisError),
)?;
Ok(EccScalarVar(CellValue::new(cell, value)))
},
)
}
fn witness_scalar_fixed(
&self,
layouter: &mut impl Layouter<pallas::Base>,
value: Option<pallas::Scalar>,
) -> Result<Self::ScalarFixed, Error> {
let config: witness_scalar_fixed::full_width::Config = self.config().into();
layouter.assign_region(
|| "witness scalar for fixed-base mul",
|mut region| config.assign_region(value, 0, &mut region),
)
}
fn witness_scalar_fixed_short(
&self,
layouter: &mut impl Layouter<pallas::Base>,
value: Option<pallas::Scalar>,
) -> Result<Self::ScalarFixedShort, Error> {
let config: witness_scalar_fixed::short::Config = self.config().into();
layouter.assign_region(
|| "witness short scalar for fixed-base mul",
|mut region| config.assign_region(value, 0, &mut region),
)
}
fn witness_point(
&self,
layouter: &mut impl Layouter<pallas::Base>,
@ -463,9 +392,9 @@ impl EccInstructions<pallas::Affine> for EccChip {
fn mul(
&self,
layouter: &mut impl Layouter<pallas::Base>,
scalar: &Self::ScalarVar,
scalar: &Self::Var,
base: &Self::Point,
) -> Result<Self::Point, Error> {
) -> Result<(Self::Point, Self::ScalarVar), Error> {
let config: mul::Config = self.config().into();
config.assign(
layouter.namespace(|| "variable-base scalar mul"),
@ -477,11 +406,10 @@ impl EccInstructions<pallas::Affine> for EccChip {
fn mul_fixed(
&self,
layouter: &mut impl Layouter<pallas::Base>,
scalar: &Self::ScalarFixed,
scalar: Option<pallas::Scalar>,
base: &Self::FixedPoints,
) -> Result<Self::Point, Error> {
let config: mul_fixed::full_width::Config<{ constants::NUM_WINDOWS }> =
self.config().into();
) -> Result<(Self::Point, Self::ScalarFixed), Error> {
let config: mul_fixed::full_width::Config = self.config().into();
config.assign(
layouter.namespace(|| format!("fixed-base mul of {:?}", base)),
scalar,
@ -492,11 +420,10 @@ impl EccInstructions<pallas::Affine> for EccChip {
fn mul_fixed_short(
&self,
layouter: &mut impl Layouter<pallas::Base>,
scalar: &Self::ScalarFixedShort,
scalar: Option<pallas::Scalar>,
base: &Self::FixedPointsShort,
) -> Result<Self::Point, Error> {
let config: mul_fixed::short::Config<{ constants::NUM_WINDOWS_SHORT }> =
self.config().into();
) -> Result<(Self::Point, Self::ScalarFixedShort), Error> {
let config: mul_fixed::short::Config = self.config().into();
config.assign(
layouter.namespace(|| format!("short fixed-base mul of {:?}", base)),
scalar,

View File

@ -1,4 +1,4 @@
use super::{add, CellValue, EccConfig, EccPoint, EccScalarVar, Var};
use super::{add, CellValue, EccConfig, EccPoint, Var};
use crate::{circuit::gadget::utilities::copy, constants::T_Q};
use std::ops::{Deref, Range};
@ -137,9 +137,9 @@ impl Config {
pub(super) fn assign(
&self,
mut layouter: impl Layouter<pallas::Base>,
alpha: EccScalarVar,
alpha: CellValue<pallas::Base>,
base: &EccPoint,
) -> Result<EccPoint, Error> {
) -> Result<(EccPoint, CellValue<pallas::Base>), Error> {
let (result, zs): (EccPoint, Vec<Z<pallas::Base>>) = layouter.assign_region(
|| "variable-base scalar mul",
|mut region| {
@ -264,7 +264,7 @@ impl Config {
self.overflow_config
.overflow_check(layouter.namespace(|| "overflow check"), alpha, &zs)?;
Ok(result)
Ok((result, alpha))
}
/// Processes the final scalar bit `k_0`.
@ -448,18 +448,26 @@ fn decompose_for_scalar_mul(scalar: Option<pallas::Base>) -> Vec<Option<bool>> {
#[cfg(test)]
pub mod tests {
use group::Curve;
use halo2::{circuit::Layouter, plonk::Error};
use halo2::{
circuit::{Chip, Layouter},
plonk::Error,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use crate::circuit::gadget::ecc::{EccInstructions, Point, ScalarVar};
use crate::circuit::gadget::{
ecc::{chip::EccChip, EccInstructions, Point},
utilities::UtilitiesInstructions,
};
pub fn test_mul<EccChip: EccInstructions<pallas::Affine> + Clone + Eq + std::fmt::Debug>(
pub fn test_mul(
chip: EccChip,
mut layouter: impl Layouter<pallas::Base>,
zero: &Point<pallas::Affine, EccChip>,
p: &Point<pallas::Affine, EccChip>,
p_val: pallas::Affine,
) -> Result<(), Error> {
let column = chip.config().advices[0];
fn constrain_equal<
EccChip: EccInstructions<pallas::Affine> + Clone + Eq + std::fmt::Debug,
>(
@ -483,10 +491,10 @@ pub mod tests {
// [a]B
{
let scalar_val = pallas::Base::rand();
let result = {
let scalar = ScalarVar::new(
chip.clone(),
let (result, _) = {
let scalar = chip.load_private(
layouter.namespace(|| "random scalar"),
column,
Some(scalar_val),
)?;
p.mul(layouter.namespace(|| "random [a]B"), &scalar)?
@ -504,9 +512,9 @@ pub mod tests {
// uses incomplete addition at the beginning of its double-and-add.
{
let scalar_val = pallas::Base::rand();
let scalar = ScalarVar::new(
chip.clone(),
let scalar = chip.load_private(
layouter.namespace(|| "random scalar"),
column,
Some(scalar_val),
)?;
zero.mul(layouter.namespace(|| "[a]𝒪"), &scalar)
@ -517,12 +525,9 @@ pub mod tests {
// uses complete addition for the final bits of the scalar.
{
let scalar_val = pallas::Base::zero();
let result = {
let scalar = ScalarVar::new(
chip.clone(),
layouter.namespace(|| "zero"),
Some(scalar_val),
)?;
let (result, _) = {
let scalar =
chip.load_private(layouter.namespace(|| "zero"), column, Some(scalar_val))?;
p.mul(layouter.namespace(|| "[0]B"), &scalar)?
};
constrain_equal(
@ -537,9 +542,9 @@ pub mod tests {
// [-1]B (the largest possible base field element)
{
let scalar_val = -pallas::Base::one();
let result = {
let (result, _) = {
let scalar =
ScalarVar::new(chip.clone(), layouter.namespace(|| "-1"), Some(scalar_val))?;
chip.load_private(layouter.namespace(|| "-1"), column, Some(scalar_val))?;
p.mul(layouter.namespace(|| "[-1]B"), &scalar)?
};
constrain_equal(

View File

@ -1,4 +1,4 @@
use super::super::{copy, CellValue, EccConfig, EccScalarVar, Var};
use super::super::{copy, CellValue, EccConfig, Var};
use super::Z;
use crate::{
circuit::gadget::utilities::lookup_range_check::LookupRangeCheckConfig, constants::T_Q,
@ -95,7 +95,7 @@ impl Config {
pub(super) fn overflow_check(
&self,
mut layouter: impl Layouter<pallas::Base>,
alpha: EccScalarVar,
alpha: CellValue<pallas::Base>,
zs: &[Z<pallas::Base>], // [z_0, z_1, ..., z_{254}, z_{255}]
) -> Result<(), Error> {
// s = alpha + k_254 ⋅ 2^130 is witnessed here, and then copied into
@ -189,7 +189,7 @@ impl Config {
|| "copy original alpha",
self.advices[1],
offset + 1,
&*alpha,
&alpha,
&self.perm,
)?;

View File

@ -1,19 +1,18 @@
use super::{
add, add_incomplete, copy, CellValue, EccBaseFieldElemFixed, EccConfig, EccPoint,
EccScalarFixed, EccScalarFixedShort, Var,
add, add_incomplete, CellValue, EccBaseFieldElemFixed, EccConfig, EccPoint, EccScalarFixed,
EccScalarFixedShort, Var,
};
use crate::constants::{
self,
load::{OrchardFixedBase, OrchardFixedBasesFull, ValueCommitV, WindowUs},
util,
};
use arrayvec::ArrayVec;
use group::Curve;
use halo2::{
circuit::Region,
plonk::{
Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, Selector,
VirtualCells,
},
plonk::{Advice, Column, Error, Expression, Fixed, Permutation, Selector, VirtualCells},
poly::Rotation,
};
use lazy_static::lazy_static;
@ -72,6 +71,7 @@ impl OrchardFixedBases {
#[derive(Clone, Debug)]
pub struct Config<const NUM_WINDOWS: usize> {
q_mul_fixed: Selector,
q_scalar_fixed: Selector,
// The fixed Lagrange interpolation coefficients for `x_p`.
lagrange_coeffs: [Column<Fixed>; constants::H],
// The fixed `z` for each window such that `y + z = u^2`.
@ -97,6 +97,7 @@ impl<const NUM_WINDOWS: usize> From<&EccConfig> for Config<NUM_WINDOWS> {
fn from(ecc_config: &EccConfig) -> Self {
let config = Self {
q_mul_fixed: ecc_config.q_mul_fixed,
q_scalar_fixed: ecc_config.q_scalar_fixed,
lagrange_coeffs: ecc_config.lagrange_coeffs,
fixed_z: ecc_config.fixed_z,
x_p: ecc_config.advices[0],
@ -143,17 +144,6 @@ impl<const NUM_WINDOWS: usize> From<&EccConfig> for Config<NUM_WINDOWS> {
}
impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
pub(super) fn create_gate_scalar(&self, meta: &mut ConstraintSystem<pallas::Base>) {
meta.create_gate(
"x_p, y_p checks for ScalarFixed, ScalarFixedShort",
|meta| {
let mul_fixed = meta.query_selector(self.q_mul_fixed);
let window = meta.query_advice(self.window, Rotation::cur());
self.coords_check(meta, mul_fixed, window)
},
)
}
#[allow(clippy::op_ref)]
fn coords_check(
&self,
@ -288,25 +278,53 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
Ok(())
}
fn copy_scalar(
/// Witnesses the given scalar as `NUM_WINDOWS` 3-bit windows.
///
/// The scalar is allowed to be non-canonical.
fn decompose_scalar_fixed<const SCALAR_NUM_BITS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
scalar: Option<pallas::Scalar>,
offset: usize,
scalar: &ScalarFixed,
) -> Result<(), Error> {
// Copy the scalar decomposition (`k`-bit windows)
for (window_idx, window) in scalar.windows().iter().enumerate() {
copy(
region,
|| format!("k[{:?}]", window),
self.window,
window_idx + offset,
window,
&self.perm,
)?;
region: &mut Region<'_, pallas::Base>,
) -> Result<ArrayVec<CellValue<pallas::Base>, NUM_WINDOWS>, Error> {
// Enable `q_scalar_fixed` selector
for idx in 0..NUM_WINDOWS {
self.q_scalar_fixed.enable(region, offset + idx)?;
}
Ok(())
// Decompose scalar into `k-bit` windows
let scalar_windows: Option<Vec<u8>> = scalar.map(|scalar| {
util::decompose_word::<pallas::Scalar>(
scalar,
SCALAR_NUM_BITS,
constants::FIXED_BASE_WINDOW_SIZE,
)
});
// Store the scalar decomposition
let mut windows: ArrayVec<CellValue<pallas::Base>, NUM_WINDOWS> = ArrayVec::new();
let scalar_windows: Vec<Option<pallas::Base>> = if let Some(windows) = scalar_windows {
assert_eq!(windows.len(), NUM_WINDOWS);
windows
.into_iter()
.map(|window| Some(pallas::Base::from_u64(window as u64)))
.collect()
} else {
vec![None; NUM_WINDOWS]
};
for (idx, window) in scalar_windows.into_iter().enumerate() {
let window_cell = region.assign_advice(
|| format!("k[{:?}]", offset + idx),
self.window,
offset + idx,
|| window.ok_or(Error::SynthesisError),
)?;
windows.push(CellValue::new(window_cell, window));
}
Ok(windows)
}
fn process_window(

View File

@ -490,6 +490,7 @@ pub mod tests {
base_val: pallas::Affine,
) -> Result<(), Error> {
let column = chip.config().advices[0];
fn constrain_equal(
chip: EccChip,
mut layouter: impl Layouter<pallas::Base>,

View File

@ -1,38 +1,90 @@
use super::super::{EccConfig, EccPoint, EccScalarFixed, OrchardFixedBasesFull};
use halo2::{circuit::Layouter, plonk::Error};
use crate::{
circuit::gadget::utilities::range_check,
constants::{self, L_ORCHARD_SCALAR, NUM_WINDOWS},
};
use halo2::{
circuit::{Layouter, Region},
plonk::{ConstraintSystem, Error, Selector},
poly::Rotation,
};
use pasta_curves::pallas;
pub struct Config<const NUM_WINDOWS: usize>(super::Config<NUM_WINDOWS>);
pub struct Config {
q_scalar_fixed: Selector,
super_config: super::Config<NUM_WINDOWS>,
}
impl<const NUM_WINDOWS: usize> From<&EccConfig> for Config<NUM_WINDOWS> {
impl From<&EccConfig> for Config {
fn from(config: &EccConfig) -> Self {
Self(config.into())
Self {
q_scalar_fixed: config.q_scalar_fixed,
super_config: config.into(),
}
}
}
impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
impl Config {
pub fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// Check that each window `k` is within 3 bits
meta.create_gate("Full-width fixed-base scalar mul", |meta| {
let q_scalar_fixed = meta.query_selector(self.q_scalar_fixed);
let window = meta.query_advice(self.super_config.window, Rotation::cur());
self.super_config
.coords_check(meta, q_scalar_fixed.clone(), window.clone())
.into_iter()
// Constrain each window to a 3-bit value:
// 1 * (window - 0) * (window - 1) * ... * (window - 7)
.chain(Some((
"window range check",
q_scalar_fixed * range_check(window, constants::H),
)))
});
}
/// Witnesses the given scalar as `NUM_WINDOWS` 3-bit windows.
///
/// The scalar is allowed to be non-canonical.
fn witness(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
scalar: Option<pallas::Scalar>,
) -> Result<EccScalarFixed, Error> {
let windows = self
.super_config
.decompose_scalar_fixed::<L_ORCHARD_SCALAR>(scalar, offset, region)?;
Ok(EccScalarFixed {
value: scalar,
windows,
})
}
pub fn assign(
&self,
mut layouter: impl Layouter<pallas::Base>,
scalar: &EccScalarFixed,
scalar: Option<pallas::Scalar>,
base: OrchardFixedBasesFull,
) -> Result<EccPoint, Error> {
let (acc, mul_b) = layouter.assign_region(
) -> Result<(EccPoint, EccScalarFixed), Error> {
let (scalar, acc, mul_b) = layouter.assign_region(
|| "Full-width fixed-base mul (incomplete addition)",
|mut region| {
let offset = 0;
// Copy the scalar decomposition
self.0.copy_scalar(&mut region, offset, &scalar.into())?;
let scalar = self.witness(&mut region, offset, scalar)?;
self.0.assign_region_inner(
let (acc, mul_b) = self.super_config.assign_region_inner(
&mut region,
offset,
&scalar.into(),
&(&scalar).into(),
base.into(),
self.0.q_mul_fixed,
)
self.super_config.q_mul_fixed,
)?;
Ok((scalar, acc, mul_b))
},
)?;
@ -40,7 +92,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
let result = layouter.assign_region(
|| "Full-width fixed-base mul (last window, complete addition)",
|mut region| {
self.0
self.super_config
.add_config
.assign_region(&mul_b, &acc, 0, &mut region)
},
@ -60,7 +112,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
}
}
Ok(result)
Ok((result, scalar))
}
}
@ -72,7 +124,7 @@ pub mod tests {
use crate::circuit::gadget::ecc::{
chip::{EccChip, OrchardFixedBasesFull},
FixedPoint, Point, ScalarFixed,
FixedPoint, Point,
};
use crate::constants;
@ -154,15 +206,7 @@ pub mod tests {
{
let scalar_fixed = pallas::Scalar::rand();
let result = {
let scalar_fixed = ScalarFixed::new(
chip.clone(),
layouter.namespace(|| "random scalar"),
Some(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "random [a]B"), &scalar_fixed)?
};
let (result, _) = base.mul(layouter.namespace(|| "random [a]B"), Some(scalar_fixed))?;
constrain_equal(
chip.clone(),
layouter.namespace(|| "random [a]B"),
@ -183,14 +227,8 @@ pub mod tests {
.fold(pallas::Scalar::zero(), |acc, c| {
acc * &h + &pallas::Scalar::from_u64(c.to_digit(8).unwrap().into())
});
let result = {
let scalar_fixed = ScalarFixed::new(
chip.clone(),
layouter.namespace(|| "mul with double"),
Some(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "mul with double"), &scalar_fixed)?
};
let (result, _) =
base.mul(layouter.namespace(|| "mul with double"), Some(scalar_fixed))?;
constrain_equal(
chip.clone(),
@ -205,14 +243,7 @@ pub mod tests {
// on the last step.
{
let scalar_fixed = pallas::Scalar::zero();
let result = {
let scalar_fixed = ScalarFixed::new(
chip.clone(),
layouter.namespace(|| "zero"),
Some(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)?
};
let (result, _) = base.mul(layouter.namespace(|| "mul by zero"), Some(scalar_fixed))?;
constrain_equal(
chip.clone(),
layouter.namespace(|| "mul by zero"),
@ -225,14 +256,7 @@ pub mod tests {
// [-1]B is the largest scalar field element.
{
let scalar_fixed = -pallas::Scalar::one();
let result = {
let scalar_fixed = ScalarFixed::new(
chip.clone(),
layouter.namespace(|| "-1"),
Some(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "mul by -1"), &scalar_fixed)?
};
let (result, _) = base.mul(layouter.namespace(|| "mul by -1"), Some(scalar_fixed))?;
constrain_equal(
chip,
layouter.namespace(|| "mul by -1"),

View File

@ -1,34 +1,54 @@
use std::array;
use super::super::{copy, CellValue, EccConfig, EccPoint, EccScalarFixedShort, Var};
use crate::constants::ValueCommitV;
use crate::constants::{ValueCommitV, L_VALUE, NUM_WINDOWS_SHORT};
use halo2::{
circuit::Layouter,
plonk::{ConstraintSystem, Error, Selector},
circuit::{Layouter, Region},
plonk::{ConstraintSystem, Error, Expression, Selector},
poly::Rotation,
};
use pasta_curves::pallas;
use pasta_curves::{arithmetic::FieldExt, pallas};
pub struct Config<const NUM_WINDOWS: usize> {
pub struct Config {
// Selector used for fixed-base scalar mul with short signed exponent.
q_mul_fixed_short: Selector,
super_config: super::Config<NUM_WINDOWS>,
q_scalar_fixed_short: Selector,
super_config: super::Config<NUM_WINDOWS_SHORT>,
}
impl<const NUM_WINDOWS: usize> From<&EccConfig> for Config<NUM_WINDOWS> {
impl From<&EccConfig> for Config {
fn from(config: &EccConfig) -> Self {
Self {
q_mul_fixed_short: config.q_mul_fixed_short,
q_scalar_fixed_short: config.q_scalar_fixed_short,
super_config: config.into(),
}
}
}
impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
impl Config {
// We reuse the constraints in the `mul_fixed` gate so exclude them here.
// Here, we add some new constraints specific to the short signed case.
pub(crate) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// Check that sign is either 1 or -1.
// Check that last window is either 0 or 1.
meta.create_gate("Check sign and last window", |meta| {
let q_scalar_fixed_short = meta.query_selector(self.q_scalar_fixed_short);
let last_window = meta.query_advice(self.super_config.window, Rotation::prev());
let sign = meta.query_advice(self.super_config.window, Rotation::cur());
let one = Expression::Constant(pallas::Base::one());
let last_window_check = last_window.clone() * (one.clone() - last_window);
let sign_check = sign.clone() * sign - one;
vec![
q_scalar_fixed_short.clone() * last_window_check,
q_scalar_fixed_short * sign_check,
]
});
meta.create_gate("Short fixed-base mul gate", |meta| {
let q_mul_fixed_short = meta.query_selector(self.q_mul_fixed_short);
let y_p = meta.query_advice(self.super_config.y_p, Rotation::cur());
@ -51,28 +71,80 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
});
}
fn witness(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
value: Option<pallas::Scalar>,
) -> Result<EccScalarFixedShort, Error> {
// Enable `q_scalar_fixed_short`
self.q_scalar_fixed_short
.enable(region, offset + NUM_WINDOWS_SHORT)?;
// Compute the scalar's sign and magnitude
let sign = value.map(|value| {
// t = (p - 1)/2
let t = (pallas::Scalar::zero() - &pallas::Scalar::one()) * &pallas::Scalar::TWO_INV;
if value > t {
-pallas::Scalar::one()
} else {
pallas::Scalar::one()
}
});
let magnitude = sign.zip(value).map(|(sign, value)| sign * &value);
// Decompose magnitude into `k`-bit windows
let windows = self
.super_config
.decompose_scalar_fixed::<L_VALUE>(magnitude, offset, region)?;
// Assign the sign and enable `q_scalar_fixed_short`
let sign = sign.map(|sign| {
assert!(sign == pallas::Scalar::one() || sign == -pallas::Scalar::one());
if sign == pallas::Scalar::one() {
pallas::Base::one()
} else {
-pallas::Base::one()
}
});
let sign_cell = region.assign_advice(
|| "sign",
self.super_config.window,
offset + NUM_WINDOWS_SHORT,
|| sign.ok_or(Error::SynthesisError),
)?;
Ok(EccScalarFixedShort {
magnitude,
sign: CellValue::<pallas::Base>::new(sign_cell, sign),
windows,
})
}
pub fn assign(
&self,
mut layouter: impl Layouter<pallas::Base>,
scalar: &EccScalarFixedShort,
scalar: Option<pallas::Scalar>,
base: &ValueCommitV,
) -> Result<EccPoint, Error> {
let (acc, mul_b) = layouter.assign_region(
) -> Result<(EccPoint, EccScalarFixedShort), Error> {
let (scalar, acc, mul_b) = layouter.assign_region(
|| "Short fixed-base mul (incomplete addition)",
|mut region| {
let offset = 0;
// Copy the scalar decomposition
self.super_config
.copy_scalar(&mut region, offset, &scalar.into())?;
let scalar = self.witness(&mut region, offset, scalar)?;
self.super_config.assign_region_inner(
let (acc, mul_b) = self.super_config.assign_region_inner(
&mut region,
offset,
&scalar.into(),
&(&scalar).into(),
base.clone().into(),
self.super_config.q_mul_fixed,
)
)?;
Ok((scalar, acc, mul_b))
},
)?;
@ -158,7 +230,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
}
}
Ok(result)
Ok((result, scalar))
}
}
@ -168,7 +240,7 @@ pub mod tests {
use halo2::{circuit::Layouter, plonk::Error};
use pasta_curves::{arithmetic::FieldExt, pallas};
use crate::circuit::gadget::ecc::{chip::EccChip, FixedPointShort, Point, ScalarFixedShort};
use crate::circuit::gadget::ecc::{chip::EccChip, FixedPointShort, Point};
use crate::constants::load::ValueCommitV;
#[allow(clippy::op_ref)]
@ -199,20 +271,17 @@ pub mod tests {
// [0]B should return (0,0) since it uses complete addition
// on the last step.
{
let scalar_fixed = pallas::Scalar::zero();
let result = {
let scalar_fixed = ScalarFixedShort::new(
chip.clone(),
layouter.namespace(|| "zero"),
Some(scalar_fixed),
)?;
value_commit_v.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)?
};
let scalar_fixed_short = pallas::Scalar::zero();
let (result, _) = value_commit_v.mul(
layouter.namespace(|| "mul by zero"),
Some(scalar_fixed_short),
)?;
constrain_equal(
chip.clone(),
layouter.namespace(|| "mul by zero"),
base_val,
scalar_fixed,
scalar_fixed_short,
result,
)?;
}
@ -225,16 +294,11 @@ pub mod tests {
sign = -sign;
}
let scalar_fixed_short = sign * &scalar_fixed_short;
let (result, _) = value_commit_v.mul(
layouter.namespace(|| "random short scalar"),
Some(scalar_fixed_short),
)?;
let result = {
let scalar_fixed_short = ScalarFixedShort::new(
chip.clone(),
layouter.namespace(|| "random short scalar"),
Some(scalar_fixed_short),
)?;
value_commit_v.mul(layouter.namespace(|| "random [a]B"), &scalar_fixed_short)?
};
constrain_equal(
chip.clone(),
layouter.namespace(|| "random [a]B"),
@ -247,15 +311,11 @@ pub mod tests {
// [2^64 - 1]B
{
let scalar_fixed_short = pallas::Scalar::from_u64(0xFFFF_FFFF_FFFF_FFFFu64);
let (result, _) = value_commit_v.mul(
layouter.namespace(|| "[2^64 - 1]B"),
Some(scalar_fixed_short),
)?;
let result = {
let scalar_fixed_short = ScalarFixedShort::new(
chip.clone(),
layouter.namespace(|| "2^64 - 1"),
Some(scalar_fixed_short),
)?;
value_commit_v.mul(layouter.namespace(|| "[2^64 - 1]B"), &scalar_fixed_short)?
};
constrain_equal(
chip.clone(),
layouter.namespace(|| "[2^64 - 1]B"),
@ -268,15 +328,11 @@ pub mod tests {
// [-(2^64 - 1)]B
{
let scalar_fixed_short = -pallas::Scalar::from_u64(0xFFFF_FFFF_FFFF_FFFFu64);
let (result, _) = value_commit_v.mul(
layouter.namespace(|| "-[2^64 - 1]B"),
Some(scalar_fixed_short),
)?;
let result = {
let scalar_fixed_short = ScalarFixedShort::new(
chip.clone(),
layouter.namespace(|| "-(2^64 - 1)"),
Some(scalar_fixed_short),
)?;
value_commit_v.mul(layouter.namespace(|| "[-(2^64 - 1)]B"), &scalar_fixed_short)?
};
constrain_equal(
chip.clone(),
layouter.namespace(|| "[-2^64 - 1]B"),
@ -292,17 +348,11 @@ pub mod tests {
{
let scalar_fixed_short = pallas::Scalar::from_u64(0xB6DB_6DB6_DB6D_B6DCu64);
let result = {
let scalar_fixed_short = ScalarFixedShort::new(
chip.clone(),
layouter.namespace(|| "mul with double"),
Some(scalar_fixed_short),
)?;
value_commit_v.mul(
layouter.namespace(|| "mul with double"),
&scalar_fixed_short,
)?
};
let (result, _) = value_commit_v.mul(
layouter.namespace(|| "mul with double"),
Some(scalar_fixed_short),
)?;
constrain_equal(
chip,
layouter.namespace(|| "mul with double"),

View File

@ -1,96 +0,0 @@
use super::{CellValue, EccConfig, Var};
use crate::{
circuit::gadget::utilities::range_check,
constants::{self, util},
};
use arrayvec::ArrayVec;
use halo2::{
circuit::Region,
plonk::{Advice, Column, ConstraintSystem, Error, Permutation, Selector},
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
pub mod full_width;
pub mod short;
pub struct Config {
q_scalar_fixed: Selector,
// Decomposition of scalar into `k`-bit windows.
window: Column<Advice>,
perm: Permutation,
}
impl From<&EccConfig> for Config {
fn from(ecc_config: &EccConfig) -> Self {
Self {
q_scalar_fixed: ecc_config.q_scalar_fixed,
window: ecc_config.advices[9],
perm: ecc_config.perm.clone(),
}
}
}
impl Config {
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// Range check gate applies to both full-width and short scalars.
// Check that `k` is within the allowed window size
meta.create_gate("witness scalar fixed", |meta| {
let q_scalar_fixed = meta.query_selector(self.q_scalar_fixed);
let window = meta.query_advice(self.window, Rotation::cur());
// Constrain each window to a 3-bit value:
// 1 * (window - 0) * (window - 1) * ... * (window - 7)
vec![q_scalar_fixed * range_check(window, constants::H)]
});
}
/// Witnesses the given scalar as `NUM_WINDOWS` 3-bit windows.
///
/// The scalar is allowed to be non-canonical.
fn decompose_scalar_fixed<const NUM_WINDOWS: usize, const SCALAR_NUM_BITS: usize>(
&self,
scalar: Option<pallas::Scalar>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<ArrayVec<CellValue<pallas::Base>, NUM_WINDOWS>, Error> {
// Enable `q_scalar_fixed` selector
for idx in 0..NUM_WINDOWS {
self.q_scalar_fixed.enable(region, offset + idx)?;
}
// Decompose scalar into `k-bit` windows
let scalar_windows: Option<Vec<u8>> = scalar.map(|scalar| {
util::decompose_word::<pallas::Scalar>(
scalar,
SCALAR_NUM_BITS,
constants::FIXED_BASE_WINDOW_SIZE,
)
});
// Store the scalar decomposition
let mut windows: ArrayVec<CellValue<pallas::Base>, NUM_WINDOWS> = ArrayVec::new();
let scalar_windows: Vec<Option<pallas::Base>> = if let Some(windows) = scalar_windows {
assert_eq!(windows.len(), NUM_WINDOWS);
windows
.into_iter()
.map(|window| Some(pallas::Base::from_u64(window as u64)))
.collect()
} else {
vec![None; NUM_WINDOWS]
};
for (idx, window) in scalar_windows.into_iter().enumerate() {
let window_cell = region.assign_advice(
|| format!("k[{:?}]", offset + idx),
self.window,
offset + idx,
|| window.ok_or(Error::SynthesisError),
)?;
windows.push(CellValue::new(window_cell, window));
}
Ok(windows)
}
}

View File

@ -1,27 +0,0 @@
use super::super::{EccConfig, EccScalarFixed};
use crate::constants::{L_ORCHARD_SCALAR, NUM_WINDOWS};
use halo2::{circuit::Region, plonk::Error};
use pasta_curves::pallas;
pub struct Config(super::Config);
impl From<&EccConfig> for Config {
fn from(ecc_config: &EccConfig) -> Self {
Self(ecc_config.into())
}
}
impl Config {
pub fn assign_region(
&self,
value: Option<pallas::Scalar>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<EccScalarFixed, Error> {
let windows = self
.0
.decompose_scalar_fixed::<NUM_WINDOWS, L_ORCHARD_SCALAR>(value, offset, region)?;
Ok(EccScalarFixed { value, windows })
}
}

View File

@ -1,98 +0,0 @@
use super::super::{CellValue, EccConfig, EccScalarFixedShort, Var};
use crate::constants::{L_VALUE, NUM_WINDOWS_SHORT};
use halo2::{
circuit::Region,
plonk::{ConstraintSystem, Error, Expression, Selector},
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
pub struct Config {
q_scalar_fixed_short: Selector,
super_config: super::Config,
}
impl From<&EccConfig> for Config {
fn from(ecc_config: &EccConfig) -> Self {
Self {
q_scalar_fixed_short: ecc_config.q_scalar_fixed_short,
super_config: ecc_config.into(),
}
}
}
impl Config {
pub fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// Check that sign is either 1 or -1.
// Check that last window is either 0 or 1.
meta.create_gate("Check sign and last window", |meta| {
let q_scalar_fixed_short = meta.query_selector(self.q_scalar_fixed_short);
let last_window = meta.query_advice(self.super_config.window, Rotation::prev());
let sign = meta.query_advice(self.super_config.window, Rotation::cur());
let one = Expression::Constant(pallas::Base::one());
let last_window_check = last_window.clone() * (one.clone() - last_window);
let sign_check = sign.clone() * sign - one;
vec![
q_scalar_fixed_short.clone() * last_window_check,
q_scalar_fixed_short * sign_check,
]
});
}
}
impl Config {
#[allow(clippy::op_ref)]
pub fn assign_region(
&self,
value: Option<pallas::Scalar>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<EccScalarFixedShort, Error> {
// Enable `q_scalar_fixed_short`
self.q_scalar_fixed_short
.enable(region, offset + NUM_WINDOWS_SHORT)?;
// Compute the scalar's sign and magnitude
let sign = value.map(|value| {
// t = (p - 1)/2
let t = (pallas::Scalar::zero() - &pallas::Scalar::one()) * &pallas::Scalar::TWO_INV;
if value > t {
-pallas::Scalar::one()
} else {
pallas::Scalar::one()
}
});
let magnitude = sign.zip(value).map(|(sign, value)| sign * &value);
// Decompose magnitude into `k`-bit windows
let windows = self
.super_config
.decompose_scalar_fixed::<NUM_WINDOWS_SHORT, L_VALUE>(magnitude, offset, region)?;
// Assign the sign and enable `q_scalar_fixed_short`
let sign = sign.map(|sign| {
assert!(sign == pallas::Scalar::one() || sign == -pallas::Scalar::one());
if sign == pallas::Scalar::one() {
pallas::Base::one()
} else {
-pallas::Base::one()
}
});
let sign_cell = region.assign_advice(
|| "sign",
self.super_config.window,
offset + NUM_WINDOWS_SHORT,
|| sign.ok_or(Error::SynthesisError),
)?;
Ok(EccScalarFixedShort {
magnitude,
sign: CellValue::<pallas::Base>::new(sign_cell, sign),
windows,
})
}
}