diff --git a/triton-vm/benches/proof_size.rs b/triton-vm/benches/proof_size.rs index 8db2abd74..e46703f97 100644 --- a/triton-vm/benches/proof_size.rs +++ b/triton-vm/benches/proof_size.rs @@ -18,7 +18,7 @@ use twenty_first::prelude::*; use triton_vm::example_programs::FIBONACCI_SEQUENCE; use triton_vm::example_programs::VERIFY_SUDOKU; use triton_vm::prelude::*; -use triton_vm::proof_stream::ProofStream; +use triton_vm::proof::Stream; use triton_vm::prove_program; /// Ties together a program and its inputs. @@ -189,7 +189,7 @@ fn log_2_fri_domain_length(stark: Stark, proof: &Proof) -> u32 { /// sizes for that type are accumulated. fn break_down_proof_size(proof: &Proof) -> HashMap { let mut proof_size_breakdown = HashMap::new(); - let proof_stream = ProofStream::try_from(proof).unwrap(); + let proof_stream = Stream::try_from(proof).unwrap(); for proof_item in &proof_stream.items { let item_name = proof_item.to_string(); let item_len = proof_item.encode().len(); diff --git a/triton-vm/src/error.rs b/triton-vm/src/error.rs index 5dab9d872..aa0febb5e 100644 --- a/triton-vm/src/error.rs +++ b/triton-vm/src/error.rs @@ -8,11 +8,10 @@ use twenty_first::error::MerkleTreeError; use twenty_first::prelude::*; use crate::instruction::Instruction; -use crate::proof_item::ProofItem; -use crate::proof_item::ProofItemVariant; -use crate::proof_stream::ProofStream; +use crate::proof; +use crate::proof::Stream; +use crate::proof::Version; use crate::vm::VMState; -use crate::BFieldElement; /// Indicates a runtime error that resulted in a crash of Triton VM. #[derive(Debug, Clone, Eq, PartialEq, Error)] @@ -107,13 +106,16 @@ pub enum ArithmeticDomainError { #[non_exhaustive] #[derive(Debug, Error)] pub enum ProofStreamError { + #[error("proof version {0} is unknown")] + UnknownProofVersion(Version), + #[error("queue must be non-empty in order to dequeue an item")] EmptyQueue, #[error("expected {expected}, got {got}")] UnexpectedItem { - expected: ProofItemVariant, - got: ProofItem, + expected: proof::ItemVariant, + got: proof::Item, }, #[error("the proof stream must contain a log2_padded_height item")] @@ -123,7 +125,7 @@ pub enum ProofStreamError { TooManyLog2PaddedHeights, #[error(transparent)] - DecodingError(#[from] ::Error), + DecodingError(#[from] ::Error), } #[non_exhaustive] diff --git a/triton-vm/src/fri.rs b/triton-vm/src/fri.rs index db3408230..d9912e37d 100644 --- a/triton-vm/src/fri.rs +++ b/triton-vm/src/fri.rs @@ -11,9 +11,8 @@ use crate::error::FriSetupError; use crate::error::FriValidationError; use crate::error::FriValidationError::*; use crate::profiler::profiler; -use crate::proof_item::FriResponse; -use crate::proof_item::ProofItem; -use crate::proof_stream::ProofStream; +use crate::proof; +use crate::proof::FriResponse; pub(crate) type SetupResult = Result; pub(crate) type ProverResult = Result; @@ -30,7 +29,7 @@ pub struct Fri { #[derive(Debug, Eq, PartialEq)] struct FriProver<'stream> { - proof_stream: &'stream mut ProofStream, + proof_stream: &'stream mut proof::Stream, rounds: Vec, first_round_domain: ArithmeticDomain, num_rounds: usize, @@ -72,7 +71,7 @@ impl<'stream> FriProver<'stream> { fn commit_to_round(&mut self, round: &ProverRound) { let merkle_root = round.merkle_tree.root(); - let proof_item = ProofItem::MerkleRoot(merkle_root); + let proof_item = proof::Item::MerkleRoot(merkle_root); self.proof_stream.enqueue(proof_item); } @@ -90,7 +89,7 @@ impl<'stream> FriProver<'stream> { fn send_last_codeword(&mut self) { let last_codeword = self.rounds.last().unwrap().codeword.clone(); - let proof_item = ProofItem::FriCodeword(last_codeword); + let proof_item = proof::Item::FriCodeword(last_codeword); self.proof_stream.enqueue(proof_item); } @@ -99,7 +98,7 @@ impl<'stream> FriProver<'stream> { let last_polynomial = ArithmeticDomain::of_length(last_codeword.len()) .unwrap() .interpolate(last_codeword); - let proof_item = ProofItem::FriPolynomial(last_polynomial); + let proof_item = proof::Item::FriPolynomial(last_polynomial); self.proof_stream.enqueue(proof_item); } @@ -148,7 +147,7 @@ impl<'stream> FriProver<'stream> { auth_structure, revealed_leaves, }; - let proof_item = ProofItem::FriResponse(fri_response); + let proof_item = proof::Item::FriResponse(fri_response); self.proof_stream.enqueue(proof_item); Ok(()) } @@ -193,7 +192,7 @@ impl ProverRound { #[derive(Debug, Eq, PartialEq)] struct FriVerifier<'stream> { - proof_stream: &'stream mut ProofStream, + proof_stream: &'stream mut proof::Stream, rounds: Vec, first_round_domain: ArithmeticDomain, last_round_codeword: Vec, @@ -524,7 +523,7 @@ impl Fri { pub fn prove( &self, codeword: &[XFieldElement], - proof_stream: &mut ProofStream, + proof_stream: &mut proof::Stream, ) -> ProverResult> { let mut prover = self.prover(proof_stream); @@ -540,7 +539,7 @@ impl Fri { Ok(prover.first_round_collinearity_check_indices) } - fn prover<'stream>(&'stream self, proof_stream: &'stream mut ProofStream) -> FriProver { + fn prover<'stream>(&'stream self, proof_stream: &'stream mut proof::Stream) -> FriProver { FriProver { proof_stream, rounds: vec![], @@ -555,7 +554,7 @@ impl Fri { /// Returns the indices and revealed elements of the codeword at the top level of the FRI proof. pub fn verify( &self, - proof_stream: &mut ProofStream, + proof_stream: &mut proof::Stream, ) -> VerifierResult> { profiler!(start "init"); let mut verifier = self.verifier(proof_stream); @@ -573,7 +572,7 @@ impl Fri { Ok(verifier.first_round_partially_revealed_codeword()) } - fn verifier<'stream>(&'stream self, proof_stream: &'stream mut ProofStream) -> FriVerifier { + fn verifier<'stream>(&'stream self, proof_stream: &'stream mut proof::Stream) -> FriVerifier { FriVerifier { proof_stream, rounds: vec![], @@ -669,7 +668,7 @@ mod tests { use rand_core::SeedableRng; use test_strategy::proptest; - use ProofItem::*; + use proof::Item::*; use crate::error::FriValidationError; use crate::shared_tests::*; @@ -745,7 +744,7 @@ mod tests { let polynomial = Polynomial::new(coefficients); let codeword = fri.domain.evaluate(&polynomial); - let mut proof_stream = ProofStream::new(); + let mut proof_stream = proof::Stream::new(); fri.prove(&codeword, &mut proof_stream).unwrap(); let mut proof_stream = prepare_proof_stream_for_verification(proof_stream); @@ -761,7 +760,7 @@ mod tests { ) { debug_assert!(polynomial.degree() <= fri.first_round_max_degree() as isize); let codeword = fri.domain.evaluate(&polynomial); - let mut proof_stream = ProofStream::new(); + let mut proof_stream = proof::Stream::new(); fri.prove(&codeword, &mut proof_stream).unwrap(); let mut proof_stream = prepare_proof_stream_for_verification(proof_stream); @@ -778,7 +777,7 @@ mod tests { ) { debug_assert!(polynomial.degree() > fri.first_round_max_degree() as isize); let codeword = fri.domain.evaluate(&polynomial); - let mut proof_stream = ProofStream::new(); + let mut proof_stream = proof::Stream::new(); fri.prove(&codeword, &mut proof_stream).unwrap(); let mut proof_stream = prepare_proof_stream_for_verification(proof_stream); @@ -847,11 +846,11 @@ mod tests { #[strategy(arbitrary_polynomial_of_degree(#_degree))] polynomial: Polynomial, ) { let codeword = fri.domain.evaluate(&polynomial); - let mut prover_proof_stream = ProofStream::new(); + let mut prover_proof_stream = proof::Stream::new(); fri.prove(&codeword, &mut prover_proof_stream).unwrap(); let proof = (&prover_proof_stream).into(); - let verifier_proof_stream = ProofStream::try_from(&proof).unwrap(); + let verifier_proof_stream = proof::Stream::try_from(&proof).unwrap(); let prover_items = prover_proof_stream.items.iter(); let verifier_items = verifier_proof_stream.items.iter(); @@ -873,7 +872,7 @@ mod tests { rng_seed: u64, ) { let codeword = fri.domain.evaluate(&polynomial); - let mut proof_stream = ProofStream::new(); + let mut proof_stream = proof::Stream::new(); fri.prove(&codeword, &mut proof_stream).unwrap(); let proof_stream = prepare_proof_stream_for_verification(proof_stream); @@ -888,7 +887,7 @@ mod tests { } #[must_use] - fn prepare_proof_stream_for_verification(mut proof_stream: ProofStream) -> ProofStream { + fn prepare_proof_stream_for_verification(mut proof_stream: proof::Stream) -> proof::Stream { proof_stream.items_index = 0; proof_stream.sponge = Tip5::init(); proof_stream @@ -896,9 +895,9 @@ mod tests { #[must_use] fn modify_last_round_codeword_in_proof_stream_using_seed( - mut proof_stream: ProofStream, + mut proof_stream: proof::Stream, seed: u64, - ) -> ProofStream { + ) -> proof::Stream { let mut proof_items = proof_stream.items.iter_mut(); let last_round_codeword = proof_items.find_map(fri_codeword_filter()).unwrap(); @@ -910,7 +909,7 @@ mod tests { proof_stream } - fn fri_codeword_filter() -> fn(&mut ProofItem) -> Option<&mut Vec> { + fn fri_codeword_filter() -> fn(&mut proof::Item) -> Option<&mut Vec> { |proof_item| match proof_item { FriCodeword(codeword) => Some(codeword), _ => None, @@ -924,7 +923,7 @@ mod tests { rng_seed: u64, ) { let codeword = fri.domain.evaluate(&polynomial); - let mut proof_stream = ProofStream::new(); + let mut proof_stream = proof::Stream::new(); fri.prove(&codeword, &mut proof_stream).unwrap(); let proof_stream = prepare_proof_stream_for_verification(proof_stream); @@ -940,9 +939,9 @@ mod tests { #[must_use] fn change_size_of_some_fri_response_in_proof_stream_using_seed( - mut proof_stream: ProofStream, + mut proof_stream: proof::Stream, seed: u64, - ) -> ProofStream { + ) -> proof::Stream { let proof_items = proof_stream.items.iter_mut(); let fri_responses = proof_items.filter_map(fri_response_filter()); @@ -958,7 +957,7 @@ mod tests { proof_stream } - fn fri_response_filter() -> fn(&mut ProofItem) -> Option<&mut super::FriResponse> { + fn fri_response_filter() -> fn(&mut proof::Item) -> Option<&mut super::FriResponse> { |proof_item| match proof_item { FriResponse(fri_response) => Some(fri_response), _ => None, @@ -978,7 +977,7 @@ mod tests { } let codeword = fri.domain.evaluate(&polynomial); - let mut proof_stream = ProofStream::new(); + let mut proof_stream = proof::Stream::new(); fri.prove(&codeword, &mut proof_stream).unwrap(); let proof_stream = prepare_proof_stream_for_verification(proof_stream); @@ -992,9 +991,9 @@ mod tests { #[must_use] fn modify_some_auth_structure_in_proof_stream_using_seed( - mut proof_stream: ProofStream, + mut proof_stream: proof::Stream, seed: u64, - ) -> ProofStream { + ) -> proof::Stream { let proof_items = proof_stream.items.iter_mut(); let auth_structures = proof_items.filter_map(non_trivial_auth_structure_filter()); @@ -1012,7 +1011,7 @@ mod tests { } fn non_trivial_auth_structure_filter( - ) -> fn(&mut ProofItem) -> Option<&mut super::AuthenticationStructure> { + ) -> fn(&mut proof::Item) -> Option<&mut super::AuthenticationStructure> { |proof_item| match proof_item { FriResponse(fri_response) if fri_response.auth_structure.is_empty() => None, FriResponse(fri_response) => Some(&mut fri_response.auth_structure), @@ -1028,12 +1027,12 @@ mod tests { incorrect_polynomial: Polynomial, ) { let codeword = fri.domain.evaluate(&fri_polynomial); - let mut proof_stream = ProofStream::new(); + let mut proof_stream = proof::Stream::new(); fri.prove(&codeword, &mut proof_stream).unwrap(); let mut proof_stream = prepare_proof_stream_for_verification(proof_stream); proof_stream.items.iter_mut().for_each(|item| { - if let ProofItem::FriPolynomial(polynomial) = item { + if let proof::Item::FriPolynomial(polynomial) = item { *polynomial = incorrect_polynomial.clone(); } }); @@ -1051,7 +1050,7 @@ mod tests { #[strategy(arbitrary_polynomial_of_degree(#_degree))] poly: Polynomial, ) { let codeword = fri.domain.evaluate(&poly); - let mut proof_stream = ProofStream::new(); + let mut proof_stream = proof::Stream::new(); fri.prove(&codeword, &mut proof_stream).unwrap(); let mut proof_stream = prepare_proof_stream_for_verification(proof_stream); @@ -1063,7 +1062,7 @@ mod tests { #[proptest] fn verifying_arbitrary_proof_does_not_panic( fri: Fri, - #[strategy(arb())] mut proof_stream: ProofStream, + #[strategy(arb())] mut proof_stream: proof::Stream, ) { let _verdict = fri.verify(&mut proof_stream); } diff --git a/triton-vm/src/lib.rs b/triton-vm/src/lib.rs index 141768594..5391d8ebb 100644 --- a/triton-vm/src/lib.rs +++ b/triton-vm/src/lib.rs @@ -181,8 +181,6 @@ pub mod prelude; pub mod profiler; pub mod program; pub mod proof; -pub mod proof_item; -pub mod proof_stream; pub mod stark; pub mod table; pub mod vm; @@ -718,9 +716,9 @@ mod tests { implements_auto_traits::(); implements_auto_traits::(); implements_auto_traits::(); - implements_auto_traits::(); - implements_auto_traits::(); - implements_auto_traits::(); + implements_auto_traits::(); + implements_auto_traits::(); + implements_auto_traits::(); implements_auto_traits::(); } diff --git a/triton-vm/src/proof.rs b/triton-vm/src/proof.rs index 61195e131..235b3b323 100644 --- a/triton-vm/src/proof.rs +++ b/triton-vm/src/proof.rs @@ -1,25 +1,61 @@ +use std::fmt::Display; +use std::fmt::Formatter; + use arbitrary::Arbitrary; use get_size::GetSize; use itertools::Itertools; use serde::Deserialize; use serde::Serialize; +use strum::Display; +use strum::EnumCount; +use strum::EnumDiscriminants; +use strum::EnumIter; use twenty_first::prelude::*; use crate::error::ProofStreamError; +use crate::fri::AuthenticationStructure; use crate::program::Program; -use crate::proof_stream::ProofStream; +use crate::table::BaseRow; +use crate::table::ExtensionRow; +use crate::table::QuotientSegments; + +pub(crate) const CURRENT_PROOF_VERSION: Version = Version(42_000); + +/// The version of the [`Proof`] format and, transitively, the [`Stream`]. +#[derive( + Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, GetSize, BFieldCodec, Arbitrary, +)] +pub struct Version(u32); + +impl Display for Version { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let precision = 1000; + let patch = self.0 % precision; + let minor = (self.0 / precision) % precision; + let major = self.0 / (precision * precision); + + write!(f, "{major:03}_{minor:03}_{patch:03}") + } +} /// Contains the necessary cryptographic information to verify a computation. /// Should be used together with a [`Claim`]. +/// +/// See also [`Stream`]. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, GetSize, BFieldCodec, Arbitrary)] -pub struct Proof(pub Vec); +pub struct Proof { + version: Version, + + // Kept Separate for future-proofing. + encoded_proof_stream: Vec, +} impl Proof { /// Get the height of the trace used during proof generation. /// This is an upper bound on the length of the computation this proof is for. /// It is one of the main contributing factors to the length of the FRI domain. pub fn padded_height(&self) -> Result { - let proof_stream = ProofStream::try_from(self)?; + let proof_stream = Stream::try_from(self)?; let proof_items = proof_stream.items.into_iter(); let log_2_padded_heights = proof_items .filter_map(|item| item.try_into_log2_padded_height().ok()) @@ -28,10 +64,10 @@ impl Proof { if log_2_padded_heights.is_empty() { return Err(ProofStreamError::NoLog2PaddedHeight); } - if log_2_padded_heights.len() > 1 { + let [log_2_height] = log_2_padded_heights[..] else { return Err(ProofStreamError::TooManyLog2PaddedHeights); - } - Ok(1 << log_2_padded_heights[0]) + }; + Ok(1 << log_2_height) } } @@ -81,17 +117,238 @@ impl Claim { } } +/// A stream of proof [`Item`]s. +#[derive(Debug, Default, Clone, Eq, PartialEq, BFieldCodec, Arbitrary)] +pub struct Stream { + pub items: Vec, + + #[bfield_codec(ignore)] + pub items_index: usize, + + #[bfield_codec(ignore)] + pub sponge: Tip5, +} + +impl Stream { + pub fn new() -> Self { + Stream { + items: vec![], + items_index: 0, + sponge: Tip5::init(), + } + } + + /// Alters the Fiat-Shamir's sponge state with the encoding of the given item. + /// Does _not_ record the given item in the proof stream. + /// This is useful for items that are not sent to the verifier, _e.g._, the + /// [`Claim`]. + /// + /// See also [`Self::enqueue()`] and [`Self::dequeue()`]. + pub fn alter_fiat_shamir_state_with(&mut self, item: &impl BFieldCodec) { + self.sponge.pad_and_absorb_all(&item.encode()) + } + + /// Send a proof item as prover to verifier. + /// Some items do not need to be included in the Fiat-Shamir heuristic, _i.e._, they do not + /// need to modify the sponge state. For those items, namely those that evaluate to `false` + /// according to [`Item::include_in_fiat_shamir_heuristic`], the sponge state is not + /// modified. + /// For example: + /// - Merkle authentication structure do not need to be hashed if the root of the tree + /// in question was hashed previously. + /// - If the proof stream is not used to sample any more randomness, _i.e._, after the last + /// round of interaction, no further items need to be hashed. + pub fn enqueue(&mut self, item: Item) { + if item.include_in_fiat_shamir_heuristic() { + self.alter_fiat_shamir_state_with(&item); + } + self.items.push(item); + } + + /// Receive a proof item from prover as verifier. + /// See [`Stream::enqueue`] for more details. + pub fn dequeue(&mut self) -> Result { + let Some(item) = self.items.get(self.items_index) else { + return Err(ProofStreamError::EmptyQueue); + }; + let item = item.to_owned(); + if item.include_in_fiat_shamir_heuristic() { + self.alter_fiat_shamir_state_with(&item); + } + self.items_index += 1; + Ok(item) + } + + /// Given an `upper_bound` that is a power of 2, produce `num_indices` uniform random numbers + /// in the interval `[0; upper_bound)`. + /// + /// - `upper_bound`: The (non-inclusive) upper bound. Must be a power of two. + /// - `num_indices`: The number of indices to sample + pub fn sample_indices(&mut self, upper_bound: usize, num_indices: usize) -> Vec { + assert!(upper_bound.is_power_of_two()); + assert!(upper_bound <= BFieldElement::MAX as usize); + self.sponge + .sample_indices(upper_bound as u32, num_indices) + .into_iter() + .map(|i| i as usize) + .collect() + } + + /// A thin wrapper around [`H::sample_scalars`](AlgebraicHasher::sample_scalars). + pub fn sample_scalars(&mut self, num_scalars: usize) -> Vec { + self.sponge.sample_scalars(num_scalars) + } +} + +impl TryFrom<&Proof> for Stream { + type Error = ProofStreamError; + + fn try_from(proof: &Proof) -> Result { + if proof.version != CURRENT_PROOF_VERSION { + return Err(ProofStreamError::UnknownProofVersion(proof.version)); + } + + let proof_stream = *Stream::decode(&proof.encoded_proof_stream)?; + Ok(proof_stream) + } +} + +impl From<&Stream> for Proof { + fn from(proof_stream: &Stream) -> Self { + Proof { + version: CURRENT_PROOF_VERSION, + encoded_proof_stream: proof_stream.encode(), + } + } +} + +impl From for Proof { + fn from(proof_stream: Stream) -> Self { + (&proof_stream).into() + } +} + +/// A `FriResponse` is an `AuthenticationStructure` together with the values of the +/// revealed leaves of the Merkle tree. Together, they correspond to the +/// queried indices of the FRI codeword (of that round). +#[derive(Debug, Clone, Eq, PartialEq, Hash, BFieldCodec, Arbitrary)] +pub struct FriResponse { + /// The authentication structure of the Merkle tree. + pub auth_structure: AuthenticationStructure, + + /// The values of the opened leaves of the Merkle tree. + pub revealed_leaves: Vec, +} + +macro_rules! proof_items { + ($($variant:ident($payload:ty) => $in_fiat_shamir_heuristic:literal, $try_into_fn:ident,)+) => { + #[derive( + Debug, + Display, + Clone, + Eq, + PartialEq, + Hash, + EnumCount, + EnumDiscriminants, + BFieldCodec, + Arbitrary, + )] + #[strum_discriminants(name(ItemVariant))] + // discriminants' default derives: Debug, Copy, Clone, Eq, PartialEq + #[strum_discriminants(derive(Display, EnumIter, BFieldCodec, Arbitrary))] + pub enum Item { + $( $variant($payload), )+ + } + + impl Item { + /// Whether a given proof item should be considered in the Fiat-Shamir heuristic. + /// The Fiat-Shamir heuristic is sound only if all elements in the (current) transcript + /// are considered. However, certain elements indirectly appear more than once. For + /// example, a Merkle root is a commitment to any number of elements. If the Merkle root + /// is part of the transcript, has been considered in the Fiat-Shamir heuristic, and + /// assuming collision resistance of the hash function in use, none of the committed-to + /// elements have to be considered in the Fiat-Shamir heuristic again. + /// This also extends to the authentication structure of these elements, et cetera. + pub const fn include_in_fiat_shamir_heuristic(&self) -> bool { + match self { + $( Self::$variant(_) => $in_fiat_shamir_heuristic, )+ + } + } + + $( + pub fn $try_into_fn(self) -> Result<$payload, ProofStreamError> { + if let Self::$variant(payload) = self { + Ok(payload) + } else { + Err(ProofStreamError::UnexpectedItem { + expected: ItemVariant::$variant, + got: self, + }) + } + } + )+ + } + + impl ItemVariant { + pub fn payload_static_length(self) -> Option { + match self { + $( Self::$variant => <$payload>::static_length(), )+ + } + } + + /// See [`Item::include_in_fiat_shamir_heuristic`]. + pub const fn include_in_fiat_shamir_heuristic(self) -> bool { + match self { + $( Self::$variant => $in_fiat_shamir_heuristic, )+ + } + } + + /// Can be used as “reflection”, for example through `syn`. + pub const fn payload_type(self) -> &'static str { + match self { + $( Self::$variant => stringify!($payload), )+ + } + } + } + }; +} + +proof_items!( + MerkleRoot(Digest) => true, try_into_merkle_root, + OutOfDomainBaseRow(Box>) => true, try_into_out_of_domain_base_row, + OutOfDomainExtRow(Box) => true, try_into_out_of_domain_ext_row, + OutOfDomainQuotientSegments(QuotientSegments) => true, try_into_out_of_domain_quot_segments, + + // the following are implied by some Merkle root, thus not included in the Fiat-Shamir heuristic + AuthenticationStructure(AuthenticationStructure) => false, try_into_authentication_structure, + MasterBaseTableRows(Vec>) => false, try_into_master_base_table_rows, + MasterExtTableRows(Vec) => false, try_into_master_ext_table_rows, + Log2PaddedHeight(u32) => false, try_into_log2_padded_height, + QuotientSegmentsElements(Vec) => false, try_into_quot_segments_elements, + FriCodeword(Vec) => false, try_into_fri_codeword, + FriPolynomial(Polynomial) => false, try_into_fri_polynomial, + FriResponse(FriResponse) => false, try_into_fri_response, +); + #[cfg(test)] mod tests { + use std::collections::HashSet; + use std::collections::VecDeque; + use assert2::assert; + use assert2::let_assert; + use itertools::Itertools; use proptest::collection::vec; use proptest::prelude::*; use proptest_arbitrary_interop::arb; + use strum::IntoEnumIterator; use test_strategy::proptest; - - use crate::proof_item::ProofItem; + use twenty_first::math::other::random_elements; use super::*; + use crate::error::ProofStreamError::UnexpectedItem; + use crate::shared_tests::LeavedMerkleTreeTestData; impl Default for Claim { /// For testing purposes only. @@ -100,6 +357,19 @@ mod tests { } } + /// To allow backwards compatibility, the encoding of the [`Proof`]'s + /// `version` field must not change. + #[test] + fn version_has_static_length_of_one() { + assert!(Some(1) == Version::static_length()); + } + + #[test] + fn version_is_formatted_correctly() { + assert!("012_345_678" == format!("{}", Version(12_345_678))); + assert!("123_004_005" == format!("{}", Version(123_004_005))); + } + #[proptest] fn decode_proof(#[strategy(arb())] proof: Proof) { let encoded = proof.encode(); @@ -116,8 +386,8 @@ mod tests { #[proptest(cases = 10)] fn proof_with_no_padded_height_gives_err(#[strategy(arb())] root: Digest) { - let mut proof_stream = ProofStream::new(); - proof_stream.enqueue(ProofItem::MerkleRoot(root)); + let mut proof_stream = Stream::new(); + proof_stream.enqueue(Item::MerkleRoot(root)); let proof: Proof = proof_stream.into(); let maybe_padded_height = proof.padded_height(); assert!(maybe_padded_height.is_err()); @@ -125,10 +395,10 @@ mod tests { #[proptest(cases = 10)] fn proof_with_multiple_padded_height_gives_err(#[strategy(arb())] root: Digest) { - let mut proof_stream = ProofStream::new(); - proof_stream.enqueue(ProofItem::Log2PaddedHeight(8)); - proof_stream.enqueue(ProofItem::MerkleRoot(root)); - proof_stream.enqueue(ProofItem::Log2PaddedHeight(7)); + let mut proof_stream = Stream::new(); + proof_stream.enqueue(Item::Log2PaddedHeight(8)); + proof_stream.enqueue(Item::MerkleRoot(root)); + proof_stream.enqueue(Item::Log2PaddedHeight(7)); let proof: Proof = proof_stream.into(); let maybe_padded_height = proof.padded_height(); assert!(maybe_padded_height.is_err()); @@ -140,4 +410,254 @@ mod tests { ) { let _proof = Proof::decode(&proof_data); } + + #[proptest] + fn serialize_proof_with_fiat_shamir( + #[strategy(vec(arb(), 2..100))] base_rows: Vec>, + #[strategy(vec(arb(), 2..100))] ext_rows: Vec, + #[strategy(arb())] ood_base_row: Box>, + #[strategy(arb())] ood_ext_row: Box, + #[strategy(arb())] quot_elements: Vec, + leaved_merkle_tree: LeavedMerkleTreeTestData, + ) { + let auth_structure = leaved_merkle_tree.auth_structure.clone(); + let root = leaved_merkle_tree.root(); + let fri_codeword = leaved_merkle_tree.leaves().to_owned(); + let fri_response = leaved_merkle_tree.into_fri_response(); + + let mut sponge_states = VecDeque::new(); + let mut proof_stream = Stream::new(); + + sponge_states.push_back(proof_stream.sponge.state); + proof_stream.enqueue(Item::AuthenticationStructure(auth_structure.clone())); + sponge_states.push_back(proof_stream.sponge.state); + proof_stream.enqueue(Item::MasterBaseTableRows(base_rows.clone())); + sponge_states.push_back(proof_stream.sponge.state); + proof_stream.enqueue(Item::MasterExtTableRows(ext_rows.clone())); + sponge_states.push_back(proof_stream.sponge.state); + proof_stream.enqueue(Item::OutOfDomainBaseRow(ood_base_row.clone())); + sponge_states.push_back(proof_stream.sponge.state); + proof_stream.enqueue(Item::OutOfDomainExtRow(ood_ext_row.clone())); + sponge_states.push_back(proof_stream.sponge.state); + proof_stream.enqueue(Item::MerkleRoot(root)); + sponge_states.push_back(proof_stream.sponge.state); + proof_stream.enqueue(Item::QuotientSegmentsElements(quot_elements.clone())); + sponge_states.push_back(proof_stream.sponge.state); + proof_stream.enqueue(Item::FriCodeword(fri_codeword.clone())); + sponge_states.push_back(proof_stream.sponge.state); + proof_stream.enqueue(Item::FriResponse(fri_response.clone())); + sponge_states.push_back(proof_stream.sponge.state); + + let proof = proof_stream.into(); + let mut proof_stream = Stream::try_from(&proof).unwrap(); + + assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); + let_assert!(Ok(proof_item) = proof_stream.dequeue()); + let_assert!(Item::AuthenticationStructure(auth_structure_) = proof_item); + assert!(auth_structure == auth_structure_); + + assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); + let_assert!(Ok(Item::MasterBaseTableRows(base_rows_)) = proof_stream.dequeue()); + assert!(base_rows == base_rows_); + + assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); + let_assert!(Ok(Item::MasterExtTableRows(ext_rows_)) = proof_stream.dequeue()); + assert!(ext_rows == ext_rows_); + + assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); + let_assert!(Ok(Item::OutOfDomainBaseRow(ood_base_row_)) = proof_stream.dequeue()); + assert!(ood_base_row == ood_base_row_); + + assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); + let_assert!(Ok(Item::OutOfDomainExtRow(ood_ext_row_)) = proof_stream.dequeue()); + assert!(ood_ext_row == ood_ext_row_); + + assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); + let_assert!(Ok(Item::MerkleRoot(root_)) = proof_stream.dequeue()); + assert!(root == root_); + + assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); + let_assert!(Ok(proof_item) = proof_stream.dequeue()); + let_assert!(Item::QuotientSegmentsElements(quot_elements_) = proof_item); + assert!(quot_elements == quot_elements_); + + assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); + let_assert!(Ok(Item::FriCodeword(fri_codeword_)) = proof_stream.dequeue()); + assert!(fri_codeword == fri_codeword_); + + assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); + let_assert!(Ok(Item::FriResponse(fri_response_)) = proof_stream.dequeue()); + assert!(fri_response == fri_response_); + + assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); + assert!(0 == sponge_states.len()); + } + + #[test] + fn enqueue_dequeue_verify_partial_authentication_structure() { + let tree_height = 8; + let num_leaves = 1 << tree_height; + let leaf_values: Vec = random_elements(num_leaves); + let leaf_digests = leaf_values.iter().map(|&xfe| xfe.into()).collect_vec(); + let merkle_tree = MerkleTree::new::(&leaf_digests).unwrap(); + let indices_to_check = vec![5, 173, 175, 167, 228, 140, 252, 149, 232, 182, 5, 5, 182]; + let auth_structure = merkle_tree + .authentication_structure(&indices_to_check) + .unwrap(); + let revealed_leaves = indices_to_check + .iter() + .map(|&idx| leaf_values[idx]) + .collect_vec(); + let fri_response = FriResponse { + auth_structure, + revealed_leaves, + }; + + let mut proof_stream = Stream::new(); + proof_stream.enqueue(Item::FriResponse(fri_response)); + + // TODO: Also check that deserializing from Proof works here. + + let proof_item = proof_stream.dequeue().unwrap(); + let maybe_same_fri_response = proof_item.try_into_fri_response().unwrap(); + let FriResponse { + auth_structure, + revealed_leaves, + } = maybe_same_fri_response; + let maybe_same_leaf_digests = revealed_leaves.iter().map(|&xfe| xfe.into()).collect_vec(); + let indexed_leafs = indices_to_check + .into_iter() + .zip_eq(maybe_same_leaf_digests) + .collect(); + + let inclusion_proof = MerkleTreeInclusionProof { + tree_height, + indexed_leafs, + authentication_structure: auth_structure, + }; + assert!(inclusion_proof.verify(merkle_tree.root())); + } + + #[test] + fn dequeuing_from_empty_stream_fails() { + let mut proof_stream = Stream::new(); + let_assert!(Err(ProofStreamError::EmptyQueue) = proof_stream.dequeue()); + } + + #[test] + fn dequeuing_more_items_than_have_been_enqueued_fails() { + let mut proof_stream = Stream::new(); + proof_stream.enqueue(Item::FriCodeword(vec![])); + proof_stream.enqueue(Item::Log2PaddedHeight(7)); + + let_assert!(Ok(_) = proof_stream.dequeue()); + let_assert!(Ok(_) = proof_stream.dequeue()); + let_assert!(Err(ProofStreamError::EmptyQueue) = proof_stream.dequeue()); + } + + #[test] + fn encoded_length_of_prove_stream_is_not_known_at_compile_time() { + assert!(Stream::static_length().is_none()); + } + + #[proptest] + fn serialize_fri_response_in_isolation(leaved_merkle_tree: LeavedMerkleTreeTestData) { + let fri_response = leaved_merkle_tree.into_fri_response(); + let encoding = fri_response.encode(); + let_assert!(Ok(decoding) = FriResponse::decode(&encoding)); + prop_assert_eq!(fri_response, *decoding); + } + + #[proptest] + fn serialize_fri_response_in_proof_stream(leaved_merkle_tree: LeavedMerkleTreeTestData) { + let fri_response = leaved_merkle_tree.into_fri_response(); + let mut proof_stream = Stream::new(); + proof_stream.enqueue(Item::FriResponse(fri_response.clone())); + let proof: Proof = proof_stream.into(); + + let_assert!(Ok(mut proof_stream) = Stream::try_from(&proof)); + let_assert!(Ok(proof_item) = proof_stream.dequeue()); + let_assert!(Ok(fri_response_) = proof_item.try_into_fri_response()); + prop_assert_eq!(fri_response, fri_response_); + } + + #[proptest] + fn serialize_authentication_structure_in_isolation( + leaved_merkle_tree: LeavedMerkleTreeTestData, + ) { + let auth_structure = leaved_merkle_tree.auth_structure; + let encoding = auth_structure.encode(); + let_assert!(Ok(decoding) = AuthenticationStructure::decode(&encoding)); + prop_assert_eq!(auth_structure, *decoding); + } + + #[proptest] + fn serialize_authentication_structure_in_proof_stream( + leaved_merkle_tree: LeavedMerkleTreeTestData, + ) { + let auth_structure = leaved_merkle_tree.auth_structure; + let mut proof_stream = Stream::new(); + proof_stream.enqueue(Item::AuthenticationStructure(auth_structure.clone())); + let proof: Proof = proof_stream.into(); + + let_assert!(Ok(mut proof_stream) = Stream::try_from(&proof)); + let_assert!(Ok(proof_item) = proof_stream.dequeue()); + let_assert!(Ok(auth_structure_) = proof_item.try_into_authentication_structure()); + prop_assert_eq!(auth_structure, auth_structure_); + } + + #[test] + fn interpreting_a_merkle_root_as_anything_else_gives_appropriate_error() { + let fake_root = Digest::default(); + let item = Item::MerkleRoot(fake_root); + assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_authentication_structure()); + assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_fri_response()); + assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_master_base_table_rows()); + assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_master_ext_table_rows()); + assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_out_of_domain_base_row()); + assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_out_of_domain_ext_row()); + assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_out_of_domain_quot_segments()); + assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_log2_padded_height()); + assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_quot_segments_elements()); + assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_fri_codeword()); + assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_fri_polynomial()); + assert!(let Err(UnexpectedItem{..}) = item.try_into_fri_response()); + } + + #[test] + fn proof_item_payload_static_length_is_as_expected() { + assert!(let Some(_) = ItemVariant::MerkleRoot.payload_static_length()); + assert!(let Some(_) = ItemVariant::Log2PaddedHeight.payload_static_length()); + assert_eq!(None, ItemVariant::FriCodeword.payload_static_length()); + assert_eq!(None, ItemVariant::FriResponse.payload_static_length()); + } + + #[test] + fn can_loop_over_proof_item_variants() { + let all_discriminants: HashSet<_> = ItemVariant::iter() + .map(|variant| variant.bfield_codec_discriminant()) + .collect(); + assert_eq!(Item::COUNT, all_discriminants.len()); + } + + #[test] + fn proof_item_and_its_variant_have_same_bfield_codec_discriminant() { + assert_eq!( + Item::MerkleRoot(Digest::default()).bfield_codec_discriminant(), + ItemVariant::MerkleRoot.bfield_codec_discriminant() + ); + assert_eq!( + Item::Log2PaddedHeight(0).bfield_codec_discriminant(), + ItemVariant::Log2PaddedHeight.bfield_codec_discriminant() + ); + assert_eq!( + Item::FriCodeword(vec![]).bfield_codec_discriminant(), + ItemVariant::FriCodeword.bfield_codec_discriminant() + ); + } + + #[test] + fn proof_item_variants_payload_type_has_expected_format() { + assert_eq!("Digest", ItemVariant::MerkleRoot.payload_type()); + } } diff --git a/triton-vm/src/proof_item.rs b/triton-vm/src/proof_item.rs deleted file mode 100644 index 1ad3253cb..000000000 --- a/triton-vm/src/proof_item.rs +++ /dev/null @@ -1,233 +0,0 @@ -use arbitrary::Arbitrary; -use strum::Display; -use strum::EnumCount; -use strum::EnumDiscriminants; -use strum::EnumIter; -use twenty_first::prelude::*; - -use crate::error::ProofStreamError; -use crate::error::ProofStreamError::UnexpectedItem; -use crate::fri::AuthenticationStructure; -use crate::table::BaseRow; -use crate::table::ExtensionRow; -use crate::table::QuotientSegments; - -/// A `FriResponse` is an `AuthenticationStructure` together with the values of the -/// revealed leaves of the Merkle tree. Together, they correspond to the -/// queried indices of the FRI codeword (of that round). -#[derive(Debug, Clone, Eq, PartialEq, Hash, BFieldCodec, Arbitrary)] -pub struct FriResponse { - /// The authentication structure of the Merkle tree. - pub auth_structure: AuthenticationStructure, - - /// The values of the opened leaves of the Merkle tree. - pub revealed_leaves: Vec, -} - -macro_rules! proof_items { - ($($variant:ident($payload:ty) => $in_fiat_shamir_heuristic:literal, $try_into_fn:ident,)+) => { - #[derive( - Debug, - Display, - Clone, - Eq, - PartialEq, - Hash, - EnumCount, - EnumDiscriminants, - BFieldCodec, - Arbitrary, - )] - #[strum_discriminants(name(ProofItemVariant))] - // discriminants' default derives: Debug, Copy, Clone, Eq, PartialEq - #[strum_discriminants(derive(Display, EnumIter, BFieldCodec, Arbitrary))] - pub enum ProofItem { - $( $variant($payload), )+ - } - - impl ProofItem { - /// Whether a given proof item should be considered in the Fiat-Shamir heuristic. - /// The Fiat-Shamir heuristic is sound only if all elements in the (current) transcript - /// are considered. However, certain elements indirectly appear more than once. For - /// example, a Merkle root is a commitment to any number of elements. If the Merkle root - /// is part of the transcript, has been considered in the Fiat-Shamir heuristic, and - /// assuming collision resistance of the hash function in use, none of the committed-to - /// elements have to be considered in the Fiat-Shamir heuristic again. - /// This also extends to the authentication structure of these elements, et cetera. - pub const fn include_in_fiat_shamir_heuristic(&self) -> bool { - match self { - $( Self::$variant(_) => $in_fiat_shamir_heuristic, )+ - } - } - - $( - pub fn $try_into_fn(self) -> Result<$payload, ProofStreamError> { - match self { - Self::$variant(payload) => Ok(payload), - _ => Err(UnexpectedItem { - expected: ProofItemVariant::$variant, - got: self, - }), - } - } - )+ - } - - impl ProofItemVariant { - pub fn payload_static_length(self) -> Option { - match self { - $( Self::$variant => <$payload>::static_length(), )+ - } - } - - /// See [`ProofItem::include_in_fiat_shamir_heuristic`]. - pub const fn include_in_fiat_shamir_heuristic(self) -> bool { - match self { - $( Self::$variant => $in_fiat_shamir_heuristic, )+ - } - } - - /// Can be used as “reflection”, for example through `syn`. - pub const fn payload_type(self) -> &'static str { - match self { - $( Self::$variant => stringify!($payload), )+ - } - } - } - }; -} - -proof_items!( - MerkleRoot(Digest) => true, try_into_merkle_root, - OutOfDomainBaseRow(Box>) => true, try_into_out_of_domain_base_row, - OutOfDomainExtRow(Box) => true, try_into_out_of_domain_ext_row, - OutOfDomainQuotientSegments(QuotientSegments) => true, try_into_out_of_domain_quot_segments, - - // the following are implied by some Merkle root, thus not included in the Fiat-Shamir heuristic - AuthenticationStructure(AuthenticationStructure) => false, try_into_authentication_structure, - MasterBaseTableRows(Vec>) => false, try_into_master_base_table_rows, - MasterExtTableRows(Vec) => false, try_into_master_ext_table_rows, - Log2PaddedHeight(u32) => false, try_into_log2_padded_height, - QuotientSegmentsElements(Vec) => false, try_into_quot_segments_elements, - FriCodeword(Vec) => false, try_into_fri_codeword, - FriPolynomial(Polynomial) => false, try_into_fri_polynomial, - FriResponse(FriResponse) => false, try_into_fri_response, -); - -#[cfg(test)] -pub(crate) mod tests { - use std::collections::HashSet; - - use assert2::assert; - use assert2::let_assert; - use proptest::prelude::*; - use strum::IntoEnumIterator; - use test_strategy::proptest; - - use crate::proof::Proof; - use crate::proof_stream::ProofStream; - use crate::shared_tests::LeavedMerkleTreeTestData; - - use super::*; - - #[proptest] - fn serialize_fri_response_in_isolation(leaved_merkle_tree: LeavedMerkleTreeTestData) { - let fri_response = leaved_merkle_tree.into_fri_response(); - let encoding = fri_response.encode(); - let_assert!(Ok(decoding) = FriResponse::decode(&encoding)); - prop_assert_eq!(fri_response, *decoding); - } - - #[proptest] - fn serialize_fri_response_in_proof_stream(leaved_merkle_tree: LeavedMerkleTreeTestData) { - let fri_response = leaved_merkle_tree.into_fri_response(); - let mut proof_stream = ProofStream::new(); - proof_stream.enqueue(ProofItem::FriResponse(fri_response.clone())); - let proof: Proof = proof_stream.into(); - - let_assert!(Ok(mut proof_stream) = ProofStream::try_from(&proof)); - let_assert!(Ok(proof_item) = proof_stream.dequeue()); - let_assert!(Ok(fri_response_) = proof_item.try_into_fri_response()); - prop_assert_eq!(fri_response, fri_response_); - } - - #[proptest] - fn serialize_authentication_structure_in_isolation( - leaved_merkle_tree: LeavedMerkleTreeTestData, - ) { - let auth_structure = leaved_merkle_tree.auth_structure; - let encoding = auth_structure.encode(); - let_assert!(Ok(decoding) = AuthenticationStructure::decode(&encoding)); - prop_assert_eq!(auth_structure, *decoding); - } - - #[proptest] - fn serialize_authentication_structure_in_proof_stream( - leaved_merkle_tree: LeavedMerkleTreeTestData, - ) { - let auth_structure = leaved_merkle_tree.auth_structure; - let mut proof_stream = ProofStream::new(); - proof_stream.enqueue(ProofItem::AuthenticationStructure(auth_structure.clone())); - let proof: Proof = proof_stream.into(); - - let_assert!(Ok(mut proof_stream) = ProofStream::try_from(&proof)); - let_assert!(Ok(proof_item) = proof_stream.dequeue()); - let_assert!(Ok(auth_structure_) = proof_item.try_into_authentication_structure()); - prop_assert_eq!(auth_structure, auth_structure_); - } - - #[test] - fn interpreting_a_merkle_root_as_anything_else_gives_appropriate_error() { - let fake_root = Digest::default(); - let item = ProofItem::MerkleRoot(fake_root); - assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_authentication_structure()); - assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_fri_response()); - assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_master_base_table_rows()); - assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_master_ext_table_rows()); - assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_out_of_domain_base_row()); - assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_out_of_domain_ext_row()); - assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_out_of_domain_quot_segments()); - assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_log2_padded_height()); - assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_quot_segments_elements()); - assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_fri_codeword()); - assert!(let Err(UnexpectedItem{..}) = item.clone().try_into_fri_polynomial()); - assert!(let Err(UnexpectedItem{..}) = item.try_into_fri_response()); - } - - #[test] - fn proof_item_payload_static_length_is_as_expected() { - assert!(let Some(_) = ProofItemVariant::MerkleRoot.payload_static_length()); - assert!(let Some(_) = ProofItemVariant::Log2PaddedHeight.payload_static_length()); - assert_eq!(None, ProofItemVariant::FriCodeword.payload_static_length()); - assert_eq!(None, ProofItemVariant::FriResponse.payload_static_length()); - } - - #[test] - fn can_loop_over_proof_item_variants() { - let all_discriminants: HashSet<_> = ProofItemVariant::iter() - .map(|variant| variant.bfield_codec_discriminant()) - .collect(); - assert_eq!(ProofItem::COUNT, all_discriminants.len()); - } - - #[test] - fn proof_item_and_its_variant_have_same_bfield_codec_discriminant() { - assert_eq!( - ProofItem::MerkleRoot(Digest::default()).bfield_codec_discriminant(), - ProofItemVariant::MerkleRoot.bfield_codec_discriminant() - ); - assert_eq!( - ProofItem::Log2PaddedHeight(0).bfield_codec_discriminant(), - ProofItemVariant::Log2PaddedHeight.bfield_codec_discriminant() - ); - assert_eq!( - ProofItem::FriCodeword(vec![]).bfield_codec_discriminant(), - ProofItemVariant::FriCodeword.bfield_codec_discriminant() - ); - } - - #[test] - fn proof_item_variants_payload_type_has_expected_format() { - assert_eq!("Digest", ProofItemVariant::MerkleRoot.payload_type()); - } -} diff --git a/triton-vm/src/proof_stream.rs b/triton-vm/src/proof_stream.rs deleted file mode 100644 index f0c90e28f..000000000 --- a/triton-vm/src/proof_stream.rs +++ /dev/null @@ -1,286 +0,0 @@ -use arbitrary::Arbitrary; -use twenty_first::prelude::*; - -use crate::error::ProofStreamError; -use crate::proof::Proof; -use crate::proof_item::ProofItem; - -#[derive(Debug, Default, Clone, Eq, PartialEq, BFieldCodec, Arbitrary)] -pub struct ProofStream { - pub items: Vec, - - #[bfield_codec(ignore)] - pub items_index: usize, - - #[bfield_codec(ignore)] - pub sponge: Tip5, -} - -impl ProofStream { - pub fn new() -> Self { - ProofStream { - items: vec![], - items_index: 0, - sponge: Tip5::init(), - } - } - - /// The number of field elements required to encode the proof. - pub fn transcript_length(&self) -> usize { - let Proof(b_field_elements) = self.into(); - b_field_elements.len() - } - - /// Alters the Fiat-Shamir's sponge state with the encoding of the given item. - /// Does _not_ record the given item in the proof stream. - /// This is useful for items that are not sent to the verifier, _e.g._, the - /// [`Claim`](crate::proof::Claim). - /// - /// See also [`Self::enqueue()`] and [`Self::dequeue()`]. - pub fn alter_fiat_shamir_state_with(&mut self, item: &impl BFieldCodec) { - self.sponge.pad_and_absorb_all(&item.encode()) - } - - /// Send a proof item as prover to verifier. - /// Some items do not need to be included in the Fiat-Shamir heuristic, _i.e._, they do not - /// need to modify the sponge state. For those items, namely those that evaluate to `false` - /// according to [`ProofItem::include_in_fiat_shamir_heuristic`], the sponge state is not - /// modified. - /// For example: - /// - Merkle authentication structure do not need to be hashed if the root of the tree - /// in question was hashed previously. - /// - If the proof stream is not used to sample any more randomness, _i.e._, after the last - /// round of interaction, no further items need to be hashed. - pub fn enqueue(&mut self, item: ProofItem) { - if item.include_in_fiat_shamir_heuristic() { - self.alter_fiat_shamir_state_with(&item); - } - self.items.push(item); - } - - /// Receive a proof item from prover as verifier. - /// See [`ProofStream::enqueue`] for more details. - pub fn dequeue(&mut self) -> Result { - let Some(item) = self.items.get(self.items_index) else { - return Err(ProofStreamError::EmptyQueue); - }; - let item = item.to_owned(); - if item.include_in_fiat_shamir_heuristic() { - self.alter_fiat_shamir_state_with(&item); - } - self.items_index += 1; - Ok(item) - } - - /// Given an `upper_bound` that is a power of 2, produce `num_indices` uniform random numbers - /// in the interval `[0; upper_bound)`. - /// - /// - `upper_bound`: The (non-inclusive) upper bound. Must be a power of two. - /// - `num_indices`: The number of indices to sample - pub fn sample_indices(&mut self, upper_bound: usize, num_indices: usize) -> Vec { - assert!(upper_bound.is_power_of_two()); - assert!(upper_bound <= BFieldElement::MAX as usize); - self.sponge - .sample_indices(upper_bound as u32, num_indices) - .into_iter() - .map(|i| i as usize) - .collect() - } - - /// A thin wrapper around [`H::sample_scalars`](AlgebraicHasher::sample_scalars). - pub fn sample_scalars(&mut self, num_scalars: usize) -> Vec { - self.sponge.sample_scalars(num_scalars) - } -} - -impl TryFrom<&Proof> for ProofStream { - type Error = ProofStreamError; - - fn try_from(proof: &Proof) -> Result { - let proof_stream = *ProofStream::decode(&proof.0)?; - Ok(proof_stream) - } -} - -impl From<&ProofStream> for Proof { - fn from(proof_stream: &ProofStream) -> Self { - Proof(proof_stream.encode()) - } -} - -impl From for Proof { - fn from(proof_stream: ProofStream) -> Self { - (&proof_stream).into() - } -} - -#[cfg(test)] -mod tests { - use std::collections::VecDeque; - - use assert2::assert; - use assert2::let_assert; - use itertools::Itertools; - use proptest::collection::vec; - use proptest_arbitrary_interop::arb; - use test_strategy::proptest; - use twenty_first::math::other::random_elements; - - use crate::proof_item::FriResponse; - use crate::proof_item::ProofItem; - use crate::shared_tests::LeavedMerkleTreeTestData; - use crate::table::BaseRow; - use crate::table::ExtensionRow; - use crate::table::QuotientSegments; - - use super::*; - - #[proptest] - fn serialize_proof_with_fiat_shamir( - #[strategy(vec(arb(), 2..100))] base_rows: Vec>, - #[strategy(vec(arb(), 2..100))] ext_rows: Vec, - #[strategy(arb())] ood_base_row: Box>, - #[strategy(arb())] ood_ext_row: Box, - #[strategy(arb())] quot_elements: Vec, - leaved_merkle_tree: LeavedMerkleTreeTestData, - ) { - let auth_structure = leaved_merkle_tree.auth_structure.clone(); - let root = leaved_merkle_tree.root(); - let fri_codeword = leaved_merkle_tree.leaves().to_owned(); - let fri_response = leaved_merkle_tree.into_fri_response(); - - let mut sponge_states = VecDeque::new(); - let mut proof_stream = ProofStream::new(); - - sponge_states.push_back(proof_stream.sponge.state); - proof_stream.enqueue(ProofItem::AuthenticationStructure(auth_structure.clone())); - sponge_states.push_back(proof_stream.sponge.state); - proof_stream.enqueue(ProofItem::MasterBaseTableRows(base_rows.clone())); - sponge_states.push_back(proof_stream.sponge.state); - proof_stream.enqueue(ProofItem::MasterExtTableRows(ext_rows.clone())); - sponge_states.push_back(proof_stream.sponge.state); - proof_stream.enqueue(ProofItem::OutOfDomainBaseRow(ood_base_row.clone())); - sponge_states.push_back(proof_stream.sponge.state); - proof_stream.enqueue(ProofItem::OutOfDomainExtRow(ood_ext_row.clone())); - sponge_states.push_back(proof_stream.sponge.state); - proof_stream.enqueue(ProofItem::MerkleRoot(root)); - sponge_states.push_back(proof_stream.sponge.state); - proof_stream.enqueue(ProofItem::QuotientSegmentsElements(quot_elements.clone())); - sponge_states.push_back(proof_stream.sponge.state); - proof_stream.enqueue(ProofItem::FriCodeword(fri_codeword.clone())); - sponge_states.push_back(proof_stream.sponge.state); - proof_stream.enqueue(ProofItem::FriResponse(fri_response.clone())); - sponge_states.push_back(proof_stream.sponge.state); - - let proof = proof_stream.into(); - let mut proof_stream = ProofStream::try_from(&proof).unwrap(); - - assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); - let_assert!(Ok(proof_item) = proof_stream.dequeue()); - let_assert!(ProofItem::AuthenticationStructure(auth_structure_) = proof_item); - assert!(auth_structure == auth_structure_); - - assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); - let_assert!(Ok(ProofItem::MasterBaseTableRows(base_rows_)) = proof_stream.dequeue()); - assert!(base_rows == base_rows_); - - assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); - let_assert!(Ok(ProofItem::MasterExtTableRows(ext_rows_)) = proof_stream.dequeue()); - assert!(ext_rows == ext_rows_); - - assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); - let_assert!(Ok(ProofItem::OutOfDomainBaseRow(ood_base_row_)) = proof_stream.dequeue()); - assert!(ood_base_row == ood_base_row_); - - assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); - let_assert!(Ok(ProofItem::OutOfDomainExtRow(ood_ext_row_)) = proof_stream.dequeue()); - assert!(ood_ext_row == ood_ext_row_); - - assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); - let_assert!(Ok(ProofItem::MerkleRoot(root_)) = proof_stream.dequeue()); - assert!(root == root_); - - assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); - let_assert!(Ok(proof_item) = proof_stream.dequeue()); - let_assert!(ProofItem::QuotientSegmentsElements(quot_elements_) = proof_item); - assert!(quot_elements == quot_elements_); - - assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); - let_assert!(Ok(ProofItem::FriCodeword(fri_codeword_)) = proof_stream.dequeue()); - assert!(fri_codeword == fri_codeword_); - - assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); - let_assert!(Ok(ProofItem::FriResponse(fri_response_)) = proof_stream.dequeue()); - assert!(fri_response == fri_response_); - - assert!(sponge_states.pop_front() == Some(proof_stream.sponge.state)); - assert!(0 == sponge_states.len()); - } - - #[test] - fn enqueue_dequeue_verify_partial_authentication_structure() { - let tree_height = 8; - let num_leaves = 1 << tree_height; - let leaf_values: Vec = random_elements(num_leaves); - let leaf_digests = leaf_values.iter().map(|&xfe| xfe.into()).collect_vec(); - let merkle_tree = MerkleTree::new::(&leaf_digests).unwrap(); - let indices_to_check = vec![5, 173, 175, 167, 228, 140, 252, 149, 232, 182, 5, 5, 182]; - let auth_structure = merkle_tree - .authentication_structure(&indices_to_check) - .unwrap(); - let revealed_leaves = indices_to_check - .iter() - .map(|&idx| leaf_values[idx]) - .collect_vec(); - let fri_response = FriResponse { - auth_structure, - revealed_leaves, - }; - - let mut proof_stream = ProofStream::new(); - proof_stream.enqueue(ProofItem::FriResponse(fri_response)); - - // TODO: Also check that deserializing from Proof works here. - - let proof_item = proof_stream.dequeue().unwrap(); - let maybe_same_fri_response = proof_item.try_into_fri_response().unwrap(); - let FriResponse { - auth_structure, - revealed_leaves, - } = maybe_same_fri_response; - let maybe_same_leaf_digests = revealed_leaves.iter().map(|&xfe| xfe.into()).collect_vec(); - let indexed_leafs = indices_to_check - .into_iter() - .zip_eq(maybe_same_leaf_digests) - .collect(); - - let inclusion_proof = MerkleTreeInclusionProof { - tree_height, - indexed_leafs, - authentication_structure: auth_structure, - }; - assert!(inclusion_proof.verify(merkle_tree.root())); - } - - #[test] - fn dequeuing_from_empty_stream_fails() { - let mut proof_stream = ProofStream::new(); - let_assert!(Err(ProofStreamError::EmptyQueue) = proof_stream.dequeue()); - } - - #[test] - fn dequeuing_more_items_than_have_been_enqueued_fails() { - let mut proof_stream = ProofStream::new(); - proof_stream.enqueue(ProofItem::FriCodeword(vec![])); - proof_stream.enqueue(ProofItem::Log2PaddedHeight(7)); - - let_assert!(Ok(_) = proof_stream.dequeue()); - let_assert!(Ok(_) = proof_stream.dequeue()); - let_assert!(Err(ProofStreamError::EmptyQueue) = proof_stream.dequeue()); - } - - #[test] - fn encoded_length_of_prove_stream_is_not_known_at_compile_time() { - assert!(ProofStream::static_length().is_none()); - } -} diff --git a/triton-vm/src/shared_tests.rs b/triton-vm/src/shared_tests.rs index c1fe25686..b4923f051 100644 --- a/triton-vm/src/shared_tests.rs +++ b/triton-vm/src/shared_tests.rs @@ -13,7 +13,7 @@ use crate::fri::AuthenticationStructure; use crate::profiler::profiler; use crate::program::Program; use crate::proof::Claim; -use crate::proof_item::FriResponse; +use crate::proof::FriResponse; use crate::stark::Stark; use crate::table::master_table::MasterBaseTable; use crate::NonDeterminism; diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index 653bc5144..b4b7e7cc7 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -23,10 +23,11 @@ use crate::error::VerificationError; use crate::fri; use crate::fri::Fri; use crate::profiler::profiler; +use crate::proof; use crate::proof::Claim; use crate::proof::Proof; -use crate::proof_item::ProofItem; -use crate::proof_stream::ProofStream; +use crate::proof::Stream; +use crate::proof::CURRENT_PROOF_VERSION; use crate::table::challenges::Challenges; use crate::table::extension_table::Evaluable; use crate::table::extension_table::Quotientable; @@ -106,7 +107,8 @@ impl Stark { aet: &AlgebraicExecutionTrace, ) -> Result { profiler!(start "Fiat-Shamir: claim" ("hash")); - let mut proof_stream = ProofStream::new(); + let mut proof_stream = Stream::new(); + proof_stream.alter_fiat_shamir_state_with(&CURRENT_PROOF_VERSION); proof_stream.alter_fiat_shamir_state_with(claim); profiler!(stop "Fiat-Shamir: claim"); @@ -115,7 +117,7 @@ impl Stark { let max_degree = self.derive_max_degree(padded_height); let fri = self.derive_fri(padded_height)?; let quotient_domain = Self::quotient_domain(fri.domain, max_degree)?; - proof_stream.enqueue(ProofItem::Log2PaddedHeight(padded_height.ilog2())); + proof_stream.enqueue(proof::Item::Log2PaddedHeight(padded_height.ilog2())); profiler!(stop "derive additional parameters"); profiler!(start "base tables"); @@ -141,7 +143,7 @@ impl Stark { profiler!(stop "Merkle tree"); profiler!(start "Fiat-Shamir" ("hash")); - proof_stream.enqueue(ProofItem::MerkleRoot(base_merkle_tree.root())); + proof_stream.enqueue(proof::Item::MerkleRoot(base_merkle_tree.root())); let challenges = proof_stream.sample_scalars(Challenges::SAMPLE_COUNT); let challenges = Challenges::new(challenges, claim); profiler!(stop "Fiat-Shamir"); @@ -165,7 +167,7 @@ impl Stark { profiler!(stop "Merkle tree"); profiler!(start "Fiat-Shamir" ("hash")); - proof_stream.enqueue(ProofItem::MerkleRoot(ext_merkle_tree.root())); + proof_stream.enqueue(proof::Item::MerkleRoot(ext_merkle_tree.root())); // Get the weights with which to compress the many quotients into one. let quotient_combination_weights = @@ -199,7 +201,7 @@ impl Stark { let quot_merkle_tree = MerkleTree::new::(&fri_domain_quotient_segment_codewords_digests)?; let quot_merkle_tree_root = quot_merkle_tree.root(); - proof_stream.enqueue(ProofItem::MerkleRoot(quot_merkle_tree_root)); + proof_stream.enqueue(proof::Item::MerkleRoot(quot_merkle_tree_root)); profiler!(stop "Merkle tree"); debug_assert_eq!(fri.domain.length, quot_merkle_tree.num_leafs()); @@ -211,19 +213,19 @@ impl Stark { let ood_base_row = master_base_table.out_of_domain_row(out_of_domain_point_curr_row); let ood_base_row = MasterBaseTable::try_to_base_row(ood_base_row)?; - proof_stream.enqueue(ProofItem::OutOfDomainBaseRow(Box::new(ood_base_row))); + proof_stream.enqueue(proof::Item::OutOfDomainBaseRow(Box::new(ood_base_row))); let ood_ext_row = master_ext_table.out_of_domain_row(out_of_domain_point_curr_row); let ood_ext_row = MasterExtTable::try_to_ext_row(ood_ext_row)?; - proof_stream.enqueue(ProofItem::OutOfDomainExtRow(Box::new(ood_ext_row))); + proof_stream.enqueue(proof::Item::OutOfDomainExtRow(Box::new(ood_ext_row))); let ood_next_base_row = master_base_table.out_of_domain_row(out_of_domain_point_next_row); let ood_next_base_row = MasterBaseTable::try_to_base_row(ood_next_base_row)?; - proof_stream.enqueue(ProofItem::OutOfDomainBaseRow(Box::new(ood_next_base_row))); + proof_stream.enqueue(proof::Item::OutOfDomainBaseRow(Box::new(ood_next_base_row))); let ood_next_ext_row = master_ext_table.out_of_domain_row(out_of_domain_point_next_row); let ood_next_ext_row = MasterExtTable::try_to_ext_row(ood_next_ext_row)?; - proof_stream.enqueue(ProofItem::OutOfDomainExtRow(Box::new(ood_next_ext_row))); + proof_stream.enqueue(proof::Item::OutOfDomainExtRow(Box::new(ood_next_ext_row))); let out_of_domain_point_curr_row_pow_num_segments = out_of_domain_point_curr_row.mod_pow_u32(NUM_QUOTIENT_SEGMENTS as u32); @@ -232,7 +234,7 @@ impl Stark { .to_vec() .try_into() .unwrap(); - proof_stream.enqueue(ProofItem::OutOfDomainQuotientSegments( + proof_stream.enqueue(proof::Item::OutOfDomainQuotientSegments( out_of_domain_curr_row_quot_segments, )); profiler!(stop "out-of-domain rows"); @@ -372,8 +374,8 @@ impl Stark { }; let base_authentication_structure = base_merkle_tree.authentication_structure(&revealed_current_row_indices)?; - proof_stream.enqueue(ProofItem::MasterBaseTableRows(revealed_base_elems)); - proof_stream.enqueue(ProofItem::AuthenticationStructure( + proof_stream.enqueue(proof::Item::MasterBaseTableRows(revealed_base_elems)); + proof_stream.enqueue(proof::Item::AuthenticationStructure( base_authentication_structure, )); @@ -389,8 +391,8 @@ impl Stark { }; let ext_authentication_structure = ext_merkle_tree.authentication_structure(&revealed_current_row_indices)?; - proof_stream.enqueue(ProofItem::MasterExtTableRows(revealed_ext_elems)); - proof_stream.enqueue(ProofItem::AuthenticationStructure( + proof_stream.enqueue(proof::Item::MasterExtTableRows(revealed_ext_elems)); + proof_stream.enqueue(proof::Item::AuthenticationStructure( ext_authentication_structure, )); @@ -404,10 +406,10 @@ impl Stark { .collect_vec(); let revealed_quotient_authentication_structure = quot_merkle_tree.authentication_structure(&revealed_current_row_indices)?; - proof_stream.enqueue(ProofItem::QuotientSegmentsElements( + proof_stream.enqueue(proof::Item::QuotientSegmentsElements( revealed_quotient_segments_rows, )); - proof_stream.enqueue(ProofItem::AuthenticationStructure( + proof_stream.enqueue(proof::Item::AuthenticationStructure( revealed_quotient_authentication_structure, )); profiler!(stop "open trace leafs"); @@ -724,10 +726,11 @@ impl Stark { pub fn verify(&self, claim: &Claim, proof: &Proof) -> Result<(), VerificationError> { profiler!(start "deserialize"); - let mut proof_stream = ProofStream::try_from(proof)?; + let mut proof_stream = Stream::try_from(proof)?; profiler!(stop "deserialize"); profiler!(start "Fiat-Shamir: Claim" ("hash")); + proof_stream.alter_fiat_shamir_state_with(&CURRENT_PROOF_VERSION); proof_stream.alter_fiat_shamir_state_with(claim); profiler!(stop "Fiat-Shamir: Claim"); @@ -1284,7 +1287,7 @@ impl LinearCombinationWeights { const NUM: usize = NUM_BASE_COLUMNS + NUM_EXT_COLUMNS + NUM_QUOTIENT_SEGMENTS + NUM_DEEP_CODEWORD_COMPONENTS; - fn sample(proof_stream: &mut ProofStream) -> Self { + fn sample(proof_stream: &mut Stream) -> Self { const BASE_END: usize = NUM_BASE_COLUMNS; const EXT_END: usize = BASE_END + NUM_EXT_COLUMNS; const QUOT_END: usize = EXT_END + NUM_QUOTIENT_SEGMENTS; @@ -2615,7 +2618,7 @@ pub(crate) mod tests { #[proptest] fn linear_combination_weights_samples_correct_number_of_elements( - #[strategy(arb())] mut proof_stream: ProofStream, + #[strategy(arb())] mut proof_stream: Stream, ) { let weights = LinearCombinationWeights::sample(&mut proof_stream);