Skip to content

Commit

Permalink
feat: abstract over the vector commitment scheme (#285)
Browse files Browse the repository at this point in the history
  • Loading branch information
Al-Kindi-0 authored Jul 8, 2024
1 parent 00f2579 commit 3c163ba
Show file tree
Hide file tree
Showing 57 changed files with 1,073 additions and 756 deletions.
10 changes: 3 additions & 7 deletions air/src/proof/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use alloc::vec::Vec;
use core::cmp;

use crypto::Hasher;
use crypto::{Hasher, MerkleTree};
use fri::FriProof;
use math::FieldElement;
use utils::{ByteReader, Deserializable, DeserializationError, Serializable, SliceReader};
Expand Down Expand Up @@ -154,12 +154,8 @@ impl Proof {
num_unique_queries: 0,
commitments: Commitments::default(),
trace_queries: Vec::new(),
constraint_queries: Queries::new::<_, DummyField>(
BatchMerkleProof::<DummyHasher<DummyField>> {
leaves: Vec::new(),
nodes: Vec::new(),
depth: 0,
},
constraint_queries: Queries::new::<DummyHasher<DummyField>, DummyField, MerkleTree<_>>(
BatchMerkleProof::<DummyHasher<DummyField>> { nodes: Vec::new(), depth: 0 },
vec![vec![DummyField::ONE]],
),
ood_frame: OodFrame::default(),
Expand Down
86 changes: 42 additions & 44 deletions air/src/proof/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use alloc::vec::Vec;

use crypto::{BatchMerkleProof, ElementHasher, Hasher};
use crypto::{ElementHasher, Hasher, VectorCommitment};
use math::FieldElement;
use utils::{
ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader,
Expand All @@ -17,48 +17,45 @@ use super::Table;
// ================================================================================================
/// Decommitments to evaluations of a set of functions at multiple points.
///
/// Given a set of functions evaluated over a domain *D*, a commitment is assumed to be a Merkle
/// tree where a leaf at position *i* contains evaluations of all functions at *x<sub>i</sub>*.
/// Given a set of functions evaluated over a domain *D*, a commitment is assumed to be a vector
/// commitment where the *i*-th vector entry contains evaluations of all functions at *x<sub>i</sub>*.
/// Thus, a query (i.e. a single decommitment) for position *i* includes evaluations of all
/// functions at *x<sub>i</sub>*, accompanied by a Merkle authentication path from the leaf *i* to
/// the tree root.
/// functions at *x<sub>i</sub>*, accompanied by an opening proof of leaf *i* against the vector
/// commitment string.
///
/// This struct can contain one or more queries. In cases when more than one query is stored,
/// Merkle authentication paths are compressed to remove redundant nodes.
/// a batch opening proof is used in order to compress the individual opening proofs.
///
/// Internally, all Merkle paths and query values are stored as a sequence of bytes. Thus, to
/// retrieve query values and the corresponding Merkle authentication paths,
/// [parse()](Queries::parse) function should be used.
/// Internally, all opening proofs and query values are stored as a sequence of bytes. Thus, to
/// retrieve query values and their corresponding opening proofs, [parse()](Queries::parse)
/// function should be used.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Queries {
paths: Vec<u8>,
opening_proof: Vec<u8>,
values: Vec<u8>,
}

impl Queries {
// CONSTRUCTOR
// --------------------------------------------------------------------------------------------
/// Returns queries constructed from evaluations of a set of functions at some number of points
/// in a domain and their corresponding Merkle authentication paths.
/// in a domain and their corresponding batch opening proof.
///
/// For each evaluation point, the same number of values must be provided, and a hash of
/// these values must be equal to a leaf node in the corresponding Merkle authentication path.
/// For each evaluation point, the same number of values must be provided.
///
/// # Panics
/// Panics if:
/// * No queries were provided (`query_values` is an empty vector).
/// * Any of the queries does not contain any evaluations.
/// * Not all queries contain the same number of evaluations.
pub fn new<H: Hasher, E: FieldElement>(
merkle_proof: BatchMerkleProof<H>,
pub fn new<H: Hasher, E: FieldElement, V: VectorCommitment<H>>(
opening_proof: V::MultiProof,
query_values: Vec<Vec<E>>,
) -> Self {
assert!(!query_values.is_empty(), "query values cannot be empty");
let elements_per_query = query_values[0].len();
assert_ne!(elements_per_query, 0, "a query must contain at least one evaluation");

// TODO: add debug check that values actually hash into the leaf nodes of the batch proof

// concatenate all elements together into a single vector of bytes
let num_queries = query_values.len();
let mut values = Vec::with_capacity(num_queries * elements_per_query * E::ELEMENT_BYTES);
Expand All @@ -70,33 +67,31 @@ impl Queries {
);
values.write_many(elements);
}
let opening_proof = opening_proof.to_bytes();

// serialize internal nodes of the batch Merkle proof; we care about internal nodes only
// because leaf nodes can be reconstructed from hashes of query values
let paths = merkle_proof.serialize_nodes();

Queries { paths, values }
Queries { opening_proof, values }
}

// PARSER
// --------------------------------------------------------------------------------------------
/// Convert internally stored bytes into a set of query values and the corresponding Merkle
/// authentication paths.
/// Convert internally stored bytes into a set of query values and the corresponding batch
/// opening proof.
///
/// # Panics
/// Panics if:
/// * `domain_size` is not a power of two.
/// * `num_queries` is zero.
/// * `values_per_query` is zero.
pub fn parse<H, E>(
pub fn parse<E, H, V>(
self,
domain_size: usize,
num_queries: usize,
values_per_query: usize,
) -> Result<(BatchMerkleProof<H>, Table<E>), DeserializationError>
) -> Result<(V::MultiProof, Table<E>), DeserializationError>
where
E: FieldElement,
H: ElementHasher<BaseField = E::BaseField>,
V: VectorCommitment<H>,
{
assert!(domain_size.is_power_of_two(), "domain size must be a power of two");
assert!(num_queries > 0, "there must be at least one query");
Expand All @@ -113,20 +108,27 @@ impl Queries {
)));
}

// read bytes corresponding to each query, convert them into field elements,
// and also hash them to build leaf nodes of the batch Merkle proof
// read bytes corresponding to each query and convert them into field elements.
let query_values = Table::<E>::from_bytes(&self.values, num_queries, values_per_query)?;
let hashed_queries = query_values.rows().map(|row| H::hash_elements(row)).collect();

// build batch Merkle proof
let mut reader = SliceReader::new(&self.paths);
let tree_depth = domain_size.ilog2() as u8;
let merkle_proof = BatchMerkleProof::deserialize(&mut reader, hashed_queries, tree_depth)?;
// build batch opening proof
let mut reader = SliceReader::new(&self.opening_proof);
let opening_proof = <V::MultiProof as Deserializable>::read_from(&mut reader)?;

// check that the opening proof matches the domain length
if <V as VectorCommitment<H>>::get_multiproof_domain_len(&opening_proof) != domain_size {
return Err(DeserializationError::InvalidValue(format!(
"expected a domain of size {} but was {}",
domain_size,
<V as VectorCommitment<H>>::get_multiproof_domain_len(&opening_proof),
)));
}

if reader.has_more_bytes() {
return Err(DeserializationError::UnconsumedBytes);
}

Ok((merkle_proof, query_values))
Ok((opening_proof, query_values))
}
}

Expand All @@ -137,17 +139,15 @@ impl Serializable for Queries {
/// Serializes `self` and writes the resulting bytes into the `target`.
fn write_into<W: ByteWriter>(&self, target: &mut W) {
// write value bytes
target.write_u32(self.values.len() as u32);
target.write_bytes(&self.values);
self.values.write_into(target);

// write path bytes
target.write_u32(self.paths.len() as u32);
target.write_bytes(&self.paths);
self.opening_proof.write_into(target);
}

/// Returns an estimate of how many bytes are needed to represent self.
fn get_size_hint(&self) -> usize {
self.paths.len() + self.values.len() + 8
self.opening_proof.len() + self.values.len() + 8
}
}

Expand All @@ -158,13 +158,11 @@ impl Deserializable for Queries {
/// Returns an error of a valid query struct could not be read from the specified source.
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
// read values
let num_value_bytes = source.read_u32()?;
let values = source.read_vec(num_value_bytes as usize)?;
let values = Vec::<_>::read_from(source)?;

// read paths
let num_paths_bytes = source.read_u32()?;
let paths = source.read_vec(num_paths_bytes as usize)?;
let paths = Vec::<_>::read_from(source)?;

Ok(Queries { paths, values })
Ok(Queries { opening_proof: paths, values })
}
}
86 changes: 86 additions & 0 deletions crypto/src/commitment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

use alloc::vec::Vec;
use core::fmt::Debug;

use utils::{Deserializable, Serializable};

use crate::Hasher;

/// A vector commitment (VC) scheme.
///
/// This is a cryptographic primitive allowing one to commit, using a commitment string `com`, to
/// a vector of values (v_0, ..., v_{n-1}) such that one can later reveal the value at the i-th
/// position.
///
/// This is achieved by providing the value `v_i` together with a proof `proof_i` such that anyone
/// posessing `com` can be convinced, with high confidence, that the claim is true.
///
/// Vector commitment schemes usually have some batching properties in the sense that opening
/// proofs for a number of `(i, v_i)` can be batched together into one batch opening proof in order
/// to optimize both the proof size as well as the verification time.
///
/// The current implementation restricts both of the commitment string as well as the leaf values
/// to be `H::Digest` where `H` is a type parameter such that `H: Hasher`.
pub trait VectorCommitment<H: Hasher>: Sized {
/// Options defining the VC i.e., public parameters.
type Options: Default;
/// Opening proof of some value at some position index.
type Proof: Clone + Serializable + Deserializable;
/// Batch opening proof of a number of {(i, v_i)}_{i ∈ S} for an index set.
type MultiProof: Serializable + Deserializable;
/// Error returned by the scheme.
type Error: Debug;

/// Creates a commitment to a vector of values (v_0, ..., v_{n-1}) using the default
/// options.
fn new(items: Vec<H::Digest>) -> Result<Self, Self::Error> {
Self::with_options(items, Self::Options::default())
}

/// Creates a commitment to a vector of values (v_0, ..., v_{n-1}) given a set of
/// options.
fn with_options(items: Vec<H::Digest>, options: Self::Options) -> Result<Self, Self::Error>;

/// Returns the commitment string to the committed values.
fn commitment(&self) -> H::Digest;

/// Returns the length of the vector committed to for `Self`.
fn domain_len(&self) -> usize;

/// Returns the length of the vector committed to for `Self::Proof`.
fn get_proof_domain_len(proof: &Self::Proof) -> usize;

/// Returns the length of the vector committed to for `Self::MultiProof`.
fn get_multiproof_domain_len(proof: &Self::MultiProof) -> usize;

/// Opens the value at a given index and provides a proof for the correctness of claimed value.
fn open(&self, index: usize) -> Result<(H::Digest, Self::Proof), Self::Error>;

/// Opens the values at a given index set and provides a proof for the correctness of claimed
/// values.
#[allow(clippy::type_complexity)]
fn open_many(
&self,
indexes: &[usize],
) -> Result<(Vec<H::Digest>, Self::MultiProof), Self::Error>;

/// Verifies that the claimed value is at the given index using a proof.
fn verify(
commitment: H::Digest,
index: usize,
item: H::Digest,
proof: &Self::Proof,
) -> Result<(), Self::Error>;

/// Verifies that the claimed values are at the given set of indices using a batch proof.
fn verify_many(
commitment: H::Digest,
indexes: &[usize],
items: &[H::Digest],
proof: &Self::MultiProof,
) -> Result<(), Self::Error>;
}
3 changes: 3 additions & 0 deletions crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ pub use random::{DefaultRandomCoin, RandomCoin};

mod errors;
pub use errors::{MerkleTreeError, RandomCoinError};

mod commitment;
pub use commitment::VectorCommitment;
Loading

0 comments on commit 3c163ba

Please sign in to comment.