Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract over the vector commitment scheme #285

Merged
merged 12 commits into from
Jul 8, 2024
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,
irakliyk marked this conversation as resolved.
Show resolved Hide resolved
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
irakliyk marked this conversation as resolved.
Show resolved Hide resolved
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 {
irakliyk marked this conversation as resolved.
Show resolved Hide resolved
/// 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