Skip to content

Commit

Permalink
Merge pull request #980 from private-attribution/random_challenge
Browse files Browse the repository at this point in the history
Next part of the ZKPs
  • Loading branch information
benjaminsavage authored Mar 17, 2024
2 parents 3777d9f + 1ba4fda commit af58b0f
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 50 deletions.
93 changes: 43 additions & 50 deletions ipa-core/src/protocol/ipa_prf/malicious_security/lagrange.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
use std::fmt::Debug;
use std::{borrow::Borrow, fmt::Debug};

use generic_array::{ArrayLength, GenericArray};
use typenum::{Unsigned, U1};

use crate::ff::{Field, PrimeField, Serializable};

/// A degree `N-1` polynomial is stored as `N` points `(x,y)`
/// where the "x coordinates" of the input points are `x_0` to `x_N` are `F::ZERO` to `(N-1)*F::ONE`
/// Therefore, we only need to store the `y` coordinates.
#[derive(Debug, PartialEq, Clone)]
pub struct Polynomial<F: Field, N: ArrayLength> {
y_coordinates: GenericArray<F, N>,
}

/// The Canonical Lagrange denominator is defined as the denominator of the Lagrange base polynomials
/// `https://en.wikipedia.org/wiki/Lagrange_polynomial`
/// where the "x coordinates" of the input points are `x_0` to `x_N` are `F::ZERO` to `(N-1)*F::ONE`
Expand Down Expand Up @@ -99,16 +91,25 @@ where
N: ArrayLength,
M: ArrayLength,
{
/// This function uses the `LagrangeTable` to evaluate `polynomial` on the specified output "x coordinates"
/// outputs the "y coordinates" such that `(x,y)` lies on `polynomial`
pub fn eval(&self, polynomial: &Polynomial<F, N>) -> GenericArray<F, M> {
/// This function uses the `LagrangeTable` to evaluate `polynomial` on the _output_ "x coordinates"
/// that were used to generate this table.
/// It is assumed that the `y_coordinates` provided to this function correspond the values of the _input_ "x coordinates"
/// that were used to generate this table.
pub fn eval<I, J>(&self, y_coordinates: I) -> GenericArray<F, M>
where
I: IntoIterator<Item = J> + Copy,
I::IntoIter: ExactSizeIterator,
J: Borrow<F>,
{
debug_assert_eq!(y_coordinates.into_iter().len(), N::USIZE);

self.table
.iter()
.map(|table_row| {
table_row
.iter()
.zip(polynomial.y_coordinates.iter())
.fold(F::ZERO, |acc, (&base, &y)| acc + base * y)
.zip(y_coordinates)
.fold(F::ZERO, |acc, (&base, y)| acc + base * (*y.borrow()))
})
.collect()
}
Expand Down Expand Up @@ -168,84 +169,77 @@ where

#[cfg(all(test, unit_test))]
mod test {
use std::fmt::Debug;
use std::{borrow::Borrow, fmt::Debug};

use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray};
use generic_array::{ArrayLength, GenericArray};
use proptest::{prelude::*, proptest};
use typenum::{U1, U32, U7, U8};

use crate::{
ff::Field,
ff::PrimeField,
protocol::ipa_prf::malicious_security::lagrange::{
CanonicalLagrangeDenominator, LagrangeTable, Polynomial,
CanonicalLagrangeDenominator, LagrangeTable,
},
};

type TestField = crate::ff::Fp32BitPrime;

#[derive(Debug, PartialEq, Clone)]
struct MonomialFormPolynomial<F: Field, N: ArrayLength> {
struct MonomialFormPolynomial<F: PrimeField, N: ArrayLength> {
coefficients: GenericArray<F, N>,
}

impl<F, N> MonomialFormPolynomial<F, N>
where
F: Field,
F: PrimeField,
N: ArrayLength,
{
fn gen_y_values_of_canonical_points(self) -> GenericArray<F, N> {
// Sadly, we cannot just use the range (0..N::U128) because it does not implement ExactSizeIterator
let canonical_points =
(0..N::USIZE).map(|i| F::try_from(u128::try_from(i).unwrap()).unwrap());
self.eval(canonical_points)
}

/// test helper function that evaluates a polynomial in monomial form, i.e. `sum_i c_i x^i` on points `x_output`
/// where `c_0` to `c_N` are stored in `polynomial`
fn eval<M>(&self, x_output: &GenericArray<F, M>) -> GenericArray<F, M>
fn eval<M, I, J>(&self, x_output: I) -> GenericArray<F, M>
where
I: IntoIterator<Item = J>,
I::IntoIter: ExactSizeIterator,
J: Borrow<F>,
M: ArrayLength,
{
x_output
.iter()
.map(|&x| {
.into_iter()
.map(|x| {
// monomial base, i.e. `x^k`
// evaluate p via `sum_k coefficient_k * x^k`
let (_, y) = self
.coefficients
.iter()
.fold((F::ONE, F::ZERO), |(base, y), &coef| {
(base * x, y + coef * base)
(base * (*x.borrow()), y + coef * base)
});
y
})
.collect()
}
}

impl<F, N> From<MonomialFormPolynomial<F, N>> for Polynomial<F, N>
where
F: Field + TryFrom<u128>,
<F as TryFrom<u128>>::Error: Debug,
N: ArrayLength,
{
fn from(value: MonomialFormPolynomial<F, N>) -> Self {
let canonical_points: GenericArray<F, N> =
GenericArray::generate(|i| F::try_from(u128::try_from(i).unwrap()).unwrap());
Polynomial {
y_coordinates: value.eval(&canonical_points),
}
}
}

fn lagrange_single_output_point_using_new(
output_point: TestField,
input_points: [TestField; 32],
) {
let polynomial_monomial_form = MonomialFormPolynomial {
coefficients: GenericArray::<TestField, U32>::from_array(input_points),
};
let output_expected = polynomial_monomial_form.eval(
&GenericArray::<TestField, U1>::from_array([output_point; 1]),
);
let polynomial = Polynomial::from(polynomial_monomial_form.clone());
let output_expected = polynomial_monomial_form.eval(&[output_point]);
let denominator = CanonicalLagrangeDenominator::<TestField, U32>::new();
// generate table using new
let lagrange_table = LagrangeTable::<TestField, U32, U1>::new(&denominator, &output_point);
let output = lagrange_table.eval(&polynomial);
let output =
lagrange_table.eval(&polynomial_monomial_form.gen_y_values_of_canonical_points());
assert_eq!(output, output_expected);
}

Expand All @@ -261,15 +255,14 @@ mod test {
coefficients: GenericArray::<TestField, U8>::from_array(input_points),
};
// the canonical x coordinates are 0..7, the outputs use coordinates 8..15:
let x_coordinates_output = GenericArray::<_, U7>::generate(|i| {
TestField::try_from(u128::try_from(i).unwrap() + 8).unwrap()
});
let output_expected = polynomial_monomial_form.eval(&x_coordinates_output);
let polynomial = Polynomial::from(polynomial_monomial_form.clone());
let x_coordinates_output =
(0..7).map(|i| TestField::try_from(u128::try_from(i).unwrap() + 8).unwrap());
let output_expected = polynomial_monomial_form.eval(x_coordinates_output);
let denominator = CanonicalLagrangeDenominator::<TestField, U8>::new();
// generate table using from
let lagrange_table = LagrangeTable::<TestField, U8, U7>::from(denominator);
let output = lagrange_table.eval(&polynomial);
let output =
lagrange_table.eval(&polynomial_monomial_form.gen_y_values_of_canonical_points());
assert_eq!(output, output_expected);
}

Expand Down
1 change: 1 addition & 0 deletions ipa-core/src/protocol/ipa_prf/malicious_security/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod lagrange;
pub mod prover;
143 changes: 143 additions & 0 deletions ipa-core/src/protocol/ipa_prf/malicious_security/prover.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use std::{
iter::zip,
ops::{Add, Sub},
};

use generic_array::{ArrayLength, GenericArray};
use typenum::{Diff, Sum, U1};

use crate::{
ff::PrimeField,
protocol::ipa_prf::malicious_security::lagrange::{
CanonicalLagrangeDenominator, LagrangeTable,
},
};

pub struct ZeroKnowledgeProof<F: PrimeField, N: ArrayLength> {
g: GenericArray<F, N>,
}

pub struct ProofGenerator<F: PrimeField> {
u: Vec<F>,
v: Vec<F>,
}

type TwoNMinusOne<N> = Diff<Sum<N, N>, U1>;

///
/// Distributed Zero Knowledge Proofs algorithm drawn from
/// `https://eprint.iacr.org/2023/909.pdf`
///
#[allow(non_camel_case_types)]
impl<F> ProofGenerator<F>
where
F: PrimeField,
{
pub fn new(u: Vec<F>, v: Vec<F>) -> Self {
debug_assert_eq!(u.len(), v.len(), "u and v must be of equal length");
Self { u, v }
}

pub fn compute_proof<λ: ArrayLength>(
&self,
r: F,
) -> (ZeroKnowledgeProof<F, TwoNMinusOne<λ>>, ProofGenerator<F>)
where
λ: ArrayLength + Add + Sub<U1>,
<λ as Add>::Output: Sub<U1>,
<<λ as Add>::Output as Sub<U1>>::Output: ArrayLength,
<λ as Sub<U1>>::Output: ArrayLength,
{
debug_assert_eq!(self.u.len() % λ::USIZE, 0); // We should pad with zeroes eventually

let s = self.u.len() / λ::USIZE;

assert!(
s > 1,
"When the output is this small, you should call `compute_final_proof`"
);

let mut next_proof_generator = ProofGenerator {
u: Vec::<F>::with_capacity(s),
v: Vec::<F>::with_capacity(s),
};

let denominator = CanonicalLagrangeDenominator::<F, λ>::new();
let lagrange_table_r = LagrangeTable::<F, λ, U1>::new(&denominator, &r);
let lagrange_table = LagrangeTable::<F, λ, <λ as Sub<U1>>::Output>::from(denominator);
let extrapolated_points = (0..s).map(|i| {
let start = i * λ::USIZE;
let end = start + λ::USIZE;
let p = &self.u[start..end];
let q = &self.v[start..end];
let p_extrapolated = lagrange_table.eval(p);
let q_extrapolated = lagrange_table.eval(q);
let p_r = lagrange_table_r.eval(p)[0];
let q_r = lagrange_table_r.eval(q)[0];
next_proof_generator.u.push(p_r);
next_proof_generator.v.push(q_r);
// p.into_iter() has elements that are &F
// p_extrapolated.into_iter() has elements that are F
// So these iterators cannot be chained.
zip(p, q)
.map(|(a, b)| *a * *b)
.chain(zip(p_extrapolated, q_extrapolated).map(|(a, b)| a * b))
.collect::<GenericArray<F, _>>()
});
let proof = ZeroKnowledgeProof {
g: extrapolated_points
.reduce(|acc, pts| zip(acc, pts).map(|(a, b)| a + b).collect())
.unwrap(),
};
(proof, next_proof_generator)
}
}

#[cfg(all(test, unit_test))]
mod test {
use typenum::U4;

use super::ProofGenerator;
use crate::ff::{Fp31, U128Conversions};

#[test]
fn sample_proof() {
const U: [u128; 32] = [
0, 30, 0, 16, 0, 1, 0, 15, 0, 0, 0, 16, 0, 30, 0, 16, 29, 1, 1, 15, 0, 0, 1, 15, 2, 30,
30, 16, 0, 0, 30, 16,
];
const V: [u128; 32] = [
0, 0, 0, 30, 0, 0, 0, 1, 30, 30, 30, 30, 0, 0, 30, 30, 0, 30, 0, 30, 0, 0, 0, 1, 0, 0,
1, 1, 0, 0, 1, 1,
];
const EXPECTED: [u128; 7] = [0, 30, 29, 30, 5, 28, 13];
const R1: u128 = 22;
const EXPECTED_NEXT_U: [u128; 8] = [0, 0, 26, 0, 7, 18, 24, 13];
const EXPECTED_NEXT_V: [u128; 8] = [10, 21, 30, 28, 15, 21, 3, 3];
let pg: ProofGenerator<Fp31> = ProofGenerator::new(
U.into_iter().map(|x| Fp31::try_from(x).unwrap()).collect(),
V.into_iter().map(|x| Fp31::try_from(x).unwrap()).collect(),
);
let (proof, next_proof_generator) = pg.compute_proof::<U4>(Fp31::try_from(R1).unwrap());
assert_eq!(
proof.g.into_iter().map(|x| x.as_u128()).collect::<Vec<_>>(),
EXPECTED,
);
assert_eq!(
next_proof_generator
.u
.into_iter()
.map(|x| x.as_u128())
.collect::<Vec<_>>(),
EXPECTED_NEXT_U,
);
assert_eq!(
next_proof_generator
.v
.into_iter()
.map(|x| x.as_u128())
.collect::<Vec<_>>(),
EXPECTED_NEXT_V,
);
}
}

0 comments on commit af58b0f

Please sign in to comment.