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,
};
use crate::circuit::gadget::utilities::CellValue;
pub mod chip;
/// The set of circuit instructions required to use the ECC gadgets.
@ -128,6 +130,16 @@ pub trait EccInstructions<C: CurveAffine>: Chip<C::Base> {
scalar: &Self::ScalarFixedShort,
base: &Self::FixedPointsShort,
) -> 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
@ -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.
pub fn from_inner(chip: EccChip, inner: EccChip::FixedPoints) -> Self {
FixedPoint { chip, inner }
@ -521,18 +548,26 @@ mod tests {
// Test signed short fixed-base scalar multiplication
{
super::chip::mul_fixed::short::tests::test_mul_fixed_short(
chip,
chip.clone(),
layouter.namespace(|| "signed short fixed-base scalar mul"),
)?;
}
// Test fixed-base scalar multiplication with a base field element
{
super::chip::mul_fixed::base_field_elem::tests::test_mul_fixed_base_field(
chip,
layouter.namespace(|| "fixed-base scalar mul with base field element"),
)?;
}
Ok(())
}
}
#[test]
fn ecc() {
let k = 12;
let k = 13;
let circuit = MyCircuit {};
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()))

View File

@ -101,10 +101,11 @@ pub struct EccConfig {
pub q_mul_overflow: Selector,
/// Fixed-base full-width scalar multiplication
pub q_mul_fixed: Selector,
pub mul_fixed: Column<Fixed>,
/// Fixed-base signed short scalar multiplication
pub q_mul_fixed_short: Selector,
/// Fixed-base multiplication using a base field element as the scalar
pub base_field_fixed: Column<Fixed>,
/// Witness point
pub q_point: Selector,
@ -174,8 +175,9 @@ impl EccChip {
q_init_z: meta.selector(),
q_mul_z: meta.selector(),
q_mul_overflow: meta.selector(),
q_mul_fixed: meta.selector(),
mul_fixed: meta.fixed_column(),
q_mul_fixed_short: meta.selector(),
base_field_fixed: meta.fixed_column(),
q_point: meta.selector(),
q_scalar_fixed: meta.selector(),
q_scalar_fixed_short: meta.selector(),
@ -224,7 +226,7 @@ impl EccChip {
// and short multiplication.
{
let mul_fixed_config: mul_fixed::Config<{ constants::NUM_WINDOWS }> = (&config).into();
mul_fixed_config.create_gate(meta);
mul_fixed_config.create_gate_scalar(meta);
}
// Create gate that is only used in short fixed-base scalar mul.
@ -234,6 +236,12 @@ impl EccChip {
short_config.create_gate(meta);
}
// Create gate ths is only used in fixed-base mul using a base field element.
{
let base_field_config: mul_fixed::base_field_elem::Config = (&config).into();
base_field_config.create_gate(meta);
}
config
}
}
@ -250,10 +258,10 @@ impl std::ops::Deref for EccScalarVar {
}
/// A full-width scalar used for fixed-base scalar multiplication.
/// This is decomposed in chunks of `window_width` bits in little-endian order.
/// For example, if `window_width` = 3, we will have [k_0, k_1, ..., k_n]
/// where `scalar = k_0 + k_1 * (2^3) + ... + k_n * (2^3)^n` and each `k_i` is
/// in the range [0..2^3).
/// This is decomposed into 85 3-bit windows in little-endian order,
/// i.e. `windows` = [k_0, k_1, ..., k_84] (for a 255-bit scalar)
/// where `scalar = k_0 + k_1 * (2^3) + ... + k_84 * (2^3)^84` and
/// each `k_i` is in the range [0..2^3).
#[derive(Clone, Debug)]
pub struct EccScalarFixed {
value: Option<pallas::Scalar>,
@ -261,10 +269,13 @@ pub struct EccScalarFixed {
}
/// A signed short scalar used for fixed-base scalar multiplication.
/// This is decomposed in chunks of `window_width` bits in little-endian order.
/// For example, if `window_width` = 3, we will have [k_0, k_1, ..., k_n]
/// where `scalar = k_0 + k_1 * (2^3) + ... + k_n * (2^3)^n` and each `k_i` is
/// in the range [0..2^3).
/// A short scalar must have magnitude in the range [0..2^64), with
/// a sign of either 1 or -1.
/// This is decomposed into 22 3-bit windows in little-endian order,
/// i.e. `windows` = [k_0, k_1, ..., k_21] (for a 64-bit magnitude)
/// where `scalar = k_0 + k_1 * (2^3) + ... + k_84 * (2^3)^84` and
/// each `k_i` is in the range [0..2^3).
/// k_21 must be a single bit, i.e. 0 or 1.
#[derive(Clone, Debug)]
pub struct EccScalarFixedShort {
magnitude: Option<pallas::Scalar>,
@ -272,6 +283,26 @@ pub struct EccScalarFixedShort {
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 {
type ScalarFixed = EccScalarFixed;
type ScalarFixedShort = EccScalarFixedShort;
@ -426,4 +457,17 @@ impl EccInstructions<pallas::Affine> for EccChip {
|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::{
add, add_incomplete, copy, CellValue, EccConfig, EccPoint, EccScalarFixed, EccScalarFixedShort,
Var,
add, add_incomplete, copy, CellValue, EccBaseFieldElemFixed, EccConfig, EccPoint,
EccScalarFixed, EccScalarFixedShort, Var,
};
use crate::constants::{
self,
@ -12,7 +10,9 @@ use crate::constants::{
use group::Curve;
use halo2::{
circuit::Region,
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, Selector},
plonk::{
Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, VirtualCells,
},
poly::Rotation,
};
use pasta_curves::{
@ -20,6 +20,7 @@ use pasta_curves::{
pallas,
};
pub mod base_field_elem;
pub mod full_width;
pub mod short;
@ -67,9 +68,7 @@ impl OrchardFixedBases {
#[derive(Clone, Debug)]
pub struct Config<const NUM_WINDOWS: usize> {
// Selector used in both short and full-width fixed-base scalar mul.
q_mul_fixed: Selector,
mul_fixed: Column<Fixed>,
// 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`.
@ -94,7 +93,7 @@ pub struct Config<const NUM_WINDOWS: usize> {
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,
mul_fixed: ecc_config.mul_fixed,
lagrange_coeffs: ecc_config.lagrange_coeffs,
fixed_z: ecc_config.fixed_z,
x_p: ecc_config.advices[0],
@ -141,40 +140,50 @@ impl<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_fixed(self.mul_fixed, Rotation::cur());
let window = meta.query_advice(self.window, Rotation::cur());
self.coords_check(meta, mul_fixed, window)
},
)
}
#[allow(clippy::op_ref)]
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
meta.create_gate("Fixed-base scalar mul gate", |meta| {
let q_mul_fixed = meta.query_selector(self.q_mul_fixed);
let y_p = meta.query_advice(self.y_p, Rotation::cur());
fn coords_check(
&self,
meta: &mut VirtualCells<'_, pallas::Base>,
toggle: Expression<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 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<Expression<pallas::Base>> = (0..constants::H)
.map(|pow| {
(0..pow).fold(Expression::Constant(pallas::Base::one()), |acc, _| {
acc * window.clone()
})
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(
Expression::Constant(pallas::Base::zero()),
|acc, (window_pow, coeff)| {
acc + (window_pow.clone() * meta.query_fixed(*coeff, Rotation::cur()))
},
);
let interpolated_x = window_pow.iter().zip(self.lagrange_coeffs.iter()).fold(
Expression::Constant(pallas::Base::zero()),
|acc, (window_pow, coeff)| {
acc + (window_pow.clone() * meta.query_fixed(*coeff, Rotation::cur()))
},
);
// Check interpolation of x-coordinate
let x_check = interpolated_x - x_p;
// Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed
let y_check = u.clone() * u - y_p - z;
// Check interpolation of x-coordinate
let x_check = interpolated_x - x_p;
// Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed
let y_check = u.clone() * u - y_p - z;
array::IntoIter::new([x_check, y_check]).map(move |poly| q_mul_fixed.clone() * poly)
});
vec![toggle.clone() * x_check, toggle * y_check]
}
#[allow(clippy::type_complexity)]
@ -184,12 +193,10 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
offset: usize,
scalar: &ScalarFixed,
base: OrchardFixedBases,
coords_check_toggle: Column<Fixed>,
) -> Result<(EccPoint, EccPoint), Error> {
// Assign fixed columns for given fixed base
self.assign_fixed_constants(region, offset, base)?;
// Copy the scalar decomposition
self.copy_scalar(region, offset, scalar)?;
self.assign_fixed_constants(region, offset, base, coords_check_toggle)?;
// Initialize accumulator
let acc = self.initialize_accumulator(region, offset, base, scalar)?;
@ -208,6 +215,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
region: &mut Region<'_, pallas::Base>,
offset: usize,
base: OrchardFixedBases,
fixed_column: Column<Fixed>,
) -> Result<(), Error> {
let (lagrange_coeffs, z) = match base {
OrchardFixedBases::ValueCommitV => {
@ -230,8 +238,12 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
// Assign fixed columns for given fixed base
for window in 0..NUM_WINDOWS {
// Enable `q_mul_fixed` selector
self.q_mul_fixed.enable(region, window + offset)?;
region.assign_fixed(
|| "Enable coords check",
fixed_column,
window + offset,
|| Ok(pallas::Base::one()),
)?;
// Assign x-coordinate Lagrange interpolation coefficients
for k in 0..(constants::H) {
@ -483,6 +495,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
enum ScalarFixed {
FullWidth(EccScalarFixed),
Short(EccScalarFixedShort),
BaseFieldElem(EccBaseFieldElemFixed),
}
impl From<&EccScalarFixed> for ScalarFixed {
@ -497,33 +510,66 @@ impl From<&EccScalarFixedShort> for ScalarFixed {
}
}
impl From<&EccBaseFieldElemFixed> for ScalarFixed {
fn from(base_field_elem: &EccBaseFieldElemFixed) -> Self {
Self::BaseFieldElem(base_field_elem.clone())
}
}
impl ScalarFixed {
fn windows(&self) -> &[CellValue<pallas::Base>] {
match self {
ScalarFixed::FullWidth(scalar) => &scalar.windows,
ScalarFixed::Short(scalar) => &scalar.windows,
_ => unreachable!("The base field element is not witnessed as windows."),
}
}
// The scalar decomposition was done in the base field. For computation
// outside the circuit, we now convert them back into the scalar field.
fn windows_field(&self) -> Vec<Option<pallas::Scalar>> {
self.windows()
.iter()
.map(|bits| {
bits.value()
.map(|value| pallas::Scalar::from_bytes(&value.to_bytes()).unwrap())
})
.collect::<Vec<_>>()
match self {
Self::BaseFieldElem(scalar) => {
let mut zs = vec![scalar.base_field_elem];
zs.extend_from_slice(&scalar.running_sum);
(0..(zs.len() - 1))
.map(|idx| {
let z_cur = zs[idx].value();
let z_next = zs[idx + 1].value();
let word = z_cur.zip(z_next).map(|(z_cur, z_next)| {
z_cur - z_next * pallas::Base::from_u64(constants::H as u64)
});
word.map(|word| pallas::Scalar::from_bytes(&word.to_bytes()).unwrap())
})
.collect::<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,
// so we also cast the least significant byte in their serialisation
// into usize for convenient indexing into `u`-values
fn windows_usize(&self) -> Vec<Option<usize>> {
self.windows()
self.windows_field()
.iter()
.map(|bits| bits.value().map(|value| value.to_bytes()[0] as usize))
.map(|window| {
if let Some(window) = window {
let window = window.to_bytes()[0] as usize;
assert!(window < constants::H);
Some(window)
} else {
None
}
})
.collect::<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,
region: &mut Region<'_, pallas::Base>,
) -> Result<EccPoint, Error> {
let (acc, mul_b) =
self.0
.assign_region_inner(region, offset, &scalar.into(), base.into())?;
// Copy the scalar decomposition
self.0.copy_scalar(region, offset, &scalar.into())?;
let (acc, mul_b) = self.0.assign_region_inner(
region,
offset,
&scalar.into(),
base.into(),
self.0.mul_fixed,
)?;
// Add to the accumulator and return the final result as `[scalar]B`.
let result = self
@ -33,13 +40,9 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
// Check that the correct multiple is obtained.
{
use group::Curve;
use halo2::arithmetic::FieldExt;
let base: super::OrchardFixedBases = base.into();
let scalar = scalar
.value
.map(|scalar| pallas::Scalar::from_bytes(&scalar.to_bytes()).unwrap());
let real_mul = scalar.map(|scalar| base.generator() * scalar);
let real_mul = scalar.value.map(|scalar| base.generator() * scalar);
let result = result.point();
if let (Some(real_mul), Some(result)) = (real_mul, result) {
@ -159,13 +162,24 @@ pub mod tests {
{
let scalar_fixed = pallas::Scalar::zero();
let scalar_fixed = ScalarFixed::new(
chip,
chip.clone(),
layouter.namespace(|| "ScalarFixed"),
Some(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)?;
}
// [-1]B is the largest scalar field element.
{
let scalar_fixed = -pallas::Scalar::one();
let scalar_fixed = ScalarFixed::new(
chip,
layouter.namespace(|| "ScalarFixed"),
Some(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "mul by -1"), &scalar_fixed)?;
}
Ok(())
}
}

View File

@ -58,11 +58,16 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> 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(
region,
offset,
&scalar.into(),
base.clone().into(),
self.super_config.mul_fixed,
)?;
// Add to the cumulative sum to get `[magnitude]B`.

View File

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