use spongefish::{Encoding, NargDeserialize};
use crate::field::Goldilocks4;
use crate::hash::hash_vc_leaf;
use crate::merkle::MerkleTree;
#[derive(spongefish::Encoding, spongefish::NargDeserialize)]
pub(crate) struct Opening<VC: VectorCommitment> {
pub openings: Vec<VC::Alphabet>,
pub vc_proof: VC::OpeningProof,
}
pub(crate) trait VectorCommitment: Sized {
type Alphabet: Clone + Encoding;
type Commitment: Encoding + NargDeserialize + Clone;
type OpeningProof: Encoding;
type CommitState;
fn open(&self, state: &Self::CommitState, indexes: &[usize]) -> Opening<Self>;
fn verify(
&self,
commitment: &Self::Commitment,
indexes: &[usize],
proof: &Opening<Self>,
) -> bool;
}
pub(crate) struct MerkleVc {
pub(crate) n: usize,
}
impl MerkleVc {
pub(crate) fn new(n: usize) -> Self {
Self { n }
}
}
impl MerkleVc {
pub(crate) fn commit_slab(
&self,
slab: super::code::CodewordSlab,
) -> ([u8; 32], (MerkleTree, super::code::CodewordSlab)) {
assert_eq!(
slab.positions(),
self.n,
"MerkleVc::commit_slab: positions ({}) != n ({})",
slab.positions(),
self.n
);
let leaf_hashes: Vec<[u8; 32]> = slab.iter_positions().map(hash_vc_leaf).collect();
let tree = MerkleTree::build_from_hashes(leaf_hashes);
let root = tree.root();
(root, (tree, slab))
}
}
impl VectorCommitment for MerkleVc {
type Alphabet = Vec<Goldilocks4>;
type Commitment = [u8; 32];
type OpeningProof = Vec<[u8; 32]>;
type CommitState = (MerkleTree, super::code::CodewordSlab);
fn open(&self, state: &Self::CommitState, indexes: &[usize]) -> Opening<Self> {
let (tree, slab) = state;
let openings: Vec<Vec<Goldilocks4>> =
indexes.iter().map(|&i| slab.position(i).to_vec()).collect();
let mut sorted_unique: Vec<usize> = indexes.to_vec();
sorted_unique.sort_unstable();
sorted_unique.dedup();
let vc_proof = tree.multiproof(&sorted_unique);
Opening { openings, vc_proof }
}
fn verify(
&self,
commitment: &Self::Commitment,
indexes: &[usize],
proof: &Opening<Self>,
) -> bool {
if proof.openings.len() != indexes.len() {
return false;
}
let leaf_hashes_input_order: Vec<[u8; 32]> = proof
.openings
.iter()
.map(|symbols| hash_vc_leaf(symbols))
.collect();
let mut sorted_unique: Vec<(usize, [u8; 32])> = Vec::new();
for (k, &idx) in indexes.iter().enumerate() {
let leaf_hash = leaf_hashes_input_order[k];
match sorted_unique.binary_search_by_key(&idx, |&(x, _)| x) {
Ok(pos) => {
if sorted_unique[pos].1 != leaf_hash {
return false;
}
}
Err(pos) => {
sorted_unique.insert(pos, (idx, leaf_hash));
}
}
}
let sorted_indices: Vec<usize> = sorted_unique.iter().map(|&(i, _)| i).collect();
let sorted_leaf_hashes: Vec<[u8; 32]> = sorted_unique.iter().map(|&(_, h)| h).collect();
crate::merkle::verify_multiproof(
*commitment,
self.n,
&sorted_indices,
&sorted_leaf_hashes,
&proof.vc_proof,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::Goldilocks;
fn dummy_symbols(i: usize, k: usize) -> Vec<Goldilocks4> {
(0..k)
.map(|j| {
Goldilocks4::new([
Goldilocks::new((i * 100 + j) as u64),
Goldilocks::new(0),
Goldilocks::new(0),
Goldilocks::new(0),
])
})
.collect()
}
fn dummy_slab(n: usize, k: usize) -> super::super::code::CodewordSlab {
let mut data: Vec<Goldilocks4> = Vec::with_capacity(n * k);
for i in 0..n {
data.extend(dummy_symbols(i, k));
}
super::super::code::CodewordSlab::new(data, k)
}
#[test]
fn round_trip() {
let n = 64;
let k = 4;
let vc = MerkleVc::new(n);
let (commit, state) = vc.commit_slab(dummy_slab(n, k));
let indexes = [3, 17, 42, 50];
let opening = vc.open(&state, &indexes);
assert!(vc.verify(&commit, &indexes, &opening));
}
#[test]
fn tampering_rejected() {
let n = 32;
let k = 4;
let vc = MerkleVc::new(n);
let (commit, state) = vc.commit_slab(dummy_slab(n, k));
let indexes = [10];
let mut opening = vc.open(&state, &indexes);
opening.openings[0][0] += Goldilocks4::ONE;
assert!(!vc.verify(&commit, &indexes, &opening));
}
}