#![doc = include_str!("../README.md")]
use std::iter::zip;
use ark_ff::{FftField, Field};
use ark_poly::{
univariate::DensePolynomial, EvaluationDomain, GeneralEvaluationDomain, Polynomial,
};
pub use commit::FriCommitments;
use derive_where::derive_where;
use dragoonfri_proc_macros::{CanonicalDeserializeAlt, CanonicalSerializeAlt};
use folding::{fold_positions, FoldedEvaluationsSlice};
use rng::ReseedableRng;
use rs_merkle::Hasher;
use utils::{horner_evaluate, to_evaluations, AssertPowerOfTwo, HasherExt, MerkleProof};
pub mod algorithms;
pub mod commit;
pub mod folding;
#[cfg(feature = "frida")]
pub mod frida;
#[cfg(feature = "interpolation")]
pub mod interpolation;
pub mod rng;
pub mod utils;
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum VerifyError {
BadFoldingFactor(usize),
BadDomainSize(usize),
BadDegreeBound(usize),
DegreeTruncation {
depth: usize,
},
CommitmentMismatch {
depth: usize,
},
InvalidFolding {
depth: usize,
},
WrongNumberOfEvaluations {
depth: usize,
},
InvalidRemainderDegree,
InvalidRemainder,
}
#[derive_where(Clone, PartialEq; F)]
#[derive_where(Debug; F, H::Hash)]
#[derive(CanonicalSerializeAlt, CanonicalDeserializeAlt)]
pub struct FriProof<F, H: Hasher> {
layers: Vec<FriProofLayer<F, H>>,
remainder: Vec<F>,
}
impl<F: Field, H: Hasher> FriProof<F, H> {
pub(crate) fn first_layer(&self) -> &FriProofLayer<F, H> {
&self.layers[0]
}
}
#[derive_where(Clone, PartialEq; F)]
#[derive_where(Debug; F, H::Hash)]
#[derive(CanonicalSerializeAlt, CanonicalDeserializeAlt)]
pub(crate) struct FriProofLayer<F, H: Hasher> {
proof: MerkleProof<H>,
commitment: H::Hash,
evaluations: Vec<F>,
}
impl<F: Field, H: Hasher> FriProofLayer<F, H> {
pub(crate) fn evaluations<const N: usize>(&self) -> Option<&FoldedEvaluationsSlice<N, F>> {
(self.evaluations.len() % N == 0).then(|| {
FoldedEvaluationsSlice::<N, _>::from_flat_evaluations_unchecked(&self.evaluations)
})
}
pub(crate) fn queried_evaluations<const N: usize>(
&self,
positions: &[usize],
folded_positions: &[usize],
domain_size: usize,
) -> Option<Vec<F>> {
let mask = domain_size / N - 1;
let folded_evals = self.evaluations::<N>()?;
let mut vec = Vec::with_capacity(positions.len());
for pos in positions {
let index = folded_positions.iter().position(|&p| p == pos & mask)?;
let queried = folded_evals
.as_ref()
.get(index)?
.get(pos / (domain_size / N))?;
vec.push(*queried);
}
Some(vec)
}
}
pub fn commit_polynomial<const N: usize, F, H, R>(
polynomial: Vec<F>,
rng: R,
blowup_factor: usize,
remainder_degree_plus_one: usize,
) -> FriCommitments<N, F, H>
where
F: FftField,
H: Hasher,
R: ReseedableRng<Seed = H::Hash>,
{
let domain_size = (polynomial.len() * blowup_factor)
.checked_next_power_of_two()
.unwrap_or_else(|| panic!(
"Domain size out of bounds for blowup factor {blowup_factor} and polynomial of degree-bound {}", polynomial.len()
));
let evaluations = to_evaluations(polynomial, domain_size);
FriCommitments::new(evaluations, rng, blowup_factor, remainder_degree_plus_one)
}
pub fn build_proof<const N: usize, F, H, R>(
commitments: FriCommitments<N, F, H>,
mut rng: R,
num_queries: usize,
) -> FriProof<F, H>
where
F: FftField,
H: Hasher,
R: ReseedableRng<Seed = H::Hash>,
{
let mut domain_size = commitments.layers()[0].evaluations().domain_size();
let mut positions = rng.draw_positions(num_queries, domain_size);
let mut layers = Vec::with_capacity(commitments.layers().len());
for layer in commitments.layers() {
domain_size /= N;
positions = fold_positions(&positions, domain_size);
let mut positions_sorted = positions.clone();
positions_sorted.sort_unstable();
let proof = layer.tree().proof(&positions_sorted).into();
let mut evaluations = Vec::with_capacity(N * positions.len());
for &pos in &positions {
evaluations.extend_from_slice(&layer.evaluations()[pos]);
}
layers.push(FriProofLayer {
proof,
evaluations,
commitment: layer.tree().root().unwrap(),
});
}
FriProof {
layers,
remainder: commitments.remainder(),
}
}
impl<F: FftField, H: Hasher> FriProof<F, H> {
#[inline]
pub fn prove<const N: usize, R>(
evaluations: Vec<F>,
mut rng: R,
blowup_factor: usize,
remainder_degree_plus_one: usize,
num_queries: usize,
) -> Self
where
R: ReseedableRng<Seed = H::Hash>,
{
let commitments = FriCommitments::<N, _, _>::new(
evaluations,
&mut rng,
blowup_factor,
remainder_degree_plus_one,
);
build_proof(commitments, &mut rng, num_queries)
}
pub fn verify<const N: usize, R: ReseedableRng<Seed = H::Hash>>(
&self,
mut rng: R,
num_queries: usize,
mut degree_bound: usize,
mut domain_size: usize,
) -> Result<(), VerifyError> {
let () = AssertPowerOfTwo::<N>::OK;
if !domain_size.is_power_of_two() || domain_size <= degree_bound {
return Err(VerifyError::BadDomainSize(domain_size));
}
if !degree_bound.is_power_of_two() {
return Err(VerifyError::BadDegreeBound(degree_bound));
}
let Some(domain) = GeneralEvaluationDomain::<F>::new(N) else {
return Err(VerifyError::BadFoldingFactor(N));
};
let Some(mut root) = F::get_root_of_unity(domain_size as u64) else {
return Err(VerifyError::BadDomainSize(domain_size));
};
let mut alphas = Vec::<F>::with_capacity(self.layers.len());
for layer in &self.layers {
rng.reseed(layer.commitment);
alphas.push(rng.draw_alpha());
}
rng.reseed(H::hash_item(&self.remainder));
let mut positions = rng.draw_positions(num_queries, domain_size);
let mut evaluations = self.layers[0]
.queried_evaluations::<N>(
&positions,
&fold_positions(&positions, domain_size / N),
domain_size,
)
.ok_or(VerifyError::WrongNumberOfEvaluations { depth: 0 })?;
for (depth, layer) in self.layers.iter().enumerate() {
let folded_positions = fold_positions(&positions, domain_size / N);
let layer_evaluations = layer
.evaluations::<N>()
.ok_or(VerifyError::WrongNumberOfEvaluations { depth })?;
let queried_evaluations = layer
.queried_evaluations::<N>(&positions, &folded_positions, domain_size)
.ok_or(VerifyError::WrongNumberOfEvaluations { depth })?;
if queried_evaluations != evaluations {
return Err(VerifyError::InvalidFolding { depth });
}
if folded_positions.len() != layer_evaluations.folded_len() {
return Err(VerifyError::WrongNumberOfEvaluations { depth });
}
evaluations.clear();
let mut buffer = Vec::with_capacity(N);
for (&pos, eval) in std::iter::zip(&folded_positions, layer_evaluations) {
buffer.extend_from_slice(eval);
domain.ifft_in_place(&mut buffer);
let poly = DensePolynomial { coeffs: buffer };
let offset = root.pow([(domain_size - pos % (domain_size / N)) as u64]);
evaluations.push(poly.evaluate(&(alphas[depth] * offset)));
buffer = poly.coeffs;
buffer.clear();
}
if domain_size % N != 0 || degree_bound % N != 0 {
return Err(VerifyError::DegreeTruncation { depth });
}
root = root.pow([N as u64]);
domain_size /= N;
degree_bound /= N;
positions = folded_positions;
if !layer.proof.verify(
layer.commitment,
&positions,
&H::hash_many(layer_evaluations.as_ref()),
domain_size,
) {
return Err(VerifyError::CommitmentMismatch { depth });
}
}
if self.remainder.len() > degree_bound
&& self.remainder[degree_bound..]
.iter()
.any(|&coeff| coeff != F::ZERO)
{
return Err(VerifyError::InvalidRemainderDegree);
}
for (position, &evaluation) in zip(positions, &evaluations) {
if horner_evaluate(&self.remainder, root.pow([position as u64])) != evaluation {
return Err(VerifyError::InvalidRemainder);
}
}
Ok(())
}
}
#[macro_export]
macro_rules! dynamic_folding_factor {
(let $n:ident = $factor: expr => $function: expr) => {
dynamic_folding_factor!(2, 4, 8, 16, let $n = $factor => $function)
};
($($values: literal,)+ let $n:ident = $factor: expr => $function: expr) => {
match $factor {
$($values => {
const $n: usize = $values;
$function
})+
f => unimplemented!("Unexpected folding factor {f}")
}
}
}