diff --git a/benches/multi_stark.rs b/benches/multi_stark.rs index 3feff51..c7eda5a 100644 --- a/benches/multi_stark.rs +++ b/benches/multi_stark.rs @@ -176,7 +176,7 @@ fn build_witness(num_adds: usize, system: &System) -> SystemWitness { } /// Build claims for the first `num_adds` additions (same PRNG seed as `build_witness`). -fn build_claims(num_adds: usize) -> Vec<[Val; 4]> { +fn build_claims(num_adds: usize) -> Vec> { let f = Val::from_u32; let mut a: u32 = 0xdead_beef; let mut b: u32 = 0xcafe_babe; @@ -191,7 +191,7 @@ fn build_claims(num_adds: usize) -> Vec<[Val; 4]> { let x = a; let y = b; let (z, _carry) = x.overflowing_add(y); - claims.push([f(1), f(x), f(y), f(z)]); + claims.push(vec![f(1), f(x), f(y), f(z)]); } claims } @@ -223,15 +223,12 @@ fn bench_prove(c: &mut Criterion) { for log_height in [12, 13, 14] { let num_adds = 1 << log_height; let claims = build_claims(num_adds); - let claim_refs: Vec<&[Val]> = claims.iter().map(|c| c.as_slice()).collect(); group.bench_function( BenchmarkId::new("u32_add", format!("2^{log_height}")), |b| { b.iter_batched( - || build_witness(num_adds, &system), - |witness| { - system.prove_multiple_claims(fri_parameters, &key, &claim_refs, witness) - }, + || (build_witness(num_adds, &system), claims.clone()), + |(witness, claims)| system.prove(fri_parameters, &key, claims, witness), criterion::BatchSize::LargeInput, ); }, @@ -263,17 +260,12 @@ fn bench_verify(c: &mut Criterion) { for log_height in [12, 13, 14] { let num_adds = 1 << log_height; let claims = build_claims(num_adds); - let claim_refs: Vec<&[Val]> = claims.iter().map(|c| c.as_slice()).collect(); let witness = build_witness(num_adds, &system); - let proof = system.prove_multiple_claims(fri_parameters, &key, &claim_refs, witness); + let proof = system.prove(fri_parameters, &key, claims, witness); group.bench_function( BenchmarkId::new("u32_add", format!("2^{log_height}")), |b| { - b.iter(|| { - system - .verify_multiple_claims(fri_parameters, &claim_refs, &proof) - .unwrap() - }); + b.iter(|| system.verify(fri_parameters, &proof).unwrap()); }, ); } diff --git a/examples/lookup_proof.rs b/examples/lookup_proof.rs index 9e87168..f166e74 100644 --- a/examples/lookup_proof.rs +++ b/examples/lookup_proof.rs @@ -141,7 +141,7 @@ fn main() { ); // Claim: [even_index=0, input=4, expected_output=1] — is_even(4) should be 1 - let claim = &[f(0), f(4), f(1)]; + let claim = vec![f(0), f(4), f(1)]; let fri_parameters = FriParameters { log_final_poly_len: 0, max_log_arity: 1, @@ -150,8 +150,8 @@ fn main() { query_proof_of_work_bits: 0, }; - let proof = system.prove(fri_parameters, &key, claim, witness); - system.verify(fri_parameters, claim, &proof).unwrap(); + let proof = system.prove(fri_parameters, &key, vec![claim], witness); + system.verify(fri_parameters, &proof).unwrap(); println!("Lookup proof verified successfully!"); let bytes = proof.to_bytes().expect("serialization failed"); diff --git a/examples/preprocessed_proof.rs b/examples/preprocessed_proof.rs index c3ff6f2..d490454 100644 --- a/examples/preprocessed_proof.rs +++ b/examples/preprocessed_proof.rs @@ -125,11 +125,8 @@ fn main() { query_proof_of_work_bits: 0, }; - let no_claims: &[&[Val]] = &[]; - let proof = system.prove_multiple_claims(fri_parameters, &key, no_claims, witness); - system - .verify_multiple_claims(fri_parameters, no_claims, &proof) - .unwrap(); + let proof = system.prove(fri_parameters, &key, vec![], witness); + system.verify(fri_parameters, &proof).unwrap(); println!("Preprocessed proof verified successfully!"); let bytes = proof.to_bytes().expect("serialization failed"); diff --git a/examples/simple_proof.rs b/examples/simple_proof.rs index 02973da..cdd92a0 100644 --- a/examples/simple_proof.rs +++ b/examples/simple_proof.rs @@ -83,13 +83,10 @@ fn main() { }; // Prove - let no_claims = &[]; - let proof = system.prove_multiple_claims(fri_parameters, &key, no_claims, witness); + let proof = system.prove(fri_parameters, &key, vec![], witness); // Verify - system - .verify_multiple_claims(fri_parameters, no_claims, &proof) - .unwrap(); + system.verify(fri_parameters, &proof).unwrap(); println!("Proof verified successfully!"); // Show proof size diff --git a/src/lookup.rs b/src/lookup.rs index 113cbd5..6f6785d 100644 --- a/src/lookup.rs +++ b/src/lookup.rs @@ -1,5 +1,5 @@ use p3_air::{Air, BaseAir, ExtensionBuilder, WindowAccess}; -use p3_field::{PrimeCharacteristicRing, batch_multiplicative_inverse}; +use p3_field::{Field, PrimeCharacteristicRing, batch_multiplicative_inverse}; use p3_matrix::{Matrix, dense::RowMajorMatrix}; use p3_maybe_rayon::prelude::*; @@ -74,6 +74,24 @@ impl> LookupAir { } } +/// Computes the accumulator contribution from a list of claims with multiplicities. +/// +/// Both the prover and verifier use this to initialize the lookup accumulator from +/// external claims before running the circuit-level lookup argument. +pub(crate) fn claims_accumulator( + lookup_argument_challenge: ExtVal, + fingerprint_challenge: ExtVal, + claims: &[(Vec, u64)], +) -> ExtVal { + let mut acc = ExtVal::ZERO; + for (claim, multiplicity) in claims { + let message = + lookup_argument_challenge + fingerprint(&fingerprint_challenge, claim.iter().cloned()); + acc += message.inverse() * ExtVal::from_u64(*multiplicity); + } + acc +} + /// Computes a fingerprint of the coefficients using Horner's method. #[inline] pub(crate) fn fingerprint(r: &F, coeffs: Iter) -> F @@ -414,7 +432,7 @@ mod tests { ], &system, ); - let claim = &[f(0), f(4), f(1)]; + let claims = vec![vec![f(0), f(4), f(1)]]; let fri_parameters = FriParameters { log_final_poly_len: 0, max_log_arity: 1, @@ -422,7 +440,7 @@ mod tests { commit_proof_of_work_bits: 0, query_proof_of_work_bits: 0, }; - let proof = system.prove(fri_parameters, &key, claim, witness); - system.verify(fri_parameters, claim, &proof).unwrap(); + let proof = system.prove(fri_parameters, &key, claims, witness); + system.verify(fri_parameters, &proof).unwrap(); } } diff --git a/src/prover.rs b/src/prover.rs index 5308b89..d89e2c9 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -154,7 +154,7 @@ use std::ops::Deref; use crate::{ builder::folder::ProverConstraintFolder, - lookup::{Lookup, fingerprint}, + lookup::{Lookup, claims_accumulator}, system::{ProverKey, System, SystemWitness}, types::{ Challenger, Commitment, Domain, EvaluationsOnDomain, ExtVal, FriParameters, PackedExtVal, @@ -170,8 +170,7 @@ use p3_air::{Air, BaseAir, RowWindow}; use p3_challenger::{CanObserve, FieldChallenger}; use p3_commit::{LagrangeSelectors, OpenedValuesForRound, Pcs as PcsTrait, PolynomialSpace}; use p3_field::{ - BasedVectorSpace, Field, PackedValue, PrimeCharacteristicRing, - extension::BinomialExtensionField, + BasedVectorSpace, PackedValue, PrimeCharacteristicRing, extension::BinomialExtensionField, }; use p3_matrix::{Matrix, dense::RowMajorMatrix}; use p3_maybe_rayon::prelude::*; @@ -203,6 +202,13 @@ pub struct Proof { pub preprocessed_opened_values: Option>, pub stage_1_opened_values: OpenedValuesForRound, pub stage_2_opened_values: OpenedValuesForRound, + /// Claims with multiplicities bound to this proof. Observed into the Fiat-Shamir + /// transcript before lookup challenges are sampled. The verifier reads them directly. + pub claims: Vec<(Vec, u64)>, + /// Remaining claims with multiplicities that must become the `claims` of the next + /// proof shard. The final lookup accumulator equals the accumulator derived from these, + /// and the next shard's initial accumulator (from its own claims) must match. + pub remaining_claims: Vec<(Vec, u64)>, } impl Proof { @@ -223,29 +229,30 @@ impl Proof { } impl + for<'a> Air>> System { - /// Generates a STARK proof for the system with a single claim. - /// - /// This is a convenience wrapper around [`Self::prove_multiple_claims`]. + /// Generates a full STARK proof binding the given claims (all with multiplicity 1). pub fn prove( &self, fri_parameters: FriParameters, key: &ProverKey, - claim: &[Val], + claims: Vec>, witness: SystemWitness, ) -> Proof { - self.prove_multiple_claims(fri_parameters, key, &[claim], witness) + let claims = claims.into_iter().map(|c| (c, 1u64)).collect(); + self.partially_prove(fri_parameters, key, claims, vec![], witness) } - /// Generates a STARK proof for the system with multiple claims. + /// Generates a partial STARK proof (proof shard). /// - /// Each claim is a slice of field elements that is observed by the challenger - /// before lookup challenges are sampled, binding the proof to the claimed values. - #[tracing::instrument(level = "info", skip_all, name = "stark/prove")] - pub fn prove_multiple_claims( + /// `claims` with multiplicities are observed into the Fiat-Shamir transcript before + /// lookup challenges are sampled. `remaining_claims` encode the obligations passed to + /// the next shard: the next shard's `claims` must equal these (so their accumulators + /// match), and this shard's final accumulator equals the accumulator derived from them. + pub fn partially_prove( &self, fri_parameters: FriParameters, key: &ProverKey, - claims: &[&[Val]], + claims: Vec<(Vec, u64)>, + remaining_claims: Vec<(Vec, u64)>, witness: SystemWitness, ) -> Proof { // initialize pcs and challenger @@ -279,11 +286,15 @@ impl + for<'a> Air>> System { challenger.observe(Val::from_usize(*log_degree)); } - // observe the claims - // this has to be done before generating the lookup argument challenge - // otherwise the lookup argument can be attacked - for claim in claims { - challenger.observe_slice(claim); + // observe claims and remaining claims before lookup challenges are sampled; + // this is required for soundness of the lookup argument + for (values, multiplicity) in &claims { + challenger.observe_slice(values); + challenger.observe(Val::from_u64(*multiplicity)); + } + for (values, multiplicity) in &remaining_claims { + challenger.observe_slice(values); + challenger.observe(Val::from_u64(*multiplicity)); } // generate lookup challenges @@ -292,13 +303,7 @@ impl + for<'a> Air>> System { let fingerprint_challenge: ExtVal = challenger.sample_algebra_element(); challenger.observe_algebra_element(fingerprint_challenge); - // construct the accumulator from the claims - let mut acc = ExtVal::ZERO; - for claim in claims { - let message = lookup_argument_challenge - + fingerprint(&fingerprint_challenge, claim.iter().cloned()); - acc += message.inverse(); - } + let acc = claims_accumulator(lookup_argument_challenge, fingerprint_challenge, &claims); // Cost: "Lookup trace construction" — fingerprint (Horner), batch // inversion, and accumulator update. Total: Σ n_i·L_i extension field ops. @@ -335,6 +340,7 @@ impl + for<'a> Air>> System { debug_assert_eq!(intermediate_accumulators.len(), self.circuits.len()); debug_assert_eq!(log_degrees.len(), self.circuits.len()); let mut quotient_degrees = vec![]; + let mut curr_acc = acc; let quotient_evaluations = self .circuits .iter() @@ -384,7 +390,7 @@ impl + for<'a> Air>> System { let stage_2_public_values = [ lookup_argument_challenge, fingerprint_challenge, - acc, + curr_acc, *next_acc, ]; let quotient_values = quotient_values( @@ -408,7 +414,7 @@ impl + for<'a> Air>> System { let quotient_sub_domains = quotient_domain.split_domains(quotient_degree); // need to save the quotient degree for later quotient_degrees.push(quotient_degree); - acc = *next_acc; + curr_acc = *next_acc; quotient_sub_domains .into_iter() .zip(quotient_sub_evaluations) @@ -477,6 +483,8 @@ impl + for<'a> Air>> System { preprocessed_opened_values, stage_1_opened_values, stage_2_opened_values, + claims, + remaining_claims, } } } diff --git a/src/test_circuits/blake3.rs b/src/test_circuits/blake3.rs index 8fcbf3a..0ca5622 100644 --- a/src/test_circuits/blake3.rs +++ b/src/test_circuits/blake3.rs @@ -2321,9 +2321,6 @@ mod tests { let (_traces, witness) = claims.witness(&system); - let claims_slice: Vec<&[Val]> = claims.claims.iter().map(|v| v.as_slice()).collect(); - let claims_slice: &[&[Val]] = &claims_slice; - let fri_parameters = FriParameters { log_final_poly_len: 0, max_log_arity: 1, @@ -2332,10 +2329,9 @@ mod tests { query_proof_of_work_bits: 0, }; - let proof = - system.prove_multiple_claims(fri_parameters, &prover_key, claims_slice, witness); + let proof = system.prove(fri_parameters, &prover_key, claims.claims, witness); system - .verify_multiple_claims(fri_parameters, claims_slice, &proof) + .verify(fri_parameters, &proof) .expect("verification issue"); } @@ -2495,9 +2491,7 @@ mod tests { ); let (_traces, witness) = claims.witness(&system); - - let claims_slice: Vec<&[Val]> = claims.claims.iter().map(|v| v.as_slice()).collect(); - let claims_slice: &[&[Val]] = &claims_slice; + let claim_vecs = claims.claims.clone(); let fri_parameters = FriParameters { log_final_poly_len: 0, @@ -2507,10 +2501,9 @@ mod tests { query_proof_of_work_bits: 0, }; - let proof = - system.prove_multiple_claims(fri_parameters, &prover_key, claims_slice, witness); + let proof = system.prove(fri_parameters, &prover_key, claim_vecs, witness); system - .verify_multiple_claims(fri_parameters, claims_slice, &proof) + .verify(fri_parameters, &proof) .expect("verification issue"); } diff --git a/src/test_circuits/byte_operations.rs b/src/test_circuits/byte_operations.rs index 9839077..2fe5ab1 100644 --- a/src/test_circuits/byte_operations.rs +++ b/src/test_circuits/byte_operations.rs @@ -139,11 +139,12 @@ mod tests { }; let witness = calls.witness(&system); let f = Val::from_u32; - let claim1 = &[f(0), f(10), f(5), f(10 ^ 5)]; - let claim2 = &[f(1), f(30), f(20), f(30 & 20)]; - let claim3 = &[f(2), f(100), f(40), f(100 | 40)]; - let claim4 = &[f(3), f(200), f(100)]; - let claims: &[&[Val]] = &[claim1, claim2, claim3, claim4]; + let claims = vec![ + vec![f(0), f(10), f(5), f(10 ^ 5)], + vec![f(1), f(30), f(20), f(30 & 20)], + vec![f(2), f(100), f(40), f(100 | 40)], + vec![f(3), f(200), f(100)], + ]; let fri_parameters = FriParameters { log_final_poly_len: 0, max_log_arity: 1, @@ -151,9 +152,7 @@ mod tests { commit_proof_of_work_bits: 0, query_proof_of_work_bits: 0, }; - let proof = system.prove_multiple_claims(fri_parameters, &key, claims, witness); - system - .verify_multiple_claims(fri_parameters, claims, &proof) - .unwrap(); + let proof = system.prove(fri_parameters, &key, claims, witness); + system.verify(fri_parameters, &proof).unwrap(); } } diff --git a/src/test_circuits/u32_add.rs b/src/test_circuits/u32_add.rs index 0a8abb4..f30b41d 100644 --- a/src/test_circuits/u32_add.rs +++ b/src/test_circuits/u32_add.rs @@ -197,11 +197,12 @@ mod tests { }; let witness = calls.witness(&system); let f = Val::from_u32; - let claim1 = &[f(1), f(10), f(5), f(15)]; - let claim2 = &[f(1), f(30), f(20), f(50)]; - let claim3 = &[f(1), f(100), f(100), f(200)]; - let claim4 = &[f(1), f(8000), f(10000), f(18000)]; - let claims: &[&[Val]] = &[claim1, claim2, claim3, claim4]; + let claims = vec![ + vec![f(1), f(10), f(5), f(15)], + vec![f(1), f(30), f(20), f(50)], + vec![f(1), f(100), f(100), f(200)], + vec![f(1), f(8000), f(10000), f(18000)], + ]; let fri_parameters = FriParameters { log_final_poly_len: 0, max_log_arity: 1, @@ -209,9 +210,7 @@ mod tests { commit_proof_of_work_bits: 0, query_proof_of_work_bits: 0, }; - let proof = system.prove_multiple_claims(fri_parameters, &key, claims, witness); - system - .verify_multiple_claims(fri_parameters, claims, &proof) - .unwrap(); + let proof = system.prove(fri_parameters, &key, claims, witness); + system.verify(fri_parameters, &proof).unwrap(); } } diff --git a/src/verifier.rs b/src/verifier.rs index 224c1a2..697dd54 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -107,7 +107,7 @@ use crate::{ builder::folder::VerifierConstraintFolder, ensure, ensure_eq, - lookup::fingerprint, + lookup::claims_accumulator, prover::Proof, system::System, types::{Challenger, ExtVal, FriParameters, Pcs, PcsError, StarkConfig, Val}, @@ -133,31 +133,43 @@ pub enum VerificationError { InvalidProofShape, /// The system configuration is invalid (e.g. no circuits). InvalidSystem, + /// The proof is not a full proof: either some claim has multiplicity ≠ 1 + /// or `remaining_claims` is non-empty. Use [`System::partially_verify`] instead. + NotFullProof, /// The recomputed composition polynomial does not match the quotient. OodEvaluationMismatch, - /// The lookup accumulator did not balance to zero. + /// The lookup accumulator did not reach the expected value. UnbalancedChannel, } impl + for<'a> Air>> System { - /// Verifies a STARK proof against a single claim. + /// Verifies a complete STARK proof (all claim multiplicities 1, no remaining claims). /// - /// Returns `Ok(())` if the proof is valid, or a [`VerificationError`] describing - /// the first check that failed. + /// Checks that every claim has multiplicity 1 and that `remaining_claims` is empty, + /// then delegates to [`Self::partially_verify`]. pub fn verify( &self, fri_parameters: FriParameters, - claim: &[Val], proof: &Proof, ) -> Result<(), VerificationError> { - self.verify_multiple_claims(fri_parameters, &[claim], proof) + ensure!( + proof.claims.iter().all(|(_, m)| *m == 1) && proof.remaining_claims.is_empty(), + VerificationError::NotFullProof + ); + self.partially_verify(fri_parameters, proof) } - /// Verifies a STARK proof against multiple claims. - pub fn verify_multiple_claims( + /// Verifies a STARK proof shard. Claims and remaining claims are read from the proof. + /// + /// The final lookup accumulator must equal the accumulator derived from + /// `proof.remaining_claims`. The next shard's `claims` must equal these + /// `remaining_claims` — it is the initial accumulator of the next shard (derived + /// from its own claims) that must match, not its final accumulator. + /// Use [`Self::verify`] for complete proofs where all multiplicities are 1 and + /// `remaining_claims` is empty. + pub fn partially_verify( &self, fri_parameters: FriParameters, - claims: &[&[Val]], proof: &Proof, ) -> Result<(), VerificationError> { let Proof { @@ -169,21 +181,12 @@ impl + for<'a> Air>> System { preprocessed_opened_values, stage_1_opened_values, stage_2_opened_values, + claims, + remaining_claims, } = proof; // first, verify the proof shape let quotient_degrees = self.verify_shape(proof)?; - // Soundness: lookup argument. The accumulator was computed by the prover - // under challenges (β, γ) that were sampled after the traces and claims were - // committed. If the pushed and pulled multisets differ, the accumulator is a - // nonzero rational function of (β, γ) and evaluates to zero with probability - // ≤ N / |F_ext| (Schwartz-Zippel on the numerator polynomial). - ensure_eq!( - intermediate_accumulators.last(), - Some(&ExtVal::ZERO), - VerificationError::UnbalancedChannel - ); - // Soundness: Fiat-Shamir. All challenges below are derived deterministically // from the transcript via Keccak-256 (random oracle model). The verifier // replays exactly the same observations as the prover, so any divergence @@ -204,11 +207,15 @@ impl + for<'a> Air>> System { challenger.observe(Val::from_u8(*log_degree)); } - // Soundness: claims must be observed BEFORE lookup challenges are sampled. - // Otherwise, the prover could choose claims adaptively after seeing the - // challenges, breaking the lookup argument's binding property. - for claim in claims { - challenger.observe_slice(claim); + // Soundness: claims and remaining_claims must be observed BEFORE lookup + // challenges are sampled, otherwise the prover could choose them adaptively. + for (values, multiplicity) in claims { + challenger.observe_slice(values); + challenger.observe(Val::from_u64(*multiplicity)); + } + for (values, multiplicity) in remaining_claims { + challenger.observe_slice(values); + challenger.observe(Val::from_u64(*multiplicity)); } // Soundness: lookup argument. The challenges are random elements of F_ext. @@ -223,13 +230,21 @@ impl + for<'a> Air>> System { // observe stage_2 commitment challenger.observe(commitments.stage_2_trace.clone()); - // construct the accumulator from the claims - let mut acc = ExtVal::ZERO; - for claim in claims { - let message = lookup_argument_challenge - + fingerprint(&fingerprint_challenge, claim.iter().cloned()); - acc += message.inverse(); - } + // initial accumulator from claims; target accumulator from remaining_claims + let acc = claims_accumulator(lookup_argument_challenge, fingerprint_challenge, claims); + let remaining_acc = claims_accumulator( + lookup_argument_challenge, + fingerprint_challenge, + remaining_claims, + ); + + // Soundness: the final intermediate accumulator must equal the remaining_acc. + // With no remaining_claims, this is zero (fully balanced lookup argument). + ensure_eq!( + intermediate_accumulators.last(), + Some(&remaining_acc), + VerificationError::UnbalancedChannel + ); // Soundness: constraint folding. All k constraints are combined via powers // of α. The folded sum has degree k-1 in α, so by Schwartz-Zippel a violated @@ -247,6 +262,7 @@ impl + for<'a> Air>> System { let mut stage_2_trace_evaluations = vec![]; let mut quotient_chunks_evaluations = vec![]; let mut last_quotient_i = 0; + let mut curr_acc = acc; for i in 0..self.circuits.len() { let log_degree = log_degrees[i]; let quotient_degree = quotient_degrees[i]; @@ -375,7 +391,7 @@ impl + for<'a> Air>> System { let stage_2_public_values = &[ lookup_argument_challenge, fingerprint_challenge, - acc, + curr_acc, next_acc, ]; let mut folder = VerifierConstraintFolder { @@ -430,7 +446,7 @@ impl + for<'a> Air>> System { VerificationError::OodEvaluationMismatch ); // the accumulator must become the next accumulator for the next iteration - acc = next_acc; + curr_acc = next_acc; } Ok(()) @@ -649,11 +665,8 @@ mod tests { commit_proof_of_work_bits: 0, query_proof_of_work_bits: 0, }; - let no_claims = &[]; - let proof = system.prove_multiple_claims(fri_parameters, &key, no_claims, witness); - system - .verify_multiple_claims(fri_parameters, no_claims, &proof) - .unwrap(); + let proof = system.prove(fri_parameters, &key, vec![], witness); + system.verify(fri_parameters, &proof).unwrap(); } #[test] @@ -685,14 +698,11 @@ mod tests { commit_proof_of_work_bits: 0, query_proof_of_work_bits: 0, }; - let no_claims = &[]; - let proof = system.prove_multiple_claims(fri_parameters, &key, no_claims, witness); + let proof = system.prove(fri_parameters, &key, vec![], witness); // Serialization round-trip let proof_bytes = proof.to_bytes().expect("Failed to serialize proof"); let proof2 = Proof::from_bytes(&proof_bytes).expect("Failed to deserialize proof"); - system - .verify_multiple_claims(fri_parameters, no_claims, &proof2) - .unwrap(); + system.verify(fri_parameters, &proof2).unwrap(); } // -- Negative / adversarial tests -- @@ -722,17 +732,17 @@ mod tests { commit_proof_of_work_bits: 0, query_proof_of_work_bits: 0, }; - let no_claims = &[]; - let proof = system.prove_multiple_claims(fri_parameters, &key, no_claims, witness); + let proof = system.prove(fri_parameters, &key, vec![], witness); (system, fri_parameters, proof) } #[test] fn test_wrong_claim_rejected() { - let (system, fri_parameters, proof) = small_system_and_proof(); + let (system, fri_parameters, mut proof) = small_system_and_proof(); let f = Val::from_u32; - // Verify with a bogus claim — the prover used no claims, so any claim should fail. - let result = system.verify(fri_parameters, &[f(42)], &proof); + // Tamper with the proof's claims — verifier transcript diverges from prover's. + proof.claims = vec![(vec![f(42)], 1)]; + let result = system.verify(fri_parameters, &proof); assert!(result.is_err()); } @@ -741,8 +751,7 @@ mod tests { let (system, fri_parameters, mut proof) = small_system_and_proof(); // Mutate a value in the stage 1 opened values — FRI should catch this. proof.stage_1_opened_values[0][0][0] += ExtVal::ONE; - let no_claims: &[&[Val]] = &[]; - let result = system.verify_multiple_claims(fri_parameters, no_claims, &proof); + let result = system.verify(fri_parameters, &proof); assert!(result.is_err()); } @@ -752,8 +761,7 @@ mod tests { // Set the last intermediate accumulator to non-zero. let last = proof.intermediate_accumulators.len() - 1; proof.intermediate_accumulators[last] = ExtVal::ONE; - let no_claims: &[&[Val]] = &[]; - let result = system.verify_multiple_claims(fri_parameters, no_claims, &proof); + let result = system.verify(fri_parameters, &proof); assert!(result.is_err()); } @@ -762,8 +770,7 @@ mod tests { let (system, fri_parameters, mut proof) = small_system_and_proof(); // Remove a quotient opened value — shape check should fail. proof.quotient_opened_values.pop(); - let no_claims: &[&[Val]] = &[]; - let result = system.verify_multiple_claims(fri_parameters, no_claims, &proof); + let result = system.verify(fri_parameters, &proof); assert!(result.is_err()); } @@ -772,9 +779,52 @@ mod tests { let (system, fri_parameters, proof) = small_system_and_proof(); let bytes = proof.to_bytes().expect("serialize"); let proof2 = Proof::from_bytes(&bytes).expect("deserialize"); - let no_claims: &[&[Val]] = &[]; - system - .verify_multiple_claims(fri_parameters, no_claims, &proof2) - .unwrap(); + system.verify(fri_parameters, &proof2).unwrap(); + } + + #[test] + fn test_partial_proof_extra_claim_cancels() { + // A claim in remaining_claims sets the target accumulator for this shard. + // The same claim in claims means this shard's initial accumulator already equals + // that target, so with no circuit lookups the final accumulator matches and + // partially_verify accepts. This simulates passing the claim to the next shard. + let commitment_parameters = CommitmentParameters { + log_blowup: 1, + cap_height: 0, + }; + let (system, key) = system(commitment_parameters); + let f = Val::from_u32; + let witness = SystemWitness::from_stage_1( + vec![ + RowMajorMatrix::new( + [3, 4, 5, 5, 12, 13, 8, 15, 17, 7, 24, 25].map(f).to_vec(), + 3, + ), + RowMajorMatrix::new([4, 2, 3, 1, 10, 10, 3, 2, 5, 1, 13, 13].map(f).to_vec(), 6), + ], + &system, + ); + let fri_parameters = FriParameters { + log_final_poly_len: 0, + max_log_arity: 1, + num_queries: 64, + commit_proof_of_work_bits: 0, + query_proof_of_work_bits: 0, + }; + let extra_claim = vec![f(42), f(43)]; + let proof = system.partially_prove( + fri_parameters, + &key, + vec![(extra_claim.clone(), 1)], + vec![(extra_claim, 1)], + witness, + ); + // partially_verify must accept — claim and remaining cancel + system.partially_verify(fri_parameters, &proof).unwrap(); + // verify must reject — remaining_claims is non-empty + assert!(matches!( + system.verify(fri_parameters, &proof), + Err(VerificationError::NotFullProof) + )); } }