base_field_elem.rs: Support fixed-base mul using base field element.

In Orchard nullifier derivation, we multiply the fixed base
K^Orchard by a value encoded as a base field element. This commit
introduces an API that allows using a base field element as the
"scalar" in fixed-base scalar multiplication.

The API currently assumes that the base field element is output by
another instruction (i.e. there is no instruction to directly
witness it).
This commit is contained in:
therealyingtong 2021-06-18 17:41:13 +08:00
parent 37074c64f5
commit 09b4da197d
7 changed files with 599 additions and 82 deletions

View File

@ -9,6 +9,8 @@ use halo2::{
plonk::Error, plonk::Error,
}; };
use crate::circuit::gadget::utilities::CellValue;
pub mod chip; pub mod chip;
/// The set of circuit instructions required to use the ECC gadgets. /// The set of circuit instructions required to use the ECC gadgets.
@ -128,6 +130,16 @@ pub trait EccInstructions<C: CurveAffine>: Chip<C::Base> {
scalar: &Self::ScalarFixedShort, scalar: &Self::ScalarFixedShort,
base: &Self::FixedPointsShort, base: &Self::FixedPointsShort,
) -> Result<Self::Point, Error>; ) -> Result<Self::Point, 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
/// instruction.
fn mul_fixed_base_field_elem(
&self,
layouter: &mut impl Layouter<C::Base>,
base_field_elem: CellValue<C::Base>,
base: &Self::FixedPoints,
) -> Result<Self::Point, Error>;
} }
/// An element of the given elliptic curve's base field, that is used as a scalar /// An element of the given elliptic curve's base field, that is used as a scalar
@ -352,6 +364,21 @@ where
}) })
} }
/// Multiplies `self` using a value encoded in a base field element
/// as the scalar.
pub fn mul_base_field_elem(
&self,
mut layouter: impl Layouter<C::Base>,
by: CellValue<C::Base>,
) -> Result<Point<C, EccChip>, Error> {
self.chip
.mul_fixed_base_field_elem(&mut layouter, by, &self.inner)
.map(|inner| Point {
chip: self.chip.clone(),
inner,
})
}
/// Wraps the given fixed base (obtained directly from an instruction) in a gadget. /// 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::FixedPoints) -> Self {
FixedPoint { chip, inner } FixedPoint { chip, inner }
@ -521,18 +548,26 @@ mod tests {
// Test signed short fixed-base scalar multiplication // Test signed short fixed-base scalar multiplication
{ {
super::chip::mul_fixed::short::tests::test_mul_fixed_short( super::chip::mul_fixed::short::tests::test_mul_fixed_short(
chip, chip.clone(),
layouter.namespace(|| "signed short fixed-base scalar mul"), layouter.namespace(|| "signed short fixed-base scalar mul"),
)?; )?;
} }
// Test fixed-base scalar multiplication with a base field element
{
super::chip::mul_fixed::base_field_elem::tests::test_mul_fixed_base_field(
chip,
layouter.namespace(|| "fixed-base scalar mul with base field element"),
)?;
}
Ok(()) Ok(())
} }
} }
#[test] #[test]
fn ecc() { fn ecc() {
let k = 12; let k = 13;
let circuit = MyCircuit {}; let circuit = MyCircuit {};
let prover = MockProver::run(k, &circuit, vec![]).unwrap(); let prover = MockProver::run(k, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(())) assert_eq!(prover.verify(), Ok(()))

View File

@ -101,10 +101,11 @@ pub struct EccConfig {
pub q_mul_overflow: Selector, pub q_mul_overflow: Selector,
/// Fixed-base full-width scalar multiplication /// Fixed-base full-width scalar multiplication
pub q_mul_fixed: Selector, pub mul_fixed: Column<Fixed>,
/// Fixed-base signed short scalar multiplication /// Fixed-base signed short scalar multiplication
pub q_mul_fixed_short: Selector, pub q_mul_fixed_short: Selector,
/// Fixed-base multiplication using a base field element as the scalar
pub base_field_fixed: Column<Fixed>,
/// Witness point /// Witness point
pub q_point: Selector, pub q_point: Selector,
@ -174,8 +175,9 @@ impl EccChip {
q_init_z: meta.selector(), q_init_z: meta.selector(),
q_mul_z: meta.selector(), q_mul_z: meta.selector(),
q_mul_overflow: meta.selector(), q_mul_overflow: meta.selector(),
q_mul_fixed: meta.selector(), mul_fixed: meta.fixed_column(),
q_mul_fixed_short: meta.selector(), q_mul_fixed_short: meta.selector(),
base_field_fixed: meta.fixed_column(),
q_point: meta.selector(), q_point: meta.selector(),
q_scalar_fixed: meta.selector(), q_scalar_fixed: meta.selector(),
q_scalar_fixed_short: meta.selector(), q_scalar_fixed_short: meta.selector(),
@ -224,7 +226,7 @@ impl EccChip {
// and short multiplication. // and short multiplication.
{ {
let mul_fixed_config: mul_fixed::Config<{ constants::NUM_WINDOWS }> = (&config).into(); let mul_fixed_config: mul_fixed::Config<{ constants::NUM_WINDOWS }> = (&config).into();
mul_fixed_config.create_gate(meta); mul_fixed_config.create_gate_scalar(meta);
} }
// Create gate that is only used in short fixed-base scalar mul. // Create gate that is only used in short fixed-base scalar mul.
@ -234,6 +236,12 @@ impl EccChip {
short_config.create_gate(meta); short_config.create_gate(meta);
} }
// Create gate ths is only used in fixed-base mul using a base field element.
{
let base_field_config: mul_fixed::base_field_elem::Config = (&config).into();
base_field_config.create_gate(meta);
}
config config
} }
} }
@ -250,10 +258,10 @@ impl std::ops::Deref for EccScalarVar {
} }
/// A full-width scalar used for fixed-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. /// This is decomposed into 85 3-bit windows in little-endian order,
/// For example, if `window_width` = 3, we will have [k_0, k_1, ..., k_n] /// i.e. `windows` = [k_0, k_1, ..., k_84] (for a 255-bit scalar)
/// where `scalar = k_0 + k_1 * (2^3) + ... + k_n * (2^3)^n` and each `k_i` is /// where `scalar = k_0 + k_1 * (2^3) + ... + k_84 * (2^3)^84` and
/// in the range [0..2^3). /// each `k_i` is in the range [0..2^3).
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct EccScalarFixed { pub struct EccScalarFixed {
value: Option<pallas::Scalar>, value: Option<pallas::Scalar>,
@ -261,10 +269,13 @@ pub struct EccScalarFixed {
} }
/// A signed short scalar used for fixed-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. /// A short scalar must have magnitude in the range [0..2^64), with
/// For example, if `window_width` = 3, we will have [k_0, k_1, ..., k_n] /// a sign of either 1 or -1.
/// where `scalar = k_0 + k_1 * (2^3) + ... + k_n * (2^3)^n` and each `k_i` is /// This is decomposed into 22 3-bit windows in little-endian order,
/// in the range [0..2^3). /// i.e. `windows` = [k_0, k_1, ..., k_21] (for a 64-bit magnitude)
/// where `scalar = k_0 + k_1 * (2^3) + ... + k_84 * (2^3)^84` and
/// each `k_i` is in the range [0..2^3).
/// k_21 must be a single bit, i.e. 0 or 1.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct EccScalarFixedShort { pub struct EccScalarFixedShort {
magnitude: Option<pallas::Scalar>, magnitude: Option<pallas::Scalar>,
@ -272,6 +283,26 @@ pub struct EccScalarFixedShort {
windows: ArrayVec<CellValue<pallas::Base>, { constants::NUM_WINDOWS_SHORT }>, windows: ArrayVec<CellValue<pallas::Base>, { constants::NUM_WINDOWS_SHORT }>,
} }
/// A base field element used for fixed-base scalar multiplication.
/// This is decomposed into 3-bit windows in little-endian order
/// using a running sum `z`, where z_{i+1} = (z_i - a_i) / (2^3)
/// for element α = a_0 + (2^3) a_1 + ... + (2^{3(n-1)}) a_{n-1}.
/// Each `a_i` is in the range [0..2^3).
///
/// `windows` = [z_1, ..., z_85], where we expect z_85 = 0.
/// Since z_0 is initialized as α, we store it as `base_field_elem`.
#[derive(Clone, Debug)]
struct EccBaseFieldElemFixed {
base_field_elem: CellValue<pallas::Base>,
running_sum: ArrayVec<CellValue<pallas::Base>, { constants::NUM_WINDOWS }>,
}
impl EccBaseFieldElemFixed {
fn base_field_elem(&self) -> CellValue<pallas::Base> {
self.base_field_elem
}
}
impl EccInstructions<pallas::Affine> for EccChip { impl EccInstructions<pallas::Affine> for EccChip {
type ScalarFixed = EccScalarFixed; type ScalarFixed = EccScalarFixed;
type ScalarFixedShort = EccScalarFixedShort; type ScalarFixedShort = EccScalarFixedShort;
@ -426,4 +457,17 @@ impl EccInstructions<pallas::Affine> for EccChip {
|mut region| config.assign_region(scalar, base, 0, &mut region), |mut region| config.assign_region(scalar, base, 0, &mut region),
) )
} }
fn mul_fixed_base_field_elem(
&self,
layouter: &mut impl Layouter<pallas::Base>,
base_field_elem: CellValue<pallas::Base>,
base: &Self::FixedPoints,
) -> Result<Self::Point, Error> {
let config: mul_fixed::base_field_elem::Config = self.config().into();
layouter.assign_region(
|| format!("base field elem fixed-base mul of {:?}", base),
|mut region| config.assign_region(base_field_elem, *base, 0, &mut region),
)
}
} }

View File

@ -1,8 +1,6 @@
use std::array;
use super::{ use super::{
add, add_incomplete, copy, CellValue, EccConfig, EccPoint, EccScalarFixed, EccScalarFixedShort, add, add_incomplete, copy, CellValue, EccBaseFieldElemFixed, EccConfig, EccPoint,
Var, EccScalarFixed, EccScalarFixedShort, Var,
}; };
use crate::constants::{ use crate::constants::{
self, self,
@ -12,7 +10,9 @@ use crate::constants::{
use group::Curve; use group::Curve;
use halo2::{ use halo2::{
circuit::Region, circuit::Region,
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, Selector}, plonk::{
Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, VirtualCells,
},
poly::Rotation, poly::Rotation,
}; };
use pasta_curves::{ use pasta_curves::{
@ -20,6 +20,7 @@ use pasta_curves::{
pallas, pallas,
}; };
pub mod base_field_elem;
pub mod full_width; pub mod full_width;
pub mod short; pub mod short;
@ -67,9 +68,7 @@ impl OrchardFixedBases {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Config<const NUM_WINDOWS: usize> { pub struct Config<const NUM_WINDOWS: usize> {
// Selector used in both short and full-width fixed-base scalar mul. mul_fixed: Column<Fixed>,
q_mul_fixed: Selector,
// The fixed Lagrange interpolation coefficients for `x_p`. // The fixed Lagrange interpolation coefficients for `x_p`.
lagrange_coeffs: [Column<Fixed>; constants::H], lagrange_coeffs: [Column<Fixed>; constants::H],
// The fixed `z` for each window such that `y + z = u^2`. // The fixed `z` for each window such that `y + z = u^2`.
@ -94,7 +93,7 @@ pub struct Config<const NUM_WINDOWS: usize> {
impl<const NUM_WINDOWS: usize> From<&EccConfig> for Config<NUM_WINDOWS> { impl<const NUM_WINDOWS: usize> From<&EccConfig> for Config<NUM_WINDOWS> {
fn from(ecc_config: &EccConfig) -> Self { fn from(ecc_config: &EccConfig) -> Self {
let config = Self { let config = Self {
q_mul_fixed: ecc_config.q_mul_fixed, mul_fixed: ecc_config.mul_fixed,
lagrange_coeffs: ecc_config.lagrange_coeffs, lagrange_coeffs: ecc_config.lagrange_coeffs,
fixed_z: ecc_config.fixed_z, fixed_z: ecc_config.fixed_z,
x_p: ecc_config.advices[0], x_p: ecc_config.advices[0],
@ -141,40 +140,50 @@ impl<const NUM_WINDOWS: usize> From<&EccConfig> for Config<NUM_WINDOWS> {
} }
impl<const NUM_WINDOWS: usize> 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_fixed(self.mul_fixed, Rotation::cur());
let window = meta.query_advice(self.window, Rotation::cur());
self.coords_check(meta, mul_fixed, window)
},
)
}
#[allow(clippy::op_ref)] #[allow(clippy::op_ref)]
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) { fn coords_check(
meta.create_gate("Fixed-base scalar mul gate", |meta| { &self,
let q_mul_fixed = meta.query_selector(self.q_mul_fixed); meta: &mut VirtualCells<'_, pallas::Base>,
let y_p = meta.query_advice(self.y_p, Rotation::cur()); toggle: Expression<pallas::Base>,
window: Expression<pallas::Base>,
) -> Vec<Expression<pallas::Base>> {
let y_p = meta.query_advice(self.y_p, Rotation::cur());
let x_p = meta.query_advice(self.x_p, Rotation::cur());
let z = meta.query_fixed(self.fixed_z, Rotation::cur());
let u = meta.query_advice(self.u, Rotation::cur());
let window = meta.query_advice(self.window, Rotation::cur()); let window_pow: Vec<Expression<pallas::Base>> = (0..constants::H)
let x_p = meta.query_advice(self.x_p, Rotation::cur()); .map(|pow| {
(0..pow).fold(Expression::Constant(pallas::Base::one()), |acc, _| {
let z = meta.query_fixed(self.fixed_z, Rotation::cur()); acc * window.clone()
let u = meta.query_advice(self.u, Rotation::cur());
let window_pow: Vec<Expression<pallas::Base>> = (0..constants::H)
.map(|pow| {
(0..pow).fold(Expression::Constant(pallas::Base::one()), |acc, _| {
acc * window.clone()
})
}) })
.collect(); })
.collect();
let interpolated_x = window_pow.iter().zip(self.lagrange_coeffs.iter()).fold( let interpolated_x = window_pow.iter().zip(self.lagrange_coeffs.iter()).fold(
Expression::Constant(pallas::Base::zero()), Expression::Constant(pallas::Base::zero()),
|acc, (window_pow, coeff)| { |acc, (window_pow, coeff)| {
acc + (window_pow.clone() * meta.query_fixed(*coeff, Rotation::cur())) acc + (window_pow.clone() * meta.query_fixed(*coeff, Rotation::cur()))
}, },
); );
// Check interpolation of x-coordinate // Check interpolation of x-coordinate
let x_check = interpolated_x - x_p; let x_check = interpolated_x - x_p;
// Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed // Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed
let y_check = u.clone() * u - y_p - z; let y_check = u.clone() * u - y_p - z;
array::IntoIter::new([x_check, y_check]).map(move |poly| q_mul_fixed.clone() * poly) vec![toggle.clone() * x_check, toggle * y_check]
});
} }
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
@ -184,12 +193,10 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
offset: usize, offset: usize,
scalar: &ScalarFixed, scalar: &ScalarFixed,
base: OrchardFixedBases, base: OrchardFixedBases,
coords_check_toggle: Column<Fixed>,
) -> Result<(EccPoint, EccPoint), Error> { ) -> Result<(EccPoint, EccPoint), Error> {
// Assign fixed columns for given fixed base // Assign fixed columns for given fixed base
self.assign_fixed_constants(region, offset, base)?; self.assign_fixed_constants(region, offset, base, coords_check_toggle)?;
// Copy the scalar decomposition
self.copy_scalar(region, offset, scalar)?;
// Initialize accumulator // Initialize accumulator
let acc = self.initialize_accumulator(region, offset, base, scalar)?; let acc = self.initialize_accumulator(region, offset, base, scalar)?;
@ -208,6 +215,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
region: &mut Region<'_, pallas::Base>, region: &mut Region<'_, pallas::Base>,
offset: usize, offset: usize,
base: OrchardFixedBases, base: OrchardFixedBases,
fixed_column: Column<Fixed>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let (lagrange_coeffs, z) = match base { let (lagrange_coeffs, z) = match base {
OrchardFixedBases::ValueCommitV => { OrchardFixedBases::ValueCommitV => {
@ -230,8 +238,12 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
// Assign fixed columns for given fixed base // Assign fixed columns for given fixed base
for window in 0..NUM_WINDOWS { for window in 0..NUM_WINDOWS {
// Enable `q_mul_fixed` selector region.assign_fixed(
self.q_mul_fixed.enable(region, window + offset)?; || "Enable coords check",
fixed_column,
window + offset,
|| Ok(pallas::Base::one()),
)?;
// Assign x-coordinate Lagrange interpolation coefficients // Assign x-coordinate Lagrange interpolation coefficients
for k in 0..(constants::H) { for k in 0..(constants::H) {
@ -483,6 +495,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
enum ScalarFixed { enum ScalarFixed {
FullWidth(EccScalarFixed), FullWidth(EccScalarFixed),
Short(EccScalarFixedShort), Short(EccScalarFixedShort),
BaseFieldElem(EccBaseFieldElemFixed),
} }
impl From<&EccScalarFixed> for ScalarFixed { impl From<&EccScalarFixed> for ScalarFixed {
@ -497,33 +510,66 @@ impl From<&EccScalarFixedShort> for ScalarFixed {
} }
} }
impl From<&EccBaseFieldElemFixed> for ScalarFixed {
fn from(base_field_elem: &EccBaseFieldElemFixed) -> Self {
Self::BaseFieldElem(base_field_elem.clone())
}
}
impl ScalarFixed { impl ScalarFixed {
fn windows(&self) -> &[CellValue<pallas::Base>] { fn windows(&self) -> &[CellValue<pallas::Base>] {
match self { match self {
ScalarFixed::FullWidth(scalar) => &scalar.windows, ScalarFixed::FullWidth(scalar) => &scalar.windows,
ScalarFixed::Short(scalar) => &scalar.windows, ScalarFixed::Short(scalar) => &scalar.windows,
_ => unreachable!("The base field element is not witnessed as windows."),
} }
} }
// The scalar decomposition was done in the base field. For computation // The scalar decomposition was done in the base field. For computation
// outside the circuit, we now convert them back into the scalar field. // outside the circuit, we now convert them back into the scalar field.
fn windows_field(&self) -> Vec<Option<pallas::Scalar>> { fn windows_field(&self) -> Vec<Option<pallas::Scalar>> {
self.windows() match self {
.iter() Self::BaseFieldElem(scalar) => {
.map(|bits| { let mut zs = vec![scalar.base_field_elem];
bits.value() zs.extend_from_slice(&scalar.running_sum);
.map(|value| pallas::Scalar::from_bytes(&value.to_bytes()).unwrap())
}) (0..(zs.len() - 1))
.collect::<Vec<_>>() .map(|idx| {
let z_cur = zs[idx].value();
let z_next = zs[idx + 1].value();
let word = z_cur.zip(z_next).map(|(z_cur, z_next)| {
z_cur - z_next * pallas::Base::from_u64(constants::H as u64)
});
word.map(|word| pallas::Scalar::from_bytes(&word.to_bytes()).unwrap())
})
.collect::<Vec<_>>()
}
_ => self
.windows()
.iter()
.map(|bits| {
bits.value()
.map(|value| pallas::Scalar::from_bytes(&value.to_bytes()).unwrap())
})
.collect::<Vec<_>>(),
}
} }
// The scalar decomposition is guaranteed to be in three-bit windows, // The scalar decomposition is guaranteed to be in three-bit windows,
// so we also cast the least significant byte in their serialisation // so we also cast the least significant byte in their serialisation
// into usize for convenient indexing into `u`-values // into usize for convenient indexing into `u`-values
fn windows_usize(&self) -> Vec<Option<usize>> { fn windows_usize(&self) -> Vec<Option<usize>> {
self.windows() self.windows_field()
.iter() .iter()
.map(|bits| bits.value().map(|value| value.to_bytes()[0] as usize)) .map(|window| {
if let Some(window) = window {
let window = window.to_bytes()[0] as usize;
assert!(window < constants::H);
Some(window)
} else {
None
}
})
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
} }

View File

@ -0,0 +1,375 @@
use super::super::{EccBaseFieldElemFixed, EccConfig, EccPoint, OrchardFixedBasesFull};
use crate::{
circuit::gadget::utilities::{copy, CellValue, Var},
constants::{self, util::decompose_scalar_fixed, NUM_WINDOWS},
};
use halo2::{
circuit::Region,
plonk::{Column, ConstraintSystem, Error, Expression, Fixed},
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use arrayvec::ArrayVec;
pub struct Config {
base_field_fixed: Column<Fixed>,
super_config: super::Config<{ constants::NUM_WINDOWS }>,
}
impl From<&EccConfig> for Config {
fn from(config: &EccConfig) -> Self {
Self {
base_field_fixed: config.base_field_fixed,
super_config: config.into(),
}
}
}
impl Config {
pub fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// Decompose the base field element α into three-bit windows
// using a running sum `z`, where z_{i+1} = (z_i - a_i) / (2^3)
// for α = a_0 + (2^3) a_1 + ... + (2^{3(n-1)}) a_{n-1}.
// For a Pallas base field element (255 bits), n = 255 / 3 = 85.
//
// We set z_0 = α, which implies:
// z_1 = (α - a_0) / 2^3, (subtract the lowest 3 bits)
// = a_1 + (2^3) a_2 + ... + 2^{3(n-1)} a_{n-1},
// z_2 = (z_1 - a_1) / 2^3
// = a_2 + (2^3) a_3 + ... + 2^{3(n-2)} a_{n-1},
// ...,
// z_{n-1} = a_{n-1}
// z_n = (z_{n-1} - a_{n-1}) / 2^3
// = 0.
//
// This gate checks that each a_i = z_i - z_{i+1} * (2^3) is within
// 3 bits.
meta.create_gate("Decompose base field element", |meta| {
// This gate is activated when base_field_fixed = 1
let fixed_is_one = {
let base_field_fixed = meta.query_fixed(self.base_field_fixed, Rotation::cur());
let two = Expression::Constant(pallas::Base::from_u64(2));
base_field_fixed.clone() * (two - base_field_fixed)
};
let z_cur = meta.query_advice(self.super_config.window, Rotation::cur());
let z_next = meta.query_advice(self.super_config.window, Rotation::next());
// z_{i+1} = (z_i - a_i) / (2^3)
// => a_i = z_i - (z_{i+1} * (2^3))
let word = z_cur - z_next * pallas::Base::from_u64(constants::H as u64);
// (word - 7) * (word - 6) * ... * (word - 1) * word = 0
let range_check =
(0..constants::H).fold(Expression::Constant(pallas::Base::one()), |acc, i| {
acc * (word.clone() - Expression::Constant(pallas::Base::from_u64(i as u64)))
});
vec![fixed_is_one * range_check]
});
meta.create_gate("x_p, y_p checks for BaseFieldElemFixed", |meta| {
// This gate is activated when base_field_fixed = 1
let fixed_is_one = {
let base_field_fixed = meta.query_fixed(self.base_field_fixed, Rotation::cur());
let two = Expression::Constant(pallas::Base::from_u64(2));
base_field_fixed.clone() * (base_field_fixed - two)
};
let z_cur = meta.query_advice(self.super_config.window, Rotation::cur());
let z_next = meta.query_advice(self.super_config.window, Rotation::next());
let window = z_cur - z_next * pallas::Base::from_u64(constants::H as u64);
self.super_config.coords_check(meta, fixed_is_one, window)
});
// Check that we get z_85 = 0 as the final output of the running sum.
meta.create_gate("z_85 = 0", |meta| {
// This gate is activated when base_field_fixed = 2
let fixed_is_two = {
let base_field_fixed = meta.query_fixed(self.base_field_fixed, Rotation::cur());
let one = Expression::Constant(pallas::Base::one());
base_field_fixed.clone() * (base_field_fixed - one)
};
let z_85 = meta.query_advice(self.super_config.window, Rotation::cur());
vec![fixed_is_two * z_85]
});
}
pub fn assign_region(
&self,
scalar: CellValue<pallas::Base>,
base: OrchardFixedBasesFull,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<EccPoint, Error> {
// Decompose scalar
let scalar = self.decompose_base_field_elem(scalar, offset, region)?;
let (acc, mul_b) = self.super_config.assign_region_inner(
region,
offset,
&(&scalar).into(),
base.into(),
self.base_field_fixed,
)?;
// Increase offset by 1 because the running sum decomposition takes
// up 86 rows (1 more than the number of windows.)
let offset = offset + 1;
// Add to the accumulator and return the final result as `[scalar]B`.
let result = self.super_config.add_config.assign_region(
&mul_b,
&acc,
offset + NUM_WINDOWS,
region,
)?;
#[cfg(test)]
// Check that the correct multiple is obtained.
{
use group::Curve;
let base: super::OrchardFixedBases = base.into();
let scalar = &scalar
.base_field_elem()
.value()
.map(|scalar| pallas::Scalar::from_bytes(&scalar.to_bytes()).unwrap());
let real_mul = scalar.map(|scalar| base.generator() * scalar);
let result = result.point();
if let (Some(real_mul), Some(result)) = (real_mul, result) {
assert_eq!(real_mul.to_affine(), result);
}
}
Ok(result)
}
fn decompose_base_field_elem(
&self,
base_field_elem: CellValue<pallas::Base>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<EccBaseFieldElemFixed, Error> {
// Decompose base field element into 3-bit words.
let words: Vec<Option<u8>> = {
let words = base_field_elem.value().map(|base_field_elem| {
decompose_scalar_fixed::<pallas::Base>(
base_field_elem,
constants::L_ORCHARD_BASE,
constants::FIXED_BASE_WINDOW_SIZE,
)
});
if let Some(words) = words {
words.into_iter().map(Some).collect()
} else {
vec![None; constants::NUM_WINDOWS]
}
};
// Initialize empty ArrayVec to store running sum values [z_1, ..., z_85].
let mut running_sum: ArrayVec<CellValue<pallas::Base>, { constants::NUM_WINDOWS }> =
ArrayVec::new();
// Assign running sum `z_i`, i = 0..=n, where z_{i+1} = (z_i - a_i) / (2^3)
// and `z_0` is initialized as `base_field_elem`.
let mut z = copy(
region,
|| "z_0 = base_field_elem",
self.super_config.window,
offset,
&base_field_elem,
&self.super_config.perm,
)?;
for idx in 0..words.len() {
region.assign_fixed(
|| "Decomposition check",
self.base_field_fixed,
offset + idx,
|| Ok(pallas::Base::from_u64(1)),
)?;
}
let offset = offset + 1;
let eight_inv = pallas::Base::TWO_INV.square() * pallas::Base::TWO_INV;
for (idx, word) in words.iter().enumerate() {
// z_next = (z_cur - word) / (2^3)
let z_next = {
let word = word.map(|word| pallas::Base::from_u64(word as u64));
let z_next_val = z
.value()
.zip(word)
.map(|(z_cur_val, word)| (z_cur_val - word) * eight_inv);
let cell = region.assign_advice(
|| format!("word {:?}", idx),
self.super_config.window,
offset + idx,
|| z_next_val.ok_or(Error::SynthesisError),
)?;
CellValue::new(cell, z_next_val)
};
// Update `z`.
z = z_next;
running_sum.push(z);
}
let offset = offset + words.len() - 1;
region.assign_fixed(
|| "Check z_85 = 0",
self.base_field_fixed,
offset,
|| Ok(pallas::Base::from_u64(2)),
)?;
Ok(EccBaseFieldElemFixed {
base_field_elem,
running_sum,
})
}
}
#[cfg(test)]
pub mod tests {
use halo2::{
circuit::{Chip, Layouter},
plonk::Error,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use crate::circuit::gadget::{
ecc::{
chip::{EccChip, OrchardFixedBasesFull},
FixedPoint,
},
utilities::{CellValue, UtilitiesInstructions},
};
use crate::constants;
pub fn test_mul_fixed_base_field(
chip: EccChip,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), Error> {
impl UtilitiesInstructions<pallas::Base> for EccChip {
type Var = CellValue<pallas::Base>;
}
// commit_ivk_r
let commit_ivk_r = OrchardFixedBasesFull::CommitIvkR;
let commit_ivk_r = FixedPoint::from_inner(chip.clone(), commit_ivk_r);
test_single_base(
chip.clone(),
layouter.namespace(|| "commit_ivk_r"),
commit_ivk_r,
)?;
// note_commit_r
let note_commit_r = OrchardFixedBasesFull::NoteCommitR;
let note_commit_r = FixedPoint::from_inner(chip.clone(), note_commit_r);
test_single_base(
chip.clone(),
layouter.namespace(|| "note_commit_r"),
note_commit_r,
)?;
// nullifier_k
let nullifier_k = OrchardFixedBasesFull::NullifierK;
let nullifier_k = FixedPoint::from_inner(chip.clone(), nullifier_k);
test_single_base(
chip.clone(),
layouter.namespace(|| "nullifier_k"),
nullifier_k,
)?;
// value_commit_r
let value_commit_r = OrchardFixedBasesFull::ValueCommitR;
let value_commit_r = FixedPoint::from_inner(chip.clone(), value_commit_r);
test_single_base(
chip.clone(),
layouter.namespace(|| "value_commit_r"),
value_commit_r,
)?;
// spend_auth_g
let spend_auth_g = OrchardFixedBasesFull::SpendAuthG;
let spend_auth_g = FixedPoint::from_inner(chip.clone(), spend_auth_g);
test_single_base(chip, layouter.namespace(|| "spend_auth_g"), spend_auth_g)?;
Ok(())
}
#[allow(clippy::op_ref)]
fn test_single_base(
chip: EccChip,
mut layouter: impl Layouter<pallas::Base>,
base: FixedPoint<pallas::Affine, EccChip>,
) -> Result<(), Error> {
let column = chip.config().advices[0];
// [a]B
{
let scalar_fixed = pallas::Base::rand();
let scalar_fixed = chip.load_private(
layouter.namespace(|| "witness random base field element"),
column,
Some(scalar_fixed),
)?;
base.mul_base_field_elem(layouter.namespace(|| "mul"), scalar_fixed)?;
}
// There is a single canonical sequence of window values for which a doubling occurs on the last step:
// 1333333333333333333333333333333333333333333333333333333333333333333333333333333333334 in octal.
// (There is another *non-canonical* sequence
// 5333333333333333333333333333333333333333332711161673731021062440252244051273333333333 in octal.)
{
let h = pallas::Base::from_u64(constants::H as u64);
let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334"
.chars()
.fold(pallas::Base::zero(), |acc, c| {
acc * &h + &pallas::Base::from_u64(c.to_digit(8).unwrap().into())
});
let scalar_fixed = chip.load_private(
layouter.namespace(|| "mul with double"),
column,
Some(scalar_fixed),
)?;
base.mul_base_field_elem(layouter.namespace(|| "mul with double"), scalar_fixed)?;
}
// [0]B should return (0,0) since it uses complete addition
// on the last step.
{
let scalar_fixed = pallas::Base::zero();
let scalar_fixed = chip.load_private(
layouter.namespace(|| "mul by zero"),
column,
Some(scalar_fixed),
)?;
base.mul_base_field_elem(layouter.namespace(|| "mul by zero"), scalar_fixed)?;
}
// [-1]B is the largest base field element
{
let scalar_fixed = -pallas::Base::one();
let scalar_fixed = chip.load_private(
layouter.namespace(|| "mul by -1"),
column,
Some(scalar_fixed),
)?;
base.mul_base_field_elem(layouter.namespace(|| "mul by -1"), scalar_fixed)?;
}
Ok(())
}
}

View File

@ -19,9 +19,16 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
offset: usize, offset: usize,
region: &mut Region<'_, pallas::Base>, region: &mut Region<'_, pallas::Base>,
) -> Result<EccPoint, Error> { ) -> Result<EccPoint, Error> {
let (acc, mul_b) = // Copy the scalar decomposition
self.0 self.0.copy_scalar(region, offset, &scalar.into())?;
.assign_region_inner(region, offset, &scalar.into(), base.into())?;
let (acc, mul_b) = self.0.assign_region_inner(
region,
offset,
&scalar.into(),
base.into(),
self.0.mul_fixed,
)?;
// Add to the accumulator and return the final result as `[scalar]B`. // Add to the accumulator and return the final result as `[scalar]B`.
let result = self let result = self
@ -33,13 +40,9 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
// Check that the correct multiple is obtained. // Check that the correct multiple is obtained.
{ {
use group::Curve; use group::Curve;
use halo2::arithmetic::FieldExt;
let base: super::OrchardFixedBases = base.into(); let base: super::OrchardFixedBases = base.into();
let scalar = scalar let real_mul = scalar.value.map(|scalar| base.generator() * scalar);
.value
.map(|scalar| pallas::Scalar::from_bytes(&scalar.to_bytes()).unwrap());
let real_mul = scalar.map(|scalar| base.generator() * scalar);
let result = result.point(); let result = result.point();
if let (Some(real_mul), Some(result)) = (real_mul, result) { if let (Some(real_mul), Some(result)) = (real_mul, result) {
@ -159,13 +162,24 @@ pub mod tests {
{ {
let scalar_fixed = pallas::Scalar::zero(); let scalar_fixed = pallas::Scalar::zero();
let scalar_fixed = ScalarFixed::new( let scalar_fixed = ScalarFixed::new(
chip, chip.clone(),
layouter.namespace(|| "ScalarFixed"), layouter.namespace(|| "ScalarFixed"),
Some(scalar_fixed), Some(scalar_fixed),
)?; )?;
base.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)?; base.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)?;
} }
// [-1]B is the largest scalar field element.
{
let scalar_fixed = -pallas::Scalar::one();
let scalar_fixed = ScalarFixed::new(
chip,
layouter.namespace(|| "ScalarFixed"),
Some(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "mul by -1"), &scalar_fixed)?;
}
Ok(()) Ok(())
} }
} }

View File

@ -58,11 +58,16 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
offset: usize, offset: usize,
region: &mut Region<'_, pallas::Base>, region: &mut Region<'_, pallas::Base>,
) -> Result<EccPoint, Error> { ) -> Result<EccPoint, Error> {
// Copy the scalar decomposition
self.super_config
.copy_scalar(region, offset, &scalar.into())?;
let (acc, mul_b) = self.super_config.assign_region_inner( let (acc, mul_b) = self.super_config.assign_region_inner(
region, region,
offset, offset,
&scalar.into(), &scalar.into(),
base.clone().into(), base.clone().into(),
self.super_config.mul_fixed,
)?; )?;
// Add to the cumulative sum to get `[magnitude]B`. // Add to the cumulative sum to get `[magnitude]B`.

View File

@ -1,10 +1,9 @@
use super::{CellValue, EccConfig, Var}; use super::{CellValue, EccConfig, Var};
use crate::constants::{self, util}; use crate::constants::{self, util};
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use ff::PrimeFieldBits;
use halo2::{ use halo2::{
circuit::Region, circuit::Region,
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector},
poly::Rotation, poly::Rotation,
}; };
use pasta_curves::{arithmetic::FieldExt, pallas}; use pasta_curves::{arithmetic::FieldExt, pallas};
@ -16,13 +15,15 @@ pub struct Config {
q_scalar_fixed: Selector, q_scalar_fixed: Selector,
// Decomposition of scalar into `k`-bit windows. // Decomposition of scalar into `k`-bit windows.
window: Column<Advice>, window: Column<Advice>,
perm: Permutation,
} }
impl From<&EccConfig> for Config { impl From<&EccConfig> for Config {
fn from(ecc_config: &EccConfig) -> Self { fn from(ecc_config: &EccConfig) -> Self {
Self { Self {
q_scalar_fixed: ecc_config.q_scalar_fixed, q_scalar_fixed: ecc_config.q_scalar_fixed,
window: ecc_config.advices[0], window: ecc_config.advices[9],
perm: ecc_config.perm.clone(),
} }
} }
} }
@ -48,10 +49,7 @@ impl Config {
scalar: Option<pallas::Scalar>, scalar: Option<pallas::Scalar>,
offset: usize, offset: usize,
region: &mut Region<'_, pallas::Base>, region: &mut Region<'_, pallas::Base>,
) -> Result<ArrayVec<CellValue<pallas::Base>, NUM_WINDOWS>, Error> ) -> Result<ArrayVec<CellValue<pallas::Base>, NUM_WINDOWS>, Error> {
where
pallas::Scalar: PrimeFieldBits,
{
// Enable `q_scalar_fixed` selector // Enable `q_scalar_fixed` selector
for idx in 0..NUM_WINDOWS { for idx in 0..NUM_WINDOWS {
self.q_scalar_fixed.enable(region, offset + idx)?; self.q_scalar_fixed.enable(region, offset + idx)?;