use std::{cmp::max, collections::HashMap, hash::Hash};
use ff::Field;
use group::Group;
use midnight_proofs::{
circuit::{Layouter, Value},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Selector},
poly::Rotation,
};
use crate::{
ecc::curves::CircuitCurve,
field::{foreign::field_chip::FieldChipConfig, AssignedNative},
instructions::{
AssignmentInstructions, ControlFlowInstructions, EccInstructions, ScalarFieldInstructions,
},
types::InnerValue,
CircuitField,
};
#[allow(clippy::type_complexity)]
pub(crate) fn msm_preprocess<F, C, EI, SFI>(
ec_chip: &EI,
scalar_chip: &SFI,
layouter: &mut impl Layouter<F>,
scalars: &[(EI::Scalar, usize)],
bases: &[EI::Point],
) -> Result<
(
Vec<(EI::Scalar, usize)>,
Vec<EI::Point>,
Vec<(EI::Point, EI::Scalar)>,
),
Error,
>
where
F: CircuitField,
C: CircuitCurve,
EI: EccInstructions<F, C, Scalar = SFI::Scalar> + AssignmentInstructions<F, EI::Point>,
SFI: ScalarFieldInstructions<F>,
SFI::Scalar: InnerValue<Element = C::ScalarField>,
EI::Point: PartialEq + Eq + Hash,
{
let identity = ec_chip.assign_fixed(layouter, C::CryptographicGroup::identity())?;
let (scalars, bases): (Vec<_>, Vec<_>) = scalars
.iter()
.zip(bases.iter())
.filter(|(_, base)| *base != &identity)
.map(|(s, b)| (s.clone(), b.clone()))
.unzip();
let one: EI::Scalar = scalar_chip.assign_fixed(layouter, C::ScalarField::ONE)?;
let mut bases_with_1bit_scalar = vec![];
let mut filtered_scalars = vec![];
let mut filtered_bases = vec![];
for (scalar, base) in scalars.iter().zip(bases.iter()) {
if scalar.0 == one || scalar.1 == 1 {
bases_with_1bit_scalar.push((base.clone(), scalar.0.clone()));
} else {
filtered_scalars.push(scalar.clone());
filtered_bases.push(base.clone());
}
}
let scalars = filtered_scalars;
let bases = filtered_bases;
let mut cache_bases: HashMap<EI::Point, (EI::Scalar, usize)> = HashMap::new();
let mut unique_bases: Vec<EI::Point> = vec![];
for (base, scalar) in bases.iter().zip(scalars.iter()) {
if let Some(acc) = cache_bases.insert(base.clone(), scalar.clone()) {
let new_scalar = scalar_chip.add(layouter, &acc.0, &scalar.0)?;
let new_bound = max(acc.1, scalar.1) + 1;
cache_bases.insert(base.clone(), (new_scalar, new_bound));
} else {
unique_bases.push(base.clone());
}
}
let scalars = unique_bases
.iter()
.map(|b| cache_bases.get(b).unwrap().clone())
.collect::<Vec<_>>();
let bases = unique_bases;
let mut cache_scalars: HashMap<(EI::Scalar, usize), EI::Point> = HashMap::new();
let mut unique_scalars: Vec<(EI::Scalar, usize)> = vec![];
for (scalar, base) in scalars.iter().zip(bases.iter()) {
if let Some(acc) = cache_scalars.insert(scalar.clone(), base.clone()) {
let new_acc = ec_chip.add(layouter, &acc, base)?;
cache_scalars.insert(scalar.clone(), new_acc);
} else {
unique_scalars.push(scalar.clone());
}
}
let bases = unique_scalars
.iter()
.map(|s| cache_scalars.get(s).unwrap().clone())
.collect::<Vec<_>>();
let scalars = unique_scalars;
Ok((scalars, bases, bases_with_1bit_scalar))
}
pub(crate) fn add_1bit_scalar_bases<F, C, EI, SFI>(
layouter: &mut impl Layouter<F>,
ec_chip: &EI,
scalar_chip: &SFI,
bases_with_1bit_scalar: &[(EI::Point, EI::Scalar)],
acc: EI::Point,
) -> Result<EI::Point, Error>
where
F: CircuitField,
C: CircuitCurve,
EI: EccInstructions<F, C, Scalar = SFI::Scalar>
+ AssignmentInstructions<F, EI::Point>
+ ControlFlowInstructions<F, EI::Point>,
SFI: ScalarFieldInstructions<F>,
SFI::Scalar: InnerValue<Element = C::ScalarField>,
{
let identity = ec_chip.assign_fixed(layouter, C::CryptographicGroup::identity())?;
let one: EI::Scalar = scalar_chip.assign_fixed(layouter, C::ScalarField::ONE)?;
bases_with_1bit_scalar.iter().try_fold(acc, |acc, (b, s)| {
let s_times_b = if s == &one {
b.clone()
} else {
let s_is_zero = scalar_chip.is_zero(layouter, s)?;
ec_chip.select(layouter, &s_is_zero, &identity, b)?
};
ec_chip.add(layouter, &acc, &s_times_b)
})
}
pub(crate) fn configure_multi_select_lookup<F: CircuitField>(
meta: &mut ConstraintSystem<F>,
advice_columns: &[Column<Advice>],
base_field_config: &FieldChipConfig,
) -> (Selector, Column<Advice>, Column<Fixed>) {
let q_multi_select = meta.complex_selector();
assert!(advice_columns.len() > 2 * base_field_config.x_cols.len());
let idx_col_multi_select = *advice_columns.last().unwrap();
meta.enable_equality(idx_col_multi_select);
let tag_col_multi_select = meta.fixed_column();
meta.lookup_any("multi_select lookup", |meta| {
let sel = meta.query_selector(q_multi_select);
let not_sel = Expression::from(1) - sel.clone();
let mut identities = [idx_col_multi_select]
.iter()
.chain(base_field_config.x_cols.iter())
.chain(base_field_config.z_cols.iter())
.map(|col| {
let val = meta.query_advice(*col, Rotation::cur());
(val.clone(), not_sel.clone() * val)
})
.collect::<Vec<_>>();
let tag = meta.query_fixed(tag_col_multi_select, Rotation::cur());
identities.push((tag.clone(), not_sel * tag));
identities
});
(q_multi_select, idx_col_multi_select, tag_col_multi_select)
}
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
pub(crate) fn fill_dynamic_lookup_row<F: CircuitField>(
layouter: &mut impl Layouter<F>,
x_limbs: &[AssignedNative<F>],
y_limbs: &[AssignedNative<F>],
index: &AssignedNative<F>,
x_cols: &[Column<Advice>],
y_cols: &[Column<Advice>],
idx_col: Column<Advice>,
tag_col: Column<Fixed>,
q_multi_select: Selector,
table_tag: F,
enable_lookup: bool,
) -> Result<(Vec<AssignedNative<F>>, Vec<AssignedNative<F>>), Error> {
layouter.assign_region(
|| "multi_select table",
|mut region| {
if enable_lookup {
q_multi_select.enable(&mut region, 0)?;
};
let mut xs = vec![];
let mut ys = vec![];
for i in 0..x_limbs.len() {
if enable_lookup {
let x_val = x_limbs[i].value().copied();
let y_val = y_limbs[i].value().copied();
xs.push(region.assign_advice(|| "x", x_cols[i], 0, || x_val)?);
ys.push(region.assign_advice(|| "y", y_cols[i], 0, || y_val)?);
}
else {
xs.push(x_limbs[i].copy_advice(|| "x", &mut region, x_cols[i], 0)?);
ys.push(y_limbs[i].copy_advice(|| "y", &mut region, y_cols[i], 0)?);
}
}
index.copy_advice(|| "x", &mut region, idx_col, 0)?;
region.assign_fixed(|| "assign tag", tag_col, 0, || Value::known(table_tag))?;
Ok((xs, ys))
},
)
}