use super::{
add, add_incomplete, EccBaseFieldElemFixed, EccScalarFixed, EccScalarFixedShort, FixedPoint,
NonIdentityEccPoint, FIXED_BASE_WINDOW_SIZE, H,
};
use crate::utilities::decompose_running_sum::RunningSumConfig;
use std::marker::PhantomData;
use group::{
ff::{PrimeField, PrimeFieldBits},
Curve,
};
use halo2_proofs::{
circuit::{AssignedCell, Region, Value},
plonk::{
Advice, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector,
VirtualCells,
},
poly::Rotation,
};
use lazy_static::lazy_static;
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
pub mod base_field_elem;
pub mod full_width;
pub mod short;
lazy_static! {
static ref TWO_SCALAR: pallas::Scalar = pallas::Scalar::from(2);
static ref H_SCALAR: pallas::Scalar = pallas::Scalar::from(H as u64);
static ref H_BASE: pallas::Base = pallas::Base::from(H as u64);
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Config<FixedPoints: super::FixedPoints<pallas::Affine>> {
running_sum_config: RunningSumConfig<pallas::Base, FIXED_BASE_WINDOW_SIZE>,
lagrange_coeffs: [Column<Fixed>; H],
fixed_z: Column<Fixed>,
window: Column<Advice>,
u: Column<Advice>,
add_config: add::Config,
add_incomplete_config: add_incomplete::Config,
_marker: PhantomData<FixedPoints>,
}
impl<FixedPoints: super::FixedPoints<pallas::Affine>> Config<FixedPoints> {
#[allow(clippy::too_many_arguments)]
pub(super) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
lagrange_coeffs: [Column<Fixed>; H],
window: Column<Advice>,
u: Column<Advice>,
add_config: add::Config,
add_incomplete_config: add_incomplete::Config,
) -> Self {
meta.enable_equality(window);
meta.enable_equality(u);
let q_running_sum = meta.selector();
let running_sum_config = RunningSumConfig::configure(meta, q_running_sum, window);
let config = Self {
running_sum_config,
lagrange_coeffs,
fixed_z: meta.fixed_column(),
window,
u,
add_config,
add_incomplete_config,
_marker: PhantomData,
};
assert_eq!(
config.add_config.x_p, config.add_incomplete_config.x_p,
"add and add_incomplete are used internally in mul_fixed."
);
assert_eq!(
config.add_config.y_p, config.add_incomplete_config.y_p,
"add and add_incomplete are used internally in mul_fixed."
);
for advice in [config.window, config.u].iter() {
assert_ne!(
*advice, config.add_config.x_qr,
"Do not overlap with output columns of add."
);
assert_ne!(
*advice, config.add_config.y_qr,
"Do not overlap with output columns of add."
);
}
config.running_sum_coords_gate(meta);
config
}
fn running_sum_coords_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
meta.create_gate("Running sum coordinates check", |meta| {
let q_mul_fixed_running_sum =
meta.query_selector(self.running_sum_config.q_range_check());
let z_cur = meta.query_advice(self.window, Rotation::cur());
let z_next = meta.query_advice(self.window, Rotation::next());
let word = z_cur - z_next * pallas::Base::from(H as u64);
Constraints::with_selector(q_mul_fixed_running_sum, self.coords_check(meta, word))
});
}
#[allow(clippy::op_ref)]
fn coords_check(
&self,
meta: &mut VirtualCells<'_, pallas::Base>,
window: Expression<pallas::Base>,
) -> Vec<(&'static str, Expression<pallas::Base>)> {
let y_p = meta.query_advice(self.add_config.y_p, Rotation::cur());
let x_p = meta.query_advice(self.add_config.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..H)
.map(|pow| {
(0..pow).fold(Expression::Constant(pallas::Base::one()), |acc, _| {
acc * window.clone()
})
})
.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 x_check = interpolated_x - x_p.clone();
let y_check = u.square() - y_p.clone() - z;
let on_curve =
y_p.square() - x_p.clone().square() * x_p - Expression::Constant(pallas::Affine::b());
vec![
("check x", x_check),
("check y", y_check),
("on-curve", on_curve),
]
}
#[allow(clippy::type_complexity)]
fn assign_region_inner<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
scalar: &ScalarFixed,
base: &F,
coords_check_toggle: Selector,
) -> Result<(NonIdentityEccPoint, NonIdentityEccPoint), Error> {
self.assign_fixed_constants::<F, NUM_WINDOWS>(region, offset, base, coords_check_toggle)?;
let acc = self.initialize_accumulator::<F, NUM_WINDOWS>(region, offset, base, scalar)?;
let acc = self.add_incomplete::<F, NUM_WINDOWS>(region, offset, acc, base, scalar)?;
let mul_b = self.process_msb::<F, NUM_WINDOWS>(region, offset, base, scalar)?;
Ok((acc, mul_b))
}
fn assign_fixed_constants<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
base: &F,
coords_check_toggle: Selector,
) -> Result<(), Error> {
let mut constants = None;
let build_constants = || {
let lagrange_coeffs = base.lagrange_coeffs();
assert_eq!(lagrange_coeffs.len(), NUM_WINDOWS);
let z = base.z();
assert_eq!(z.len(), NUM_WINDOWS);
(lagrange_coeffs, z)
};
for window in 0..NUM_WINDOWS {
coords_check_toggle.enable(region, window + offset)?;
for k in 0..H {
region.assign_fixed(
|| {
format!(
"Lagrange interpolation coeff for window: {:?}, k: {:?}",
window, k
)
},
self.lagrange_coeffs[k],
window + offset,
|| {
if constants.as_ref().is_none() {
constants = Some(build_constants());
}
let lagrange_coeffs = &constants.as_ref().unwrap().0;
Value::known(lagrange_coeffs[window][k])
},
)?;
}
region.assign_fixed(
|| format!("z-value for window: {:?}", window),
self.fixed_z,
window + offset,
|| {
let z = &constants.as_ref().unwrap().1;
Value::known(pallas::Base::from(z[window]))
},
)?;
}
Ok(())
}
fn process_window<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
w: usize,
k_usize: Value<usize>,
window_scalar: Value<pallas::Scalar>,
base: &F,
) -> Result<NonIdentityEccPoint, Error> {
let base_value = base.generator();
let base_u = base.u();
assert_eq!(base_u.len(), NUM_WINDOWS);
let mul_b = {
let mul_b = window_scalar.map(|scalar| base_value * scalar);
let mul_b = mul_b.map(|mul_b| mul_b.to_affine().coordinates().unwrap());
let x = mul_b.map(|mul_b| {
let x = *mul_b.x();
assert!(x != pallas::Base::zero());
x.into()
});
let x = region.assign_advice(
|| format!("mul_b_x, window {}", w),
self.add_config.x_p,
offset + w,
|| x,
)?;
let y = mul_b.map(|mul_b| {
let y = *mul_b.y();
assert!(y != pallas::Base::zero());
y.into()
});
let y = region.assign_advice(
|| format!("mul_b_y, window {}", w),
self.add_config.y_p,
offset + w,
|| y,
)?;
NonIdentityEccPoint { x, y }
};
let u_val = k_usize.map(|k| pallas::Base::from_repr(base_u[w][k]).unwrap());
region.assign_advice(|| "u", self.u, offset + w, || u_val)?;
Ok(mul_b)
}
fn initialize_accumulator<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
base: &F,
scalar: &ScalarFixed,
) -> Result<NonIdentityEccPoint, Error> {
let w = 0;
let k0 = scalar.windows_field()[0];
let k0_usize = scalar.windows_usize()[0];
self.process_lower_bits::<_, NUM_WINDOWS>(region, offset, w, k0, k0_usize, base)
}
fn add_incomplete<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
mut acc: NonIdentityEccPoint,
base: &F,
scalar: &ScalarFixed,
) -> Result<NonIdentityEccPoint, Error> {
let scalar_windows_field = scalar.windows_field();
let scalar_windows_usize = scalar.windows_usize();
assert_eq!(scalar_windows_field.len(), NUM_WINDOWS);
for (w, (k, k_usize)) in scalar_windows_field
.into_iter()
.zip(scalar_windows_usize)
.enumerate()
.take(NUM_WINDOWS - 1)
.skip(1)
{
let mul_b =
self.process_lower_bits::<_, NUM_WINDOWS>(region, offset, w, k, k_usize, base)?;
acc = self
.add_incomplete_config
.assign_region(&mul_b, &acc, offset + w, region)?;
}
Ok(acc)
}
fn process_lower_bits<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
w: usize,
k: Value<pallas::Scalar>,
k_usize: Value<usize>,
base: &F,
) -> Result<NonIdentityEccPoint, Error> {
let scalar = k.map(|k| (k + *TWO_SCALAR) * (*H_SCALAR).pow(&[w as u64, 0, 0, 0]));
self.process_window::<_, NUM_WINDOWS>(region, offset, w, k_usize, scalar, base)
}
fn process_msb<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
base: &F,
scalar: &ScalarFixed,
) -> Result<NonIdentityEccPoint, Error> {
let k_usize = scalar.windows_usize()[NUM_WINDOWS - 1];
let offset_acc = (0..(NUM_WINDOWS - 1)).fold(pallas::Scalar::zero(), |acc, w| {
acc + (*TWO_SCALAR).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, 0, 0, 0])
});
let scalar = scalar.windows_field()[scalar.windows_field().len() - 1]
.map(|k| k * (*H_SCALAR).pow(&[(NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_acc);
self.process_window::<_, NUM_WINDOWS>(
region,
offset,
NUM_WINDOWS - 1,
k_usize,
scalar,
base,
)
}
}
enum ScalarFixed {
FullWidth(EccScalarFixed),
Short(EccScalarFixedShort),
BaseFieldElem(EccBaseFieldElemFixed),
}
impl From<&EccScalarFixed> for ScalarFixed {
fn from(scalar_fixed: &EccScalarFixed) -> Self {
Self::FullWidth(scalar_fixed.clone())
}
}
impl From<&EccScalarFixedShort> for ScalarFixed {
fn from(scalar_fixed: &EccScalarFixedShort) -> Self {
Self::Short(scalar_fixed.clone())
}
}
impl From<&EccBaseFieldElemFixed> for ScalarFixed {
fn from(base_field_elem: &EccBaseFieldElemFixed) -> Self {
Self::BaseFieldElem(base_field_elem.clone())
}
}
impl ScalarFixed {
fn windows_field(&self) -> Vec<Value<pallas::Scalar>> {
let running_sum_to_windows = |zs: Vec<AssignedCell<pallas::Base, pallas::Base>>| {
(0..(zs.len() - 1))
.map(|idx| {
let z_cur = zs[idx].value();
let z_next = zs[idx + 1].value();
let word = z_cur - z_next * Value::known(*H_BASE);
word.map(|word| pallas::Scalar::from_repr(word.to_repr()).unwrap())
})
.collect::<Vec<_>>()
};
match self {
Self::BaseFieldElem(scalar) => running_sum_to_windows(scalar.running_sum.to_vec()),
Self::Short(scalar) => running_sum_to_windows(
scalar
.running_sum
.as_ref()
.expect("EccScalarFixedShort has been constrained")
.to_vec(),
),
Self::FullWidth(scalar) => scalar
.windows
.as_ref()
.expect("EccScalarFixed has been witnessed")
.iter()
.map(|bits| {
bits.value()
.map(|value| pallas::Scalar::from_repr(value.to_repr()).unwrap())
})
.collect::<Vec<_>>(),
}
}
fn windows_usize(&self) -> Vec<Value<usize>> {
self.windows_field()
.iter()
.map(|window| {
window.map(|window| {
window
.to_le_bits()
.iter()
.by_vals()
.take(FIXED_BASE_WINDOW_SIZE)
.rev()
.fold(0, |acc, b| 2 * acc + if b { 1 } else { 0 })
})
})
.collect::<Vec<_>>()
}
}