Started implementing FRI

This commit is contained in:
wborgeaud 2021-04-09 18:24:19 +02:00
parent 1ab12c3dfd
commit aa50387d36
3 changed files with 156 additions and 4 deletions

View File

@ -1,3 +1,11 @@
use crate::field::field::Field;
use crate::hash::{compress, hash_n_to_hash};
use crate::plonk_challenger::Challenger;
use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues};
use crate::proof::{Hash, FriProof};
use crate::field::fft::fft;
use crate::gadgets::merkle_proofs::MerkleTree;
/// Somewhat arbitrary. Smaller values will increase delta, but with diminishing returns,
/// while increasing L, potentially requiring more challenge points.
const EPSILON: f64 = 0.01;
@ -10,6 +18,12 @@ struct FriConfig {
/// a 4-to-1 reduction, then a 2-to-1 reduction. After these reductions, the reduced polynomial
/// is sent directly.
reduction_arity_bits: Vec<usize>,
/// Number of reductions in the FRI protocol. So if the original domain has size `2^n`,
/// then the final domain will have size `2^(n-reduction_count)`.
reduction_count: usize,
rate_bits: usize,
}
fn fri_delta(rate_log: usize, conjecture: bool) -> f64 {
@ -34,3 +48,61 @@ fn fri_l(codeword_len: usize, rate_log: usize, conjecture: bool) -> f64 {
1.0 / (2.0 * EPSILON * rate.sqrt())
}
}
// TODO: Different arity + PoW.
/// Performs a FRI round.
fn fri_round<F: Field>(
polynomial_coeffs: &PolynomialCoeffs<F>,
polynomial_values: &PolynomialValues<F>,
challenger: &mut Challenger<F>,
config: &FriConfig,
) -> FriProof<F> {
let n = polynomial_values.values.len();
assert_eq!(
polynomial_coeffs.coeffs.len(),
n
);
let mut trees = vec![MerkleTree::new(polynomial_values.values.iter().map(|&v| vec![v]).collect())];
let mut root = trees.last().unwrap().root;
let mut coeffs = polynomial_coeffs.clone();
let mut values;
challenger.observe_hash(&root);
// Commit phase
for _ in 0..config.reduction_count {
let beta = challenger.get_challenge();
coeffs = PolynomialCoeffs::new(
coeffs
.coeffs
.chunks_exact(2)
.map(|chunk| chunk[0] + beta * chunk[1])
.collect::<Vec<_>>(),
);
values = fft(coeffs.clone().lde(config.rate_bits));
let tree = MerkleTree::new(values.values.iter().map(|&v| vec![v]).collect());
challenger.observe_hash(&tree.root);
trees.push(tree);
}
// Query phase
let mut merkle_proofs = Vec::new();
let mut evals = Vec::new();
for i in 0..config.reduction_count {
let x = challenger.get_challenge();
let x_index = (x.to_canonical_u64() as usize) % n;
let n2 = n>>1;
evals.extend(std::array::IntoIter::new([polynomial_values.values[x_index], polynomial_values.values[n2 + x_index]]));
merkle_proofs.extend(std::array::IntoIter::new([trees[i].prove(x_index), trees[i].prove(n2 + x_index)]));
}
FriProof {
commit_phase_merkle_roots: trees.iter().map(|t| t.root).collect(),
initial_merkle_proofs: vec![],
intermediate_merkle_proofs: merkle_proofs,
final_poly: coeffs
}
}

View File

@ -1,8 +1,10 @@
use crate::circuit_builder::CircuitBuilder;
use crate::field::field::Field;
use crate::hash::{compress, hash_n_to_hash};
use crate::proof::{Hash, HashTarget};
use crate::target::Target;
#[derive(Clone, Debug)]
pub struct MerkleProof<F: Field> {
/// The Merkle digest of each sibling subtree, staying from the bottommost layer.
pub siblings: Vec<Hash<F>>,
@ -13,6 +15,55 @@ pub struct MerkleProofTarget {
pub siblings: Vec<HashTarget>,
}
#[derive(Clone, Debug)]
pub struct MerkleTree<F: Field> {
/// The data in the leaves of the Merkle tree.
pub leaves: Vec<Vec<F>>,
/// The layers of hashes in the tree. The first layer is the one at the bottom.
pub layers: Vec<Vec<Hash<F>>>,
/// The Merkle root.
pub root: Hash<F>,
}
impl<F: Field> MerkleTree<F> {
pub fn new(leaves: Vec<Vec<F>>) -> Self {
let mut layers = vec![leaves.iter().map(|l| hash_n_to_hash(l.clone(), false)).collect::<Vec<_>>()];
loop {
match layers.last() {
Some(l) if l.len() > 1 => {
layers.push(l.chunks(2).map(|chunk| compress(chunk[0], chunk[1])).collect::<Vec<_>>());
},
_ => break
}
}
let root = layers.pop().unwrap()[0];
Self {
leaves,
layers,
root
}
}
/// Create a Merkle proof from a leaf index.
pub fn prove(&self, leaf_index: usize) -> MerkleProof<F> {
MerkleProof {
siblings: self.layers
.iter()
.scan(leaf_index, |acc, layer| {
let index = *acc ^ 1;
*acc >>= 1;
Some(layer[index])
})
.collect(),
}
}
}
/// Verifies that the given leaf data is present at the given index in the Merkle tree with the
/// given root.
pub(crate) fn verify_merkle_proof<F: Field>(
@ -20,8 +71,18 @@ pub(crate) fn verify_merkle_proof<F: Field>(
leaf_index: usize,
merkle_root: Hash<F>,
proof: MerkleProof<F>,
) {
todo!()
) -> bool {
let mut index = leaf_index;
let mut h = hash_n_to_hash(leaf_data, false);
for s in &proof.siblings {
h = if index & 1 == 0 {
compress(h, *s)
} else {
compress(*s, h)
};
index >>= 1;
}
h == merkle_root
}
impl<F: Field> CircuitBuilder<F> {
@ -37,3 +98,21 @@ impl<F: Field> CircuitBuilder<F> {
todo!()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::crandall_field::CrandallField;
#[test]
fn test_merkle_proofs() {
type F = CrandallField;
let num_leaves = 128;
let leaves = (0..num_leaves).map(|_| vec![F::rand()]).collect::<Vec<_>>();
let tree = MerkleTree::new(leaves);
for i in 0..num_leaves {
let proof = tree.prove(i);
assert!(verify_merkle_proof(tree.leaves[i].clone(),i, tree.root, proof));
}
}
}

View File

@ -1,9 +1,10 @@
use crate::field::field::Field;
use crate::target::Target;
use crate::gadgets::merkle_proofs::{MerkleProofTarget, MerkleProof};
use crate::polynomial::polynomial::PolynomialCoeffs;
/// Represents a ~256 bit hash output.
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Hash<F: Field> {
pub(crate) elements: [F; 4],
}
@ -60,7 +61,7 @@ pub struct FriProof<F: Field> {
/// Merkle proofs for the reduced polynomials that were sent in the commit phase.
pub intermediate_merkle_proofs: Vec<MerkleProof<F>>,
/// The final polynomial in coefficient form.
pub final_poly: Vec<F>,
pub final_poly: PolynomialCoeffs<F>,
}
/// Represents a single FRI query, i.e. a path through the reduction tree.