halo2/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs

317 lines
11 KiB
Rust

use super::super::{EccPoint, EccScalarFixed, FixedPoints, FIXED_BASE_WINDOW_SIZE, H, NUM_WINDOWS};
use crate::utilities::{decompose_word, range_check};
use arrayvec::ArrayVec;
use ff::PrimeField;
use halo2_proofs::{
circuit::{AssignedCell, Layouter, Region, Value},
plonk::{ConstraintSystem, Constraints, Error, Selector},
poly::Rotation,
};
use pasta_curves::pallas;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Config<Fixed: FixedPoints<pallas::Affine>> {
q_mul_fixed_full: Selector,
super_config: super::Config<Fixed>,
}
impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
pub(crate) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
super_config: super::Config<Fixed>,
) -> Self {
let config = Self {
q_mul_fixed_full: meta.selector(),
super_config,
};
config.create_gate(meta);
config
}
fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// Check that each window `k` is within 3 bits
// https://p.z.cash/halo2-0.1:ecc-fixed-mul-full-word
meta.create_gate("Full-width fixed-base scalar mul", |meta| {
let q_mul_fixed_full = meta.query_selector(self.q_mul_fixed_full);
let window = meta.query_advice(self.super_config.window, Rotation::cur());
Constraints::with_selector(
q_mul_fixed_full,
self.super_config
.coords_check(meta, window.clone())
.into_iter()
// Constrain each window to a 3-bit value:
// window * (1 - window) * ... * (7 - window)
.chain(Some(("window range check", range_check(window, 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: Value<pallas::Scalar>,
) -> Result<EccScalarFixed, Error> {
let windows = self.decompose_scalar_fixed::<{ pallas::Scalar::NUM_BITS as usize }>(
scalar, offset, region,
)?;
Ok(EccScalarFixed {
value: scalar,
windows: Some(windows),
})
}
/// 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,
scalar: Value<pallas::Scalar>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<ArrayVec<AssignedCell<pallas::Base, pallas::Base>, NUM_WINDOWS>, Error> {
// Enable `q_mul_fixed_full` selector
for idx in 0..NUM_WINDOWS {
self.q_mul_fixed_full.enable(region, offset + idx)?;
}
// Decompose scalar into `k-bit` windows
let scalar_windows: Value<Vec<u8>> = scalar.map(|scalar| {
decompose_word::<pallas::Scalar>(&scalar, SCALAR_NUM_BITS, FIXED_BASE_WINDOW_SIZE)
});
// Transpose `Value<Vec<u8>>` into `Vec<Value<pallas::Base>>`.
let scalar_windows = scalar_windows
.map(|windows| {
windows
.into_iter()
.map(|window| pallas::Base::from(window as u64))
})
.transpose_vec(NUM_WINDOWS);
// Store the scalar decomposition
let mut windows: ArrayVec<AssignedCell<pallas::Base, pallas::Base>, NUM_WINDOWS> =
ArrayVec::new();
for (idx, window) in scalar_windows.into_iter().enumerate() {
let window_cell = region.assign_advice(
|| format!("k[{:?}]", offset + idx),
self.super_config.window,
offset + idx,
|| window,
)?;
windows.push(window_cell);
}
Ok(windows)
}
pub fn assign(
&self,
mut layouter: impl Layouter<pallas::Base>,
scalar: &EccScalarFixed,
base: &<Fixed as FixedPoints<pallas::Affine>>::FullScalar,
) -> Result<(EccPoint, EccScalarFixed), Error>
where
<Fixed as FixedPoints<pallas::Affine>>::FullScalar:
super::super::FixedPoint<pallas::Affine>,
{
let (scalar, acc, mul_b) = layouter.assign_region(
|| "Full-width fixed-base mul (incomplete addition)",
|mut region| {
let offset = 0;
// Lazily witness the scalar.
let scalar = match scalar.windows {
None => self.witness(&mut region, offset, scalar.value),
Some(_) => todo!("unimplemented for halo2_gadgets v0.1.0"),
}?;
let (acc, mul_b) = self.super_config.assign_region_inner::<_, NUM_WINDOWS>(
&mut region,
offset,
&(&scalar).into(),
base,
self.q_mul_fixed_full,
)?;
Ok((scalar, acc, mul_b))
},
)?;
// Add to the accumulator and return the final result as `[scalar]B`.
let result = layouter.assign_region(
|| "Full-width fixed-base mul (last window, complete addition)",
|mut region| {
self.super_config.add_config.assign_region(
&mul_b.clone().into(),
&acc.clone().into(),
0,
&mut region,
)
},
)?;
#[cfg(test)]
// Check that the correct multiple is obtained.
{
use super::super::FixedPoint;
use group::Curve;
let real_mul = scalar.value.map(|scalar| base.generator() * scalar);
let result = result.point();
real_mul
.zip(result)
.assert_if_known(|(real_mul, result)| &real_mul.to_affine() == result);
}
Ok((result, scalar))
}
}
#[cfg(test)]
pub mod tests {
use group::{ff::Field, Curve};
use halo2_proofs::{
circuit::{Layouter, Value},
plonk::Error,
};
use pasta_curves::pallas;
use rand::rngs::OsRng;
use crate::ecc::{
chip::{EccChip, FixedPoint as _, H},
tests::{FullWidth, TestFixedBases},
FixedPoint, NonIdentityPoint, Point, ScalarFixed,
};
pub(crate) fn test_mul_fixed(
chip: EccChip<TestFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), Error> {
let test_base = FullWidth::from_pallas_generator();
test_single_base(
chip.clone(),
layouter.namespace(|| "full_width"),
FixedPoint::from_inner(chip, test_base.clone()),
test_base.generator(),
)?;
Ok(())
}
#[allow(clippy::op_ref)]
fn test_single_base(
chip: EccChip<TestFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
base: FixedPoint<pallas::Affine, EccChip<TestFixedBases>>,
base_val: pallas::Affine,
) -> Result<(), Error> {
fn constrain_equal_non_id(
chip: EccChip<TestFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
base_val: pallas::Affine,
scalar_val: pallas::Scalar,
result: Point<pallas::Affine, EccChip<TestFixedBases>>,
) -> Result<(), Error> {
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "expected point"),
Value::known((base_val * scalar_val).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain result"), &expected)
}
// [a]B
{
let scalar_fixed = pallas::Scalar::random(OsRng);
let by = ScalarFixed::new(
chip.clone(),
layouter.namespace(|| "random a"),
Value::known(scalar_fixed),
)?;
let (result, _) = base.mul(layouter.namespace(|| "random [a]B"), by)?;
constrain_equal_non_id(
chip.clone(),
layouter.namespace(|| "random [a]B"),
base_val,
scalar_fixed,
result,
)?;
}
// 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.)
{
const LAST_DOUBLING: &str = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334";
let h = pallas::Scalar::from(H as u64);
let scalar_fixed = LAST_DOUBLING
.chars()
.fold(pallas::Scalar::zero(), |acc, c| {
acc * &h + &pallas::Scalar::from(c.to_digit(8).unwrap() as u64)
});
let by = ScalarFixed::new(
chip.clone(),
layouter.namespace(|| LAST_DOUBLING),
Value::known(scalar_fixed),
)?;
let (result, _) = base.mul(layouter.namespace(|| "mul with double"), by)?;
constrain_equal_non_id(
chip.clone(),
layouter.namespace(|| "mul with double"),
base_val,
scalar_fixed,
result,
)?;
}
// [0]B should return (0,0) since it uses complete addition
// on the last step.
{
let scalar_fixed = pallas::Scalar::zero();
let zero = ScalarFixed::new(
chip.clone(),
layouter.namespace(|| "0"),
Value::known(scalar_fixed),
)?;
let (result, _) = base.mul(layouter.namespace(|| "mul by zero"), zero)?;
result
.inner()
.is_identity()
.assert_if_known(|is_identity| *is_identity);
}
// [-1]B is the largest scalar field element.
{
let scalar_fixed = -pallas::Scalar::one();
let neg_1 = ScalarFixed::new(
chip.clone(),
layouter.namespace(|| "-1"),
Value::known(scalar_fixed),
)?;
let (result, _) = base.mul(layouter.namespace(|| "mul by -1"), neg_1)?;
constrain_equal_non_id(
chip,
layouter.namespace(|| "mul by -1"),
base_val,
scalar_fixed,
result,
)?;
}
Ok(())
}
}