From 741c6d8f2272a8647736ca109dd8b8b499ba50d5 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Wed, 5 Jul 2023 10:49:18 +0200 Subject: [PATCH] don't include `Claim` in `Proof`, require it explicitly for `verify()` The `Claim` is still considered in the Fiat-Shamir heuristic. --- triton-vm/benches/proof_size.rs | 4 +-- triton-vm/benches/verify_halt.rs | 4 +-- triton-vm/src/lib.rs | 18 ++++++------ triton-vm/src/proof.rs | 13 --------- triton-vm/src/proof_item.rs | 14 --------- triton-vm/src/proof_stream.rs | 49 ++++++++++++-------------------- triton-vm/src/stark.rs | 43 +++++++++++++++------------- 7 files changed, 54 insertions(+), 91 deletions(-) diff --git a/triton-vm/benches/proof_size.rs b/triton-vm/benches/proof_size.rs index 94bf8b54e..2bbdca0b1 100644 --- a/triton-vm/benches/proof_size.rs +++ b/triton-vm/benches/proof_size.rs @@ -225,7 +225,7 @@ fn sum_of_proof_lengths_for_source_code( ) -> ProofSize { let mut sum_of_proof_lengths = 0; for _ in 0..num_iterations { - let (_, proof) = + let (_, _, proof) = prove_from_source(&source.source_code, &source.input, &source.secret_input).unwrap(); sum_of_proof_lengths += proof.encode().len(); } @@ -238,7 +238,7 @@ fn generate_proof_and_benchmark_id( program_name: &str, program_halt: &SourceCodeAndInput, ) -> (Proof, BenchmarkId) { - let (parameters, proof) = prove_from_source( + let (parameters, _, proof) = prove_from_source( &program_halt.source_code, &program_halt.input, &program_halt.secret_input, diff --git a/triton-vm/benches/verify_halt.rs b/triton-vm/benches/verify_halt.rs index f90e6c6fc..fc6a4b9fe 100644 --- a/triton-vm/benches/verify_halt.rs +++ b/triton-vm/benches/verify_halt.rs @@ -46,7 +46,7 @@ fn verify_halt(criterion: &mut Criterion) { }; let mut profiler = Some(TritonProfiler::new("Verify Halt")); - let verdict = Stark::verify(¶meters, &proof, &mut profiler) + let verdict = Stark::verify(¶meters, &claim, &proof, &mut profiler) .map_err(|e| panic!("The Verifier is unhappy! {e}")) .unwrap(); assert!(verdict); @@ -67,7 +67,7 @@ fn verify_halt(criterion: &mut Criterion) { group.sample_size(10); group.bench_function(bench_id, |bencher| { bencher.iter(|| { - let _ = Stark::verify(¶meters, &proof, &mut None); + let _ = Stark::verify(¶meters, &claim, &proof, &mut None); }); }); group.finish(); diff --git a/triton-vm/src/lib.rs b/triton-vm/src/lib.rs index 37ccf8c81..358d98352 100644 --- a/triton-vm/src/lib.rs +++ b/triton-vm/src/lib.rs @@ -45,7 +45,7 @@ pub fn prove_from_source( source_code: &str, public_input: &[u64], secret_input: &[u64], -) -> Result<(StarkParameters, Proof)> { +) -> Result<(StarkParameters, Claim, Proof)> { let canonical_representation_error = "input must contain only elements in canonical representation, i.e., \ elements smaller than the prime field's modulus 2^64 - 2^32 + 1."; @@ -99,7 +99,7 @@ pub fn prove_from_source( // Generate the proof. let proof = Stark::prove(¶meters, &claim, &aet, &mut None); - Ok((parameters, proof)) + Ok((parameters, claim, proof)) } /// A convenience function for proving a [`Claim`] and the program that claim corresponds to. @@ -123,8 +123,9 @@ pub fn prove( } /// Verify a proof generated by [`prove`] or [`prove_from_source`]. -pub fn verify(parameters: &StarkParameters, proof: &Proof) -> bool { - Stark::verify(parameters, proof, &mut None).unwrap_or(false) +#[must_use] +pub fn verify(parameters: &StarkParameters, claim: &Claim, proof: &Proof) -> bool { + Stark::verify(parameters, claim, proof, &mut None).unwrap_or(false) } #[cfg(test)] @@ -167,14 +168,13 @@ mod public_interface_tests { 17174585125955027015, ]; - let (parameters, proof) = + let (parameters, claim, proof) = prove_from_source(source_code, &public_input, &secret_input).unwrap(); assert_eq!( StarkParameters::default(), parameters, "Prover must return default STARK parameters" ); - let claim = proof.claim(); let program = Program::from_code(source_code).unwrap(); let expected_program_digest = StarkHasher::hash_varlen(&program.to_bwords()); assert_eq!( @@ -190,7 +190,7 @@ mod public_interface_tests { claim.output.is_empty(), "Output must be empty for program that doesn't write to output" ); - let verdict = verify(¶meters, &proof); + let verdict = verify(¶meters, &claim, &proof); assert!(verdict); } @@ -208,7 +208,7 @@ mod public_interface_tests { }; let proof = prove(¶meters, &claim, &program, &[]).unwrap(); - let verdict = verify(¶meters, &proof); + let verdict = verify(¶meters, &claim, &proof); assert!(verdict); } @@ -220,7 +220,7 @@ mod public_interface_tests { } let source_code = "nop halt"; - let (_, proof) = prove_from_source(source_code, &[], &[]).unwrap(); + let (_, _, proof) = prove_from_source(source_code, &[], &[]).unwrap(); save_proof(filename, proof.clone()).unwrap(); let loaded_proof = load_proof(filename).unwrap(); diff --git a/triton-vm/src/proof.rs b/triton-vm/src/proof.rs index 74d682545..5c539d225 100644 --- a/triton-vm/src/proof.rs +++ b/triton-vm/src/proof.rs @@ -42,19 +42,6 @@ impl Proof { } log_2_padded_height.expect("The proof must contain a log_2_padded_height.") } - - /// The [`Claim`] that this proof is for. - pub fn claim(&self) -> Claim { - let proof_stream = ProofStream::::try_from(self).unwrap(); - let mut claim = None; - for item in proof_stream.items { - if let Ok(found_claim) = item.as_claim() { - assert!(claim.is_none(), "The proof must contain exactly one claim."); - claim = Some(found_claim); - } - } - claim.expect("The proof must contain a claim.") - } } /// Contains the public information of a verifiably correct computation. diff --git a/triton-vm/src/proof_item.rs b/triton-vm/src/proof_item.rs index 722a5ed7b..b313ea50a 100644 --- a/triton-vm/src/proof_item.rs +++ b/triton-vm/src/proof_item.rs @@ -7,8 +7,6 @@ use twenty_first::shared_math::bfield_codec::BFieldCodec; use twenty_first::shared_math::tip5::Digest; use twenty_first::shared_math::x_field_element::XFieldElement; -use crate::Claim; - type AuthenticationStructure = Vec; /// A `FriResponse` is an [`AuthenticationStructure`] together with the values of the @@ -34,7 +32,6 @@ pub enum ProofItem { RevealedCombinationElements(Vec), FriCodeword(Vec), FriResponse(FriResponse), - Claim(Claim), } impl ProofItem { @@ -52,7 +49,6 @@ impl ProofItem { RevealedCombinationElements(_) => 7, FriCodeword(_) => 8, FriResponse(_) => 9, - Claim(_) => 10, }; BFieldElement::new(discriminant) } @@ -71,7 +67,6 @@ impl ProofItem { MerkleRoot(_) => true, OutOfDomainBaseRow(_) => true, OutOfDomainExtRow(_) => true, - Claim(_) => true, // all of the following are implied by a corresponding Merkle root AuthenticationStructure(_) => false, MasterBaseTableRows(_) => false, @@ -152,13 +147,6 @@ impl ProofItem { other => bail!("expected FRI proof, but got {other:?}"), } } - - pub fn as_claim(&self) -> Result { - match self { - Self::Claim(claim) => Ok(claim.to_owned()), - other => bail!("expected claim, but got {other:?}"), - } - } } impl BFieldCodec for ProofItem { @@ -182,7 +170,6 @@ impl BFieldCodec for ProofItem { 7 => Self::RevealedCombinationElements(*Vec::::decode(str)?), 8 => Self::FriCodeword(*Vec::::decode(str)?), 9 => Self::FriResponse(*FriResponse::decode(str)?), - 10 => Self::Claim(*Claim::decode(str)?), i => bail!("Unknown discriminant {i} for ProofItem."), }; Ok(Box::new(item)) @@ -216,7 +203,6 @@ impl BFieldCodec for ProofItem { RevealedCombinationElements(something) => something.encode(), FriCodeword(something) => something.encode(), FriResponse(something) => something.encode(), - Claim(something) => something.encode(), }; [discriminant, encoding].concat() } diff --git a/triton-vm/src/proof_stream.rs b/triton-vm/src/proof_stream.rs index abd14ccea..8aa40c14b 100644 --- a/triton-vm/src/proof_stream.rs +++ b/triton-vm/src/proof_stream.rs @@ -48,7 +48,7 @@ where b_field_elements.len() } - fn encode_and_pad_item(item: &ProofItem) -> Vec { + fn encode_and_pad_item(item: &impl BFieldCodec) -> Vec { let encoding = item.encode(); let last_chunk_len = (encoding.len() + 1) % H::RATE; let num_padding_zeros = match last_chunk_len { @@ -63,6 +63,19 @@ where .concat() } + /// 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) { + H::absorb_repeatedly( + &mut self.sponge_state, + Self::encode_and_pad_item(item).iter(), + ) + } + /// 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` @@ -75,10 +88,7 @@ where /// round of interaction, no further items need to be hashed. pub fn enqueue(&mut self, item: &ProofItem) { if item.include_in_fiat_shamir_heuristic() { - H::absorb_repeatedly( - &mut self.sponge_state, - Self::encode_and_pad_item(item).iter(), - ) + self.alter_fiat_shamir_state_with(item); } self.items.push(item.clone()); } @@ -89,14 +99,12 @@ where let Some(item) = self.items.get(self.items_index) else { bail!("Queue must be non-empty in order to dequeue."); }; + let item = item.to_owned(); if item.include_in_fiat_shamir_heuristic() { - H::absorb_repeatedly( - &mut self.sponge_state, - Self::encode_and_pad_item(item).iter(), - ) + self.alter_fiat_shamir_state_with(&item); } self.items_index += 1; - Ok(item.clone()) + Ok(item) } /// Given an `upper_bound` that is a power of 2, produce `num_indices` uniform random numbers @@ -183,7 +191,6 @@ where #[cfg(test)] mod proof_stream_typed_tests { - use crate::Claim; use itertools::Itertools; use rand::distributions::Standard; use rand::prelude::Distribution; @@ -258,15 +265,6 @@ mod proof_stream_typed_tests { revealed_leaves, }; - let program_digest = random_elements(rng.next_u64(), 1)[0]; - let input = random_elements(rng.next_u64(), 5); - let output = random_elements(rng.next_u64(), 5); - let claim = Claim { - program_digest, - input, - output, - }; - let mut sponge_states = VecDeque::new(); let mut proof_stream = ProofStream::::new(); @@ -291,8 +289,6 @@ mod proof_stream_typed_tests { sponge_states.push_back(proof_stream.sponge_state.state); proof_stream.enqueue(&ProofItem::FriResponse(fri_response.clone())); sponge_states.push_back(proof_stream.sponge_state.state); - proof_stream.enqueue(&ProofItem::Claim(claim.clone())); - sponge_states.push_back(proof_stream.sponge_state.state); let proof = proof_stream.into(); let mut proof_stream: ProofStream = @@ -383,15 +379,6 @@ mod proof_stream_typed_tests { _ => panic!(), }; - assert_eq!( - sponge_states.pop_front(), - Some(proof_stream.sponge_state.state) - ); - match proof_stream.dequeue().unwrap() { - ProofItem::Claim(claim_) => assert_eq!(claim, claim_), - _ => panic!(), - }; - assert_eq!( sponge_states.pop_front(), Some(proof_stream.sponge_state.state) diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index 5e9c4f593..4ad1fd6dc 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -132,7 +132,7 @@ impl Stark { ) -> Proof { prof_start!(maybe_profiler, "Fiat-Shamir: claim", "hash"); let mut proof_stream = StarkProofStream::new(); - proof_stream.enqueue(&ProofItem::Claim(claim.clone())); + proof_stream.alter_fiat_shamir_state_with(claim); prof_stop!(maybe_profiler, "Fiat-Shamir: claim"); prof_start!(maybe_profiler, "derive additional parameters"); @@ -578,6 +578,7 @@ impl Stark { pub fn verify( parameters: &StarkParameters, + claim: &Claim, proof: &Proof, maybe_profiler: &mut Option, ) -> Result { @@ -586,7 +587,7 @@ impl Stark { prof_stop!(maybe_profiler, "deserialize"); prof_start!(maybe_profiler, "Fiat-Shamir: Claim", "hash"); - let claim = proof_stream.dequeue()?.as_claim()?; + proof_stream.alter_fiat_shamir_state_with(claim); prof_stop!(maybe_profiler, "Fiat-Shamir: Claim"); prof_start!(maybe_profiler, "derive additional parameters"); @@ -601,7 +602,7 @@ impl Stark { let base_merkle_tree_root = proof_stream.dequeue()?.as_merkle_root()?; let extension_challenge_weights = proof_stream.sample_scalars(Challenges::num_challenges_to_sample()); - let challenges = Challenges::new(extension_challenge_weights, &claim); + let challenges = Challenges::new(extension_challenge_weights, claim); let extension_tree_merkle_root = proof_stream.dequeue()?.as_merkle_root()?; // Sample weights for quotient codeword, which is a part of the combination codeword. // See corresponding part in the prover for a more detailed explanation. @@ -1566,7 +1567,7 @@ pub(crate) mod triton_stark_tests { #[test] fn triton_prove_verify_simple_program_test() { let code_with_input = test_hash_nop_nop_lt(); - let (parameters, _, proof) = parse_simulate_prove( + let (parameters, claim, proof) = parse_simulate_prove( &code_with_input.source_code, code_with_input.public_input(), code_with_input.secret_input(), @@ -1575,7 +1576,7 @@ pub(crate) mod triton_stark_tests { println!("between prove and verify"); - let result = Stark::verify(¶meters, &proof, &mut None); + let result = Stark::verify(¶meters, &claim, &proof, &mut None); if let Err(e) = result { panic!("The Verifier is unhappy! {e}"); } @@ -1586,7 +1587,7 @@ pub(crate) mod triton_stark_tests { fn triton_prove_verify_halt_test() { let code_with_input = test_halt(); let mut profiler = Some(TritonProfiler::new("Prove Halt")); - let (parameters, _, proof) = parse_simulate_prove( + let (parameters, claim, proof) = parse_simulate_prove( &code_with_input.source_code, code_with_input.public_input(), code_with_input.secret_input(), @@ -1595,7 +1596,7 @@ pub(crate) mod triton_stark_tests { let mut profiler = profiler.unwrap(); profiler.finish(); - let result = Stark::verify(¶meters, &proof, &mut None); + let result = Stark::verify(¶meters, &claim, &proof, &mut None); if let Err(e) = result { panic!("The Verifier is unhappy! {e}"); } @@ -1614,7 +1615,7 @@ pub(crate) mod triton_stark_tests { let code_with_input = test_halt(); for _ in 0..100 { - let (parameters, _, proof) = parse_simulate_prove( + let (parameters, claim, proof) = parse_simulate_prove( &code_with_input.source_code, code_with_input.public_input(), code_with_input.secret_input(), @@ -1622,7 +1623,7 @@ pub(crate) mod triton_stark_tests { ); let filename = "halt_error.tsp"; - let result = Stark::verify(¶meters, &proof, &mut None); + let result = Stark::verify(¶meters, &claim, &proof, &mut None); if let Err(e) = result { if let Err(e) = save_proof(filename, proof) { panic!("Unsyntactical proof and can't save! {e}"); @@ -1637,7 +1638,7 @@ pub(crate) mod triton_stark_tests { #[ignore = "used for tracking&debugging deserialization errors"] fn triton_load_verify_halt_test() { let code_with_input = test_halt(); - let (parameters, _, _) = parse_simulate_prove( + let (parameters, claim, _) = parse_simulate_prove( &code_with_input.source_code, code_with_input.public_input(), code_with_input.secret_input(), @@ -1650,7 +1651,7 @@ pub(crate) mod triton_stark_tests { Err(e) => panic!("Could not load proof from disk at {filename}: {e}"), }; - let result = Stark::verify(¶meters, &proof, &mut None); + let result = Stark::verify(¶meters, &claim, &proof, &mut None); if let Err(e) = result { panic!("Verifier is unhappy! {e}"); } @@ -1664,14 +1665,14 @@ pub(crate) mod triton_stark_tests { let secret_in = vec![]; let mut profiler = Some(TritonProfiler::new("Prove Fib 100")); - let (parameters, _, proof) = + let (parameters, claim, proof) = parse_simulate_prove(source_code, stdin, secret_in, &mut profiler); let mut profiler = profiler.unwrap(); profiler.finish(); println!("between prove and verify"); - let result = Stark::verify(¶meters, &proof, &mut None); + let result = Stark::verify(¶meters, &claim, &proof, &mut None); if let Err(e) = result { panic!("The Verifier is unhappy! {e}"); } @@ -1693,7 +1694,7 @@ pub(crate) mod triton_stark_tests { let secret_in = vec![]; let (parameters, claim, proof) = parse_simulate_prove(code, stdin, secret_in, &mut None); - match Stark::verify(¶meters, &proof, &mut None) { + match Stark::verify(¶meters, &claim, &proof, &mut None) { Ok(result) => assert!(result, "The Verifier disagrees!"), Err(err) => panic!("The Verifier is unhappy! {err}"), } @@ -1711,12 +1712,12 @@ pub(crate) mod triton_stark_tests { #[test] fn triton_prove_verify_many_u32_operations_test() { let mut profiler = Some(TritonProfiler::new("Prove Many U32 Ops")); - let (parameters, _, proof) = + let (parameters, claim, proof) = parse_simulate_prove(MANY_U32_INSTRUCTIONS, vec![], vec![], &mut profiler); let mut profiler = profiler.unwrap(); profiler.finish(); - let result = Stark::verify(¶meters, &proof, &mut None); + let result = Stark::verify(¶meters, &claim, &proof, &mut None); if let Err(e) = result { panic!("The Verifier is unhappy! {e}"); } @@ -1759,8 +1760,9 @@ pub(crate) mod triton_stark_tests { let st0 = (rng.next_u32() as u64) << 32; let source_code = format!("push {st0} log_2_floor halt"); - let (parameters, _, proof) = parse_simulate_prove(&source_code, vec![], vec![], &mut None); - let result = Stark::verify(¶meters, &proof, &mut None); + let (parameters, claim, proof) = + parse_simulate_prove(&source_code, vec![], vec![], &mut None); + let result = Stark::verify(¶meters, &claim, &proof, &mut None); assert!(result.is_ok()); assert!(result.unwrap()); } @@ -1769,8 +1771,9 @@ pub(crate) mod triton_stark_tests { #[should_panic(expected = "The logarithm of 0 does not exist")] pub fn negative_log_2_floor_of_0_test() { let source_code = "push 0 log_2_floor halt"; - let (parameters, _, proof) = parse_simulate_prove(source_code, vec![], vec![], &mut None); - let result = Stark::verify(¶meters, &proof, &mut None); + let (parameters, claim, proof) = + parse_simulate_prove(source_code, vec![], vec![], &mut None); + let result = Stark::verify(¶meters, &claim, &proof, &mut None); assert!(result.is_ok()); assert!(result.unwrap()); }