diff --git a/Cargo.toml b/Cargo.toml index e6f2c83f0..078ccd58e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ serde = { version = "1", features = ["derive"] } serde_derive = "1" strum = { version = "0.25", features = ["derive"] } syn = "2.0" +test-strategy = "0.3.1" twenty-first = "0.34" unicode-width = "0.1" diff --git a/triton-vm/Cargo.toml b/triton-vm/Cargo.toml index c71857bfc..39aefe2a7 100644 --- a/triton-vm/Cargo.toml +++ b/triton-vm/Cargo.toml @@ -44,6 +44,7 @@ unicode-width.workspace = true cargo-husky.workspace = true proptest.workspace = true proptest-arbitrary-interop.workspace = true +test-strategy.workspace = true [[bench]] name = "prove_halt" diff --git a/triton-vm/src/arithmetic_domain.rs b/triton-vm/src/arithmetic_domain.rs index 505d34f2d..0aeb9121e 100644 --- a/triton-vm/src/arithmetic_domain.rs +++ b/triton-vm/src/arithmetic_domain.rs @@ -111,13 +111,16 @@ impl ArithmeticDomain { #[cfg(test)] mod tests { use itertools::Itertools; + use proptest::prelude::*; + use proptest_arbitrary_interop::arb; + use test_strategy::proptest; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::traits::PrimitiveRootOfUnity; - - use super::*; + use twenty_first::shared_math::x_field_element::XFieldElement; use crate::shared_tests::*; - use proptest::prelude::*; + + use super::*; prop_compose! { fn arbitrary_domain()( @@ -141,50 +144,42 @@ mod tests { prop_compose! { fn arbitrary_domain_of_length(length: usize)( - offset in arbitrary_bfield_element(), + offset in arb(), ) -> ArithmeticDomain { ArithmeticDomain::of_length(length).with_offset(offset) } } - proptest! { - #[test] - fn evaluate_empty_polynomial( - domain in arbitrary_domain(), - polynomial in arbitrary_polynomial_of_degree(-1), - ) { - domain.evaluate(&polynomial); - } + #[proptest] + fn evaluate_empty_polynomial( + #[strategy(arbitrary_domain())] domain: ArithmeticDomain, + #[strategy(arbitrary_polynomial_of_degree(-1))] polynomial: Polynomial, + ) { + domain.evaluate(&polynomial); } - proptest! { - #[test] - fn evaluate_constant_polynomial( - domain in arbitrary_domain(), - polynomial in arbitrary_polynomial_of_degree(0), - ) { - domain.evaluate(&polynomial); - } + #[proptest] + fn evaluate_constant_polynomial( + #[strategy(arbitrary_domain())] domain: ArithmeticDomain, + #[strategy(arbitrary_polynomial_of_degree(0))] polynomial: Polynomial, + ) { + domain.evaluate(&polynomial); } - proptest! { - #[test] - fn evaluate_linear_polynomial( - domain in arbitrary_domain(), - polynomial in arbitrary_polynomial_of_degree(1), - ) { - domain.evaluate(&polynomial); - } + #[proptest] + fn evaluate_linear_polynomial( + #[strategy(arbitrary_domain())] domain: ArithmeticDomain, + #[strategy(arbitrary_polynomial_of_degree(1))] polynomial: Polynomial, + ) { + domain.evaluate(&polynomial); } - proptest! { - #[test] - fn evaluate_polynomial( - domain in arbitrary_domain(), - polynomial in arbitrary_polynomial(), - ) { - domain.evaluate(&polynomial); - } + #[proptest] + fn evaluate_polynomial( + #[strategy(arbitrary_domain())] domain: ArithmeticDomain, + #[strategy(arbitrary_polynomial())] polynomial: Polynomial, + ) { + domain.evaluate(&polynomial); } #[test] @@ -251,21 +246,21 @@ mod tests { assert_eq!(short_codeword, long_codeword_sub_view); } - proptest! { - #[test] - fn halving_domain_squares_all_points(domain in arbitrary_halveable_domain()) { - let half_domain = domain.halve(); - prop_assert_eq!(domain.length / 2, half_domain.length); + #[proptest] + fn halving_domain_squares_all_points( + #[strategy(arbitrary_halveable_domain())] domain: ArithmeticDomain, + ) { + let half_domain = domain.halve(); + prop_assert_eq!(domain.length / 2, half_domain.length); - let domain_points = domain.domain_values(); - let half_domain_points = half_domain.domain_values(); + let domain_points = domain.domain_values(); + let half_domain_points = half_domain.domain_values(); - for (domain_point, halved_domain_point) in domain_points - .into_iter() - .zip(half_domain_points.into_iter()) - { - prop_assert_eq!(domain_point.square(), halved_domain_point); - } + for (domain_point, halved_domain_point) in domain_points + .into_iter() + .zip(half_domain_points.into_iter()) + { + prop_assert_eq!(domain_point.square(), halved_domain_point); } } diff --git a/triton-vm/src/error.rs b/triton-vm/src/error.rs index 14d32228b..a26b67bf5 100644 --- a/triton-vm/src/error.rs +++ b/triton-vm/src/error.rs @@ -64,6 +64,7 @@ impl Error for InstructionError {} mod tests { use proptest::prelude::*; use proptest_arbitrary_interop::arb; + use test_strategy::proptest; use crate::instruction::AnInstruction::*; use crate::instruction::LabelledInstruction; @@ -129,46 +130,42 @@ mod tests { program.run([].into(), [].into()).unwrap(); } - proptest! { - #[test] - fn assert_unequal_vec( - test_vector in arb::<[BFieldElement; DIGEST_LENGTH]>(), - disturbance_index in 0..DIGEST_LENGTH, - random_element in arb::(), - ) { - let mut disturbed_vector = test_vector; - disturbed_vector[disturbance_index] = random_element; - - if disturbed_vector == test_vector { - return Ok(()); - } + #[proptest] + fn assert_unequal_vec( + #[strategy(arb())] test_vector: [BFieldElement; DIGEST_LENGTH], + #[strategy(0..DIGEST_LENGTH)] disturbance_index: usize, + #[strategy(arb())] + #[filter(#test_vector[#disturbance_index] != #random_element)] + random_element: BFieldElement, + ) { + let mut disturbed_vector = test_vector; + disturbed_vector[disturbance_index] = random_element; - let program = triton_program!{ - push {test_vector[4]} - push {test_vector[3]} - push {test_vector[2]} - push {test_vector[1]} - push {test_vector[0]} - - push {disturbed_vector[4]} - push {disturbed_vector[3]} - push {disturbed_vector[2]} - push {disturbed_vector[1]} - push {disturbed_vector[0]} - - assert_vector - halt - }; - - let err = program.run([].into(), [].into()).unwrap_err(); - - let err = err.downcast::().unwrap(); - let VectorAssertionFailed(_, _, index, _, _) = err else { - panic!("VM panicked with unexpected error {err}.") - }; - let index: usize = index.into(); - prop_assert_eq!(disturbance_index, index); - } + let program = triton_program! { + push {test_vector[4]} + push {test_vector[3]} + push {test_vector[2]} + push {test_vector[1]} + push {test_vector[0]} + + push {disturbed_vector[4]} + push {disturbed_vector[3]} + push {disturbed_vector[2]} + push {disturbed_vector[1]} + push {disturbed_vector[0]} + + assert_vector + halt + }; + + let err = program.run([].into(), [].into()).unwrap_err(); + + let err = err.downcast::().unwrap(); + let VectorAssertionFailed(_, _, index, _, _) = err else { + panic!("VM panicked with unexpected error {err}.") + }; + let index: usize = index.into(); + prop_assert_eq!(disturbance_index, index); } #[test] diff --git a/triton-vm/src/fri.rs b/triton-vm/src/fri.rs index 8eff09bad..9815459b9 100644 --- a/triton-vm/src/fri.rs +++ b/triton-vm/src/fri.rs @@ -642,12 +642,11 @@ mod tests { use std::cmp::min; use itertools::Itertools; - use proptest::collection::vec; use proptest::prelude::*; use proptest_arbitrary_interop::arb; - use rand::prelude::IteratorRandom; - use rand::prelude::StdRng; + use rand::prelude::*; use rand_core::SeedableRng; + use test_strategy::proptest; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::polynomial::Polynomial; use twenty_first::shared_math::tip5::Tip5; @@ -660,14 +659,6 @@ mod tests { use super::*; - prop_compose! { - fn arbitrary_element_to_absorb()( - absorb_array in vec(arbitrary_bfield_element(), RATE) - ) -> [BFieldElement; RATE] { - absorb_array.try_into().unwrap() - } - } - prop_compose! { fn arbitrary_fri()( fri in arbitrary_fri_supporting_degree(-1) @@ -683,7 +674,7 @@ mod tests { log_2_expansion_factor in Just(log_2_expansion_factor), log_2_domain_length in log_2_expansion_factor..=18, num_colinearity_checks in 1_usize..=320, - offset in arbitrary_bfield_element(), + offset in arb(), ) -> Fri { let expansion_factor = (1 << log_2_expansion_factor) as usize; let sampled_domain_length = (1 << log_2_domain_length) as usize; @@ -700,135 +691,85 @@ mod tests { } } - prop_compose! { - fn arbitrary_supported_polynomial(fri: &Fri::)( - degree in -1_i64..=fri.first_round_max_degree() as i64, - )( - polynomial in arbitrary_polynomial_of_degree(degree), - ) -> Polynomial { - polynomial - } - } - - prop_compose! { - fn arbitrary_unsupported_polynomial(fri: &Fri::)( - degree in 1 + fri.first_round_max_degree()..2 * (1 + fri.first_round_max_degree()), - )( - polynomial in arbitrary_polynomial_of_degree(degree as i64), - ) -> Polynomial { - polynomial - } - } - - prop_compose! { - fn arbitrary_matching_fri_and_polynomial_pair()( - fri in arbitrary_fri(), - )( - polynomial in arbitrary_supported_polynomial(&fri), - fri in Just(fri), - ) -> (Fri::, Polynomial) { - (fri, polynomial) - } - } + #[proptest] + fn sample_indices( + #[strategy(arbitrary_fri())] fri: Fri, + #[strategy(arb())] initial_absorb: [BFieldElement; RATE], + ) { + let mut sponge_state = Tip5::init(); + Tip5::absorb(&mut sponge_state, &initial_absorb); - prop_compose! { - fn arbitrary_non_matching_fri_and_polynomial_pair()( - fri in arbitrary_fri(), - )( - polynomial in arbitrary_unsupported_polynomial(&fri), - fri in Just(fri), - ) -> (Fri::, Polynomial) { - (fri, polynomial) - } - } + // todo: Figure out by how much to oversample for the given parameters. + let oversampling_summand = 1 << 13; + let num_indices_to_sample = fri.num_colinearity_checks + oversampling_summand; + let indices = Tip5::sample_indices( + &mut sponge_state, + fri.domain.length as u32, + num_indices_to_sample, + ); + let num_unique_indices = indices.iter().unique().count(); - proptest! { - #[test] - fn sample_indices( - fri in arbitrary_fri(), - initial_absorb in arbitrary_element_to_absorb(), - ) { - let mut sponge_state = Tip5::init(); - Tip5::absorb(&mut sponge_state, &initial_absorb); - - // todo: Figure out by how much to oversample for the given parameters. - let oversampling_summand = 1 << 13; - let num_indices_to_sample = fri.num_colinearity_checks + oversampling_summand; - let indices = Tip5::sample_indices( - &mut sponge_state, - fri.domain.length as u32, - num_indices_to_sample, - ); - let num_unique_indices = indices.iter().unique().count(); - - let required_unique_indices = min(fri.domain.length, fri.num_colinearity_checks); - prop_assert!(num_unique_indices >= required_unique_indices); - } + let required_unique_indices = min(fri.domain.length, fri.num_colinearity_checks); + prop_assert!(num_unique_indices >= required_unique_indices); } - proptest! { - #[test] - fn num_rounds_are_reasonable(fri in arbitrary_fri()) { - let expected_last_round_max_degree = fri.first_round_max_degree() >> fri.num_rounds(); - prop_assert_eq!(expected_last_round_max_degree, fri.last_round_max_degree()); - if fri.num_rounds() > 0 { - prop_assert!(fri.num_colinearity_checks <= expected_last_round_max_degree); - prop_assert!(expected_last_round_max_degree < 2 * fri.num_colinearity_checks); - } + #[proptest] + fn num_rounds_are_reasonable(#[strategy(arbitrary_fri())] fri: Fri) { + let expected_last_round_max_degree = fri.first_round_max_degree() >> fri.num_rounds(); + prop_assert_eq!(expected_last_round_max_degree, fri.last_round_max_degree()); + if fri.num_rounds() > 0 { + prop_assert!(fri.num_colinearity_checks <= expected_last_round_max_degree); + prop_assert!(expected_last_round_max_degree < 2 * fri.num_colinearity_checks); } } - proptest! { - #![proptest_config(ProptestConfig::with_cases(50))] - #[test] - fn prove_and_verify_low_degree_of_twice_cubing_plus_one( - fri in arbitrary_fri_supporting_degree(3) - ) { - let coefficients = [1, 0, 0, 2].map(|c| c.into()).to_vec(); - let polynomial = Polynomial::new(coefficients); - let codeword = fri.domain.evaluate(&polynomial); + #[proptest(cases = 20)] + fn prove_and_verify_low_degree_of_twice_cubing_plus_one( + #[strategy(arbitrary_fri_supporting_degree(3))] fri: Fri, + ) { + let coefficients = [1, 0, 0, 2].map(|c| c.into()).to_vec(); + let polynomial = Polynomial::new(coefficients); + let codeword = fri.domain.evaluate(&polynomial); - let mut proof_stream = ProofStream::new(); - fri.prove(&codeword, &mut proof_stream); + let mut proof_stream = ProofStream::new(); + fri.prove(&codeword, &mut proof_stream); - let mut proof_stream = prepare_proof_stream_for_verification(proof_stream); - let verdict = fri.verify(&mut proof_stream, &mut None); - prop_assert!(verdict.is_ok()); - } + let mut proof_stream = prepare_proof_stream_for_verification(proof_stream); + let verdict = fri.verify(&mut proof_stream, &mut None); + prop_assert!(verdict.is_ok()); } - proptest! { - #![proptest_config(ProptestConfig::with_cases(50))] - #[test] - fn prove_and_verify_low_degree_polynomial( - (fri, polynomial) in arbitrary_matching_fri_and_polynomial_pair(), - ) { - debug_assert!(polynomial.degree() <= fri.first_round_max_degree() as isize); - let codeword = fri.domain.evaluate(&polynomial); - let mut proof_stream = ProofStream::new(); - fri.prove(&codeword, &mut proof_stream); - - let mut proof_stream = prepare_proof_stream_for_verification(proof_stream); - let verdict = fri.verify(&mut proof_stream, &mut None); - prop_assert!(verdict.is_ok()); - } - } + #[proptest(cases = 50)] + fn prove_and_verify_low_degree_polynomial( + #[strategy(arbitrary_fri())] fri: Fri, + #[strategy(-1_i64..=#fri.first_round_max_degree() as i64)] _degree: i64, + #[strategy(arbitrary_polynomial_of_degree(#_degree))] polynomial: Polynomial, + ) { + debug_assert!(polynomial.degree() <= fri.first_round_max_degree() as isize); + let codeword = fri.domain.evaluate(&polynomial); + let mut proof_stream = ProofStream::new(); + fri.prove(&codeword, &mut proof_stream); + + let mut proof_stream = prepare_proof_stream_for_verification(proof_stream); + let verdict = fri.verify(&mut proof_stream, &mut None); + prop_assert!(verdict.is_ok()); + } + + #[proptest(cases = 50)] + fn prove_and_fail_to_verify_high_degree_polynomial( + #[strategy(arbitrary_fri())] fri: Fri, + #[strategy(Just((1 + #fri.first_round_max_degree()) as i64))] _too_high_degree: i64, + #[strategy(#_too_high_degree..2 * #_too_high_degree)] _degree: i64, + #[strategy(arbitrary_polynomial_of_degree(#_degree))] polynomial: Polynomial, + ) { + debug_assert!(polynomial.degree() > fri.first_round_max_degree() as isize); + let codeword = fri.domain.evaluate(&polynomial); + let mut proof_stream = ProofStream::new(); + fri.prove(&codeword, &mut proof_stream); - proptest! { - #![proptest_config(ProptestConfig::with_cases(50))] - #[test] - fn prove_and_fail_to_verify_high_degree_polynomial( - (fri, polynomial) in arbitrary_non_matching_fri_and_polynomial_pair(), - ) { - debug_assert!(polynomial.degree() > fri.first_round_max_degree() as isize); - let codeword = fri.domain.evaluate(&polynomial); - let mut proof_stream = ProofStream::new(); - fri.prove(&codeword, &mut proof_stream); - - let mut proof_stream = prepare_proof_stream_for_verification(proof_stream); - let verdict = fri.verify(&mut proof_stream, &mut None); - prop_assert!(verdict.is_err()); - } + let mut proof_stream = prepare_proof_stream_for_verification(proof_stream); + let verdict = fri.verify(&mut proof_stream, &mut None); + prop_assert!(verdict.is_err()); } #[test] @@ -857,95 +798,77 @@ mod tests { Fri::::new(domain, expansion_factor, num_colinearity_checks); } - proptest! { - #[test] - #[should_panic] - fn expansion_factor_not_a_power_of_two_is_rejected( - expansion_factor in 2..=usize::MAX, - offset in arbitrary_bfield_element(), - ) { - if expansion_factor.is_power_of_two() { - return Ok(()); - } - let domain = ArithmeticDomain::of_length(2 * expansion_factor).with_offset(offset); - let num_colinearity_checks = 1; - Fri::::new( - domain, - expansion_factor, - num_colinearity_checks, - ); + #[proptest] + #[should_panic] + fn expansion_factor_not_a_power_of_two_is_rejected( + #[strategy(2_usize..)] expansion_factor: usize, + #[strategy(arb())] offset: BFieldElement, + ) { + if expansion_factor.is_power_of_two() { + return Ok(()); } + let domain = ArithmeticDomain::of_length(2 * expansion_factor).with_offset(offset); + let num_colinearity_checks = 1; + Fri::::new(domain, expansion_factor, num_colinearity_checks); } - proptest! { - #[test] - #[should_panic] - fn domain_size_smaller_than_expansion_factor_is_rejected( - log_2_expansion_factor in 1..=8, - offset in arbitrary_bfield_element(), - ) { - let expansion_factor = (1 << log_2_expansion_factor) as usize; - let domain = ArithmeticDomain::of_length(expansion_factor - 1).with_offset(offset); - let num_colinearity_checks = 1; - Fri::::new( - domain, - expansion_factor, - num_colinearity_checks, - ); - } + #[proptest] + #[should_panic] + fn domain_size_smaller_than_expansion_factor_is_rejected( + #[strategy(1_usize..=8)] log_2_expansion_factor: usize, + #[strategy(arb())] offset: BFieldElement, + ) { + let expansion_factor = (1 << log_2_expansion_factor) as usize; + let domain = ArithmeticDomain::of_length(expansion_factor - 1).with_offset(offset); + let num_colinearity_checks = 1; + Fri::::new(domain, expansion_factor, num_colinearity_checks); } // todo: add test fuzzing proof_stream - proptest! { - #![proptest_config(ProptestConfig::with_cases(50))] - #[test] - fn serialization( - (fri, polynomial) in arbitrary_matching_fri_and_polynomial_pair(), - ) { - let codeword = fri.domain.evaluate(&polynomial); - let mut prover_proof_stream = ProofStream::new(); - fri.prove(&codeword, &mut prover_proof_stream); - - let proof = (&prover_proof_stream).into(); - let verifier_proof_stream = ProofStream::::try_from(&proof).unwrap(); - - let prover_items = prover_proof_stream.items.iter(); - let verifier_items = verifier_proof_stream.items.iter(); - for (prover_item, verifier_item) in prover_items.zip_eq(verifier_items) { - match (prover_item, verifier_item) { - (MerkleRoot(p), MerkleRoot(v)) => prop_assert_eq!(p, v), - (FriResponse(p), FriResponse(v)) => prop_assert_eq!(p, v), - (FriCodeword(p), FriCodeword(v)) => prop_assert_eq!(p, v), - _ => panic!( - "Unknown items.\nProver: {prover_item:?}\nVerifier: {verifier_item:?}" - ), - } + #[proptest(cases = 50)] + fn serialization( + #[strategy(arbitrary_fri())] fri: Fri, + #[strategy(-1_i64..=#fri.first_round_max_degree() as i64)] _degree: i64, + #[strategy(arbitrary_polynomial_of_degree(#_degree))] polynomial: Polynomial, + ) { + let codeword = fri.domain.evaluate(&polynomial); + let mut prover_proof_stream = ProofStream::new(); + fri.prove(&codeword, &mut prover_proof_stream); + + let proof = (&prover_proof_stream).into(); + let verifier_proof_stream = ProofStream::::try_from(&proof).unwrap(); + + let prover_items = prover_proof_stream.items.iter(); + let verifier_items = verifier_proof_stream.items.iter(); + for (prover_item, verifier_item) in prover_items.zip_eq(verifier_items) { + match (prover_item, verifier_item) { + (MerkleRoot(p), MerkleRoot(v)) => prop_assert_eq!(p, v), + (FriResponse(p), FriResponse(v)) => prop_assert_eq!(p, v), + (FriCodeword(p), FriCodeword(v)) => prop_assert_eq!(p, v), + _ => panic!("Unknown items.\nProver: {prover_item:?}\nVerifier: {verifier_item:?}"), } } } - proptest! { - #![proptest_config(ProptestConfig::with_cases(50))] - #[test] - fn last_round_codeword_unequal_to_last_round_commitment_results_in_validation_failure( - fri in arbitrary_fri(), - polynomial in arbitrary_polynomial(), - rng_seed: u64 - ) { - let codeword = fri.domain.evaluate(&polynomial); - let mut proof_stream = ProofStream::new(); - fri.prove(&codeword, &mut proof_stream); - - let proof_stream = prepare_proof_stream_for_verification(proof_stream); - let mut proof_stream = - modify_last_round_codeword_in_proof_stream_using_seed(proof_stream, rng_seed); - - let verdict = fri.verify(&mut proof_stream, &mut None); - let err = verdict.unwrap_err(); - let err = err.downcast::().unwrap(); - prop_assert_eq!(FriValidationError::BadMerkleRootForLastCodeword, err); - } + #[proptest(cases = 50)] + fn last_round_codeword_unequal_to_last_round_commitment_results_in_validation_failure( + #[strategy(arbitrary_fri())] fri: Fri, + #[strategy(arbitrary_polynomial())] polynomial: Polynomial, + rng_seed: u64, + ) { + let codeword = fri.domain.evaluate(&polynomial); + let mut proof_stream = ProofStream::new(); + fri.prove(&codeword, &mut proof_stream); + + let proof_stream = prepare_proof_stream_for_verification(proof_stream); + let mut proof_stream = + modify_last_round_codeword_in_proof_stream_using_seed(proof_stream, rng_seed); + + let verdict = fri.verify(&mut proof_stream, &mut None); + let err = verdict.unwrap_err(); + let err = err.downcast::().unwrap(); + prop_assert_eq!(FriValidationError::BadMerkleRootForLastCodeword, err); } #[must_use] @@ -980,27 +903,24 @@ mod tests { } } - proptest! { - #![proptest_config(ProptestConfig::with_cases(50))] - #[test] - fn revealing_wrong_number_of_leaves_results_in_validation_failure( - fri in arbitrary_fri(), - polynomial in arbitrary_polynomial(), - rng_seed: u64 - ) { - let codeword = fri.domain.evaluate(&polynomial); - let mut proof_stream = ProofStream::new(); - fri.prove(&codeword, &mut proof_stream); - - let proof_stream = prepare_proof_stream_for_verification(proof_stream); - let mut proof_stream = - change_size_of_some_fri_response_in_proof_stream_using_seed(proof_stream, rng_seed); - - let verdict = fri.verify(&mut proof_stream, &mut None); - let err = verdict.unwrap_err(); - let err = err.downcast::().unwrap(); - prop_assert_eq!(FriValidationError::IncorrectNumberOfRevealedLeaves, err); - } + #[proptest(cases = 50)] + fn revealing_wrong_number_of_leaves_results_in_validation_failure( + #[strategy(arbitrary_fri())] fri: Fri, + #[strategy(arbitrary_polynomial())] polynomial: Polynomial, + rng_seed: u64, + ) { + let codeword = fri.domain.evaluate(&polynomial); + let mut proof_stream = ProofStream::new(); + fri.prove(&codeword, &mut proof_stream); + + let proof_stream = prepare_proof_stream_for_verification(proof_stream); + let mut proof_stream = + change_size_of_some_fri_response_in_proof_stream_using_seed(proof_stream, rng_seed); + + let verdict = fri.verify(&mut proof_stream, &mut None); + let err = verdict.unwrap_err(); + let err = err.downcast::().unwrap(); + prop_assert_eq!(FriValidationError::IncorrectNumberOfRevealedLeaves, err); } #[must_use] @@ -1030,33 +950,30 @@ mod tests { } } - proptest! { - #![proptest_config(ProptestConfig::with_cases(50))] - #[test] - fn incorrect_authentication_structure_results_in_validation_failure( - fri in arbitrary_fri(), - polynomial in arbitrary_polynomial(), - rng_seed: u64 - ) { - let all_authentication_structures_are_trivial = - fri.num_colinearity_checks >= fri.domain.length; - if all_authentication_structures_are_trivial { - return Ok(()); - } + #[proptest(cases = 50)] + fn incorrect_authentication_structure_results_in_validation_failure( + #[strategy(arbitrary_fri())] fri: Fri, + #[strategy(arbitrary_polynomial())] polynomial: Polynomial, + rng_seed: u64, + ) { + let all_authentication_structures_are_trivial = + fri.num_colinearity_checks >= fri.domain.length; + if all_authentication_structures_are_trivial { + return Ok(()); + } - let codeword = fri.domain.evaluate(&polynomial); - let mut proof_stream = ProofStream::new(); - fri.prove(&codeword, &mut proof_stream); + let codeword = fri.domain.evaluate(&polynomial); + let mut proof_stream = ProofStream::new(); + fri.prove(&codeword, &mut proof_stream); - let proof_stream = prepare_proof_stream_for_verification(proof_stream); - let mut proof_stream = - modify_some_auth_structure_in_proof_stream_using_seed(proof_stream, rng_seed); + let proof_stream = prepare_proof_stream_for_verification(proof_stream); + let mut proof_stream = + modify_some_auth_structure_in_proof_stream_using_seed(proof_stream, rng_seed); - let verdict = fri.verify(&mut proof_stream, &mut None); - let err = verdict.unwrap_err(); - let err = err.downcast::().unwrap(); - prop_assert_eq!(FriValidationError::BadMerkleAuthenticationPath, err); - } + let verdict = fri.verify(&mut proof_stream, &mut None); + let err = verdict.unwrap_err(); + let err = err.downcast::().unwrap(); + prop_assert_eq!(FriValidationError::BadMerkleAuthenticationPath, err); } #[must_use] @@ -1089,13 +1006,11 @@ mod tests { } } - proptest! { - #[test] - fn verifying_arbitrary_proof_does_not_panic( - fri in arbitrary_fri(), - mut proof_stream in arb::>(), - ) { - let _ = fri.verify(&mut proof_stream, &mut None); - } + #[proptest] + fn verifying_arbitrary_proof_does_not_panic( + #[strategy(arbitrary_fri())] fri: Fri, + #[strategy(arb())] mut proof_stream: ProofStream, + ) { + let _ = fri.verify(&mut proof_stream, &mut None); } } diff --git a/triton-vm/src/op_stack.rs b/triton-vm/src/op_stack.rs index 527b50b8b..87481c413 100644 --- a/triton-vm/src/op_stack.rs +++ b/triton-vm/src/op_stack.rs @@ -243,7 +243,6 @@ impl UnderflowIO { Deserialize, EnumCount, EnumIter, - Arbitrary, )] pub enum OpStackElement { #[default] @@ -405,6 +404,7 @@ mod tests { use proptest::prelude::*; use proptest_arbitrary_interop::arb; use strum::IntoEnumIterator; + use test_strategy::proptest; use twenty_first::shared_math::b_field_element::BFieldElement; use super::*; @@ -540,28 +540,26 @@ mod tests { assert_eq!(expected_sequence, sequence); } - proptest! { - #[test] - fn underflow_io_either_shrinks_stack_or_grows_stack(underflow_io in arb::()) { - let shrinks_stack = underflow_io.shrinks_stack(); - let grows_stack = underflow_io.grows_stack(); - assert!(shrinks_stack ^ grows_stack); - } - } - - proptest! { - #[test] - fn non_empty_uniform_underflow_io_sequence_is_either_reading_or_writing( - sequence in vec(arb::(), 1..OpStackElement::COUNT), - ) { - let is_reading_sequence = UnderflowIO::is_reading_sequence(&sequence); - let is_writing_sequence = UnderflowIO::is_writing_sequence(&sequence); - if UnderflowIO::is_uniform_sequence(&sequence) { - prop_assert!(is_reading_sequence ^ is_writing_sequence); - } else { - prop_assert!(!is_reading_sequence); - prop_assert!(!is_writing_sequence); - } + #[proptest] + fn underflow_io_either_shrinks_stack_or_grows_stack( + #[strategy(arb())] underflow_io: UnderflowIO, + ) { + let shrinks_stack = underflow_io.shrinks_stack(); + let grows_stack = underflow_io.grows_stack(); + assert!(shrinks_stack ^ grows_stack); + } + + #[proptest] + fn non_empty_uniform_underflow_io_sequence_is_either_reading_or_writing( + #[strategy(vec(arb(), 1..OpStackElement::COUNT))] sequence: Vec, + ) { + let is_reading_sequence = UnderflowIO::is_reading_sequence(&sequence); + let is_writing_sequence = UnderflowIO::is_writing_sequence(&sequence); + if UnderflowIO::is_uniform_sequence(&sequence) { + prop_assert!(is_reading_sequence ^ is_writing_sequence); + } else { + prop_assert!(!is_reading_sequence); + prop_assert!(!is_writing_sequence); } } } diff --git a/triton-vm/src/proof.rs b/triton-vm/src/proof.rs index 90778dbd1..93dfcad6b 100644 --- a/triton-vm/src/proof.rs +++ b/triton-vm/src/proof.rs @@ -69,14 +69,14 @@ impl Claim { #[cfg(test)] mod tests { use proptest::collection::vec; - use proptest::prelude::*; + use proptest_arbitrary_interop::arb; use rand::random; + use test_strategy::proptest; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::bfield_codec::BFieldCodec; use twenty_first::shared_math::other::random_elements; use crate::proof_item::ProofItem; - use crate::shared_tests::arbitrary_bfield_element; use crate::stark::StarkHasher; use super::*; @@ -128,12 +128,10 @@ mod tests { assert!(maybe_padded_height.is_err()); } - proptest! { - #[test] - fn decoding_arbitrary_proof_data_does_not_panic( - proof_data in vec(arbitrary_bfield_element(), 0..1000), - ) { - let _ = Proof::decode(&proof_data); - } + #[proptest] + fn decoding_arbitrary_proof_data_does_not_panic( + #[strategy(vec(arb(), 0..1_000))] proof_data: Vec, + ) { + let _ = Proof::decode(&proof_data); } } diff --git a/triton-vm/src/shared_tests.rs b/triton-vm/src/shared_tests.rs index e60ddcfe9..71f24cbc1 100644 --- a/triton-vm/src/shared_tests.rs +++ b/triton-vm/src/shared_tests.rs @@ -8,12 +8,14 @@ use std::path::Path; use anyhow::anyhow; use anyhow::Result; +use num_traits::Zero; use proptest::collection::vec; use proptest::prelude::*; +use proptest_arbitrary_interop::arb; +use test_strategy::Arbitrary; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::polynomial::Polynomial; use twenty_first::shared_math::x_field_element::XFieldElement; -use twenty_first::shared_math::x_field_element::EXTENSION_DEGREE; use crate::aet::AlgebraicExecutionTrace; use crate::profiler::prof_start; @@ -29,38 +31,9 @@ use crate::table::master_table::MasterBaseTable; use crate::NonDeterminism; use crate::PublicInput; -prop_compose! { - pub(crate) fn arbitrary_bfield_element()(value in 0..BFieldElement::P) -> BFieldElement { - BFieldElement::new(value) - } -} - -prop_compose! { - pub(crate) fn arbitrary_non_zero_bfield_element()( - value in 1..BFieldElement::P - ) -> BFieldElement { - BFieldElement::new(value) - } -} - -prop_compose! { - pub(crate) fn arbitrary_xfield_element()( - coefficients in vec(arbitrary_bfield_element(), EXTENSION_DEGREE) - ) -> XFieldElement { - XFieldElement::new(coefficients.try_into().unwrap()) - } -} - -prop_compose! { - pub(crate) fn arbitrary_non_zero_xfield_element()( - coefficient_0 in arbitrary_non_zero_bfield_element(), - coefficient_1 in arbitrary_bfield_element(), - coefficient_2 in arbitrary_bfield_element(), - ) -> XFieldElement { - let coefficients = [coefficient_0, coefficient_1, coefficient_2]; - XFieldElement::new(coefficients) - } -} +#[derive(Debug, Arbitrary, Clone, Copy)] +#[filter(!#self.0.is_zero())] +struct NonZeroXFieldElement(#[strategy(arb())] XFieldElement); prop_compose! { pub(crate) fn arbitrary_polynomial()( @@ -74,9 +47,10 @@ prop_compose! { prop_compose! { pub(crate) fn arbitrary_polynomial_of_degree(degree: i64)( - leading_coefficient in arbitrary_non_zero_xfield_element(), - other_coefficients in vec(arbitrary_xfield_element(), degree.try_into().unwrap_or(0)), + leading_coefficient: NonZeroXFieldElement, + other_coefficients in vec(arb(), degree.try_into().unwrap_or(0)), ) -> Polynomial { + let leading_coefficient = leading_coefficient.0; let coefficients = match degree >= 0 { true => [other_coefficients, vec![leading_coefficient]].concat(), false => vec![], diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index ee6b72f7f..7cee9f149 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -1109,13 +1109,13 @@ impl Stark { pub(crate) mod tests { use itertools::izip; use num_traits::Zero; - use proptest::prelude::*; use proptest_arbitrary_interop::arb; use rand::prelude::ThreadRng; use rand::thread_rng; use rand::Rng; use rand_core::RngCore; use strum::EnumCount; + use test_strategy::proptest; use twenty_first::shared_math::other::random_elements; use crate::example_programs::*; @@ -2004,15 +2004,13 @@ pub(crate) mod tests { println!("{report}"); } - proptest! { - #[test] - fn verifying_arbitrary_proof_does_not_panic( - parameters in arb::(), - claim in arb::(), - proof in arb::(), - ) { - let _ = Stark::verify(parameters, &claim, &proof, &mut None); - } + #[proptest] + fn verifying_arbitrary_proof_does_not_panic( + #[strategy(arb())] parameters: StarkParameters, + #[strategy(arb())] claim: Claim, + #[strategy(arb())] proof: Proof, + ) { + let _ = Stark::verify(parameters, &claim, &proof, &mut None); } #[test] diff --git a/triton-vm/src/table/op_stack_table.rs b/triton-vm/src/table/op_stack_table.rs index 9d3f1b5f3..feab5663e 100644 --- a/triton-vm/src/table/op_stack_table.rs +++ b/triton-vm/src/table/op_stack_table.rs @@ -1,6 +1,6 @@ +use arbitrary::Arbitrary; use std::cmp::Ordering; -use arbitrary::Arbitrary; use itertools::Itertools; use ndarray::parallel::prelude::*; use ndarray::s; @@ -406,6 +406,7 @@ pub(crate) mod tests { use proptest::collection::vec; use proptest::prelude::*; use proptest_arbitrary_interop::arb; + use test_strategy::proptest; use crate::op_stack::OpStackElement; @@ -499,122 +500,112 @@ pub(crate) mod tests { true } - proptest! { - #[test] - fn op_stack_table_entry_either_shrinks_stack_or_grows_stack( - entry in arb::() - ) { - let shrinks_stack = entry.shrinks_stack(); - let grows_stack = entry.grows_stack(); - assert!(shrinks_stack ^ grows_stack); - } + #[proptest] + fn op_stack_table_entry_either_shrinks_stack_or_grows_stack( + #[strategy(arb())] entry: OpStackTableEntry, + ) { + let shrinks_stack = entry.shrinks_stack(); + let grows_stack = entry.grows_stack(); + assert!(shrinks_stack ^ grows_stack); } - proptest! { - #[test] - fn op_stack_pointer_in_sequence_of_op_stack_table_entries( - clk: u32, - stack_pointer in OpStackElement::COUNT..1024, - base_field_elements in vec(arb::(), 0..OpStackElement::COUNT), - sequence_of_writes: bool, - ) { - let sequence_length = u64::try_from(base_field_elements.len()).unwrap(); - let stack_pointer = u64::try_from(stack_pointer).unwrap(); - - let underflow_io_operation = match sequence_of_writes { - true => UnderflowIO::Write, - false => UnderflowIO::Read, - }; - let underflow_io = base_field_elements - .into_iter() - .map(underflow_io_operation) - .collect(); - - let op_stack_pointer = stack_pointer.into(); - let entries = - OpStackTableEntry::from_underflow_io_sequence(clk, op_stack_pointer, underflow_io); - let op_stack_pointers = entries - .iter() - .map(|entry| entry.op_stack_pointer.value()) - .sorted() - .collect_vec(); - - let expected_stack_pointer_range = match sequence_of_writes { - true => stack_pointer - sequence_length..stack_pointer, - false => stack_pointer..stack_pointer + sequence_length, - }; - let expected_op_stack_pointers = expected_stack_pointer_range.collect_vec(); - prop_assert_eq!(expected_op_stack_pointers, op_stack_pointers); - } + #[proptest] + fn op_stack_pointer_in_sequence_of_op_stack_table_entries( + clk: u32, + #[strategy(OpStackElement::COUNT..1024)] stack_pointer: usize, + #[strategy(vec(arb(), ..OpStackElement::COUNT))] base_field_elements: Vec, + sequence_of_writes: bool, + ) { + let sequence_length = u64::try_from(base_field_elements.len()).unwrap(); + let stack_pointer = u64::try_from(stack_pointer).unwrap(); + + let underflow_io_operation = match sequence_of_writes { + true => UnderflowIO::Write, + false => UnderflowIO::Read, + }; + let underflow_io = base_field_elements + .into_iter() + .map(underflow_io_operation) + .collect(); + + let op_stack_pointer = stack_pointer.into(); + let entries = + OpStackTableEntry::from_underflow_io_sequence(clk, op_stack_pointer, underflow_io); + let op_stack_pointers = entries + .iter() + .map(|entry| entry.op_stack_pointer.value()) + .sorted() + .collect_vec(); + + let expected_stack_pointer_range = match sequence_of_writes { + true => stack_pointer - sequence_length..stack_pointer, + false => stack_pointer..stack_pointer + sequence_length, + }; + let expected_op_stack_pointers = expected_stack_pointer_range.collect_vec(); + prop_assert_eq!(expected_op_stack_pointers, op_stack_pointers); } - proptest! { - #[test] - fn clk_stays_same_in_sequence_of_op_stack_table_entries( - clk: u32, - stack_pointer in OpStackElement::COUNT..1024, - base_field_elements in vec(arb::(), 0..OpStackElement::COUNT), - sequence_of_writes: bool, - ) { - let underflow_io_operation = match sequence_of_writes { - true => UnderflowIO::Write, - false => UnderflowIO::Read, - }; - let underflow_io = base_field_elements - .into_iter() - .map(underflow_io_operation) - .collect(); - - let op_stack_pointer = u64::try_from(stack_pointer).unwrap().into(); - let entries = - OpStackTableEntry::from_underflow_io_sequence(clk, op_stack_pointer, underflow_io); - let clk_values = entries.iter().map(|entry| entry.clk).collect_vec(); - let all_clk_values_are_clk = clk_values.iter().all(|&c| c == clk); - prop_assert!(all_clk_values_are_clk); - } + #[proptest] + fn clk_stays_same_in_sequence_of_op_stack_table_entries( + clk: u32, + #[strategy(OpStackElement::COUNT..1024)] stack_pointer: usize, + #[strategy(vec(arb(), ..OpStackElement::COUNT))] base_field_elements: Vec, + sequence_of_writes: bool, + ) { + let underflow_io_operation = match sequence_of_writes { + true => UnderflowIO::Write, + false => UnderflowIO::Read, + }; + let underflow_io = base_field_elements + .into_iter() + .map(underflow_io_operation) + .collect(); + + let op_stack_pointer = u64::try_from(stack_pointer).unwrap().into(); + let entries = + OpStackTableEntry::from_underflow_io_sequence(clk, op_stack_pointer, underflow_io); + let clk_values = entries.iter().map(|entry| entry.clk).collect_vec(); + let all_clk_values_are_clk = clk_values.iter().all(|&c| c == clk); + prop_assert!(all_clk_values_are_clk); } - proptest! { - #[test] - fn compare_rows_with_unequal_stack_pointer_and_equal_clk( - stack_pointer_0: u64, - stack_pointer_1: u64, - clk: u64, - ) { - let mut row_0 = Array1::zeros(BASE_WIDTH); - row_0[StackPointer.base_table_index()] = stack_pointer_0.into(); - row_0[CLK.base_table_index()] = clk.into(); - - let mut row_1 = Array1::zeros(BASE_WIDTH); - row_1[StackPointer.base_table_index()] = stack_pointer_1.into(); - row_1[CLK.base_table_index()] = clk.into(); - - let stack_pointer_comparison = stack_pointer_0.cmp(&stack_pointer_1); - let row_comparison = OpStackTable::compare_rows(row_0.view(), row_1.view()); - - assert_eq!(stack_pointer_comparison, row_comparison); - } + #[proptest] + fn compare_rows_with_unequal_stack_pointer_and_equal_clk( + stack_pointer_0: u64, + stack_pointer_1: u64, + clk: u64, + ) { + let mut row_0 = Array1::zeros(BASE_WIDTH); + row_0[StackPointer.base_table_index()] = stack_pointer_0.into(); + row_0[CLK.base_table_index()] = clk.into(); + + let mut row_1 = Array1::zeros(BASE_WIDTH); + row_1[StackPointer.base_table_index()] = stack_pointer_1.into(); + row_1[CLK.base_table_index()] = clk.into(); + + let stack_pointer_comparison = stack_pointer_0.cmp(&stack_pointer_1); + let row_comparison = OpStackTable::compare_rows(row_0.view(), row_1.view()); + + assert_eq!(stack_pointer_comparison, row_comparison); } - proptest! { - #[test] - fn compare_rows_with_equal_stack_pointer_and_unequal_clk( - stack_pointer: u64, - clk_0: u64, - clk_1: u64, - ) { - let mut row_0 = Array1::zeros(BASE_WIDTH); - row_0[StackPointer.base_table_index()] = stack_pointer.into(); - row_0[CLK.base_table_index()] = clk_0.into(); - - let mut row_1 = Array1::zeros(BASE_WIDTH); - row_1[StackPointer.base_table_index()] = stack_pointer.into(); - row_1[CLK.base_table_index()] = clk_1.into(); - - let clk_comparison = clk_0.cmp(&clk_1); - let row_comparison = OpStackTable::compare_rows(row_0.view(), row_1.view()); - - assert_eq!(clk_comparison, row_comparison); - } + #[proptest] + fn compare_rows_with_equal_stack_pointer_and_unequal_clk( + stack_pointer: u64, + clk_0: u64, + clk_1: u64, + ) { + let mut row_0 = Array1::zeros(BASE_WIDTH); + row_0[StackPointer.base_table_index()] = stack_pointer.into(); + row_0[CLK.base_table_index()] = clk_0.into(); + + let mut row_1 = Array1::zeros(BASE_WIDTH); + row_1[StackPointer.base_table_index()] = stack_pointer.into(); + row_1[CLK.base_table_index()] = clk_1.into(); + + let clk_comparison = clk_0.cmp(&clk_1); + let row_comparison = OpStackTable::compare_rows(row_0.view(), row_1.view()); + + assert_eq!(clk_comparison, row_comparison); } } diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index 6d0d3202a..234b8c379 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -3167,11 +3167,11 @@ impl<'a> Display for ProcessorTraceRow<'a> { pub(crate) mod tests { use ndarray::Array2; use proptest::collection::vec; - use proptest::prelude::*; use proptest_arbitrary_interop::arb; use rand::thread_rng; use rand::Rng; use strum::IntoEnumIterator; + use test_strategy::proptest; use crate::error::InstructionError; use crate::error::InstructionError::DivisionByZero; @@ -3267,25 +3267,17 @@ pub(crate) mod tests { transition_constraints_for_instruction_pop_n(0); } - proptest! { - #![proptest_config(ProptestConfig::with_cases(20))] - #[test] - fn transition_constraints_for_instruction_pop_n_in_range( - n in 1..=5_usize, - ) { - transition_constraints_for_instruction_pop_n(n); - } + #[proptest(cases = 20)] + fn transition_constraints_for_instruction_pop_n_in_range(#[strategy(1..=5_usize)] n: usize) { + transition_constraints_for_instruction_pop_n(n); } - proptest! { - #![proptest_config(ProptestConfig::with_cases(20))] - #[test] - #[should_panic(expected = "at least 1, at most 5")] - fn transition_constraints_for_instruction_pop_n_too_large( - n in 6..OpStackElement::COUNT, - ) { - transition_constraints_for_instruction_pop_n(n); - } + #[proptest(cases = 20)] + #[should_panic(expected = "at least 1, at most 5")] + fn transition_constraints_for_instruction_pop_n_too_large( + #[strategy(6..OpStackElement::COUNT)] n: usize, + ) { + transition_constraints_for_instruction_pop_n(n); } fn transition_constraints_for_instruction_pop_n(n: usize) { @@ -4013,35 +4005,31 @@ pub(crate) mod tests { } } - proptest! { - #[test] - #[should_panic(expected="[0, 15]")] - fn cannot_get_op_stack_column_for_out_of_range_index( - index in OpStackElement::COUNT.., - ) { - let _ = ProcessorTable::op_stack_column_by_index(index); - } + #[proptest] + #[should_panic(expected = "[0, 15]")] + fn cannot_get_op_stack_column_for_out_of_range_index( + #[strategy(OpStackElement::COUNT..)] index: usize, + ) { + let _ = ProcessorTable::op_stack_column_by_index(index); } - proptest! { - #[test] - fn constructing_factor_for_op_stack_table_running_product_never_panics( - has_previous_row: bool, - previous_row in vec(arb::(), BASE_WIDTH), - current_row in vec(arb::(), BASE_WIDTH), - challenges in arb::(), - ) { - let previous_row = Array1::from(previous_row); - let current_row = Array1::from(current_row); - let maybe_previous_row = match has_previous_row { - true => Some(previous_row.view()), - false => None, - }; - let _ = ProcessorTable::factor_for_op_stack_table_running_product( - maybe_previous_row, - current_row.view(), - &challenges - ); - } + #[proptest] + fn constructing_factor_for_op_stack_table_running_product_never_panics( + has_previous_row: bool, + #[strategy(vec(arb(), BASE_WIDTH))] previous_row: Vec, + #[strategy(vec(arb(), BASE_WIDTH))] current_row: Vec, + #[strategy(arb())] challenges: Challenges, + ) { + let previous_row = Array1::from(previous_row); + let current_row = Array1::from(current_row); + let maybe_previous_row = match has_previous_row { + true => Some(previous_row.view()), + false => None, + }; + let _ = ProcessorTable::factor_for_op_stack_table_running_product( + maybe_previous_row, + current_row.view(), + &challenges, + ); } }