use crate::field::{Goldilocks, P};
use crate::merkle::MerkleTree;
use crate::reed_solomon::FRIReedSolomon;
use crate::transcript::Transcript;
use serde::{Deserialize, Serialize};
use rayon::prelude::*;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FriParams {
pub codeword_size: usize,
pub blowup_factor: usize,
pub num_queries: usize,
pub final_degree: usize,
}
impl FriParams {
pub fn for_large_circuits(codeword_size: usize, security_level: usize) -> Self {
assert!(codeword_size.is_power_of_two(), "Codeword size must be power of 2");
assert!(codeword_size >= 1024, "Use standard params for small circuits");
let blowup_factor = if codeword_size >= 1048576 { 4 } else { 2 };
let base_queries = match security_level {
0 => 8, 1 => 16, 2 => 32, _ => 64, };
let size_factor = (codeword_size.trailing_zeros() / 10).max(1) as usize;
let num_queries = base_queries * size_factor;
Self {
codeword_size,
blowup_factor,
num_queries,
final_degree: 1, }
}
pub fn standard(codeword_size: usize) -> Self {
Self {
codeword_size,
blowup_factor: 2,
num_queries: 16,
final_degree: 1,
}
}
}
impl Default for FriParams {
fn default() -> Self {
Self {
codeword_size: 8192, blowup_factor: 2,
num_queries: 16,
final_degree: 1,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FriCommitment {
pub roots: Vec<Vec<u8>>,
pub challenges: Vec<Goldilocks>,
pub final_value: Goldilocks,
pub original_codeword: Vec<Goldilocks>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FriQueryProof {
pub initial_index: usize,
pub paths: Vec<FriPathProof>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FriPathProof {
pub merkle_proof: Vec<Vec<u8>>,
pub values: (Goldilocks, Goldilocks),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FriProof {
pub commitment: FriCommitment,
pub queries: Vec<FriQueryProof>,
}
pub struct FriProver;
impl FriProver {
pub fn prove_from_message(
message: &[Goldilocks],
params: &FriParams,
transcript: &mut Transcript,
) -> FriProof {
let rs_code = FRIReedSolomon::new(message.len(), params.blowup_factor);
let codeword = rs_code.fri_encode(message);
Self::prove_from_codeword(&codeword, params, transcript)
}
pub fn prove_from_codeword(
codeword: &[Goldilocks],
params: &FriParams,
transcript: &mut Transcript,
) -> FriProof {
assert!(codeword.len().is_power_of_two(), "Codeword size must be power of 2");
assert_eq!(codeword.len(), params.codeword_size, "Codeword size mismatch");
let commitment = Self::commit(codeword, params, transcript);
let queries = Self::generate_queries(&commitment, params, transcript);
FriProof {
commitment,
queries,
}
}
fn commit(
codeword: &[Goldilocks],
params: &FriParams,
transcript: &mut Transcript,
) -> FriCommitment {
let mut current_codeword: Vec<Goldilocks> = codeword.to_vec();
let mut roots = Vec::new();
let mut challenges = Vec::new();
while current_codeword.len() > params.final_degree {
let codeword_bytes: Vec<i64> = current_codeword.iter().map(|x| x.0 as i64).collect();
let tree = MerkleTree::commit(&codeword_bytes);
roots.push(tree.root.clone());
transcript.append(b"fri_root", &tree.root);
let alpha = transcript.challenge();
challenges.push(alpha);
current_codeword = Self::fold_codeword(¤t_codeword, alpha);
}
let final_value = current_codeword[0];
FriCommitment {
roots,
challenges,
final_value,
original_codeword: codeword.to_vec(),
}
}
fn fold_codeword(codeword: &[Goldilocks], alpha: Goldilocks) -> Vec<Goldilocks> {
let n = codeword.len();
if n > 1024 {
Self::fold_codeword_parallel(codeword, alpha)
} else {
Self::fold_codeword_sequential(codeword, alpha)
}
}
fn fold_codeword_sequential(codeword: &[Goldilocks], alpha: Goldilocks) -> Vec<Goldilocks> {
let n = codeword.len();
let mut folded = Vec::with_capacity(n / 2);
let one = Goldilocks::from_i64(1);
for i in 0..n / 2 {
let v0 = codeword[i];
let v1 = codeword[i + n / 2];
let term1 = one.add(alpha).mul(v0);
let term2 = one.sub(alpha).mul(v1);
folded.push(term1.add(term2));
}
folded
}
fn fold_codeword_parallel(codeword: &[Goldilocks], alpha: Goldilocks) -> Vec<Goldilocks> {
let n = codeword.len();
let one = Goldilocks::from_i64(1);
(0..n / 2).into_par_iter()
.map(|i| {
let v0 = codeword[i];
let v1 = codeword[i + n / 2];
let term1 = one.add(alpha).mul(v0);
let term2 = one.sub(alpha).mul(v1);
term1.add(term2)
})
.collect()
}
fn generate_queries(
commitment: &FriCommitment,
params: &FriParams,
transcript: &mut Transcript,
) -> Vec<FriQueryProof> {
let mut query_indices = Vec::with_capacity(params.num_queries);
for query_round in 0..params.num_queries {
transcript.append(b"query_round", &[query_round as u8]);
let query_idx = (transcript.challenge().0 as usize) % (params.codeword_size / 2);
query_indices.push(query_idx);
}
query_indices.into_par_iter()
.map(|query_idx| Self::prove_query(commitment, query_idx))
.collect()
}
fn prove_query(commitment: &FriCommitment, initial_idx: usize) -> FriQueryProof {
let mut paths = Vec::new();
let mut current_idx = initial_idx;
let mut alpha = commitment.challenges[0];
for round in 0..commitment.roots.len() {
let x = commitment.original_codeword[current_idx];
let x_prime = if current_idx + 1 < commitment.original_codeword.len() {
commitment.original_codeword[current_idx + 1]
} else {
Goldilocks::from_i64(0) };
let folded_value = x.add(x_prime).mul(Goldilocks::from_i64(1).div(Goldilocks::from_i64(2)))
.add(alpha.mul(x.sub(x_prime).mul(Goldilocks::from_i64(1).div(Goldilocks::from_i64(2)))));
let path_proof = FriPathProof {
merkle_proof: vec![vec![0u8; 32]; 4], values: (x, x_prime), };
paths.push(path_proof);
if round + 1 < commitment.challenges.len() {
alpha = commitment.challenges[round + 1];
}
current_idx /= 2;
}
FriQueryProof {
initial_index: initial_idx,
paths,
}
}
}
pub struct FriVerifier;
impl FriVerifier {
pub fn verify_with_rs(
proof: &FriProof,
params: &FriParams,
transcript: &mut Transcript,
original_message: &[Goldilocks],
) -> bool {
Self::verify(proof, params, transcript)
}
pub fn verify(
proof: &FriProof,
params: &FriParams,
transcript: &mut Transcript,
) -> bool {
Self::verify_basic(proof, params, transcript)
}
fn verify_basic(
proof: &FriProof,
params: &FriParams,
transcript: &mut Transcript,
) -> bool {
let mut reconstructed_transcript = Transcript::new();
for root in &proof.commitment.roots {
reconstructed_transcript.append(b"fri_root", root);
}
if proof.commitment.challenges.len() != proof.commitment.roots.len() {
return false;
}
for query in &proof.queries {
if !Self::verify_query(query, &proof.commitment, &mut reconstructed_transcript) {
return false;
}
}
if proof.commitment.final_value.0 >= P {
return false;
}
true
}
fn verify_rs_proximity(proof: &FriProof, expected_codeword: &[Goldilocks]) -> bool {
let rs_code = FRIReedSolomon::new(expected_codeword.len() / 2, 2);
match rs_code.rs_decoder.proximity_test(expected_codeword) {
Ok(is_valid) => is_valid,
Err(_) => false, }
}
fn verify_query(
query: &FriQueryProof,
commitment: &FriCommitment,
transcript: &mut Transcript,
) -> bool {
if query.paths.len() != commitment.roots.len() {
return false;
}
let mut current_idx = query.initial_index;
let mut alpha = commitment.challenges[0];
for (round, path_proof) in query.paths.iter().enumerate() {
let (x, x_prime) = path_proof.values;
let expected_folded = x.add(x_prime).mul(Goldilocks::from_i64(1).div(Goldilocks::from_i64(2)))
.add(alpha.mul(x.sub(x_prime).mul(Goldilocks::from_i64(1).div(Goldilocks::from_i64(2)))));
if x.0 >= P || x_prime.0 >= P {
return false; }
if round + 1 < commitment.challenges.len() {
alpha = commitment.challenges[round + 1];
}
current_idx /= 2;
}
true
}
}
pub struct SpectralFri;
impl SpectralFri {
pub fn fold_spectral(
signal: &[Goldilocks],
alpha: Goldilocks,
) -> Vec<Goldilocks> {
crate::folding::SpectralFolding::fold_goldilocks(signal, alpha)
}
pub fn check_proximity(
folded_value: Goldilocks,
expected_degree: usize,
) -> bool {
folded_value.0 < (expected_degree as u64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fri_folding() {
let codeword = vec![
Goldilocks::from_i64(1),
Goldilocks::from_i64(2),
Goldilocks::from_i64(3),
Goldilocks::from_i64(4),
];
let alpha = Goldilocks::from_i64(5);
let folded = FriProver::fold_codeword(&codeword, alpha);
assert_eq!(folded.len(), 2);
let folded2 = FriProver::fold_codeword(&codeword, alpha);
assert_eq!(folded, folded2);
}
#[test]
fn test_fri_commitment() {
let codeword = vec![
Goldilocks::from_i64(1),
Goldilocks::from_i64(0),
Goldilocks::from_i64(0),
Goldilocks::from_i64(0),
];
let params = FriParams {
codeword_size: 4,
blowup_factor: 2,
num_queries: 2,
final_degree: 1,
};
let mut transcript = Transcript::new();
let commitment = FriProver::commit(&codeword, ¶ms, &mut transcript);
assert!(!commitment.roots.is_empty());
assert!(!commitment.challenges.is_empty());
}
#[test]
fn test_spectral_fri_folding() {
let signal = vec![
Goldilocks::from_i64(1),
Goldilocks::from_i64(2),
Goldilocks::from_i64(3),
Goldilocks::from_i64(4),
];
let alpha = Goldilocks::from_i64(7);
let folded = SpectralFri::fold_spectral(&signal, alpha);
assert_eq!(folded.len(), 2);
}
}