Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Multilinear Polynomial KZG Commitments #6

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ ark-serialize-derive = {git = "https://github.com/arkworks-rs/algebra"}
ark-test-curves= {git = "https://github.com/arkworks-rs/algebra"}
ark-poly = {git = "https://github.com/arkworks-rs/algebra"}
ark-bls12-381 = {git = "https://github.com/arkworks-rs/curves"}
ark-relations = {git = "https://github.com/arkworks-rs/snark", branch = "sync-algebra"}
ark-relations = {git = "https://github.com/arkworks-rs/snark"}

[dependencies]
ark-bls12-381 = "0.3.0"
Expand Down
189 changes: 189 additions & 0 deletions src/division_stream.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
use ark_ff::Field;
use ark_std::borrow::Borrow;
use ark_std::vec::Vec;

use crate::iterable::Iterable;

/// A `Streamer` that repeatedly divides an n dimensional multilinear polynomial with binomial terms
/// of the form \\((x_i - \alpha_i)\\), for some n dimensional \\(\alpha\\).
/// Produces a stream that describes \\(q_i\\) where \\(\sum_i{q_i(x)(x_i - \alpha_i)} + f(\alpha)\\)
/// Outputs pairs of the form \\((i, x)\\), where \\(i\\) is which quotient is being referred to,
/// and \\(x\\) is the next nonzero coefficient in that quotient. Coefficients are outputted in order.
///
/// There is a special case at the end, where \\(i\\) is equal to the dimension of the polynomial.
/// Then, the corresponding \\(x\\) is the evaluation of the polynomial at \\(\alpha\\).
///
/// The stream can produce all quotient coefficients in the tree with a single pass over the initial stream.
#[derive(Clone, Copy)]
pub struct MultiPolynomialTree<'a, F, S> {
eval_point: &'a [F],
coefficients: &'a S,
}

impl<'a, F, S> MultiPolynomialTree<'a, F, S>
where
S: Iterable,
F: Field,
S::Item: Borrow<F>,
{
/// Initialize a new polynomial tree.
pub fn new(coefficients: &'a S, eval_point: &'a [F]) -> Self {
Self {
coefficients,
eval_point,
}
}

/// Outputs the depth of the polynomial tree.
#[inline]
pub fn depth(&self) -> usize {
self.eval_point.len()
}
}

impl<'a, F, S> Iterable for MultiPolynomialTree<'a, F, S>
where
S: Iterable,
F: Field,
S::Item: Borrow<F>,
{
type Item = (usize, F);

type Iter = MultiPolynomialTreeIter<'a, F, S::Iter>;

fn iter(&self) -> Self::Iter {
MultiPolynomialTreeIter::new(
self.coefficients.iter(),
self.coefficients.len(),
self.eval_point,
)
}

fn len(&self) -> usize {
self.coefficients.len()
}
}

/// Iterator of the polynomial division tree.
pub struct MultiPolynomialTreeIter<'a, F, I> {
eval_point: &'a [F],
iterator: I,
stack: Vec<(usize, F)>,
parities: Vec<bool>,
}

fn init_stack<F: Field>(n: usize, dim: usize) -> Vec<(usize, F)> {
let mut stack = Vec::with_capacity(dim);

// generally we expect the size to be a power of two.
// If not, we are going to fill the stack as if the array was padded to zero up to the expected size.
let chunk_size = 1 << dim;
if n % chunk_size != 0 {
let mut delta = chunk_size - n % chunk_size;
for i in (0..dim).rev() {
if delta >= 1 << i {
stack.push((i, F::zero()));
delta -= 1 << i
}
}
}
stack
}

impl<'a, F, I> MultiPolynomialTreeIter<'a, F, I>
where
F: Field,
I: Iterator,
I::Item: Borrow<F>,
{
fn new(iterator: I, n: usize, eval_point: &'a [F]) -> Self {
let stack = init_stack(n, eval_point.len());
let parities = vec![false; eval_point.len()];

Self {
eval_point,
iterator,
stack,
parities,
}
}
}

/// Each time we call next, a tuple (i, x) means that the next nonzero coefficient in the
/// i'th quotient is x. Note that the 0'th quotient has nonzero coeffients at the 0, 2, 4, ... indices,
/// the 1'th quotient has nonzero coefficients at the 0, 4, 8, ... indices, and so on.
///
impl<'a, F, I> Iterator for MultiPolynomialTreeIter<'a, F, I>
where
F: Field,
I: Iterator,
I::Item: Borrow<F>,
{
type Item = (usize, F);

fn next(&mut self) -> Option<<Self as Iterator>::Item> {
let len = self.stack.len();
let stack_item = if len > 1 && self.stack[len - 1].0 == self.stack[len - 2].0 {
// pop the last two elements from the stack.
// we could also use .pop() twice but truncate is slightly faster.
let (_level, lhs) = self.stack[len - 1];
let (level, rhs) = self.stack[len - 2];
self.stack.truncate(len - 2);

let folded_coefficient = lhs * self.eval_point[level] + rhs;
(level + 1, folded_coefficient)
} else {
(0, *self.iterator.next()?.borrow())
};

// do not add to the stack the coefficient of the max-depth folded polynomial.
// instead, just return it as is.
if stack_item.0 != self.eval_point.len() {
self.stack.push(stack_item)
} else {
return Some(stack_item);
}
// for each quotient, only yield every other coefficient.
self.parities[stack_item.0] = !self.parities[stack_item.0];
if self.parities[stack_item.0] {
self.next()
} else {
Some(stack_item)
}
}
}

#[test]
fn test_polynomial_divide_randomized() {
use crate::misc::{evaluate_multi_poly, mul_components, random_vector};
use ark_bls12_381::Fr as F;
use ark_ff::Zero;

let dim = 4;
let rng = &mut ark_std::test_rng();
let coefficients: Vec<F> = random_vector(1 << dim, rng);
let alpha: Vec<F> = random_vector(dim, rng); // the evaluation point
let test_point: Vec<F> = random_vector(dim, rng);
let coefficients_stream = coefficients.as_slice();
let foldstream = MultiPolynomialTree::new(&coefficients_stream, alpha.as_slice());
let mut result = F::zero();
let alpha_eval = evaluate_multi_poly(&coefficients, &alpha);
let mut quotient_evals: Vec<F> = vec![F::zero(); dim];
let mut quotient_idxs = vec![0; dim];
for (quotient_num, quotient_coefficient) in foldstream.iter() {
if quotient_num != dim {
quotient_evals[quotient_num] +=
quotient_coefficient * mul_components(&test_point, quotient_idxs[quotient_num]);
quotient_idxs[quotient_num] += 1 << (quotient_num + 1);
} else {
assert_eq!(quotient_coefficient, alpha_eval);
}
}
for i in 0..dim {
result += quotient_evals[i] * (test_point[i] - alpha[i])
}
assert_eq!(
result,
evaluate_multi_poly(&coefficients, &test_point) - alpha_eval
);
}
66 changes: 66 additions & 0 deletions src/docs/lib_docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
Gemini: Elastic Arguments for R1CS.

This library provides essentially two arguments:
- [`snark::Proof`], for non-preprocessing SNARKs.
It provides a non-interactive succinct argument of knowledge for R1CS
without indexer, and where the verifier complexity is linear in the circuit size.
- [`psnark::Proof`] for preprocessing SNARKs.
It provides a non-interactive succinct argument of knowledge for R1CS
where the verifier complexity is logarithmic in the circuit size.

The library implements the Kate-Zaverucha-Goldberg protocol [[KZG](https://www.iacr.org/archive/asiacrypt2010/6477178/6477178.pdf)]
for polynomial commitments.

### KZG Protocol Outline

1. Trusted setup and key generation
2. Commitment to a polynomial $f(x) \in \FF\[x\]$.
4. Evaluation of polynomial and proof generation
5. Verification of evaluation proof

The `kzg` and [`multikzg`] modules contain implementations of both time-efficient and space-efficient
versions of KZG polynomial commitments.

The choice of the pairing-friendly elliptic curve to be used is entirely up to the user.
For example, crate [`ark_bls12_381`] contains the implementation for curve [`Bls12_381`](ark_bls12_381::Bls12_381).

See the module documentation for `multikzg` below for an overview of protocol math.

# Building

This package can be compiled with `cargo build`. For now, this package can be built on rust stable.
Test package with `cargo test`.
Compile package documentation with `cargo rustdoc` and launch in default browser with `cargo rustdoc --open`.

Both arguments rely on some sub-protocols, implemented as separate modules in [`subprotocols`]
and free of use for other protocols.

# Building

This package can be compiled with `cargo build`, and requires rust nightly at least
until [`Iterator::advance_by`] hits stable. There's a bunch of feature flags that can be turned
on:

- `asm`, to turn on the assembly backend within [`ark-ff`](https://docs.rs/ark-ff/);
- `parallel`, to turn on multi-threading. This requires the additional dependency [`rayon`](https://docs.rs/rayon/latest/rayon/);
- `std`, to rely on the Rust Standard library;
- `print-trace`, to print additional information concerning the execution time of the sub-protocols. **This feature must be enabled if want to print the execution time of the examples.**

# Benchmarking

Micro-benchmarks aare available and can be fired with:

```bash
cargo bench
```

Execution of (preprocessing-)SNARK for arbitrary instance sizes can be done running
the examples with:

```bash
cargo run --example snark -- -i <INSTANCE_LOGSIZE>
```

# License

This package is licensed under MIT license.
62 changes: 62 additions & 0 deletions src/docs/multivariate_protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
This module implements multivariate polynomial commitments, while module `kzg` implements the univariate version. \
Like in the univariate version, the notation $\tau \in \Z_p$ is a prime field element and $G \in \GG_1$ is a generator for affine group $\GG_1$. \
$H$ is a generator for the affine group $\GG_2$. $\GG_1$ and $\GG_2$ are pairing-friendly. \
The univariate protocol is edited for commitments to multilinear polynomials $f(x_1, x_2, x_3, ... x_n)$. \
For example, $f(x_1, ... x_n) = f_0 + f_1 \cdot x_1 + f_2 \cdot x_2 + f_3 \cdot x_1 \cdot x_2 + f_4 \cdot x_3 + f_5 \cdot x_3 \cdot x_1 + f_6 \cdot x_3 \cdot x_2 + f_7 \cdot x_3 \cdot x_2 \cdot x_1 + ...$ \

##### Setup

$\tau_i \in \Z_p$ are still prime field elements. \
The commitment key is now defined as $ck = \(G, \tau_1 G, \tau_2 G, \tau_1 \tau_2 G, \tau_3 G, \tau_3 \tau_1 G ... \)$. \
The verification key is defined as $vk = \(G, H_0, \tau_1 H_1, \tau_2 H_2, ... \tau_n H_n\)$. \
The polynomial $f$ is a vector of coefficients $f(x) = \Sigma_{i=0}^{n-1} f_i \cdot x_i$. \
Commitment key is represented by struct `CommitterKeyMulti` from which `VerifierKeyMulti` can be produced.

##### Commitment

The commitment step, implemented by `commit`, hides the choice of polynomial using the commitment key $ck$. \
The commitment step again returns $C = \Sigma_i f_i \cdot ck_i$.

##### Evaluation

The evaluation step is given the committer key, polynomial, and an evaluation vector $\hat{\alpha} \in \Z_p$. \
`open` returns the evaluation (output of the polynomial evaluated at $\hat{\alpha}$) and the proof, a set of quotients. \
Proof quotients $Q_i$ are found by dividing $\(x - \alpha\)$ into $f(x)$, such that $f(x) = \Sigma_i q(x) \cdot \(x_i - \alpha\) + r(x)$. \
Thus, the evaluation $f( \alpha ) = r \in \FF_p$. \
The proof is ${ \Sigma_j q_{1,j} \cdot ck_j, ..., \Sigma_j q_{n,j} \cdot ck_j } $.

##### Verification

`verify` verifies that the group pairing equation $\epsilon(C - f(\hat{\alpha})G, H_0) = \Sigma_i \epsilon(Q_i, H_i - \alpha_i H_i)$ is true.

### Multivariate Batching

If multiple polynomials are to be opened at one evaluation point, `batched_poly` takes a linear combination of the polynomials scaled by powers of a field element challenge, and then `batch_open_multi_polys` actually opens the combined polynomial to one point. \
Say we have a set of $m$ multilinear polynomials of equal degree to evaluate at $\alpha$, $f_i$. The Verifier sends across a challenge field element $c \in \FF_p$. \
Then the scaling vector is computed as $(1, c, c^{2}, c^{3}...)$ and proofs are batched. \
The statement to verify becomes $\Sigma_i (\mu^i \cdot f_i) (\alpha) = \Sigma_i \mu_i \cdot (f_i (\alpha))$.


# Example Multivariate Protocol Usage
```
use ark_bls12_381::Bls12_381;
use ark_bls12_381::Fr;
use ark_std::UniformRand;
use ark_gemini_polyzygotic::errors::{VerificationError, VerificationResult};
use ark_gemini_polyzygotic::multikzg::{Commitment, VerifierKeyMulti, CommitterKeyMulti};
use ark_gemini_polyzygotic::misc::{evaluate_multi_poly};

let dim = 3;
let rng = &mut ark_std::test_rng();
let ck = CommitterKeyMulti::<Bls12_381>::new(dim, rng);
let vk = VerifierKeyMulti::from(&ck);

let polynomial_flat = (0..1<<dim).map(|_| Fr::rand(rng)).collect::<Vec<_>>();

let alpha = (0..dim).map(|_| Fr::rand(rng)).collect::<Vec<_>>();
let commitment = ck.commit(&polynomial_flat);
let (evaluation, proof) = ck.open(&polynomial_flat, &alpha);
assert!(vk.verify(&commitment, &alpha, &evaluation, &proof).is_ok());
```
See also: Package tests.

Loading