use std::collections::HashMap; use num::Integer; use crate::field::field_types::{Field, RichField}; use crate::hash::hashing::{compress, hash_or_noop}; use crate::hash::merkle_proofs::MerkleProof; /// Compress multiple Merkle proofs on the same tree by removing redundancy in the Merkle paths. pub(crate) fn compress_merkle_proofs( cap_height: usize, indices: &[usize], proofs: &[MerkleProof], ) -> Vec> { assert!(!proofs.is_empty()); let height = cap_height + proofs[0].siblings.len(); let num_leaves = 1 << height; let mut compressed_proofs = Vec::with_capacity(proofs.len()); // Holds the known nodes in the tree at a given time. The root is at index 1. // Valid indices are 1 through n, and each element at index `i` has // children at indices `2i` and `2i +1` its parent at index `floor(i ∕ 2)`. let mut known = vec![false; 2 * num_leaves]; for &i in indices { // The path from a leaf to the cap is known. for j in 0..(height - cap_height) { known[(i + num_leaves) >> j] = true; } } // For each proof collect all the unknown proof elements. for (&i, p) in indices.iter().zip(proofs) { let mut compressed_proof = MerkleProof { siblings: Vec::new(), }; let mut index = i + num_leaves; for &sibling in &p.siblings { let sibling_index = index ^ 1; if !known[sibling_index] { // If the sibling is not yet known, add it to the proof and set it to known. compressed_proof.siblings.push(sibling); known[sibling_index] = true; } // Go up the tree and set the parent to known. index >>= 1; known[index] = true; } compressed_proofs.push(compressed_proof); } compressed_proofs } /// Decompress compressed Merkle proofs. /// Note: The data and indices must be in the same order as in `compress_merkle_proofs`. pub(crate) fn decompress_merkle_proofs( leaves_data: &[Vec], leaves_indices: &[usize], compressed_proofs: &[MerkleProof], height: usize, cap_height: usize, ) -> Vec> { let num_leaves = 1 << height; let compressed_proofs = compressed_proofs.to_vec(); let mut decompressed_proofs = Vec::with_capacity(compressed_proofs.len()); // Holds the already seen nodes in the tree along with their value. let mut seen = HashMap::new(); for (&i, v) in leaves_indices.iter().zip(leaves_data) { // Observe the leaves. seen.insert(i + num_leaves, hash_or_noop(v.to_vec())); } // Iterators over the siblings. let mut siblings = compressed_proofs .iter() .map(|p| p.siblings.iter()) .collect::>(); // Fill the `seen` map from the bottom of the tree to the cap. for layer_height in 0..height - cap_height { for (&i, p) in leaves_indices.iter().zip(siblings.iter_mut()) { let index = (i + num_leaves) >> layer_height; let current_hash = seen[&index]; let sibling_index = index ^ 1; let sibling_hash = *seen .entry(sibling_index) .or_insert_with(|| *p.next().unwrap()); let parent_hash = if index.is_even() { compress(current_hash, sibling_hash) } else { compress(sibling_hash, current_hash) }; seen.insert(index >> 1, parent_hash); } } // For every index, go up the tree by querying `seen` to get node values. for &i in leaves_indices { let mut decompressed_proof = MerkleProof { siblings: Vec::new(), }; let mut index = i + num_leaves; for _ in 0..height - cap_height { let sibling_index = index ^ 1; let h = seen[&sibling_index]; decompressed_proof.siblings.push(h); index >>= 1; } decompressed_proofs.push(decompressed_proof); } decompressed_proofs } #[cfg(test)] mod tests { use rand::{thread_rng, Rng}; use super::*; use crate::field::field_types::Field; use crate::field::goldilocks_field::GoldilocksField; use crate::hash::merkle_tree::MerkleTree; #[test] fn test_path_compression() { type F = GoldilocksField; let h = 10; let cap_height = 3; let vs = (0..1 << h).map(|_| vec![F::rand()]).collect::>(); let mt = MerkleTree::new(vs.clone(), cap_height); let mut rng = thread_rng(); let k = rng.gen_range(1..=1 << h); let indices = (0..k).map(|_| rng.gen_range(0..1 << h)).collect::>(); let proofs = indices.iter().map(|&i| mt.prove(i)).collect::>(); let compressed_proofs = compress_merkle_proofs(cap_height, &indices, &proofs); let decompressed_proofs = decompress_merkle_proofs( &indices.iter().map(|&i| vs[i].clone()).collect::>(), &indices, &compressed_proofs, h, cap_height, ); assert_eq!(proofs, decompressed_proofs); let compressed_proof_bytes = serde_cbor::to_vec(&compressed_proofs).unwrap(); println!( "Compressed proof length: {} bytes", compressed_proof_bytes.len() ); let proof_bytes = serde_cbor::to_vec(&proofs).unwrap(); println!("Proof length: {} bytes", proof_bytes.len()); } }