use ark_ff::FftField;
use ark_poly::DenseUVPolynomial;
use ark_std::convert::TryInto;
use ark_std::marker::PhantomData;
use ark_std::ops::Div;
use ark_std::{vec, vec::Vec};
use crate::utils;
pub struct Fflonk<F: FftField, P: DenseUVPolynomial<F>> {
_field: PhantomData<F>,
_poly: PhantomData<P>,
}
impl<F: FftField, P: DenseUVPolynomial<F>> Fflonk<F, P>
where
for<'a, 'b> &'a P: Div<&'b P, Output = P>,
{
pub fn combine(t: usize, fs: &[P]) -> P {
assert!(fs.len() <= t);
let max_degree = fs.iter().map(|fi| fi.degree()).max().unwrap();
let mut res = vec![F::zero(); t * (max_degree + 1)];
for (i, fi) in fs.iter().enumerate() {
for (j, fij) in fi.coeffs().iter().enumerate() {
res[t * j + i] = *fij;
}
}
P::from_coefficients_vec(res)
}
pub fn roots(t: usize, root_t_of_x: F) -> Vec<F> {
let omega_t = F::get_root_of_unity(t.try_into().unwrap()).expect("root of unity not found");
let mut acc = root_t_of_x;
let mut res = vec![root_t_of_x];
res.resize_with(t, || {
acc *= omega_t;
acc
});
res
}
fn z_of_roots(t: usize, root_t_of_x: F) -> P {
let x = root_t_of_x.pow([t as u64]);
let mut z = vec![F::zero(); t + 1]; z[0] = -x;
z[t] = F::one();
P::from_coefficients_vec(z)
}
pub fn opening_as_polynomials(t: usize, root_of_x: F, evals_at_x: &[F]) -> (P, P) {
let z = Self::z_of_roots(t, root_of_x);
let r = P::from_coefficients_slice(evals_at_x);
(z, r)
}
pub fn opening_as_points(t: usize, root_of_x: F, evals_at_x: &[F]) -> (Vec<F>, Vec<F>) {
assert_eq!(evals_at_x.len(), t); let roots = Self::roots(t, root_of_x);
let evals_at_roots = roots
.iter()
.map(|&root| {
evals_at_x
.iter()
.zip(utils::powers(root))
.map(|(&eval, next_root)| eval * next_root)
.sum()
})
.collect();
(roots, evals_at_roots)
}
pub fn multiopening(t: usize, roots_of_xs: &[F], evals_at_xs: &[Vec<F>]) -> (Vec<F>, Vec<F>) {
assert_eq!(roots_of_xs.len(), evals_at_xs.len());
assert!(evals_at_xs.iter().all(|evals_at_x| evals_at_x.len() == t));
let polys = evals_at_xs
.iter()
.map(|evals_at_x| P::from_coefficients_slice(evals_at_x));
let roots = roots_of_xs
.iter()
.map(|&root_of_x| Self::roots(t, root_of_x));
let xs: Vec<_> = roots.clone().flatten().collect();
let vs: Vec<_> = polys
.zip(roots)
.flat_map(|(poly, roots)| Self::multievaluate(&poly, &roots))
.collect();
(xs, vs)
}
fn multievaluate(poly: &P, xs: &[F]) -> Vec<F> {
assert!(poly.degree() + 1 <= xs.len());
xs.iter().map(|p| poly.evaluate(p)).collect()
}
}
#[cfg(test)]
mod tests {
use ark_ff::Field;
use ark_poly::univariate::{DenseOrSparsePolynomial, DensePolynomial};
use ark_poly::Polynomial;
use ark_std::{test_rng, UniformRand, Zero};
use super::*;
type F = ark_bw6_761::Fr;
type P = DensePolynomial<F>;
type FflonkBw6 = Fflonk<F, P>;
#[test]
fn test_single_opening() {
let rng = &mut test_rng();
let d = 15; let t = 4; let root_t_of_x = F::rand(rng); let x = root_t_of_x.pow([t as u64]);
let fs: Vec<P> = (0..t).map(|_| P::rand(d, rng)).collect();
let fs_at_x: Vec<F> = fs
.iter() .map(|fi| fi.evaluate(&x))
.collect();
let g = FflonkBw6::combine(t, &fs);
let (z, r) = FflonkBw6::opening_as_polynomials(t, root_t_of_x, &fs_at_x);
let (xs, vs) = FflonkBw6::opening_as_points(t, root_t_of_x, &fs_at_x);
assert!(xs.iter().zip(vs.iter()).all(|(x, &v)| g.evaluate(x) == v));
assert!(xs.iter().all(|x| z.evaluate(x).is_zero()));
assert!(xs.iter().zip(vs.iter()).all(|(x, &v)| r.evaluate(x) == v));
let (_, g_mod_z) =
DenseOrSparsePolynomial::divide_with_q_and_r(&(&g.into()), &(&z.into())).unwrap();
assert_eq!(r, g_mod_z);
}
#[test]
fn test_multiopening() {
let rng = &mut test_rng();
let d = 15; let t = 4; let m = 3;
let roots_of_xs: Vec<F> = (0..m) .map(|_| F::rand(rng))
.collect();
let xs: Vec<F> = roots_of_xs
.iter() .map(|root_t_of_x| root_t_of_x.pow([t as u64]))
.collect();
let fs: Vec<P> = (0..t).map(|_| P::rand(d, rng)).collect();
let fs_at_xs: Vec<Vec<F>> = xs
.iter()
.map(|x| fs.iter().map(|fi| fi.evaluate(&x)).collect())
.collect();
let g = FflonkBw6::combine(t, &fs);
let (xs, vs) = FflonkBw6::multiopening(t, &roots_of_xs, &fs_at_xs);
assert!(xs.iter().zip(vs).all(|(x, v)| g.evaluate(x) == v));
}
#[test]
fn test_openings_consistency() {
let rng = &mut test_rng();
let d = 15; let t = 4; let root_t_of_x = F::rand(rng); let x = root_t_of_x.pow([t as u64]);
let fs: Vec<P> = (0..t).map(|_| P::rand(d, rng)).collect();
let fs_at_x: Vec<F> = fs
.iter() .map(|fi| fi.evaluate(&x))
.collect();
let (z, r) = FflonkBw6::opening_as_polynomials(t, root_t_of_x, &fs_at_x);
let (xs, vs) = FflonkBw6::multiopening(t, &[root_t_of_x], &[fs_at_x]);
assert!(xs.iter().all(|x| z.evaluate(x).is_zero()));
assert!(xs.iter().zip(vs).all(|(x, v)| r.evaluate(x) == v));
}
}