use std::{cmp::min, ops::Range};
use midnight_proofs::{
circuit::{Layouter, Value},
plonk::Error,
};
use num_bigint::BigUint;
use num_traits::One;
#[cfg(not(feature = "truncated-challenges"))]
use crate::instructions::FieldInstructions;
#[cfg(feature = "truncated-challenges")]
use crate::instructions::NativeInstructions;
use crate::{
field::AssignedNative,
instructions::{ArithInstructions, AssignmentInstructions},
CircuitField,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AssignedBoundedScalar<F: CircuitField> {
pub(crate) scalar: AssignedNative<F>,
pub(crate) bound: BigUint,
}
impl<F: CircuitField> AssignedBoundedScalar<F> {
pub(crate) fn new(scalar: &AssignedNative<F>, bound_opt: Option<BigUint>) -> Self {
Self {
scalar: scalar.clone(),
bound: bound_opt.unwrap_or(F::modulus() - BigUint::one()),
}
}
pub(crate) fn one(
layouter: &mut impl Layouter<F>,
scalar_chip: &impl AssignmentInstructions<F, AssignedNative<F>>,
) -> Result<AssignedBoundedScalar<F>, Error> {
let one = scalar_chip.assign_fixed(layouter, F::ONE)?;
Ok(AssignedBoundedScalar {
scalar: one,
bound: BigUint::one(),
})
}
}
pub(crate) fn assign_bounded_scalars<F: CircuitField>(
layouter: &mut impl Layouter<F>,
scalar_chip: &impl AssignmentInstructions<F, AssignedNative<F>>,
values: &[Value<F>],
) -> Result<Vec<AssignedBoundedScalar<F>>, Error> {
Ok(scalar_chip
.assign_many(layouter, values)?
.iter()
.map(|s| AssignedBoundedScalar::new(s, None))
.collect())
}
pub(crate) fn add_bounded_scalars<F: CircuitField>(
layouter: &mut impl Layouter<F>,
scalar_chip: &impl ArithInstructions<F, AssignedNative<F>>,
x1: &AssignedBoundedScalar<F>,
x2: &AssignedBoundedScalar<F>,
) -> Result<AssignedBoundedScalar<F>, Error> {
Ok(AssignedBoundedScalar {
scalar: scalar_chip.add(layouter, &x1.scalar, &x2.scalar)?,
bound: min(
x1.bound.clone() + x2.bound.clone(),
F::modulus() - BigUint::one(),
),
})
}
pub fn mul_bounded_scalars<F: CircuitField>(
layouter: &mut impl Layouter<F>,
scalar_chip: &impl ArithInstructions<F, AssignedNative<F>>,
x1: &AssignedBoundedScalar<F>,
x2: &AssignedBoundedScalar<F>,
) -> Result<AssignedBoundedScalar<F>, Error> {
Ok(AssignedBoundedScalar {
scalar: scalar_chip.mul(layouter, &x1.scalar, &x2.scalar, None)?,
bound: min(
x1.bound.clone() * x2.bound.clone(),
F::modulus() - BigUint::one(),
),
})
}
#[cfg(feature = "truncated-challenges")]
pub(crate) fn truncate_off_circuit<F: CircuitField>(scalar: F) -> F {
let nb_bytes = F::NUM_BITS.div_ceil(8).div_ceil(2) as usize;
let bytes = scalar.to_bytes_le();
let mut buffer = vec![0u8; F::NUM_BITS.div_ceil(8) as usize];
buffer[..nb_bytes].copy_from_slice(&bytes[..nb_bytes]);
F::from_bytes_le(&buffer).unwrap()
}
#[cfg(feature = "truncated-challenges")]
pub(crate) fn truncate<F: CircuitField>(
layouter: &mut impl Layouter<F>,
scalar_chip: &impl NativeInstructions<F>,
x: &AssignedNative<F>,
) -> Result<AssignedBoundedScalar<F>, Error> {
let bits = scalar_chip.assigned_to_le_bits(layouter, x, None, true)?;
let nb_half_bits = 8 * (F::NUM_BITS.div_ceil(8).div_ceil(2) as usize);
let scalar = scalar_chip.assigned_from_le_bits(layouter, &bits[..nb_half_bits])?;
let bound = (BigUint::one() << nb_half_bits) - BigUint::one();
Ok(AssignedBoundedScalar::new(&scalar, Some(bound)))
}
pub fn evaluate_lagrange_polynomials<F: CircuitField>(
layouter: &mut impl Layouter<F>,
scalar_chip: &impl ArithInstructions<F, AssignedNative<F>>,
n: u64,
w: F,
i_indices: Range<i32>,
x: &AssignedNative<F>,
) -> Result<Vec<AssignedNative<F>>, Error> {
let n_inv = F::from(n).invert().unwrap();
let xn = scalar_chip.pow(layouter, x, n)?;
let xn_minus_one = scalar_chip.add_constant(layouter, &xn, -F::ONE)?;
i_indices
.map(|i| {
assert!(-(n as i32) <= i);
let i = if i < 0 { n as i32 + i } else { i };
let wi = w.pow([i as u64, 0, 0, 0]);
let x_minus_wi = scalar_chip.add_constant(layouter, x, -wi)?;
let quotient = scalar_chip.div(layouter, &xn_minus_one, &x_minus_wi)?;
scalar_chip.mul_by_constant(layouter, "ient, wi * n_inv)
})
.collect()
}
pub fn evaluate_interpolated_polynomial<F: CircuitField>(
layouter: &mut impl Layouter<F>,
scalar_chip: &impl ArithInstructions<F, AssignedNative<F>>,
points: &[AssignedNative<F>],
evals: &[AssignedNative<F>],
x: &AssignedNative<F>,
) -> Result<AssignedNative<F>, Error> {
assert_eq!(points.len(), evals.len());
for i in 0..points.len() {
for j in (i + 1)..points.len() {
scalar_chip.assert_not_equal(layouter, &points[i], &points[j])?;
}
}
if points.len() == 1 {
return Ok(evals[0].clone());
}
let mut lj_s = vec![];
let x_minus_xs = points
.iter()
.map(|xi| scalar_chip.sub(layouter, x, xi))
.collect::<Result<Vec<_>, Error>>()?;
for (j, xj) in points.iter().enumerate() {
let mut num_terms = vec![];
let mut den_terms = vec![];
for (i, xi) in points.iter().enumerate() {
if i != j {
num_terms.push(x_minus_xs[i].clone());
den_terms.push(scalar_chip.sub(layouter, xj, xi)?);
}
}
let num = prod::<F>(layouter, scalar_chip, &num_terms)?;
let den = prod::<F>(layouter, scalar_chip, &den_terms)?;
lj_s.push(scalar_chip.div(layouter, &num, &den)?);
}
inner_product(layouter, scalar_chip, &lj_s, evals)
}
pub(crate) fn sum<F: CircuitField>(
layouter: &mut impl Layouter<F>,
scalar_chip: &impl ArithInstructions<F, AssignedNative<F>>,
terms: &[AssignedNative<F>],
) -> Result<AssignedNative<F>, Error> {
let terms = terms.iter().map(|t| (F::ONE, t.clone())).collect::<Vec<_>>();
scalar_chip.linear_combination(layouter, &terms, F::ZERO)
}
pub(crate) fn prod<F: CircuitField>(
layouter: &mut impl Layouter<F>,
scalar_chip: &impl ArithInstructions<F, AssignedNative<F>>,
terms: &[AssignedNative<F>],
) -> Result<AssignedNative<F>, Error> {
let mut res = terms[0].clone();
for term in terms.iter().skip(1) {
res = scalar_chip.mul(layouter, &res, term, None)?;
}
Ok(res)
}
pub(crate) fn inner_product<F: CircuitField>(
layouter: &mut impl Layouter<F>,
scalar_chip: &impl ArithInstructions<F, AssignedNative<F>>,
terms1: &[AssignedNative<F>],
terms2: &[AssignedNative<F>],
) -> Result<AssignedNative<F>, Error> {
assert_eq!(terms1.len(), terms2.len());
let mut iter = terms1.iter().zip(terms2.iter());
let (x0, y0) = iter.next().expect("inner_product received an empty input");
let init = scalar_chip.mul(layouter, x0, y0, None)?;
iter.try_fold(init, |acc, (xi, yi)| {
mul_add(layouter, scalar_chip, xi, yi, &acc)
})
}
pub(crate) fn powers<F: CircuitField>(
layouter: &mut impl Layouter<F>,
scalar_chip: &impl ArithInstructions<F, AssignedNative<F>>,
x: &AssignedNative<F>,
n: usize,
) -> Result<Vec<AssignedNative<F>>, Error> {
let one = scalar_chip.assign_fixed(layouter, F::ONE)?;
let mut powers = vec![one];
let mut acc = x.clone();
for i in 1..n {
powers.push(acc.clone());
if i < n - 1 {
acc = scalar_chip.mul(layouter, &acc, x, None)?;
}
}
Ok(powers)
}
pub(crate) fn truncated_powers<F: CircuitField>(
layouter: &mut impl Layouter<F>,
#[cfg(feature = "truncated-challenges")] scalar_chip: &impl NativeInstructions<F>,
#[cfg(not(feature = "truncated-challenges"))] scalar_chip: &impl FieldInstructions<
F,
AssignedNative<F>,
>,
x: &AssignedNative<F>,
n: usize,
) -> Result<Vec<AssignedBoundedScalar<F>>, Error> {
powers::<F>(layouter, scalar_chip, x, n)?
.iter()
.enumerate()
.map(|(i, p)| {
if i == 0 {
scalar_chip.assert_equal_to_fixed(layouter, p, F::ONE)?;
return Ok(AssignedBoundedScalar::new(p, Some(BigUint::one())));
}
#[cfg(feature = "truncated-challenges")]
return truncate::<F>(layouter, scalar_chip, p);
#[cfg(not(feature = "truncated-challenges"))]
return Ok(AssignedBoundedScalar::new(p, None));
})
.collect()
}
pub(crate) fn try_reduce<T, F>(iter: impl IntoIterator<Item = T>, f: F) -> Result<T, Error>
where
F: FnMut(T, T) -> Result<T, Error>,
{
let mut iterator = iter.into_iter();
let first = iterator.next().ok_or(Error::Synthesis(
"try_reduced: iterator must not be empty".into(),
))?;
iterator.try_fold(first, f)
}
pub(crate) fn mul_add<F: CircuitField>(
layouter: &mut impl Layouter<F>,
scalar_chip: &impl ArithInstructions<F, AssignedNative<F>>,
x: &AssignedNative<F>,
y: &AssignedNative<F>,
z: &AssignedNative<F>,
) -> Result<AssignedNative<F>, Error> {
scalar_chip.add_and_mul(
layouter,
(F::ZERO, x),
(F::ZERO, y),
(F::ONE, z),
F::ZERO,
F::ONE,
)
}