From 0d6c018c56e48240a5c4cf807189dfa322612084 Mon Sep 17 00:00:00 2001 From: danda Date: Wed, 18 Jun 2025 18:04:33 -0700 Subject: [PATCH 1/4] feat: Enable builds and tests on wasm32 Add the necessary `Cargo.toml` magic for `cargo test` to build `#[test]` and `#[proptest]` tests correctly. However the wasm test harness ignores `#[test]` and `#[proptest]`, so tests are not actually run on wasm targets when running `cargo test`. Instead, use the `#[wasm_bindgen_test]` annotation to enable tests on wasm targets. Also enables compilation on 32-bit architectures by correcting the NTT's `NUM_DOMAINS` to 29 on 32-bit systems. It remains unchanged at 32 on 64 bit systems. --- twenty-first/.cargo/config.toml | 9 +- twenty-first/Cargo.toml | 19 +- twenty-first/src/lib.rs | 64 +++- twenty-first/src/math/b_field_element.rs | 137 ++++---- twenty-first/src/math/bfield_codec.rs | 31 +- twenty-first/src/math/lattice.rs | 30 +- twenty-first/src/math/ntt.rs | 49 ++- twenty-first/src/math/polynomial.rs | 311 +++++++++--------- twenty-first/src/math/x_field_element.rs | 69 ++-- twenty-first/src/math/zerofier_tree.rs | 16 +- twenty-first/src/tip5/digest.rs | 57 ++-- twenty-first/src/tip5/inverse.rs | 17 +- twenty-first/src/tip5/mod.rs | 51 +-- twenty-first/src/tip5/naive.rs | 4 +- twenty-first/src/util_types/merkle_tree.rs | 73 ++-- .../src/util_types/mmr/archival_mmr.rs | 35 +- .../src/util_types/mmr/mmr_accumulator.rs | 33 +- .../util_types/mmr/mmr_membership_proof.rs | 47 +-- .../src/util_types/mmr/mmr_successor_proof.rs | 31 +- .../src/util_types/mmr/shared_advanced.rs | 37 ++- .../src/util_types/mmr/shared_basic.rs | 17 +- twenty-first/src/util_types/sponge.rs | 7 +- twenty-first/tests/bfield_codec_derive.rs | 4 + 23 files changed, 631 insertions(+), 517 deletions(-) diff --git a/twenty-first/.cargo/config.toml b/twenty-first/.cargo/config.toml index 0e465b2de..42bc31309 100644 --- a/twenty-first/.cargo/config.toml +++ b/twenty-first/.cargo/config.toml @@ -1,2 +1,9 @@ [target.wasm32-unknown-unknown] -rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""] +# debuginfo and symbols are stripped to decrease size of generated debug binary. +# Otherwise, the binary would be so huge that the wasm test harness barfs on it. +rustflags = [ + "--cfg", "getrandom_backend=\"wasm_js\"", + "-C", "debuginfo=0", + "-C", "strip=symbols", +] +runner = "wasm-bindgen-test-runner" diff --git a/twenty-first/Cargo.toml b/twenty-first/Cargo.toml index 12d0891ee..4308aa84e 100644 --- a/twenty-first/Cargo.toml +++ b/twenty-first/Cargo.toml @@ -15,6 +15,9 @@ readme.workspace = true keywords = ["polynomial", "merkle-tree", "post-quantum", "algebra", "tip5"] categories = ["cryptography", "mathematics"] +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] arbitrary = { version = "1", features = ["derive"] } bfieldcodec_derive = "0.7" @@ -34,27 +37,29 @@ sha3 = "^0.10.8" thiserror = "2.0" zeroize = { version = "1.8.1", features = ["derive"] } -# deps for wasm32 target arch [target.'cfg(target_arch = "wasm32")'.dependencies] - -# convince getrandom to build for wasm target. -# note that there is also a flag in .cargo/config.toml -getrandom = { version = "0.3", features = ["wasm_js"] } +getrandom = { version = "0.3", features = ["wasm_js"] } # note that there is also a flag in .cargo/config.toml wasm-bindgen = "=0.2.104" [dev-dependencies] bincode = "1.3.3" blake3 = "1.5.5" +macro_rules_attr = "0.1.3" test-strategy = "=0.4.3" trybuild = "1.0" +proptest = { version = "1.7.0", default-features = false, features = ["std"] } +proptest-arbitrary-interop = { git = "https://github.com/dan-da/proptest-arbitrary-interop.git", version = "0.1", rev = "d9fcf5b" } [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = { package = "codspeed-criterion-compat", version = "4.0", features = ["html_reports"] } -proptest = { version = "1.7.0", default-features = false, features = ["std"] } -proptest-arbitrary-interop = "0.1" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] criterion = { version = "0.7.0", default-features = false } +wasm-bindgen-test = "0.3.42" + +# Workaround for Rust 1.87.0 (see also: ) +[package.metadata.wasm-pack.profile.release] +wasm-opt = ["-O4", "--enable-bulk-memory"] [lints] workspace = true diff --git a/twenty-first/src/lib.rs b/twenty-first/src/lib.rs index 1450dcb80..2e2593448 100644 --- a/twenty-first/src/lib.rs +++ b/twenty-first/src/lib.rs @@ -40,6 +40,64 @@ pub(crate) mod tests { use super::*; + /// A crate-specific replacement of the `#[test]` attribute for tests that + /// should also be executed on `wasm` targets (which is almost all tests). + /// + /// If you specifically want to exclude a test from `wasm` targets, use the + /// usual `#[test]` attribute instead. + /// + /// # Usage + /// + /// ``` + /// #[macro_rules_attr::apply(test)] + /// fn foo() { + /// assert_eq!(4, 2 + 2); + /// } + /// ``` + macro_rules! test { + ($item:item) => { + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + $item + }; + } + pub(crate) use test; + + /// A crate-specific replacement of the `#[test_strategy::proptest]` + /// attribute for tests that should also be executed on `wasm` targets + /// (which is almost all tests). + /// + /// If you specifically want to exclude a test from `wasm` targets, use the + /// usual `#[test_strategy::proptest]` attribute instead. + /// + /// # Usage + /// + /// ``` + /// # use proptest::prop_assert_eq; + /// #[macro_rules_attr::apply(proptest)] + /// fn foo(#[strategy(0..=42)] x: i32) { + /// prop_assert_eq!(2 * x, x + x); + /// } + /// ``` + /// + /// If you want to configure the test, use the usual syntax defined by + /// [`test_strategy`]: + /// ``` + /// # use proptest::prop_assert_eq; + /// #[macro_rules_attr::apply(proptest(cases = 10, max_local_rejects = 5))] + /// fn foo(#[strategy(0..=42)] x: i32) { + /// prop_assert_eq!(2 * x, x + x); + /// } + /// ``` + macro_rules! proptest { + ($item:item $(($($config:tt)*))?) => { + #[test_strategy::proptest $(($($config)*))?] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + $item + }; + } + pub(crate) use proptest; + /// The compiler automatically adds any applicable auto trait (all of which are /// marker traits) to self-defined types. This implies that these trait bounds /// might vanish if the necessary pre-conditions are no longer met. That'd be a @@ -55,7 +113,7 @@ pub(crate) mod tests { /// Inspired by “Rust for Rustaceans” by Jon Gjengset. pub fn implements_usual_auto_traits() {} - #[test] + #[macro_rules_attr::apply(test)] fn types_in_prelude_implement_the_usual_auto_traits() { implements_usual_auto_traits::(); implements_usual_auto_traits::>(); @@ -68,7 +126,7 @@ pub(crate) mod tests { implements_usual_auto_traits::(); } - #[test] + #[macro_rules_attr::apply(test)] fn public_types_implement_the_usual_auto_traits() { implements_usual_auto_traits::(); implements_usual_auto_traits::>(); @@ -85,7 +143,7 @@ pub(crate) mod tests { >(); } - #[test] + #[macro_rules_attr::apply(test)] fn errors_implement_the_usual_auto_traits() { implements_usual_auto_traits::(); implements_usual_auto_traits::(); diff --git a/twenty-first/src/math/b_field_element.rs b/twenty-first/src/math/b_field_element.rs index 368ecab95..963256eac 100644 --- a/twenty-first/src/math/b_field_element.rs +++ b/twenty-first/src/math/b_field_element.rs @@ -822,11 +822,12 @@ mod tests { use proptest::prelude::*; use proptest_arbitrary_interop::arb; use rand::random; - use test_strategy::proptest; use crate::math::b_field_element::*; use crate::math::other::random_elements; use crate::math::polynomial::Polynomial; + use crate::tests::proptest; + use crate::tests::test; impl proptest::arbitrary::Arbitrary for BFieldElement { type Parameters = (); @@ -838,26 +839,26 @@ mod tests { type Strategy = BoxedStrategy; } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn get_size(bfe: BFieldElement) { prop_assert_eq!(8, bfe.get_size()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn serialization_and_deserialization_to_and_from_json_is_identity(bfe: BFieldElement) { let serialized = serde_json::to_string(&bfe).unwrap(); let deserialized: BFieldElement = serde_json::from_str(&serialized).unwrap(); prop_assert_eq!(bfe, deserialized); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn deserializing_u64_is_like_calling_new(#[strategy(0..=BFieldElement::MAX)] value: u64) { let bfe = BFieldElement::new(value); let deserialized: BFieldElement = serde_json::from_str(&value.to_string()).unwrap(); prop_assert_eq!(bfe, deserialized); } - #[test] + #[macro_rules_attr::apply(test)] fn parsing_interval_is_open_minus_p_to_p() { let p = i128::from(BFieldElement::P); let display_then_parse = |v: i128| BFieldElement::from_str(&v.to_string()); @@ -868,7 +869,7 @@ mod tests { assert!(display_then_parse(p).is_err()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn parsing_string_representing_canonical_negative_integer_gives_correct_bfield_element( #[strategy(0..=BFieldElement::MAX)] v: u64, ) { @@ -876,7 +877,7 @@ mod tests { prop_assert_eq!(BFieldElement::P - v, bfe.value()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn parsing_string_representing_canonical_positive_integer_gives_correct_bfield_element( #[strategy(0..=BFieldElement::MAX)] v: u64, ) { @@ -884,7 +885,7 @@ mod tests { prop_assert_eq!(v, bfe.value()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn parsing_string_representing_too_big_positive_integer_as_bfield_element_gives_error( #[strategy(i128::from(BFieldElement::P)..)] v: i128, ) { @@ -892,7 +893,7 @@ mod tests { prop_assert_eq!(ParseBFieldElementError::NotCanonical(v), err); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn parsing_string_representing_too_small_negative_integer_as_bfield_element_gives_error( #[strategy(..=i128::from(BFieldElement::P))] v: i128, ) { @@ -900,29 +901,29 @@ mod tests { prop_assert_eq!(ParseBFieldElementError::NotCanonical(v), err); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn zero_is_neutral_element_for_addition(bfe: BFieldElement) { let zero = BFieldElement::ZERO; prop_assert_eq!(bfe + zero, bfe); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn one_is_neutral_element_for_multiplication(bfe: BFieldElement) { let one = BFieldElement::ONE; prop_assert_eq!(bfe * one, bfe); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn addition_is_commutative(element_0: BFieldElement, element_1: BFieldElement) { prop_assert_eq!(element_0 + element_1, element_1 + element_0); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn multiplication_is_commutative(element_0: BFieldElement, element_1: BFieldElement) { prop_assert_eq!(element_0 * element_1, element_1 * element_0); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn addition_is_associative( element_0: BFieldElement, @@ -935,7 +936,7 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn multiplication_is_associative( element_0: BFieldElement, element_1: BFieldElement, @@ -947,7 +948,7 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn multiplication_distributes_over_addition( element_0: BFieldElement, element_1: BFieldElement, @@ -959,17 +960,17 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn multiplication_with_inverse_gives_identity(#[filter(!#bfe.is_zero())] bfe: BFieldElement) { prop_assert!((bfe.inverse() * bfe).is_one()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn division_by_self_gives_identity(#[filter(!#bfe.is_zero())] bfe: BFieldElement) { prop_assert!((bfe / bfe).is_one()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn values_larger_than_modulus_are_handled_correctly( #[strategy(BFieldElement::P..)] large_value: u64, ) { @@ -978,7 +979,7 @@ mod tests { prop_assert_eq!(expected_value, bfe.value()); } - #[test] + #[macro_rules_attr::apply(test)] fn display_test() { let seven = BFieldElement::new(7); assert_eq!("7", format!("{seven}")); @@ -1019,7 +1020,7 @@ mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] fn display_and_from_str_are_reciprocal_unit_test() { for bfe in bfe_array![ -1000, -500, -200, -100, -10, -1, 0, 1, 10, 100, 200, 500, 1000 @@ -1029,20 +1030,20 @@ mod tests { } } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn display_and_from_str_are_reciprocal_prop_test(bfe: BFieldElement) { let bfe_again = bfe.to_string().parse()?; prop_assert_eq!(bfe, bfe_again); } - #[test] + #[macro_rules_attr::apply(test)] fn zero_is_zero() { let zero = BFieldElement::zero(); assert!(zero.is_zero()); assert_eq!(zero, BFieldElement::ZERO); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn not_zero_is_nonzero(bfe: BFieldElement) { if bfe.value() == 0 { return Ok(()); @@ -1050,14 +1051,14 @@ mod tests { prop_assert!(!bfe.is_zero()); } - #[test] + #[macro_rules_attr::apply(test)] fn one_is_one() { let one = BFieldElement::one(); assert!(one.is_one()); assert_eq!(one, BFieldElement::ONE); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn not_one_is_not_one(bfe: BFieldElement) { if bfe.value() == 1 { return Ok(()); @@ -1065,14 +1066,14 @@ mod tests { prop_assert!(!bfe.is_one()); } - #[test] + #[macro_rules_attr::apply(test)] fn one_unequal_zero() { let one = BFieldElement::ONE; let zero = BFieldElement::ZERO; assert_ne!(one, zero); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn byte_array_of_small_field_elements_is_zero_at_high_indices(value: u8) { let bfe = BFieldElement::new(value as u64); let byte_array: [u8; 8] = bfe.into(); @@ -1083,25 +1084,25 @@ mod tests { }); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn byte_array_conversion(bfe: BFieldElement) { let array: [u8; 8] = bfe.into(); let bfe_recalculated: BFieldElement = array.try_into()?; prop_assert_eq!(bfe, bfe_recalculated); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn byte_array_outside_range_is_not_accepted(#[strategy(BFieldElement::P..)] value: u64) { let byte_array = value.to_le_bytes(); prop_assert!(BFieldElement::try_from(byte_array).is_err()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn value_is_preserved(#[strategy(0..BFieldElement::P)] value: u64) { prop_assert_eq!(value, BFieldElement::new(value).value()); } - #[test] + #[macro_rules_attr::apply(test)] fn supposed_generator_is_generator() { let generator = BFieldElement::generator(); let largest_meaningful_power = BFieldElement::P - 1; @@ -1112,12 +1113,12 @@ mod tests { assert_ne!(BFieldElement::ONE, generator_pow_p_half); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn lift_then_unlift_preserves_element(bfe: BFieldElement) { prop_assert_eq!(Some(bfe), bfe.lift().unlift()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn increment(mut bfe: BFieldElement) { let old_value = bfe.value(); bfe.increment(); @@ -1125,14 +1126,14 @@ mod tests { prop_assert_eq!(expected_value, bfe.value()); } - #[test] + #[macro_rules_attr::apply(test)] fn incrementing_max_value_wraps_around() { let mut bfe = BFieldElement::new(BFieldElement::MAX); bfe.increment(); assert_eq!(0, bfe.value()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn decrement(mut bfe: BFieldElement) { let old_value = bfe.value(); bfe.decrement(); @@ -1140,20 +1141,20 @@ mod tests { prop_assert_eq!(expected_value, bfe.value()); } - #[test] + #[macro_rules_attr::apply(test)] fn decrementing_min_value_wraps_around() { let mut bfe = BFieldElement::ZERO; bfe.decrement(); assert_eq!(BFieldElement::MAX, bfe.value()); } - #[test] + #[macro_rules_attr::apply(test)] fn empty_batch_inversion() { let empty_inv = BFieldElement::batch_inversion(vec![]); assert!(empty_inv.is_empty()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn batch_inversion(bfes: Vec) { let bfes_inv = BFieldElement::batch_inversion(bfes.clone()); prop_assert_eq!(bfes.len(), bfes_inv.len()); @@ -1162,7 +1163,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn power_accumulator_simple_test() { let input_a = [ BFieldElement::new(10), @@ -1183,7 +1184,7 @@ mod tests { assert_eq!(BFieldElement::new(8), powers[3]); } - #[test] + #[macro_rules_attr::apply(test)] fn mul_div_plus_minus_neg_property_based_test() { let elements: Vec = random_elements(300); let power_input_b: [BFieldElement; 6] = random(); @@ -1243,7 +1244,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn mul_div_pbt() { // Verify that the mul result is sane let rands: Vec = random_elements(100); @@ -1260,7 +1261,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn add_sub_wrap_around_test() { // Ensure that something that exceeds P but is smaller than $2^64$ // is still the correct field element. The property-based test is unlikely @@ -1273,7 +1274,7 @@ mod tests { assert_eq!(BFieldElement::new(BFieldElement::MAX), diff); } - #[test] + #[macro_rules_attr::apply(test)] fn neg_test() { assert_eq!(-BFieldElement::ZERO, BFieldElement::ZERO); assert_eq!( @@ -1287,7 +1288,7 @@ mod tests { assert_eq!(max, -max_plus_two); } - #[test] + #[macro_rules_attr::apply(test)] fn equality_and_hash_test() { assert_eq!(BFieldElement::ZERO, BFieldElement::ZERO); assert_eq!(BFieldElement::ONE, BFieldElement::ONE); @@ -1321,7 +1322,7 @@ mod tests { assert_eq!(hasher_a.finish(), hasher_b.finish()); } - #[test] + #[macro_rules_attr::apply(test)] fn create_polynomial_test() { let a = Polynomial::from([1, 3, 7]); let b = Polynomial::from([2, 5, -1]); @@ -1330,7 +1331,7 @@ mod tests { assert_eq!(expected, a + b); } - #[test] + #[macro_rules_attr::apply(test)] fn mod_pow_test_powers_of_two() { let two = BFieldElement::new(2); // 2^63 < 2^64, so no wrap-around of B-field element @@ -1339,7 +1340,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn mod_pow_test_powers_of_three() { let three = BFieldElement::new(3); // 3^40 < 2^64, so no wrap-around of B-field element @@ -1348,7 +1349,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn mod_pow_test() { // These values were found by finding primitive roots of unity and verifying // that they are group generators of the right order @@ -1364,7 +1365,7 @@ mod tests { assert!(BFieldElement::new(0).mod_pow(0).is_one()); } - #[test] + #[macro_rules_attr::apply(test)] fn get_primitive_root_of_unity_test() { for i in 1..33 { let power = 1 << i; @@ -1379,14 +1380,14 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "Attempted to find the multiplicative inverse of zero.")] fn multiplicative_inverse_of_zero() { let zero = BFieldElement::ZERO; let _ = zero.inverse(); } - #[test] + #[macro_rules_attr::apply(test)] fn u32_conversion() { let val = BFieldElement::new(u32::MAX as u64); let as_u32: u32 = val.try_into().unwrap(); @@ -1399,7 +1400,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn inverse_or_zero_bfe() { let zero = BFieldElement::ZERO; let one = BFieldElement::ONE; @@ -1414,7 +1415,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn test_random_squares() { let mut rng = rand::rng(); let p = BFieldElement::P; @@ -1432,7 +1433,7 @@ mod tests { assert_eq!(one, one * one); } - #[test] + #[macro_rules_attr::apply(test)] fn equals() { let a = BFieldElement::ONE; let b = bfe!(BFieldElement::MAX) * bfe!(BFieldElement::MAX); @@ -1442,7 +1443,7 @@ mod tests { assert_eq!(a.value(), b.value()); } - #[test] + #[macro_rules_attr::apply(test)] fn test_random_raw() { let mut rng = rand::rng(); for _ in 0..100 { @@ -1469,7 +1470,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn test_fixed_inverse() { // (8561862112314395584, 17307602810081694772) let a = BFieldElement::new(8561862112314395584); @@ -1480,7 +1481,7 @@ mod tests { assert_eq!(a_inv, expected); } - #[test] + #[macro_rules_attr::apply(test)] fn test_fixed_modpow() { let exponent = 16608971246357572739u64; let base = BFieldElement::new(7808276826625786800); @@ -1488,7 +1489,7 @@ mod tests { assert_eq!(base.mod_pow_u64(exponent), expected); } - #[test] + #[macro_rules_attr::apply(test)] fn test_fixed_mul() { { let a = BFieldElement::new(2779336007265862836); @@ -1507,7 +1508,7 @@ mod tests { } } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn conversion_from_i32_to_bfe_is_correct(v: i32) { let bfe = BFieldElement::from(v); match v { @@ -1516,7 +1517,7 @@ mod tests { } } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn conversion_from_isize_to_bfe_is_correct(v: isize) { let bfe = BFieldElement::from(v); match v { @@ -1525,7 +1526,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn bfield_element_can_be_converted_to_and_from_many_types() { let _ = BFieldElement::from(0_u8); let _ = BFieldElement::from(0_u16); @@ -1556,7 +1557,7 @@ mod tests { let _ = i128::from(max); } - #[test] + #[macro_rules_attr::apply(test)] fn bfield_conversion_works_for_types_min_and_max() { let _ = BFieldElement::from(u8::MIN); let _ = BFieldElement::from(u8::MAX); @@ -1582,7 +1583,7 @@ mod tests { let _ = BFieldElement::from(isize::MAX); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn naive_and_actual_conversion_from_u128_agree(v: u128) { fn naive_conversion(x: u128) -> BFieldElement { let p = BFieldElement::P as u128; @@ -1593,7 +1594,7 @@ mod tests { prop_assert_eq!(naive_conversion(v), BFieldElement::from(v)); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn naive_and_actual_conversion_from_i64_agree(v: i64) { fn naive_conversion(x: i64) -> BFieldElement { let p = BFieldElement::P as i128; @@ -1604,7 +1605,7 @@ mod tests { prop_assert_eq!(naive_conversion(v), BFieldElement::from(v)); } - #[test] + #[macro_rules_attr::apply(test)] fn bfe_macro_can_be_used() { let b = bfe!(42); let _ = bfe!(42u32); @@ -1619,12 +1620,12 @@ mod tests { assert_eq!(c, d); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn bfe_macro_produces_same_result_as_calling_new(value: u64) { prop_assert_eq!(BFieldElement::new(value), bfe!(value)); } - #[test] + #[macro_rules_attr::apply(test)] fn const_minus_two_inverse_is_really_minus_two_inverse() { assert_eq!(bfe!(-2).inverse(), BFieldElement::MINUS_TWO_INVERSE); } diff --git a/twenty-first/src/math/bfield_codec.rs b/twenty-first/src/math/bfield_codec.rs index aa725535a..b7e67e323 100644 --- a/twenty-first/src/math/bfield_codec.rs +++ b/twenty-first/src/math/bfield_codec.rs @@ -602,10 +602,11 @@ mod tests { use proptest::prelude::*; use proptest::test_runner::TestCaseResult; use proptest_arbitrary_interop::arb; - use test_strategy::proptest; use super::*; use crate::prelude::*; + use crate::tests::proptest; + use crate::tests::test; #[derive(Debug, PartialEq, Eq, test_strategy::Arbitrary)] struct BFieldCodecPropertyTestData @@ -711,7 +712,7 @@ mod tests { macro_rules! test_case { (fn $fn_name:ident for $t:ty: $static_len:expr) => { - #[proptest] + #[macro_rules_attr::apply(proptest)] fn $fn_name(test_data: BFieldCodecPropertyTestData<$t>) { prop_assert_eq!($static_len, <$t as BFieldCodec>::static_length()); test_data.assert_bfield_codec_properties()?; @@ -721,7 +722,7 @@ mod tests { macro_rules! neg_test_case { (fn $fn_name:ident for $t:ty) => { - #[proptest] + #[macro_rules_attr::apply(proptest)] fn $fn_name(random_encoding: Vec) { let decoding = <$t as BFieldCodec>::decode(&random_encoding); prop_assert!(decoding.is_err()); @@ -782,26 +783,26 @@ mod tests { neg_test_case! { fn poly_of_bfe_neg for Polynomial } neg_test_case! { fn poly_of_xfe_neg for Polynomial } - #[test] + #[macro_rules_attr::apply(test)] fn encoding_tuple_puts_fields_in_expected_order() { let encoding = (1_u8, 2_u16, 3_u32, 4_u64, true).encode(); let expected = bfe_vec![1, 4, 0, 3, 2, 1]; assert_eq!(expected, encoding); } - #[test] + #[macro_rules_attr::apply(test)] fn leading_zero_coefficient_have_no_effect_on_encoding_empty_poly_bfe() { let empty_poly = Polynomial::::new(vec![]); assert_eq!(empty_poly.encode(), Polynomial::new(bfe_vec![0]).encode()); } - #[test] + #[macro_rules_attr::apply(test)] fn leading_zero_coefficients_have_no_effect_on_encoding_empty_poly_xfe() { let empty_poly = Polynomial::::new(vec![]); assert_eq!(empty_poly.encode(), Polynomial::new(xfe_vec![0]).encode()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn leading_zero_coefficients_have_no_effect_on_encoding_poly_bfe_pbt( polynomial: Polynomial<'static, BFieldElement>, #[strategy(0usize..30)] num_leading_zeros: usize, @@ -815,7 +816,7 @@ mod tests { prop_assert_eq!(encoded, poly_w_leading_zeros.encode()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn leading_zero_coefficients_have_no_effect_on_encoding_poly_xfe_pbt( polynomial: Polynomial<'static, XFieldElement>, #[strategy(0usize..30)] num_leading_zeros: usize, @@ -861,7 +862,7 @@ mod tests { Ok(()) } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn disallow_trailing_zeros_in_poly_encoding_bfe( polynomial: Polynomial<'static, BFieldElement>, #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement, @@ -874,7 +875,7 @@ mod tests { )? } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn disallow_trailing_zeros_in_poly_encoding_xfe( polynomial: Polynomial<'static, XFieldElement>, #[filter(!#leading_coefficient.is_zero())] leading_coefficient: XFieldElement, @@ -901,6 +902,8 @@ mod tests { use super::*; use crate::math::x_field_element::XFieldElement; + use crate::tests::proptest; + use crate::tests::test; use crate::tip5::Digest; use crate::tip5::Tip5; use crate::util_types::mmr::mmr_accumulator::MmrAccumulator; @@ -1163,7 +1166,7 @@ mod tests { b: usize, } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn unsupported_fields_can_be_ignored_test(#[strategy(arb())] my_struct: UnsupportedFields) { let encoded = my_struct.encode(); let decoded = UnsupportedFields::decode(&encoded).unwrap(); @@ -1316,7 +1319,7 @@ mod tests { fn enum_with_generics_and_where_clause for EnumWithGenericsAndWhereClause: None } - #[test] + #[macro_rules_attr::apply(test)] fn enums_bfield_codec_discriminant_can_be_accessed() { let a = EnumWithGenericsAndWhereClause::::A; let b = EnumWithGenericsAndWhereClause::::B(1); @@ -1349,14 +1352,14 @@ mod tests { coefficients: Vec, } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn manual_poly_encoding_implementation_is_consistent_with_derived_bfe( #[strategy(arb())] coefficients: Vec, ) { manual_poly_encoding_implementation_is_consistent_with_derived(coefficients)?; } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn manual_poly_encoding_implementation_is_consistent_with_derived_xfe( #[strategy(arb())] coefficients: Vec, ) { diff --git a/twenty-first/src/math/lattice.rs b/twenty-first/src/math/lattice.rs index edfbcbd6e..c8901e318 100644 --- a/twenty-first/src/math/lattice.rs +++ b/twenty-first/src/math/lattice.rs @@ -815,9 +815,11 @@ pub mod kem { #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] mod tests { + use super::*; + use crate::tests::test; - #[test] + #[macro_rules_attr::apply(test)] fn secret_key_zeroize_test() { let mut secret_key = SecretKey { key: rand::random(), @@ -844,14 +846,14 @@ mod tests { use sha3::Sha3_256; use super::kem::CIPHERTEXT_SIZE_IN_BFES; + use super::kem::Ciphertext; + use super::kem::PublicKey; use super::kem::SecretKey; use super::kem::shake256; - use crate::math::b_field_element::BFieldElement; - use crate::math::lattice::kem::Ciphertext; - use crate::math::lattice::kem::PublicKey; - use crate::math::lattice::*; + use super::*; + use crate::tests::test; - #[test] + #[macro_rules_attr::apply(test)] fn test_kats() { // KATs lifted from // https://github.com/XKCP/XKCP/blob/master/tests/UnitTests/main.c @@ -870,7 +872,7 @@ mod tests { assert_eq!(expected_output_sha3_256, &*sha3_out); } - #[test] + #[macro_rules_attr::apply(test)] fn test_fast_mul() { let a: [BFieldElement; 64] = random(); let b: [BFieldElement; 64] = random(); @@ -893,7 +895,7 @@ mod tests { assert_eq!(c_fast, c_schoolbook); } - #[test] + #[macro_rules_attr::apply(test)] fn test_embedding() { let mut rng = rand::rng(); let msg: [u8; 32] = (0..32) @@ -907,7 +909,7 @@ mod tests { assert_eq!(msg, extracted); } - #[test] + #[macro_rules_attr::apply(test)] fn test_module_distributivity() { let mut rng = rand::rng(); let randomness = (0..(2 * 3 + 2 * 3 + 3) * 64 * 9) @@ -930,7 +932,7 @@ mod tests { assert_eq!(sumprod, prodsum); } - #[test] + #[macro_rules_attr::apply(test)] fn test_module_multiply() { let mut rng = rand::rng(); let randomness = (0..(2 * 3 + 2 * 3 + 3) * 64 * 9) @@ -949,7 +951,7 @@ mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] fn test_kem() { let mut rng = rand::rng(); let mut key_randomness: [u8; 32] = [0u8; 32]; @@ -971,7 +973,7 @@ mod tests { assert!(kem::dec(other_sk, ctxt).is_none()); } - #[test] + #[macro_rules_attr::apply(test)] fn test_ciphertext_conversion() { let bfes: [BFieldElement; CIPHERTEXT_SIZE_IN_BFES] = random(); let ciphertext: Ciphertext = bfes.into(); @@ -982,7 +984,7 @@ mod tests { assert_eq!(ciphertext, ciphertext_again); } - #[test] + #[macro_rules_attr::apply(test)] fn zero_test() { let zero_me = ModuleElement::<4>::zero(); assert!(zero_me.is_zero(), "zero must be zero"); @@ -994,7 +996,7 @@ mod tests { assert!(!not_zero.is_zero(), "not-zero must be not be zero"); } - #[test] + #[macro_rules_attr::apply(test)] fn serialization_deserialization_test() { // This is tested here since the serialization for these objects is a bit more complicated // than the standard serde stuff. So to be sure that it works, we just run this test here. diff --git a/twenty-first/src/math/ntt.rs b/twenty-first/src/math/ntt.rs index 9be110096..69a99ef7f 100644 --- a/twenty-first/src/math/ntt.rs +++ b/twenty-first/src/math/ntt.rs @@ -13,9 +13,23 @@ use super::traits::PrimitiveRootOfUnity; /// The number of different domains over which this library can compute (i)NTT. /// /// In particular, the maximum slice length for both [NTT][ntt] and [iNTT][intt] -/// supported by this library is 2^31. All domains of length some power of 2 -/// smaller than this, plus the empty domain, are supported as well. -const NUM_DOMAINS: usize = 32; +/// supported by this library is 2^31 on 64-bit systems and 2^28 on 32-bit +/// systems. All domains of length some power of 2 smaller than this, plus the +/// empty domain, are supported as well. +const NUM_DOMAINS: usize = { + #[cfg(target_pointer_width = "16")] + compile_error!("pointer width 16 is not supported"); + + #[cfg(target_pointer_width = "32")] + { + 29 // avoid isize::MAX overflow. + } + + #[cfg(target_pointer_width = "64")] + { + 32 // NTT currently relies on `usize` to `u32` `as`-casting + } +}; /// ## Perform NTT on slices of prime-field elements /// @@ -318,16 +332,17 @@ mod tests { use proptest::collection::vec; use proptest::prelude::*; use proptest_arbitrary_interop::arb; - use test_strategy::proptest; use super::*; use crate::math::other::random_elements; use crate::math::traits::PrimitiveRootOfUnity; use crate::math::x_field_element::EXTENSION_DEGREE; use crate::prelude::*; + use crate::tests::proptest; + use crate::tests::test; use crate::xfe; - #[test] + #[macro_rules_attr::apply(test)] fn chu_ntt_b_field_prop_test() { for log_2_n in 1..10 { let n = 1 << log_2_n; @@ -349,7 +364,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn chu_ntt_x_field_prop_test() { for log_2_n in 1..10 { let n = 1 << log_2_n; @@ -379,7 +394,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn xfield_basic_test_of_chu_ntt() { let mut input_output = vec![ XFieldElement::new_const(BFieldElement::ONE), @@ -405,7 +420,7 @@ mod tests { assert_eq!(original_input, input_output); } - #[test] + #[macro_rules_attr::apply(test)] fn bfield_basic_test_of_chu_ntt() { let mut input_output = vec![ BFieldElement::new(1), @@ -429,7 +444,7 @@ mod tests { assert_eq!(original_input, input_output); } - #[test] + #[macro_rules_attr::apply(test)] fn bfield_max_value_test_of_chu_ntt() { let mut input_output = vec![ BFieldElement::new(BFieldElement::MAX), @@ -453,7 +468,7 @@ mod tests { assert_eq!(original_input, input_output); } - #[test] + #[macro_rules_attr::apply(test)] fn ntt_on_empty_input() { let mut input_output = vec![]; let original_input = input_output.clone(); @@ -466,7 +481,7 @@ mod tests { assert_eq!(original_input, input_output); } - #[proptest] + #[macro_rules_attr::apply(proptest(cases = 10))] fn ntt_on_input_of_length_one(bfe: BFieldElement) { let mut test_vector = vec![bfe]; ntt(&mut test_vector); @@ -474,7 +489,7 @@ mod tests { } // Make sure that caches are correctly populated in edge cases. - #[test] + #[macro_rules_attr::apply(test)] fn ntt_on_input_of_length_0_then_1_then_0() { let mut empty = Vec::::new(); ntt(&mut empty); @@ -482,7 +497,7 @@ mod tests { ntt(&mut empty); } - #[proptest(cases = 10)] + #[macro_rules_attr::apply(proptest(cases = 10))] fn ntt_then_intt_is_identity_operation( #[strategy((0_usize..18).prop_map(|l| 1 << l))] _vector_length: usize, #[strategy(vec(arb(), #_vector_length))] mut input: Vec, @@ -493,7 +508,7 @@ mod tests { assert_eq!(original_input, input); } - #[test] + #[macro_rules_attr::apply(test)] fn b_field_ntt_with_length_32() { let mut input_output = bfe_vec![ 1, 4, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, @@ -544,7 +559,7 @@ mod tests { assert_eq!(original_input, input_output); } - #[test] + #[macro_rules_attr::apply(test)] fn test_compare_ntt_to_eval() { for log_size in 1..10 { let size = 1 << log_size; @@ -563,7 +578,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn swap_indices_can_be_computed() { // exponential growth is powerful; cap the number of domains for log_size in 0..NUM_DOMAINS - 2 { @@ -571,7 +586,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn twiddle_factors_can_be_computed() { // exponential growth is powerful; cap the number of domains for log_size in 0..NUM_DOMAINS - 5 { diff --git a/twenty-first/src/math/polynomial.rs b/twenty-first/src/math/polynomial.rs index 7b16c35b2..4272ead0a 100644 --- a/twenty-first/src/math/polynomial.rs +++ b/twenty-first/src/math/polynomial.rs @@ -2708,10 +2708,11 @@ mod tests { use proptest::collection::vec; use proptest::prelude::*; use proptest_arbitrary_interop::arb; - use test_strategy::proptest; use super::*; use crate::prelude::*; + use crate::tests::proptest; + use crate::tests::test; /// A type alias exclusive to the test module. type BfePoly = Polynomial<'static, BFieldElement>; @@ -2739,13 +2740,13 @@ mod tests { type Strategy = BoxedStrategy; } - #[test] + #[macro_rules_attr::apply(test)] fn polynomial_can_be_debug_printed() { let polynomial = Polynomial::new(bfe_vec![1, 2, 3]); println!("{polynomial:?}"); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn unequal_hash_implies_unequal_polynomials(poly_0: BfePoly, poly_1: BfePoly) { let hash = |poly: &Polynomial<_>| { let mut hasher = std::hash::DefaultHasher::new(); @@ -2763,7 +2764,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn polynomial_display_test() { fn polynomial(coeffs: [u64; N]) -> BfePoly { Polynomial::new(coeffs.map(BFieldElement::new).to_vec()) @@ -2788,14 +2789,14 @@ mod tests { assert_eq!("2x^4 + 1", polynomial([1, 0, 0, 0, 2]).to_string()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn leading_coefficient_of_zero_polynomial_is_none(#[strategy(0usize..30)] num_zeros: usize) { let coefficients = vec![BFieldElement::ZERO; num_zeros]; let polynomial = Polynomial::new(coefficients); prop_assert!(polynomial.leading_coefficient().is_none()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn leading_coefficient_of_non_zero_polynomial_is_some( polynomial: BfePoly, leading_coefficient: BFieldElement, @@ -2811,14 +2812,14 @@ mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] fn normalizing_canonical_zero_polynomial_has_no_effect() { let mut zero_polynomial = Polynomial::::zero(); zero_polynomial.normalize(); assert_eq!(Polynomial::zero(), zero_polynomial); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn spurious_leading_zeros_dont_affect_equality( polynomial: BfePoly, #[strategy(0usize..30)] num_leading_zeros: usize, @@ -2830,7 +2831,7 @@ mod tests { prop_assert_eq!(polynomial, polynomial_with_leading_zeros); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn normalizing_removes_spurious_leading_zeros( polynomial: BfePoly, #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement, @@ -2849,14 +2850,14 @@ mod tests { prop_assert_eq!(expected_num_coefficients, num_coefficients); } - #[test] + #[macro_rules_attr::apply(test)] fn accessing_coefficients_of_empty_polynomial_gives_empty_slice() { let poly = BfePoly::new(vec![]); assert!(poly.coefficients().is_empty()); assert!(poly.into_coefficients().is_empty()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn accessing_coefficients_of_polynomial_with_only_zero_coefficients_gives_empty_slice( #[strategy(0_usize..30)] num_zeros: usize, ) { @@ -2865,7 +2866,7 @@ mod tests { prop_assert!(poly.into_coefficients().is_empty()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn accessing_the_coefficients_is_equivalent_to_normalizing_then_raw_access( mut coefficients: Vec, #[strategy(0_usize..30)] num_leading_zeros: usize, @@ -2883,19 +2884,19 @@ mod tests { prop_assert_eq!(&raw_coefficients, &accessed_coefficients_owned); } - #[test] + #[macro_rules_attr::apply(test)] fn x_to_the_0_is_constant_1() { assert!(Polynomial::::x_to_the(0).is_one()); assert!(Polynomial::::x_to_the(0).is_one()); } - #[test] + #[macro_rules_attr::apply(test)] fn x_to_the_1_is_x() { assert!(Polynomial::::x_to_the(1).is_x()); assert!(Polynomial::::x_to_the(1).is_x()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn x_to_the_n_to_the_m_is_homomorphic( #[strategy(0_usize..50)] n: usize, #[strategy(0_usize..50)] m: usize, @@ -2905,7 +2906,7 @@ mod tests { prop_assert_eq!(to_the_n_times_m, to_the_n_then_to_the_m); } - #[test] + #[macro_rules_attr::apply(test)] fn scaling_a_polynomial_works_with_different_fields_as_the_offset() { let bfe_poly = Polynomial::new(bfe_vec![0, 1, 2]); let _ = bfe_poly.scale(bfe!(42)); @@ -2916,7 +2917,7 @@ mod tests { let _ = xfe_poly.scale(xfe!(42)); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_scaling_is_equivalent_in_extension_field( bfe_polynomial: BfePoly, alpha: BFieldElement, @@ -2930,7 +2931,7 @@ mod tests { prop_assert_eq!(xfe_poly_bfe_scalar, bfe_poly_xfe_scalar); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn evaluating_scaled_polynomial_is_equivalent_to_evaluating_original_in_offset_point( polynomial: BfePoly, alpha: BFieldElement, @@ -2943,7 +2944,7 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_multiplication_with_scalar_is_equivalent_for_the_two_methods( mut polynomial: BfePoly, scalar: BFieldElement, @@ -2953,7 +2954,7 @@ mod tests { prop_assert_eq!(polynomial, new_polynomial); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_multiplication_with_scalar_is_equivalent_for_all_mul_traits( polynomial: BfePoly, scalar: BFieldElement, @@ -2969,7 +2970,7 @@ mod tests { prop_assert_eq!(bfe_lhs * XFieldElement::ONE, xfe_lhs); } - #[test] + #[macro_rules_attr::apply(test)] fn polynomial_multiplication_with_scalar_works_for_various_types() { let bfe_poly = Polynomial::new(bfe_vec![0, 1, 2]); let _: Polynomial = bfe_poly.scalar_mul(bfe!(42)); @@ -2987,7 +2988,7 @@ mod tests { xfe_poly.scalar_mul_mut(xfe!(42)); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn slow_lagrange_interpolation( polynomial: BfePoly, #[strategy(Just(#polynomial.coefficients.len().max(1)))] _min_points: usize, @@ -3001,7 +3002,7 @@ mod tests { prop_assert_eq!(polynomial, interpolation_polynomial); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn three_colinear_points_are_colinear( p0: (BFieldElement, BFieldElement), #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement), @@ -3012,7 +3013,7 @@ mod tests { prop_assert!(Polynomial::are_colinear(&[p0, p1, p2])); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn three_non_colinear_points_are_not_colinear( p0: (BFieldElement, BFieldElement), #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement), @@ -3024,7 +3025,7 @@ mod tests { prop_assert!(!Polynomial::are_colinear(&[p0, p1, p2])); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn colinearity_check_needs_at_least_three_points( p0: (BFieldElement, BFieldElement), #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement), @@ -3034,7 +3035,7 @@ mod tests { prop_assert!(!Polynomial::are_colinear(&[p0, p1])); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn colinearity_check_with_repeated_points_fails( p0: (BFieldElement, BFieldElement), #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement), @@ -3042,7 +3043,7 @@ mod tests { prop_assert!(!Polynomial::are_colinear(&[p0, p1, p1])); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn colinear_points_are_colinear( p0: (BFieldElement, BFieldElement), #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement), @@ -3061,7 +3062,7 @@ mod tests { prop_assert!(Polynomial::are_colinear(&all_points)); } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "Line must not be parallel to y-axis")] fn getting_point_on_invalid_line_fails() { let one = BFieldElement::ONE; @@ -3070,7 +3071,7 @@ mod tests { Polynomial::::get_colinear_y((one, one), (one, three), two); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn point_on_line_and_colinear_point_are_identical( p0: (BFieldElement, BFieldElement), #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement), @@ -3082,7 +3083,7 @@ mod tests { prop_assert_eq!(y, y_from_get_point_on_line); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn point_on_line_and_colinear_point_are_identical_in_extension_field( p0: (XFieldElement, XFieldElement), #[filter(#p0.0 != #p1.0)] p1: (XFieldElement, XFieldElement), @@ -3094,12 +3095,12 @@ mod tests { prop_assert_eq!(y, y_from_get_point_on_line); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn shifting_polynomial_coefficients_by_zero_is_the_same_as_not_shifting_it(poly: BfePoly) { prop_assert_eq!(poly.clone(), poly.shift_coefficients(0)); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn shifting_polynomial_one_is_equivalent_to_raising_polynomial_x_to_the_power_of_the_shift( #[strategy(0usize..30)] shift: usize, ) { @@ -3108,7 +3109,7 @@ mod tests { prop_assert_eq!(shifted_one, x_to_the_shift); } - #[test] + #[macro_rules_attr::apply(test)] fn polynomial_shift_test() { fn polynomial(coeffs: [u64; N]) -> BfePoly { Polynomial::new(coeffs.map(BFieldElement::new).to_vec()) @@ -3138,7 +3139,7 @@ mod tests { assert_eq!(polynomial([0, 0, 0, 0, 17, 14]), poly_shift_4); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn shifting_a_polynomial_means_prepending_zeros_to_its_coefficients( poly: BfePoly, #[strategy(0usize..30)] shift: usize, @@ -3149,25 +3150,25 @@ mod tests { prop_assert_eq!(expected_coefficients, shifted_poly.coefficients.to_vec()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn any_polynomial_to_the_power_of_zero_is_one(poly: BfePoly) { let poly_to_the_zero = poly.pow(0); prop_assert_eq!(Polynomial::one(), poly_to_the_zero); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn any_polynomial_to_the_power_one_is_itself(poly: BfePoly) { let poly_to_the_one = poly.pow(1); prop_assert_eq!(poly, poly_to_the_one); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_one_to_any_power_is_one(#[strategy(0u32..30)] exponent: u32) { let one_to_the_exponent = Polynomial::::one().pow(exponent); prop_assert_eq!(Polynomial::one(), one_to_the_exponent); } - #[test] + #[macro_rules_attr::apply(test)] fn pow_test() { fn polynomial(coeffs: [u64; N]) -> BfePoly { Polynomial::new(coeffs.map(BFieldElement::new).to_vec()) @@ -3188,7 +3189,7 @@ mod tests { assert_eq!(parabola_squared, parabola.pow(2)); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn pow_arbitrary_test(poly: BfePoly, #[strategy(0u32..15)] exponent: u32) { let actual = poly.pow(exponent); let fast_actual = poly.fast_pow(exponent); @@ -3201,19 +3202,19 @@ mod tests { prop_assert_eq!(expected, fast_actual); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_zero_is_neutral_element_for_addition(a: BfePoly) { prop_assert_eq!(a.clone() + Polynomial::zero(), a.clone()); prop_assert_eq!(Polynomial::zero() + a.clone(), a); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_one_is_neutral_element_for_multiplication(a: BfePoly) { prop_assert_eq!(a.clone() * Polynomial::::one(), a.clone()); prop_assert_eq!(Polynomial::::one() * a.clone(), a); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn multiplication_by_zero_is_zero(a: BfePoly) { let zero = Polynomial::::zero(); @@ -3221,27 +3222,27 @@ mod tests { prop_assert_eq!(Polynomial::zero(), zero * a); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_addition_is_commutative(a: BfePoly, b: BfePoly) { prop_assert_eq!(a.clone() + b.clone(), b + a); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_multiplication_is_commutative(a: BfePoly, b: BfePoly) { prop_assert_eq!(a.clone() * b.clone(), b * a); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_addition_is_associative(a: BfePoly, b: BfePoly, c: BfePoly) { prop_assert_eq!((a.clone() + b.clone()) + c.clone(), a + (b + c)); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_multiplication_is_associative(a: BfePoly, b: BfePoly, c: BfePoly) { prop_assert_eq!((a.clone() * b.clone()) * c.clone(), a * (b * c)); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_multiplication_is_distributive(a: BfePoly, b: BfePoly, c: BfePoly) { prop_assert_eq!( (a.clone() + b.clone()) * c.clone(), @@ -3249,22 +3250,22 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_subtraction_of_self_is_zero(a: BfePoly) { prop_assert_eq!(Polynomial::zero(), a.clone() - a); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_division_by_self_is_one(#[filter(!#a.is_zero())] a: BfePoly) { prop_assert_eq!(Polynomial::one(), a.clone() / a); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_division_removes_common_factors(a: BfePoly, #[filter(!#b.is_zero())] b: BfePoly) { prop_assert_eq!(a.clone(), a * b.clone() / b); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_multiplication_raises_degree_at_maximum_to_sum_of_degrees( a: BfePoly, b: BfePoly, @@ -3273,7 +3274,7 @@ mod tests { prop_assert!((a * b).degree() <= sum_of_degrees); } - #[test] + #[macro_rules_attr::apply(test)] fn leading_zeros_dont_affect_polynomial_division() { // This test was used to catch a bug where the polynomial division was // wrong when the divisor has a leading zero coefficient, i.e. when it @@ -3310,7 +3311,7 @@ mod tests { assert_eq!(expected, res_numerator_not_normalized_2); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn leading_coefficient_of_truncated_polynomial_is_same_as_original_leading_coefficient( poly: BfePoly, #[strategy(..50_usize)] truncation_point: usize, @@ -3327,7 +3328,7 @@ mod tests { prop_assert_eq!(lc, trunc_lc); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn truncated_polynomial_is_of_degree_min_of_truncation_point_and_poly_degree( poly: BfePoly, #[strategy(..50_usize)] truncation_point: usize, @@ -3336,7 +3337,7 @@ mod tests { prop_assert_eq!(expected_degree, poly.truncate(truncation_point).degree()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn truncating_zero_polynomial_gives_zero_polynomial( #[strategy(..50_usize)] truncation_point: usize, ) { @@ -3344,7 +3345,7 @@ mod tests { prop_assert!(poly.is_zero()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn truncation_negates_degree_shifting( #[strategy(0_usize..30)] shift: usize, #[strategy(..50_usize)] truncation_point: usize, @@ -3356,13 +3357,13 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn zero_polynomial_mod_any_power_of_x_is_zero_polynomial(power: usize) { let must_be_zero = Polynomial::::zero().mod_x_to_the_n(power); prop_assert!(must_be_zero.is_zero()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_mod_some_power_of_x_results_in_polynomial_of_degree_one_less_than_power( #[filter(!#poly.is_zero())] poly: BfePoly, #[strategy(..=usize::try_from(#poly.degree()).unwrap())] power: usize, @@ -3371,7 +3372,7 @@ mod tests { prop_assert_eq!(isize::try_from(power).unwrap() - 1, remainder.degree()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_mod_some_power_of_x_shares_low_degree_terms_coefficients_with_original_polynomial( #[filter(!#poly.is_zero())] poly: BfePoly, power: usize, @@ -3384,30 +3385,30 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn fast_multiplication_by_zero_gives_zero(poly: BfePoly) { let product = poly.fast_multiply(&Polynomial::::zero()); prop_assert_eq!(Polynomial::zero(), product); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn fast_multiplication_by_one_gives_self(poly: BfePoly) { let product = poly.fast_multiply(&Polynomial::::one()); prop_assert_eq!(poly, product); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn fast_multiplication_is_commutative(a: BfePoly, b: BfePoly) { prop_assert_eq!(a.fast_multiply(&b), b.fast_multiply(&a)); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn fast_multiplication_and_normal_multiplication_are_equivalent(a: BfePoly, b: BfePoly) { let product = a.fast_multiply(&b); prop_assert_eq!(a * b, product); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn batch_multiply_agrees_with_iterative_multiply(a: Vec) { let mut acc = Polynomial::one(); for factor in &a { @@ -3416,7 +3417,7 @@ mod tests { prop_assert_eq!(acc, Polynomial::batch_multiply(&a)); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn par_batch_multiply_agrees_with_batch_multiply(a: Vec) { prop_assert_eq!( Polynomial::batch_multiply(&a), @@ -3424,7 +3425,7 @@ mod tests { ); } - #[proptest(cases = 50)] + #[macro_rules_attr::apply(proptest(cases = 50))] fn naive_zerofier_and_fast_zerofier_are_identical( #[any(size_range(..Polynomial::::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())] roots: Vec, @@ -3434,7 +3435,7 @@ mod tests { prop_assert_eq!(naive_zerofier, fast_zerofier); } - #[proptest(cases = 50)] + #[macro_rules_attr::apply(proptest(cases = 50))] fn smart_zerofier_and_fast_zerofier_are_identical( #[any(size_range(..Polynomial::::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())] roots: Vec, @@ -3444,7 +3445,7 @@ mod tests { prop_assert_eq!(smart_zerofier, fast_zerofier); } - #[proptest(cases = 50)] + #[macro_rules_attr::apply(proptest(cases = 50))] fn zerofier_and_naive_zerofier_are_identical( #[any(size_range(..Polynomial::::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())] roots: Vec, @@ -3454,7 +3455,7 @@ mod tests { prop_assert_eq!(zerofier, naive_zerofier); } - #[proptest(cases = 50)] + #[macro_rules_attr::apply(proptest(cases = 50))] fn zerofier_is_zero_only_on_domain( #[any(size_range(..1024).lift())] domain: Vec, #[filter(#out_of_domain_points.iter().all(|p| !#domain.contains(p)))] @@ -3469,12 +3470,12 @@ mod tests { } } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn zerofier_has_leading_coefficient_one(domain: Vec) { let zerofier = Polynomial::zerofier(&domain); prop_assert_eq!(BFieldElement::ONE, zerofier.leading_coefficient().unwrap()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn par_zerofier_agrees_with_zerofier(domain: Vec) { prop_assert_eq!( Polynomial::zerofier(&domain), @@ -3482,7 +3483,7 @@ mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] fn fast_evaluate_on_hardcoded_domain_and_polynomial() { let domain = bfe_array![6, 12]; let x_to_the_5_plus_x_to_the_3 = Polynomial::new(bfe_vec![0, 0, 0, 1, 0, 1]); @@ -3492,7 +3493,7 @@ mod tests { assert_eq!(manual_evaluations, fast_evaluations); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn slow_and_fast_polynomial_evaluation_are_equivalent( poly: BfePoly, #[any(size_range(..1024).lift())] domain: Vec, @@ -3505,31 +3506,31 @@ mod tests { prop_assert_eq!(evaluations, fast_evaluations); } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "zero points")] fn interpolation_through_no_points_is_impossible() { let _ = Polynomial::::interpolate(&[], &[]); } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "zero points")] fn lagrange_interpolation_through_no_points_is_impossible() { let _ = Polynomial::::lagrange_interpolate(&[], &[]); } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "zero points")] fn zipped_lagrange_interpolation_through_no_points_is_impossible() { let _ = Polynomial::::lagrange_interpolate_zipped(&[]); } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "zero points")] fn fast_interpolation_through_no_points_is_impossible() { let _ = Polynomial::::fast_interpolate(&[], &[]); } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "equal length")] fn interpolation_with_domain_size_different_from_number_of_points_is_impossible() { let domain = bfe_array![1, 2, 3]; @@ -3537,7 +3538,7 @@ mod tests { let _ = Polynomial::interpolate(&domain, &points); } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "Repeated")] fn zipped_lagrange_interpolate_using_repeated_domain_points_is_impossible() { let domain = bfe_array![1, 1, 2]; @@ -3546,7 +3547,7 @@ mod tests { let _ = Polynomial::lagrange_interpolate_zipped(&zipped); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn interpolating_through_one_point_gives_constant_polynomial( x: BFieldElement, y: BFieldElement, @@ -3556,7 +3557,7 @@ mod tests { prop_assert_eq!(polynomial, interpolant); } - #[proptest(cases = 10)] + #[macro_rules_attr::apply(proptest(cases = 10))] fn lagrange_and_fast_interpolation_are_identical( #[any(size_range(1..2048).lift())] #[filter(#domain.iter().all_unique())] @@ -3568,7 +3569,7 @@ mod tests { prop_assert_eq!(lagrange_interpolant, fast_interpolant); } - #[proptest(cases = 10)] + #[macro_rules_attr::apply(proptest(cases = 10))] fn par_fast_interpolate_and_fast_interpolation_are_identical( #[any(size_range(1..2048).lift())] #[filter(#domain.iter().all_unique())] @@ -3580,13 +3581,13 @@ mod tests { prop_assert_eq!(par_fast_interpolant, fast_interpolant); } - #[test] + #[macro_rules_attr::apply(test)] fn fast_interpolation_through_a_single_point_succeeds() { let zero_arr = bfe_array![0]; let _ = Polynomial::fast_interpolate(&zero_arr, &zero_arr); } - #[proptest(cases = 20)] + #[macro_rules_attr::apply(proptest(cases = 20))] fn interpolation_then_evaluation_is_identity( #[any(size_range(1..2048).lift())] #[filter(#domain.iter().all_unique())] @@ -3598,7 +3599,7 @@ mod tests { prop_assert_eq!(values, evaluations); } - #[proptest(cases = 1)] + #[macro_rules_attr::apply(proptest(cases = 1))] fn fast_batch_interpolation_is_equivalent_to_fast_interpolation( #[any(size_range(1..2048).lift())] #[filter(#domain.iter().all_unique())] @@ -3630,7 +3631,7 @@ mod tests { domain } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn fast_coset_evaluation_and_fast_evaluation_on_coset_are_identical( polynomial: BfePoly, offset: BFieldElement, @@ -3649,7 +3650,7 @@ mod tests { prop_assert_eq!(fast_values, fast_coset_values); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn fast_coset_interpolation_and_and_fast_interpolation_on_coset_are_identical( #[filter(!#offset.is_zero())] offset: BFieldElement, #[strategy(1..8usize)] @@ -3666,7 +3667,7 @@ mod tests { prop_assert_eq!(fast_interpolant, fast_coset_interpolant); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn naive_division_gives_quotient_and_remainder_with_expected_properties( a: BfePoly, #[filter(!#b.is_zero())] b: BfePoly, @@ -3676,7 +3677,7 @@ mod tests { prop_assert_eq!(a, quot * b + rem); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn clean_naive_division_gives_quotient_and_remainder_with_expected_properties( #[filter(!#a_roots.is_empty())] a_roots: Vec, #[strategy(vec(0..#a_roots.len(), 1..=#a_roots.len()))] @@ -3691,7 +3692,7 @@ mod tests { prop_assert_eq!(a, quot * b); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn clean_division_agrees_with_divide_on_clean_division( #[strategy(arb())] a: BfePoly, #[strategy(arb())] @@ -3706,7 +3707,7 @@ mod tests { prop_assert_eq!(Polynomial::::zero(), naive_remainder); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn clean_division_agrees_with_division_if_divisor_has_only_0_as_root( #[strategy(arb())] mut dividend_roots: Vec, ) { @@ -3720,7 +3721,7 @@ mod tests { prop_assert_eq!(Polynomial::::zero(), remainder); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn clean_division_agrees_with_division_if_divisor_has_only_0_as_multiple_root( #[strategy(arb())] mut dividend_roots: Vec, #[strategy(0_usize..300)] num_roots: usize, @@ -3736,7 +3737,7 @@ mod tests { prop_assert_eq!(Polynomial::::zero(), remainder); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn clean_division_agrees_with_division_if_divisor_has_0_as_root( #[strategy(arb())] mut dividend_roots: Vec, #[strategy(vec(0..#dividend_roots.len(), 0..=#dividend_roots.len()))] @@ -3759,7 +3760,7 @@ mod tests { prop_assert_eq!(dividend / divisor, quotient); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn clean_division_agrees_with_division_if_divisor_has_0_through_9_as_roots( #[strategy(arb())] additional_dividend_roots: Vec, ) { @@ -3773,7 +3774,7 @@ mod tests { prop_assert_eq!(dividend / divisor, quotient); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn clean_division_gives_quotient_and_remainder_with_expected_properties( #[filter(!#a_roots.is_empty())] a_roots: Vec, #[strategy(vec(0..#a_roots.len(), 1..=#a_roots.len()))] @@ -3787,7 +3788,7 @@ mod tests { prop_assert_eq!(a, quotient * b); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn dividing_constant_polynomials_is_equivalent_to_dividing_constants( a: BFieldElement, #[filter(!#b.is_zero())] b: BFieldElement, @@ -3798,7 +3799,7 @@ mod tests { prop_assert_eq!(expected_quotient, a_poly / b_poly); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn dividing_any_polynomial_by_a_constant_polynomial_results_in_remainder_zero( a: BfePoly, #[filter(!#b.is_zero())] b: BFieldElement, @@ -3808,7 +3809,7 @@ mod tests { prop_assert_eq!(Polynomial::zero(), remainder); } - #[test] + #[macro_rules_attr::apply(test)] fn polynomial_division_by_and_with_shah_polynomial() { fn polynomial(coeffs: [u64; N]) -> BfePoly { Polynomial::new(coeffs.map(BFieldElement::new).to_vec()) @@ -3832,7 +3833,7 @@ mod tests { assert_eq!(expected_rem, x_to_the_6_mod_shah); } - #[test] + #[macro_rules_attr::apply(test)] fn xgcd_does_not_panic_on_input_zero() { let zero = Polynomial::::zero; let (gcd, a, b) = Polynomial::xgcd(zero(), zero()); @@ -3841,28 +3842,28 @@ mod tests { println!("b = {b}"); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn xgcd_b_field_pol_test(x: BfePoly, y: BfePoly) { let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone()); // Bezout relation prop_assert_eq!(gcd, a * x + b * y); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn xgcd_x_field_pol_test(x: XfePoly, y: XfePoly) { let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone()); // Bezout relation prop_assert_eq!(gcd, a * x + b * y); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn add_assign_is_equivalent_to_adding_and_assigning(a: BfePoly, b: BfePoly) { let mut c = a.clone(); c += b.clone(); prop_assert_eq!(a + b, c); } - #[test] + #[macro_rules_attr::apply(test)] fn only_monic_polynomial_of_degree_1_is_x() { fn polynomial(coeffs: [u64; N]) -> BfePoly { Polynomial::new(coeffs.map(BFieldElement::new).to_vec()) @@ -3880,7 +3881,7 @@ mod tests { assert!(!polynomial([0, 0, 1]).is_x()); } - #[test] + #[macro_rules_attr::apply(test)] fn hardcoded_polynomial_squaring() { fn polynomial(coeffs: [u64; N]) -> BfePoly { Polynomial::new(coeffs.map(BFieldElement::new).to_vec()) @@ -3902,22 +3903,22 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn polynomial_squaring_is_equivalent_to_multiplication_with_self(poly: BfePoly) { prop_assert_eq!(poly.clone() * poly.clone(), poly.square()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn slow_and_normal_squaring_are_equivalent(poly: BfePoly) { prop_assert_eq!(poly.slow_square(), poly.square()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn normal_and_fast_squaring_are_equivalent(poly: BfePoly) { prop_assert_eq!(poly.square(), poly.fast_square()); } - #[test] + #[macro_rules_attr::apply(test)] fn constant_zero_eq_constant_zero() { let zero_polynomial1 = Polynomial::::zero(); let zero_polynomial2 = Polynomial::::zero(); @@ -3925,13 +3926,13 @@ mod tests { assert_eq!(zero_polynomial1, zero_polynomial2) } - #[test] + #[macro_rules_attr::apply(test)] fn zero_polynomial_is_zero() { assert!(Polynomial::::zero().is_zero()); assert!(Polynomial::::zero().is_zero()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn zero_polynomial_is_zero_independent_of_spurious_leading_zeros( #[strategy(..500usize)] num_zeros: usize, ) { @@ -3942,7 +3943,7 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn no_constant_polynomial_with_non_zero_coefficient_is_zero( #[filter(!#constant.is_zero())] constant: BFieldElement, ) { @@ -3950,7 +3951,7 @@ mod tests { prop_assert!(!constant_polynomial.is_zero()); } - #[test] + #[macro_rules_attr::apply(test)] fn constant_one_eq_constant_one() { let one_polynomial1 = Polynomial::::one(); let one_polynomial2 = Polynomial::::one(); @@ -3958,13 +3959,13 @@ mod tests { assert_eq!(one_polynomial1, one_polynomial2) } - #[test] + #[macro_rules_attr::apply(test)] fn one_polynomial_is_one() { assert!(Polynomial::::one().is_one()); assert!(Polynomial::::one().is_one()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn one_polynomial_is_one_independent_of_spurious_leading_zeros( #[strategy(..500usize)] num_leading_zeros: usize, ) { @@ -3977,7 +3978,7 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn no_constant_polynomial_with_non_one_coefficient_is_one( #[filter(!#constant.is_one())] constant: BFieldElement, ) { @@ -3985,7 +3986,7 @@ mod tests { prop_assert!(!constant_polynomial.is_one()); } - #[test] + #[macro_rules_attr::apply(test)] fn formal_derivative_of_zero_is_zero() { let bfe_0_poly = Polynomial::::zero(); assert!(bfe_0_poly.formal_derivative().is_zero()); @@ -3994,33 +3995,33 @@ mod tests { assert!(xfe_0_poly.formal_derivative().is_zero()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn formal_derivative_of_constant_polynomial_is_zero(constant: BFieldElement) { let formal_derivative = Polynomial::from_constant(constant).formal_derivative(); prop_assert!(formal_derivative.is_zero()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn formal_derivative_of_non_zero_polynomial_is_of_degree_one_less_than_the_polynomial( #[filter(!#poly.is_zero())] poly: BfePoly, ) { prop_assert_eq!(poly.degree() - 1, poly.formal_derivative().degree()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn formal_derivative_of_product_adheres_to_the_leibniz_product_rule(a: BfePoly, b: BfePoly) { let product_formal_derivative = (a.clone() * b.clone()).formal_derivative(); let product_rule = a.formal_derivative() * b.clone() + a * b.formal_derivative(); prop_assert_eq!(product_rule, product_formal_derivative); } - #[test] + #[macro_rules_attr::apply(test)] fn zero_is_zero() { let f = Polynomial::new(vec![BFieldElement::new(0)]); assert!(f.is_zero()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn formal_power_series_inverse_newton( #[strategy(2usize..20)] precision: usize, #[filter(!#f.coefficients.is_empty())] @@ -4036,7 +4037,7 @@ mod tests { prop_assert!(remainder.is_one()); } - #[test] + #[macro_rules_attr::apply(test)] fn formal_power_series_inverse_newton_concrete() { let f = Polynomial::new(vec![ BFieldElement::new(3618372803227210457), @@ -4057,7 +4058,7 @@ mod tests { assert!(remainder.is_one()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn formal_power_series_inverse_minimal( #[strategy(2usize..20)] precision: usize, #[filter(!#f.coefficients.is_empty())] @@ -4078,7 +4079,7 @@ mod tests { prop_assert!(g.degree() <= precision as isize); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn structured_multiple_is_multiple( #[filter(#coefficients.iter().any(|c|!c.is_zero()))] #[strategy(vec(arb(), 1..30))] @@ -4090,7 +4091,7 @@ mod tests { prop_assert!(remainder.is_zero()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn structured_multiple_of_modulus_with_trailing_zeros_is_multiple( #[filter(!#raw_modulus.is_zero())] raw_modulus: BfePoly, #[strategy(0usize..100)] num_trailing_zeros: usize, @@ -4100,7 +4101,7 @@ mod tests { prop_assert!(multiple.reduce_long_division(&modulus).is_zero()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn structured_multiple_generates_structure( #[filter(#coefficients.iter().filter(|c|!c.is_zero()).count() >= 3)] #[strategy(vec(arb(), 1..30))] @@ -4123,7 +4124,7 @@ mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] fn structured_multiple_generates_structure_concrete() { let polynomial = Polynomial::new( [884763262770, 0, 51539607540, 14563891882495327437] @@ -4146,7 +4147,7 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn structured_multiple_of_degree_is_multiple( #[strategy(2usize..100)] n: usize, #[filter(#coefficients.iter().any(|c|!c.is_zero()))] @@ -4159,7 +4160,7 @@ mod tests { prop_assert!(remainder.is_zero()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn structured_multiple_of_degree_generates_structure( #[strategy(4usize..100)] n: usize, #[strategy(vec(arb(), 3..usize::min(30, #n)))] mut coefficients: Vec, @@ -4186,7 +4187,7 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn structured_multiple_of_degree_has_given_degree( #[strategy(2usize..100)] n: usize, #[filter(#coefficients.iter().any(|c|!c.is_zero()))] @@ -4204,13 +4205,13 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn reverse_polynomial_with_nonzero_constant_term_twice_gives_original_back(f: BfePoly) { let fx_plus_1 = f.shift_coefficients(1) + Polynomial::from_constant(bfe!(1)); prop_assert_eq!(fx_plus_1.clone(), fx_plus_1.reverse().reverse()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn reverse_polynomial_with_zero_constant_term_twice_gives_shift_back( #[filter(!#f.is_zero())] f: BfePoly, ) { @@ -4222,7 +4223,7 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn reduce_by_structured_modulus_and_reduce_long_division_agree( #[strategy(1usize..10)] n: usize, #[strategy(1usize..10)] m: usize, @@ -4242,7 +4243,7 @@ mod tests { prop_assert_eq!(long_remainder, structured_remainder); } - #[test] + #[macro_rules_attr::apply(test)] fn reduce_by_structured_modulus_and_reduce_agree_long_division_concrete() { let a = Polynomial::new( [1, 0, 0, 3, 4, 3, 1, 5, 1, 0, 1, 2, 9, 2, 0, 3, 1] @@ -4267,7 +4268,7 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn reduce_by_ntt_friendly_modulus_and_reduce_long_division_agree( #[strategy(1usize..10)] m: usize, #[strategy(vec(arb(), #m))] b_coefficients: Vec, @@ -4295,7 +4296,7 @@ mod tests { prop_assert_eq!(long_remainder, structured_remainder); } - #[test] + #[macro_rules_attr::apply(test)] fn reduce_by_ntt_friendly_modulus_and_reduce_agree_concrete() { let m = 1; let a_coefficients = bfe_vec![0, 0, 75944580]; @@ -4320,7 +4321,7 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn reduce_fast_and_reduce_long_division_agree( numerator: BfePoly, #[filter(!#modulus.is_zero())] modulus: BfePoly, @@ -4331,7 +4332,7 @@ mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] fn reduce_and_fast_reduce_long_division_agree_on_fixed_input() { // The bug exhibited by this minimal failing test case has since been // fixed. The comments are kept as-is for historical accuracy and @@ -4362,7 +4363,7 @@ mod tests { assert_eq!(0, failures.len(), "failures at indices: {failures:?}"); } - #[test] + #[macro_rules_attr::apply(test)] fn reduce_long_division_and_fast_reduce_agree_simple_fixed() { let roots = (0..10).map(BFieldElement::new).collect_vec(); let numerator = Polynomial::zerofier(&roots).formal_derivative(); @@ -4384,7 +4385,7 @@ mod tests { assert_eq!(long_div_remainder, preprocessed_remainder); } - #[proptest(cases = 100)] + #[macro_rules_attr::apply(proptest(cases = 100))] fn batch_evaluate_methods_are_equivalent( #[strategy(vec(arb(), (1<<10)..(1<<11)))] coefficients: Vec, #[strategy(vec(arb(), (1<<5)..(1<<7)))] domain: Vec, @@ -4399,12 +4400,12 @@ mod tests { prop_assert_eq!(evaluations_iterative, evaluations_reduce_then); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn reduce_agrees_with_division(a: BfePoly, #[filter(!#b.is_zero())] b: BfePoly) { prop_assert_eq!(a.divide(&b).1, a.reduce(&b)); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn structured_multiple_of_monomial_term_is_actually_multiple_and_of_right_degree( #[strategy(1usize..1000)] degree: usize, #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement, @@ -4417,7 +4418,7 @@ mod tests { prop_assert_eq!(multiple.degree() as usize, target_degree); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn monomial_term_divided_by_smaller_monomial_term_gives_clean_division( #[strategy(100usize..102)] high_degree: usize, #[filter(!#high_lc.is_zero())] high_lc: BFieldElement, @@ -4438,7 +4439,7 @@ mod tests { prop_assert_eq!(Polynomial::zero(), remainder); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn fast_modular_coset_interpolate_agrees_with_interpolate_then_reduce_property( #[filter(!#modulus.is_zero())] modulus: BfePoly, #[strategy(0usize..10)] _logn: usize, @@ -4460,7 +4461,7 @@ mod tests { ) } - #[test] + #[macro_rules_attr::apply(test)] fn fast_modular_coset_interpolate_agrees_with_interpolate_then_reduce_concrete() { let logn = 8; let n = 1u64 << logn; @@ -4480,7 +4481,7 @@ mod tests { ) } - #[proptest(cases = 100)] + #[macro_rules_attr::apply(proptest(cases = 100))] fn coset_extrapolation_methods_agree_with_interpolate_then_evaluate( #[strategy(0usize..10)] _logn: usize, #[strategy(Just(1 << #_logn))] n: usize, @@ -4505,7 +4506,7 @@ mod tests { prop_assert_eq!(fast_coset_extrapolation, interpolation_then_evaluation); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn coset_extrapolate_and_batch_coset_extrapolate_agree( #[strategy(1usize..10)] _logn: usize, #[strategy(Just(1<<#_logn))] n: usize, @@ -4547,7 +4548,7 @@ mod tests { prop_assert_eq!(one_by_one_naive, par_batched_naive); } - #[test] + #[macro_rules_attr::apply(test)] fn fast_modular_coset_interpolate_thresholds_relate_properly() { type BfePoly = Polynomial<'static, BFieldElement>; @@ -4556,7 +4557,7 @@ mod tests { assert!(intt > lagrange); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn interpolate_and_par_interpolate_agree( #[filter(!#points.is_empty())] points: Vec, #[strategy(vec(arb(), #points.len()))] domain: Vec, @@ -4566,7 +4567,7 @@ mod tests { prop_assert_eq!(expected_interpolant, observed_interpolant); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn batch_evaluate_agrees_with_par_batch_evalaute( polynomial: BfePoly, points: Vec, @@ -4577,7 +4578,7 @@ mod tests { ); } - #[proptest(cases = 20)] + #[macro_rules_attr::apply(proptest(cases = 20))] fn polynomial_evaluation_and_barycentric_evaluation_are_equivalent( #[strategy(1_usize..8)] _log_num_coefficients: usize, #[strategy(1_usize..6)] log_expansion_factor: usize, @@ -4602,7 +4603,7 @@ mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] fn regular_evaluation_works_with_various_types() { let bfe_poly = Polynomial::new(bfe_vec![1]); let _: BFieldElement = bfe_poly.evaluate(bfe!(0)); @@ -4614,7 +4615,7 @@ mod tests { let _: XFieldElement = xfe_poly.evaluate(xfe!(0)); } - #[test] + #[macro_rules_attr::apply(test)] fn barycentric_evaluation_works_with_many_types() { let bfe_codeword = bfe_array![1]; let _ = barycentric_evaluate(&bfe_codeword, bfe!(0)); @@ -4625,7 +4626,7 @@ mod tests { let _ = barycentric_evaluate(&xfe_codeword, xfe!(0)); } - #[test] + #[macro_rules_attr::apply(test)] fn various_multiplications_work_with_various_types() { let b = Polynomial::::zero; let x = Polynomial::::zero; @@ -4651,7 +4652,7 @@ mod tests { let _ = x().fast_multiply(&x()); } - #[test] + #[macro_rules_attr::apply(test)] fn evaluating_polynomial_with_borrowed_coefficients_leaves_coefficients_borrowed() { let coefficients = bfe_vec![4, 5, 6]; let poly = Polynomial::new_borrowed(&coefficients); diff --git a/twenty-first/src/math/x_field_element.rs b/twenty-first/src/math/x_field_element.rs index 5be2042c5..98f9c1a9e 100644 --- a/twenty-first/src/math/x_field_element.rs +++ b/twenty-first/src/math/x_field_element.rs @@ -670,7 +670,6 @@ mod tests { use proptest::collection::vec; use proptest::prelude::*; use proptest_arbitrary_interop::arb; - use test_strategy::proptest; use super::*; use crate::bfe; @@ -678,6 +677,8 @@ mod tests { use crate::math::ntt::intt; use crate::math::ntt::ntt; use crate::math::other::random_elements; + use crate::tests::proptest; + use crate::tests::test; impl proptest::arbitrary::Arbitrary for XFieldElement { type Parameters = (); @@ -689,13 +690,13 @@ mod tests { type Strategy = BoxedStrategy; } - #[test] + #[macro_rules_attr::apply(test)] fn display_is_as_expected() { assert_eq!("42_xfe", xfe!(42).to_string()); assert_eq!("(3·x² + 2·x + 1)", xfe!([1, 2, 3]).to_string()); } - #[test] + #[macro_rules_attr::apply(test)] fn one_zero_test() { let one = XFieldElement::one(); assert!(one.is_one()); @@ -732,7 +733,7 @@ mod tests { assert!(!one_as_constant_term_1.is_zero()); } - #[test] + #[macro_rules_attr::apply(test)] fn x_field_random_element_generation_test() { let rand_xs: Vec = random_elements(14); assert_eq!(14, rand_xs.len()); @@ -741,12 +742,12 @@ mod tests { assert!(rand_xs.into_iter().all_unique()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn unlifting_random_xfe_doesnt_work(xfe: XFieldElement) { prop_assert!(xfe.unlift().is_none()); } - #[test] + #[macro_rules_attr::apply(test)] fn summing_gives_expected_result() { let empty_sum = [].into_iter().sum(); assert_eq!(XFieldElement::ZERO, empty_sum); @@ -760,14 +761,14 @@ mod tests { assert_eq!(xfe!([41, 52, 63]), sum); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn bfe_vector_of_correct_length_can_become_xfe( #[strategy(vec(arb(), EXTENSION_DEGREE))] bfes: Vec, ) { prop_assert!(XFieldElement::try_from(bfes).is_ok()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn bfe_vector_of_incorrect_length_cannot_become_xfe( #[filter(#bfes.len() != EXTENSION_DEGREE)] bfes: Vec, ) { @@ -776,7 +777,7 @@ mod tests { /// To be removed once [XFieldElement::increment] and /// [XFieldElement::decrement] are gone. - #[test] + #[macro_rules_attr::apply(test)] #[expect(deprecated)] fn incr_decr_test() { let one_const = XFieldElement::new([1, 0, 0].map(BFieldElement::new)); @@ -838,7 +839,7 @@ mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] fn x_field_add_test() { let poly1 = XFieldElement::new([2, 0, 0].map(BFieldElement::new)); let poly2 = XFieldElement::new([3, 0, 0].map(BFieldElement::new)); @@ -871,7 +872,7 @@ mod tests { assert_eq!(poly_sum, poly9 + poly10); } - #[test] + #[macro_rules_attr::apply(test)] fn x_field_sub_test() { let poly1 = XFieldElement::new([2, 0, 0].map(BFieldElement::new)); let poly2 = XFieldElement::new([3, 0, 0].map(BFieldElement::new)); @@ -904,7 +905,7 @@ mod tests { assert_eq!(poly_diff, poly10 - poly9); } - #[test] + #[macro_rules_attr::apply(test)] fn x_field_mul_test() { let poly1 = XFieldElement::new([2, 0, 0].map(BFieldElement::new)); let poly2 = XFieldElement::new([3, 0, 0].map(BFieldElement::new)); @@ -947,7 +948,7 @@ mod tests { assert_eq!(poly1112_product, poly11 * poly12); } - #[test] + #[macro_rules_attr::apply(test)] fn x_field_overloaded_arithmetic_test() { let mut rng = rand::rng(); for _ in 0..100 { @@ -977,7 +978,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn x_field_into_test() { let zero_poly: XFieldElement = Polynomial::::new(vec![]).into(); assert!(zero_poly.is_zero()); @@ -990,7 +991,7 @@ mod tests { assert!(neg_shah_zero.is_zero()); } - #[test] + #[macro_rules_attr::apply(test)] fn x_field_xgcp_test() { // Verify expected properties of XGCP: symmetry and that gcd is always // one. gcd is always one for all field elements. @@ -1055,7 +1056,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn x_field_inv_test() { let one = XFieldElement::new([1, 0, 0].map(BFieldElement::new)); let one_inv = one.inverse(); @@ -1109,7 +1110,7 @@ mod tests { } } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn field_element_inversion( #[filter(!#x.is_zero())] x: XFieldElement, #[filter(!#disturbance.is_zero())] @@ -1122,7 +1123,7 @@ mod tests { prop_assert_ne!(XFieldElement::ONE, x * not_x.inverse()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn field_element_batch_inversion( #[filter(!#xs.iter().any(|x| x.is_zero()))] xs: Vec, ) { @@ -1132,7 +1133,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn mul_xfe_with_bfe_pbt() { let test_iterations = 100; let rands_x: Vec = random_elements(test_iterations); @@ -1150,7 +1151,7 @@ mod tests { } } - #[proptest(cases = 1_000)] + #[macro_rules_attr::apply(proptest(cases = 1_000))] fn x_field_division_mul_pbt(a: XFieldElement, b: XFieldElement) { let a_b = a * b; let b_a = b * a; @@ -1214,7 +1215,7 @@ mod tests { prop_assert_eq!(a_minus_b_field_b_as_b, a_minus_b_field_b_as_x); } - #[test] + #[macro_rules_attr::apply(test)] fn xfe_mod_pow_zero() { assert!(XFieldElement::ZERO.mod_pow_u32(0).is_one()); assert!(XFieldElement::ZERO.mod_pow_u64(0).is_one()); @@ -1222,7 +1223,7 @@ mod tests { assert!(XFieldElement::ONE.mod_pow_u64(0).is_one()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn xfe_mod_pow(base: XFieldElement, #[strategy(0_u32..200)] exponent: u32) { let mut acc = XFieldElement::ONE; for i in 0..exponent { @@ -1231,7 +1232,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn xfe_mod_pow_static() { let three_to_the_n = |n| xfe!(3).mod_pow_u64(n); let actual = [0, 1, 2, 3, 4, 5].map(three_to_the_n); @@ -1239,7 +1240,7 @@ mod tests { assert_eq!(expected, actual); } - #[proptest(cases = 100)] + #[macro_rules_attr::apply(proptest(cases = 100))] fn xfe_intt_is_inverse_of_xfe_ntt( #[strategy(1..=11)] #[map(|log| 1_usize << log)] @@ -1252,7 +1253,7 @@ mod tests { prop_assert_eq!(inputs, rv); } - #[proptest(cases = 40)] + #[macro_rules_attr::apply(proptest(cases = 40))] fn xfe_ntt_corresponds_to_polynomial_evaluation( #[strategy(1..=11)] #[map(|log_2| 1_u64 << log_2)] @@ -1269,34 +1270,34 @@ mod tests { prop_assert_eq!(evaluations, rv); } - #[test] + #[macro_rules_attr::apply(test)] fn inverse_or_zero_of_zero_is_zero() { let zero = XFieldElement::ZERO; assert_eq!(zero, zero.inverse_or_zero()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn inverse_or_zero_of_non_zero_is_inverse(#[filter(!#xfe.is_zero())] xfe: XFieldElement) { let inv = xfe.inverse_or_zero(); prop_assert_ne!(XFieldElement::ZERO, inv); prop_assert_eq!(XFieldElement::ONE, xfe * inv); } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "Cannot invert the zero element in the extension field.")] fn multiplicative_inverse_of_zero() { let zero = XFieldElement::ZERO; let _ = zero.inverse(); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn xfe_to_digest_to_xfe_is_invariant(xfe: XFieldElement) { let digest: Digest = xfe.into(); let xfe2: XFieldElement = digest.try_into().unwrap(); assert_eq!(xfe, xfe2); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn converting_random_digest_to_xfield_element_fails(digest: Digest) { if XFieldElement::try_from(digest).is_ok() { let reason = "Should not be able to convert random `Digest` to an `XFieldElement`."; @@ -1304,7 +1305,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn xfe_macro_can_be_used() { let x = xfe!(42); let _ = xfe!(42u32); @@ -1323,19 +1324,19 @@ mod tests { assert_eq!(m.to_vec(), n); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn xfe_macro_produces_same_result_as_calling_new(coeffs: [BFieldElement; EXTENSION_DEGREE]) { let xfe = XFieldElement::new(coeffs); prop_assert_eq!(xfe, xfe!(coeffs)); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn xfe_macro_produces_same_result_as_calling_new_const(scalar: BFieldElement) { let xfe = XFieldElement::new_const(scalar); prop_assert_eq!(xfe, xfe!(scalar)); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn as_flat_slice_produces_expected_slices(xfes: Vec) { let bfes = xfes.iter().flat_map(|&x| x.coefficients).collect_vec(); prop_assert_eq!(&bfes, as_flat_slice(&xfes)); diff --git a/twenty-first/src/math/zerofier_tree.rs b/twenty-first/src/math/zerofier_tree.rs index 993b3d2b1..f470df746 100644 --- a/twenty-first/src/math/zerofier_tree.rs +++ b/twenty-first/src/math/zerofier_tree.rs @@ -101,23 +101,25 @@ where #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] -mod test { +mod tests { use num_traits::ConstZero; use num_traits::Zero; use proptest::collection::vec; use proptest::prop_assert_eq; use proptest_arbitrary_interop::arb; - use test_strategy::proptest; use crate::math::zerofier_tree::ZerofierTree; use crate::prelude::BFieldElement; use crate::prelude::Polynomial; + use crate::tests::proptest; + use crate::tests::test; - #[test] + #[macro_rules_attr::apply(test)] fn zerofier_tree_can_be_empty() { ZerofierTree::::new_from_domain(&[]); } - #[proptest] + + #[macro_rules_attr::apply(proptest)] fn zerofier_tree_root_is_multiple_of_children( #[strategy(vec(arb(), 2*ZerofierTree::::RECURSION_CUTOFF_THRESHOLD))] points: Vec, @@ -136,7 +138,7 @@ mod test { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn zerofier_tree_root_has_right_degree( #[strategy(vec(arb(), 1..(1<<10)))] points: Vec, ) { @@ -144,7 +146,7 @@ mod test { prop_assert_eq!(points.len(), zerofier_tree.zerofier().degree() as usize); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn zerofier_tree_root_zerofies( #[strategy(vec(arb(), 1..(1<<10)))] points: Vec, #[strategy(0usize..#points.len())] index: usize, @@ -156,7 +158,7 @@ mod test { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn zerofier_tree_and_polynomial_agree_on_zerofiers( #[strategy(vec(arb(), 1..(1<<10)))] points: Vec, ) { diff --git a/twenty-first/src/tip5/digest.rs b/twenty-first/src/tip5/digest.rs index dbf6d83e0..7b805e4e4 100644 --- a/twenty-first/src/tip5/digest.rs +++ b/twenty-first/src/tip5/digest.rs @@ -280,11 +280,12 @@ pub(crate) mod tests { use proptest::prelude::Arbitrary as ProptestArbitrary; use proptest::prelude::*; use proptest_arbitrary_interop::arb; - use test_strategy::proptest; use super::*; use crate::error::ParseBFieldElementError; use crate::prelude::*; + use crate::tests::proptest; + use crate::tests::test; impl ProptestArbitrary for Digest { type Parameters = (); @@ -321,7 +322,7 @@ pub(crate) mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn digest_corruptor_rejects_uncorrupting_corruption() { let digest = Digest(bfe_array![1, 2, 3, 4, 5]); let corruptor = DigestCorruptor { @@ -332,7 +333,7 @@ pub(crate) mod tests { assert!(matches!(err, TestCaseError::Reject(_))); } - #[test] + #[macro_rules_attr::apply(test)] fn display_is_as_expected() { let digest = Digest::new(bfe_array![1, 2, 3, 4, 5]); assert_eq!("1,2,3,4,5", format!("{digest}")); @@ -342,7 +343,7 @@ pub(crate) mod tests { assert_eq!(hex_digest, format!("{digest:x}")); } - #[test] + #[macro_rules_attr::apply(test)] fn get_size() { let stack = Digest::get_stack_size(); @@ -355,7 +356,7 @@ pub(crate) mod tests { assert_eq!(stack + heap, total) } - #[test] + #[macro_rules_attr::apply(test)] fn digest_from_str() { let valid_digest_string = "12063201067205522823,\ 1529663126377206632,\ @@ -374,12 +375,12 @@ pub(crate) mod tests { assert!(second_invalid_digest.is_err()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn test_reversed_involution(digest: Digest) { prop_assert_eq!(digest, digest.reversed().reversed()) } - #[test] + #[macro_rules_attr::apply(test)] fn digest_biguint_conversion_simple_test() { let fourteen: BigUint = 14u128.into(); let fourteen_converted_expected = Digest(bfe_array![14, 0, 0, 0, 0]); @@ -443,7 +444,7 @@ pub(crate) mod tests { assert_eq!(two_pow_315, two_pow_315_converted_expected.into()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn digest_biguint_conversion_pbt(components_0: [u64; 4], component_1: u32) { let big_uint = components_0 .into_iter() @@ -455,7 +456,7 @@ pub(crate) mod tests { prop_assert_eq!(big_uint, big_uint_again); } - #[test] + #[macro_rules_attr::apply(test)] fn digest_ordering() { let val0 = Digest::new(bfe_array![0; Digest::LEN]); let val1 = Digest::new(bfe_array![14, 0, 0, 0, 0]); @@ -477,7 +478,7 @@ pub(crate) mod tests { assert!(val4 > val0); } - #[test] + #[macro_rules_attr::apply(test)] fn digest_biguint_overflow_test() { let mut two_pow_384: BigUint = (1u128 << 96).into(); two_pow_384 = two_pow_384.pow(4); @@ -486,14 +487,14 @@ pub(crate) mod tests { assert_eq!(TryFromDigestError::Overflow, err); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn digest_to_bfe_vector_involution(digest: Digest) { let bfes = >::from(digest); let digest_again = Digest::try_from(bfes)?; prop_assert_eq!(digest, digest_again); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn bfe_vector_of_incorrect_length_cannot_become_a_digest( #[filter(#bfes.len() != Digest::LEN)] bfes: Vec, ) { @@ -504,7 +505,7 @@ pub(crate) mod tests { prop_assert_eq!(bfes_len, len); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn forty_bytes_can_be_converted_to_digest(bytes: [u8; Digest::BYTES]) { let digest = Digest::try_from(bytes).unwrap(); let bytes_again: [u8; Digest::BYTES] = digest.into(); @@ -512,7 +513,7 @@ pub(crate) mod tests { } // note: for background on this test, see issue 195 - #[test] + #[macro_rules_attr::apply(test)] fn try_from_bytes_not_canonical() -> Result<(), TryFromDigestError> { let bytes: [u8; Digest::BYTES] = [255; Digest::BYTES]; @@ -525,7 +526,7 @@ pub(crate) mod tests { } // note: for background on this test, see issue 195 - #[test] + #[macro_rules_attr::apply(test)] fn from_str_not_canonical() -> Result<(), TryFromDigestError> { let str = format!("0,0,0,0,{}", u64::MAX); @@ -537,7 +538,7 @@ pub(crate) mod tests { Ok(()) } - #[test] + #[macro_rules_attr::apply(test)] fn bytes_in_matches_bytes_out() -> Result<(), TryFromDigestError> { let bytes1: [u8; Digest::BYTES] = [254; Digest::BYTES]; let d1 = Digest::try_from(bytes1)?; @@ -551,13 +552,15 @@ pub(crate) mod tests { Ok(()) } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn any_digest_can_be_hashed(digest: Digest) { digest.hash(); } mod hex_test { use super::*; + use crate::tests::proptest; + use crate::tests::test; pub(super) fn hex_examples() -> Vec<(Digest, &'static str)> { vec![ @@ -584,14 +587,14 @@ pub(crate) mod tests { ] } - #[test] + #[macro_rules_attr::apply(test)] fn digest_to_hex() { for (digest, hex) in hex_examples() { assert_eq!(&digest.to_hex(), hex); } } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn to_hex_and_from_hex_are_reciprocal_proptest(bytes: [u8; Digest::BYTES]) { let digest = Digest::try_from(bytes).unwrap(); let hex = digest.to_hex(); @@ -609,7 +612,7 @@ pub(crate) mod tests { prop_assert_eq!(digest, digest_from_upper_hex); } - #[test] + #[macro_rules_attr::apply(test)] fn to_hex_and_from_hex_are_reciprocal() -> Result<(), TryFromHexDigestError> { let hex_vals = vec![ "00000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -625,7 +628,7 @@ pub(crate) mod tests { Ok(()) } - #[test] + #[macro_rules_attr::apply(test)] fn digest_from_hex() -> Result<(), TryFromHexDigestError> { for (digest, hex) in hex_examples() { assert_eq!(digest, Digest::try_from_hex(hex)?); @@ -634,7 +637,7 @@ pub(crate) mod tests { Ok(()) } - #[test] + #[macro_rules_attr::apply(test)] fn digest_from_invalid_hex_errors() { use hex::FromHexError; @@ -672,8 +675,9 @@ pub(crate) mod tests { mod json_test { use super::*; + use crate::tests::test; - #[test] + #[macro_rules_attr::apply(test)] fn serialize() -> Result<(), serde_json::Error> { for (digest, hex) in hex_examples() { assert_eq!(serde_json::to_string(&digest)?, format!("\"{hex}\"")); @@ -681,7 +685,7 @@ pub(crate) mod tests { Ok(()) } - #[test] + #[macro_rules_attr::apply(test)] fn deserialize() -> Result<(), serde_json::Error> { for (digest, hex) in hex_examples() { let json_hex = format!("\"{hex}\""); @@ -694,6 +698,7 @@ pub(crate) mod tests { mod bincode_test { use super::*; + use crate::tests::test; fn bincode_examples() -> Vec<(Digest, [u8; Digest::BYTES])> { vec![ @@ -708,14 +713,14 @@ pub(crate) mod tests { ] } - #[test] + #[macro_rules_attr::apply(test)] fn serialize() { for (digest, bytes) in bincode_examples() { assert_eq!(bincode::serialize(&digest).unwrap(), bytes); } } - #[test] + #[macro_rules_attr::apply(test)] fn deserialize() { for (digest, bytes) in bincode_examples() { assert_eq!(bincode::deserialize::(&bytes).unwrap(), digest); diff --git a/twenty-first/src/tip5/inverse.rs b/twenty-first/src/tip5/inverse.rs index 3048086dd..f7885cc9d 100644 --- a/twenty-first/src/tip5/inverse.rs +++ b/twenty-first/src/tip5/inverse.rs @@ -113,11 +113,12 @@ impl InverseTip5 { #[cfg(test)] mod tests { use proptest::prelude::*; - use test_strategy::proptest; use super::*; + use crate::tests::proptest; + use crate::tests::test; - #[test] + #[macro_rules_attr::apply(test)] fn inv_lookup_table_is_inv_of_lookup_table() { for (idx, looked_up) in LOOKUP_TABLE.into_iter().enumerate() { let idx_again = INV_LOOKUP_TABLE[looked_up as usize] as usize; @@ -125,19 +126,19 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn inv_power_map_exponent_is_bezout_coefficient_of_7() { let one = (INV_POWER_MAP_EXPONENT as u128 * 7) % (BFieldElement::P as u128 - 1); assert_eq!(1, one); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn inv_power_map_exponent_computes_the_correct_root(bfe: BFieldElement) { let bfe_again = bfe.mod_pow(7).mod_pow(INV_POWER_MAP_EXPONENT); prop_assert_eq!(bfe, bfe_again); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn sbox_layer(mut tip5: Tip5) { let orig_state = tip5.state; tip5.sbox_layer(); @@ -147,7 +148,7 @@ mod tests { prop_assert_eq!(orig_state, inverse_tip5.state); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn mds_matrix_mul(mut tip5: Tip5) { let orig_state = tip5.state; tip5.mds_generated(); @@ -157,7 +158,7 @@ mod tests { prop_assert_eq!(orig_state, inverse_tip5.state); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn round(mut tip5: Tip5, #[strategy(0..NUM_ROUNDS)] round_idx: usize) { let orig_state = tip5.state; tip5.round(round_idx); @@ -167,7 +168,7 @@ mod tests { prop_assert_eq!(orig_state, inverse_tip5.state); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn permutation(mut tip5: Tip5) { let orig_state = tip5.state; tip5.permutation(); diff --git a/twenty-first/src/tip5/mod.rs b/twenty-first/src/tip5/mod.rs index 923181199..d102a60d5 100644 --- a/twenty-first/src/tip5/mod.rs +++ b/twenty-first/src/tip5/mod.rs @@ -732,11 +732,12 @@ pub(crate) mod tests { use rand::RngCore; use rayon::prelude::IntoParallelIterator; use rayon::prelude::ParallelIterator; - use test_strategy::proptest; use super::*; use crate::math::other::random_elements; use crate::math::x_field_element::XFieldElement; + use crate::tests::proptest; + use crate::tests::test; impl proptest::arbitrary::Arbitrary for Tip5 { type Parameters = (); @@ -1025,12 +1026,12 @@ pub(crate) mod tests { } } - #[proptest(cases = 10)] + #[macro_rules_attr::apply(proptest)] fn get_size(tip5: Tip5) { assert_eq!(STATE_SIZE * BFieldElement::ZERO.get_size(), tip5.get_size()); } - #[test] + #[macro_rules_attr::apply(test)] fn lookup_table_is_correct() { let table: [u8; 256] = (0..256) .map(|t| Tip5::offset_fermat_cube_map(t as u16) as u8) @@ -1051,7 +1052,7 @@ pub(crate) mod tests { }); } - #[test] + #[macro_rules_attr::apply(test)] fn round_constants_are_correct() { let to_int = |bytes: &[u8]| { bytes @@ -1083,7 +1084,7 @@ pub(crate) mod tests { }); } - #[test] + #[macro_rules_attr::apply(test)] fn test_fermat_cube_map_is_permutation() { let mut touched = [false; 256]; for i in 0..256 { @@ -1118,7 +1119,7 @@ pub(crate) mod tests { /// sure that all round constants have the required property. /// /// See also: https://www.hyrumslaw.com/. Sorry about that. - #[proptest] + #[macro_rules_attr::apply(proptest)] fn adding_degenerate_lhs_and_small_enough_rhs_makes_sum_non_degenerate( #[strategy(BFieldElement::P..)] raw_a: u64, #[strategy(0..BFieldElement::P + 2 - (1 << 32))] raw_b: u64, @@ -1133,7 +1134,7 @@ pub(crate) mod tests { /// /// [`adding_degenerate_lhs_and_small_enough_rhs_makes_sum_non_degenerate`] /// explains the requirement in greater detail. - #[test] + #[macro_rules_attr::apply(test)] fn round_constants_correct_degenerate_lhs_when_adding() { for constant in ROUND_CONSTANTS { assert!(constant.raw_u64() < BFieldElement::P + 2 - (1 << 32)); @@ -1141,7 +1142,7 @@ pub(crate) mod tests { } /// Ensure that the claims made in [`Tip5::mds_generated`] are true. - #[test] + #[macro_rules_attr::apply(test)] fn tip5_recovers_from_degenerate_field_element_representations() { let state = [ 0x1063_c4bf_5d8b_b0dd, @@ -1204,7 +1205,7 @@ pub(crate) mod tests { assert_eq!(expected, naive.state); } - #[test] + #[macro_rules_attr::apply(test)] fn calculate_differential_uniformity() { // cargo test calculate_differential_uniformity -- --include-ignored --nocapture // addition-differential @@ -1264,7 +1265,7 @@ pub(crate) mod tests { println!("bitwise differential uniformity for fermat cube map: {du_fermat_bitwise}"); } - #[test] + #[macro_rules_attr::apply(test)] fn calculate_approximation_quality() { let mut fermat_cubed = [0u16; 65536]; let mut bfield_cubed = [0u16; 65536]; @@ -1293,7 +1294,7 @@ pub(crate) mod tests { const MAGIC_SNAPSHOT_HEX: &str = "109cc2fe453bd9962f754b96d8f5b919b60af030940a275f5540da195fef65ee651c1b6fa19b2c6a"; - #[test] + #[macro_rules_attr::apply(test)] fn hash10_test_vectors_snapshot() { let mut preimage = [BFieldElement::ZERO; RATE]; for i in 0..6 { @@ -1304,7 +1305,7 @@ pub(crate) mod tests { assert_eq!(MAGIC_SNAPSHOT_HEX, final_digest); } - #[test] + #[macro_rules_attr::apply(test)] fn hash_varlen_test_vectors() { let mut digest_sum = [BFieldElement::ZERO; Digest::LEN]; for i in 0..20 { @@ -1323,7 +1324,7 @@ pub(crate) mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] fn snapshot() { let state = [ 0x0000_000f_ffff_fff0, @@ -1368,7 +1369,7 @@ pub(crate) mod tests { Digest::new((&squeeze_result[..Digest::LEN]).try_into().unwrap()) } - #[test] + #[macro_rules_attr::apply(test)] fn hash_var_len_equivalence_edge_cases() { for preimage_length in 0..=11 { let preimage = vec![BFieldElement::new(42); preimage_length]; @@ -1379,14 +1380,14 @@ pub(crate) mod tests { } } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn hash_var_len_equivalence(#[strategy(arb())] preimage: Vec) { let hash_varlen_digest = Tip5::hash_varlen(&preimage); let digest_through_pad_squeeze_absorb = manual_hash_varlen(&preimage); prop_assert_eq!(digest_through_pad_squeeze_absorb, hash_varlen_digest); } - #[test] + #[macro_rules_attr::apply(test)] fn test_linearity_of_mds() { type SpongeState = [BFieldElement; STATE_SIZE]; @@ -1417,7 +1418,7 @@ pub(crate) mod tests { assert_eq!(w, w_); } - #[test] + #[macro_rules_attr::apply(test)] fn test_mds_circulancy() { let mut sponge = Tip5::init(); sponge.state = [BFieldElement::ZERO; STATE_SIZE]; @@ -1453,7 +1454,7 @@ pub(crate) mod tests { assert_eq!(sponge_2.state, mv); } - #[test] + #[macro_rules_attr::apply(test)] fn test_complex_karatsuba() { const N: usize = 4; let mut f = [(0i64, 0i64); N]; @@ -1471,7 +1472,7 @@ pub(crate) mod tests { assert_eq!(h0, h1); } - #[test] + #[macro_rules_attr::apply(test)] fn test_complex_product() { let mut rng = rand::rng(); let mut random_small_i64 = || (rng.next_u32() % (1 << 16)) as i64; @@ -1484,7 +1485,7 @@ pub(crate) mod tests { } } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn sample_scalars(mut tip5: Tip5, #[strategy(0_usize..=100)] num_scalars: usize) { tip5.permutation(); // remove any 0s that exist due to shrinking @@ -1504,7 +1505,7 @@ pub(crate) mod tests { target_feature = "avx512bw", target_feature = "avx512vbmi" )))] - #[proptest] + #[macro_rules_attr::apply(proptest)] fn test_mds_matrix_mul_methods_agree(state: [BFieldElement; STATE_SIZE]) { let mut sponge_cyclomut = Tip5 { state }; let mut sponge_generated = Tip5 { state }; @@ -1521,7 +1522,7 @@ pub(crate) mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] fn tip5_hasher_trait_snapshot_test() { let mut hasher = Tip5::init(); let data = b"hello world"; @@ -1529,7 +1530,7 @@ pub(crate) mod tests { assert_eq!(hasher.finish(), 2267905471610932299); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn tip5_hasher_consumes_small_data(#[filter(!#bytes.is_empty())] bytes: Vec) { let mut hasher = Tip5::init(); bytes.hash(&mut hasher); @@ -1537,7 +1538,7 @@ pub(crate) mod tests { prop_assert_ne!(Tip5::init().finish(), hasher.finish()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn appending_small_data_to_big_data_changes_tip5_hash( #[any(size_range(2_000..8_000).lift())] big_data: Vec, #[filter(!#small_data.is_empty())] small_data: Vec, @@ -1553,7 +1554,7 @@ pub(crate) mod tests { prop_assert_ne!(big_data_hash, all_data_hash); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn tip5_trace_starts_with_initial_state_and_is_equivalent_to_permutation(mut tip5: Tip5) { let [first, .., last] = tip5.clone().trace(); prop_assert_eq!(first, tip5.state); diff --git a/twenty-first/src/tip5/naive.rs b/twenty-first/src/tip5/naive.rs index 38f64e775..fec607d8b 100644 --- a/twenty-first/src/tip5/naive.rs +++ b/twenty-first/src/tip5/naive.rs @@ -84,13 +84,13 @@ impl PartialEq for NaiveTip5 { #[cfg(test)] mod tests { use proptest::prelude::*; - use test_strategy::proptest; use super::*; + use crate::tests::proptest; /// The entire point of this module: test the naïve against the optimized /// Tip5 implementation. - #[proptest] + #[macro_rules_attr::apply(proptest)] fn tip5_corresponds_to_naive_tip5( state: [BFieldElement; STATE_SIZE], #[strategy(0..NUM_ROUNDS)] round_no: usize, diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index fded25392..09b269370 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -951,9 +951,10 @@ pub(crate) mod tests { use proptest::collection::vec; use proptest::prelude::*; use proptest_arbitrary_interop::arb; - use test_strategy::proptest; use super::*; + use crate::tests::proptest; + use crate::tests::test; use crate::tip5::digest::tests::DigestCorruptor; impl MerkleTree { @@ -1002,14 +1003,14 @@ pub(crate) mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn building_merkle_tree_from_empty_list_of_digests_fails_with_expected_error() { let maybe_tree = MerkleTree::par_new(&[]); let err = maybe_tree.unwrap_err(); assert_eq!(MerkleTreeError::TooFewLeafs, err); } - #[test] + #[macro_rules_attr::apply(test)] fn merkle_tree_with_one_leaf_has_expected_height_and_number_of_leafs() { let digest = Digest::default(); let tree = MerkleTree::par_new(&[digest]).unwrap(); @@ -1017,13 +1018,13 @@ pub(crate) mod tests { assert_eq!(0, tree.height()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn building_merkle_tree_from_one_digest_makes_that_digest_the_root(digest: Digest) { let tree = MerkleTree::par_new(&[digest]).unwrap(); assert_eq!(digest, tree.root()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn building_merkle_tree_from_list_of_digests_with_incorrect_number_of_leafs_fails( #[filter(!#num_leafs.is_power_of_two())] #[strategy(1_usize..1 << 13)] @@ -1036,14 +1037,14 @@ pub(crate) mod tests { assert_eq!(MerkleTreeError::IncorrectNumberOfLeafs, err); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn merkle_tree_construction_strategies_behave_identically_on_random_input(leafs: Vec) { let sequential = MerkleTree::sequential_new(&leafs); let parallel = MerkleTree::par_new(&leafs); prop_assert_eq!(sequential, parallel); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn merkle_tree_construction_strategies_produce_identical_trees( #[strategy(0_usize..10)] _tree_height: usize, #[strategy(vec(arb(), 1 << #_tree_height))] leafs: Vec, @@ -1053,7 +1054,7 @@ pub(crate) mod tests { prop_assert_eq!(sequential, parallel); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn merkle_tree_construction_strategies_are_independent_of_parallelization_cutoff( #[strategy(0_usize..10)] _tree_height: usize, #[strategy(vec(arb(), 1 << #_tree_height))] leafs: Vec, @@ -1066,7 +1067,7 @@ pub(crate) mod tests { prop_assert_eq!(sequential, parallel); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn ram_frugal_merkle_root_is_identical_to_full_tree_root( #[strategy(0_usize..10)] _tree_height: usize, #[strategy(vec(arb(), 1 << #_tree_height))] leafs: Vec, @@ -1079,7 +1080,7 @@ pub(crate) mod tests { prop_assert_eq!(par_frugal, hungry); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn ram_frugal_merkle_root_is_independent_of_parallelization_cutoff( #[strategy(0_usize..10)] _tree_height: usize, #[strategy(vec(arb(), 1 << #_tree_height))] leafs: Vec, @@ -1095,7 +1096,7 @@ pub(crate) mod tests { prop_assert_eq!(par_frugal, hungry); } - #[proptest(cases = 100)] + #[macro_rules_attr::apply(proptest(cases = 100))] fn various_small_parallelization_cutoffs_dont_cause_infinite_iterations( #[strategy(0_usize..10)] _tree_height: usize, #[strategy(vec(arb(), 1 << #_tree_height))] leafs: Vec, @@ -1107,7 +1108,7 @@ pub(crate) mod tests { } } - #[proptest(cases = 100)] + #[macro_rules_attr::apply(proptest(cases = 100))] fn accessing_number_of_leafs_and_height_never_panics( #[strategy(arb())] merkle_tree: MerkleTree, ) { @@ -1115,7 +1116,7 @@ pub(crate) mod tests { let _ = merkle_tree.height(); } - #[proptest(cases = 50)] + #[macro_rules_attr::apply(proptest(cases = 50))] fn trivial_proof_can_be_verified(#[strategy(arb())] merkle_tree: MerkleTree) { let proof = merkle_tree.inclusion_proof_for_leaf_indices(&[]).unwrap(); prop_assert!(proof.authentication_structure.is_empty()); @@ -1123,14 +1124,14 @@ pub(crate) mod tests { prop_assert!(verdict); } - #[proptest(cases = 40)] + #[macro_rules_attr::apply(proptest(cases = 40))] fn honestly_generated_authentication_structure_can_be_verified(test_tree: MerkleTreeToTest) { let proof = test_tree.proof(); let verdict = proof.verify(test_tree.tree.root()); prop_assert!(verdict); } - #[proptest(cases = 30)] + #[macro_rules_attr::apply(proptest(cases = 30))] fn corrupt_root_leads_to_verification_failure( #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, corruptor: DigestCorruptor, @@ -1141,7 +1142,7 @@ pub(crate) mod tests { prop_assert!(!verdict); } - #[proptest(cases = 20)] + #[macro_rules_attr::apply(proptest(cases = 20))] fn corrupt_authentication_structure_leads_to_verification_failure( #[filter(!#test_tree.proof().authentication_structure.is_empty())] test_tree: MerkleTreeToTest, @@ -1166,7 +1167,7 @@ pub(crate) mod tests { prop_assert!(!verdict); } - #[proptest(cases = 30)] + #[macro_rules_attr::apply(proptest(cases = 30))] fn corrupt_leaf_digests_lead_to_verification_failure( #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, #[strategy(Just(#test_tree.proof().indexed_leafs.len()))] _n_leafs: usize, @@ -1190,7 +1191,7 @@ pub(crate) mod tests { prop_assert!(!verdict); } - #[proptest(cases = 30)] + #[macro_rules_attr::apply(proptest(cases = 30))] fn removing_leafs_from_proof_leads_to_verification_failure( #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, #[strategy(Just(#test_tree.proof().indexed_leafs.len()))] _n_leafs: usize, @@ -1213,7 +1214,7 @@ pub(crate) mod tests { prop_assert!(!verdict); } - #[proptest(cases = 30)] + #[macro_rules_attr::apply(proptest(cases = 30))] fn checking_set_inclusion_of_items_not_in_set_leads_to_verification_failure( #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, #[strategy(vec(0..#test_tree.tree.num_leafs(), 1..=(#test_tree.tree.num_leafs())))] @@ -1231,7 +1232,7 @@ pub(crate) mod tests { prop_assert!(!verdict); } - #[proptest(cases = 30)] + #[macro_rules_attr::apply(proptest(cases = 30))] fn honestly_generated_proof_with_duplicate_leafs_can_be_verified( #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, #[strategy(Just(#test_tree.proof().indexed_leafs.len()))] _n_leafs: usize, @@ -1247,7 +1248,7 @@ pub(crate) mod tests { prop_assert!(verdict); } - #[proptest(cases = 40)] + #[macro_rules_attr::apply(proptest(cases = 40))] fn incorrect_tree_height_leads_to_verification_failure( #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, #[strategy(0_u32..64)] @@ -1260,7 +1261,7 @@ pub(crate) mod tests { prop_assert!(!verdict); } - #[proptest(cases = 20)] + #[macro_rules_attr::apply(proptest(cases = 20))] fn honestly_generated_proof_with_all_leafs_revealed_can_be_verified( #[strategy(arb())] tree: MerkleTree, ) { @@ -1272,7 +1273,7 @@ pub(crate) mod tests { prop_assert!(verdict); } - #[proptest(cases = 30)] + #[macro_rules_attr::apply(proptest(cases = 30))] fn requesting_inclusion_proof_for_nonexistent_leaf_fails_with_expected_error( #[strategy(arb())] tree: MerkleTree, #[filter(#leaf_indices.iter().any(|&i| i > #tree.num_leafs()))] leaf_indices: Vec< @@ -1285,7 +1286,7 @@ pub(crate) mod tests { assert_eq!(MerkleTreeError::LeafIndexInvalid, err); } - #[test] + #[macro_rules_attr::apply(test)] fn authentication_paths_of_extremely_small_tree_use_expected_digests() { // _ 1_ // / \ @@ -1305,7 +1306,7 @@ pub(crate) mod tests { assert_eq!(auth_path_with_nodes([6, 2]), auth_path_for_leaf(3)); } - #[test] + #[macro_rules_attr::apply(test)] fn authentication_paths_of_very_small_tree_use_expected_digests() { // ──── 1 ──── // ╱ ╲ @@ -1332,7 +1333,7 @@ pub(crate) mod tests { assert_eq!(auth_path_with_nodes([14, 6, 2]), auth_path_for_leaf(7)); } - #[test] + #[macro_rules_attr::apply(test)] fn authentication_paths_of_very_small_tree_are_identical_when_using_tree_or_only_leafs() { let tree = MerkleTree::test_tree_of_height(3); let leafs = tree.leafs().copied().collect_vec(); @@ -1350,7 +1351,7 @@ pub(crate) mod tests { } } - #[proptest(cases = 100)] + #[macro_rules_attr::apply(proptest(cases = 100))] fn auth_structure_is_independent_of_compute_method(test_tree: MerkleTreeToTest) { let tree = test_tree.tree; let selected_indices = test_tree.selected_indices; @@ -1366,7 +1367,7 @@ pub(crate) mod tests { prop_assert_eq!(&cached_auth_structure, &par_auth_structure); } - #[test] + #[macro_rules_attr::apply(test)] fn subtree_leafs_are_actually_sub_tree_leafs() { let tree = MerkleTree::test_tree_of_height(5); let leafs = tree.leafs().copied().collect_vec(); @@ -1385,7 +1386,7 @@ pub(crate) mod tests { } } - #[proptest(cases = 10)] + #[macro_rules_attr::apply(proptest(cases = 10))] fn each_leaf_can_be_verified_individually(test_tree: MerkleTreeToTest) { let tree = test_tree.tree; for (leaf_index, &leaf) in tree.leafs().enumerate() { @@ -1400,7 +1401,7 @@ pub(crate) mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn partial_merkle_tree_built_from_authentication_structure_contains_expected_nodes() { let merkle_tree = MerkleTree::test_tree_of_height(3); let proof = merkle_tree @@ -1424,7 +1425,7 @@ pub(crate) mod tests { assert_eq!(expected_node_indices, node_indices); } - #[test] + #[macro_rules_attr::apply(test)] fn manually_constructed_partial_tree_can_be_filled() { // ──── _ ─── // ╱ ╲ @@ -1446,7 +1447,7 @@ pub(crate) mod tests { partial_tree.fill().unwrap(); } - #[test] + #[macro_rules_attr::apply(test)] fn trying_to_compute_root_of_partial_tree_with_necessary_node_missing_gives_expected_error() { // ──── _ ──── // ╱ ╲ @@ -1470,7 +1471,7 @@ pub(crate) mod tests { assert_eq!(MerkleTreeError::MissingNodeIndex(3), err); } - #[test] + #[macro_rules_attr::apply(test)] fn trying_to_compute_root_of_partial_tree_with_redundant_node_gives_expected_error() { // ──── _ ──── // ╱ ╲ @@ -1494,7 +1495,7 @@ pub(crate) mod tests { assert_eq!(MerkleTreeError::SpuriousNodeIndex(2), err); } - #[test] + #[macro_rules_attr::apply(test)] fn converting_authentication_structure_to_authentication_paths_results_in_expected_paths() { const TREE_HEIGHT: MerkleTreeHeight = 3; let merkle_tree = MerkleTree::test_tree_of_height(TREE_HEIGHT); @@ -1513,7 +1514,7 @@ pub(crate) mod tests { assert_eq!(expected_paths, auth_paths); } - #[test] + #[macro_rules_attr::apply(test)] fn merkle_subtrees_are_sliced_correctly() { const TREE_HEIGHT: usize = 5; const NUM_LEAFS: u32 = 1 << TREE_HEIGHT; @@ -1571,7 +1572,7 @@ pub(crate) mod tests { assert_eq!((56..64).collect_vec().as_slice(), right_right_tree[3]); } - #[test] + #[macro_rules_attr::apply(test)] fn auth_structure_node_indices_can_be_computed_with_different_types() -> Result<()> { let u32_indices: Vec = MerkleTree::authentication_structure_node_indices(8, &[0_u32, 1])?.collect(); diff --git a/twenty-first/src/util_types/mmr/archival_mmr.rs b/twenty-first/src/util_types/mmr/archival_mmr.rs index 8fb48329e..d49be8056 100644 --- a/twenty-first/src/util_types/mmr/archival_mmr.rs +++ b/twenty-first/src/util_types/mmr/archival_mmr.rs @@ -322,10 +322,11 @@ mod tests { use super::*; use crate::math::b_field_element::BFieldElement; use crate::math::other::random_elements; + use crate::tests::test; use crate::util_types::mmr::mmr_accumulator::util::mmra_with_mps; use crate::util_types::mmr::shared_advanced::get_peak_heights_and_peak_node_indices; - #[test] + #[macro_rules_attr::apply(test)] fn empty_mmr_behavior_test() { let mut archival_mmr = ArchivalMmr::default(); let mut accumulator_mmr = MmrAccumulator::new_from_leafs(vec![]); @@ -390,11 +391,11 @@ mod tests { &archival_mmr.peaks(), archival_mmr.num_leafs(), ), - "membership proof from arhival MMR must validate" + "membership proof from archival MMR must validate" ); } - #[test] + #[macro_rules_attr::apply(test)] fn verify_against_correct_peak_test() { // This test addresses a bug that was discovered late in the development // process where it was possible to fake a verification proof by @@ -431,7 +432,7 @@ mod tests { )); } - #[test] + #[macro_rules_attr::apply(test)] fn mutate_leaf_archival_test() { let leaf_count: u64 = 3; let leaf_hashes: Vec = random_elements(leaf_count as usize); @@ -499,7 +500,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn bagging_peaks_is_equivalent_for_archival_and_accumulator_mmrs() { let leaf_digests: Vec = random_elements(3); let leafs = leaf_digests.clone(); @@ -519,7 +520,7 @@ mod tests { assert!(no_peak_is_bag); } - #[test] + #[macro_rules_attr::apply(test)] fn accumulator_mmr_mutate_leaf_test() { // Verify that updating leafs in archival and in accumulator MMR results // in the same peaks and verify that updating all leafs in an MMR @@ -546,7 +547,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn mmr_prove_verify_leaf_mutation_test() { for size in 1..150 { let new_leaf: Digest = random(); @@ -583,7 +584,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn mmr_append_test() { // Verify that building an MMR iteratively or in *one* function call // results in the same MMR @@ -640,7 +641,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn one_input_mmr_test() { let input_hash = Tip5::hash(&BFieldElement::new(14)); let new_input_hash = Tip5::hash(&BFieldElement::new(201)); @@ -706,7 +707,7 @@ mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] fn two_input_mmr_test() { let num_leafs: u64 = 3; let input_digests: Vec = random_elements(num_leafs as usize); @@ -752,7 +753,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn variable_size_tip5_mmr_test() { let leaf_counts = (1..34).collect_vec(); let node_counts = [ @@ -817,7 +818,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn variable_size_mmr_test() { let node_counts = [ 1, 3, 4, 7, 8, 10, 11, 15, 16, 18, 19, 22, 23, 25, 26, 31, 32, 34, 35, 38, 39, 41, 42, @@ -896,13 +897,13 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic] fn disallow_repeated_leaf_indices_in_construction() { mmra_with_mps(14, vec![(0, random()), (0, random())]); } - #[test] + #[macro_rules_attr::apply(test)] fn mmra_and_mps_construct_test_cornercases() { let mut rng = rand::rng(); for leaf_count in 0..5 { @@ -941,7 +942,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn mmra_and_mps_construct_test_small() { let mut rng = rand::rng(); let digest_leaf_idx12: Digest = rng.random(); @@ -952,7 +953,7 @@ mod tests { assert!(mps[1].verify(14, digest_leaf_idx14, &mmra.peaks(), mmra.num_leafs())); } - #[test] + #[macro_rules_attr::apply(test)] fn mmra_and_mps_construct_test_pbt() { let mut rng = rand::rng(); @@ -977,7 +978,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn mmra_and_mps_construct_test_big() { let mut rng = rand::rng(); let leaf_count = (1 << 59) + (1 << 44) + 1234567890; diff --git a/twenty-first/src/util_types/mmr/mmr_accumulator.rs b/twenty-first/src/util_types/mmr/mmr_accumulator.rs index 3bbddb9ba..09f22b370 100644 --- a/twenty-first/src/util_types/mmr/mmr_accumulator.rs +++ b/twenty-first/src/util_types/mmr/mmr_accumulator.rs @@ -559,10 +559,11 @@ mod tests { use rand::distr::Uniform; use rand::prelude::*; use rand::random; - use test_strategy::proptest; use super::*; use crate::math::other::random_elements; + use crate::tests::proptest; + use crate::tests::test; use crate::util_types::mmr::archival_mmr::ArchivalMmr; impl From for MmrAccumulator { @@ -580,7 +581,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn conversion_test() { let leaf_hashes = random_elements(3); let ammr = ArchivalMmr::new_from_leafs(leaf_hashes); @@ -594,7 +595,7 @@ mod tests { assert_eq!(3, mmra.num_leafs()); } - #[test] + #[macro_rules_attr::apply(test)] fn verify_batch_update_single_append_test() { let leaf_hashes_start: Vec = random_elements(3); let appended_leaf: Digest = random(); @@ -614,7 +615,7 @@ mod tests { assert!(leafs_were_appended); } - #[test] + #[macro_rules_attr::apply(test)] fn verify_batch_update_single_mutate_test() { let leaf0: Digest = random(); let leaf1: Digest = random(); @@ -659,7 +660,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn verify_batch_update_two_append_test() { let leaf_hashes_start: Vec = random_elements(3); let appended_leafs: Vec = random_elements(2); @@ -677,7 +678,7 @@ mod tests { assert!(leafs_were_appended); } - #[test] + #[macro_rules_attr::apply(test)] fn verify_batch_update_two_mutate_test() { let leaf14: Digest = random(); let leaf15: Digest = random(); @@ -708,7 +709,7 @@ mod tests { )); } - #[test] + #[macro_rules_attr::apply(test)] fn batch_mutate_leaf_and_update_mps_test() { let mut rng = rand::rng(); for mmr_leaf_count in 1..100 { @@ -818,7 +819,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn verify_batch_update_pbt() { for start_size in 1..18 { let leaf_hashes_start: Vec = random_elements(start_size); @@ -930,7 +931,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn mmra_serialization_test() { // You could argue that this test doesn't belong here, as it tests the behavior of // an imported library. I included it here, though, because the setup seems a bit clumsy @@ -945,7 +946,7 @@ mod tests { assert_eq!(1, mmra.num_leafs()); } - #[test] + #[macro_rules_attr::apply(test)] fn get_size_test() { // 10 digests produces an MMRA with two peaks let digests: Vec = random_elements(10); @@ -963,7 +964,7 @@ mod tests { assert!(mmra.get_size() < 100 * std::mem::size_of::()); } - #[test] + #[macro_rules_attr::apply(test)] fn test_mmr_accumulator_decode() { for _ in 0..100 { let num_leafs = rand::random_range(0..100); @@ -975,7 +976,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "Lists must have same length. Got: 0 and 3")] fn test_diff_len_lists_batch_mutate_leaf_and_update_mps() { // Checks that batch_mutate_leaf_and_update_mps panics when passed differing length lists @@ -996,14 +997,14 @@ mod tests { ); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn arbitrary_mmra_has_consistent_num_leafs_and_peaks( #[strategy(arb::())] mmra: MmrAccumulator, ) { prop_assert_eq!(mmra.peaks().len(), mmra.num_leafs().count_ones() as usize); } - #[proptest(cases = 20)] + #[macro_rules_attr::apply(proptest(cases = 20))] fn mmra_with_mps_produces_valid_output( #[strategy(0..u64::MAX / 2)] mmr_leaf_count: u64, #[strategy(0usize..10)] _num_revealed_leafs: usize, @@ -1022,12 +1023,12 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn computing_mmr_root_for_no_leafs_produces_some_digest() { MmrAccumulator::new_from_leafs(vec![]).bag_peaks(); } - #[test] + #[macro_rules_attr::apply(test)] fn bag_peaks_snapshot() { let snapshot = |mmr: MmrAccumulator| mmr.bag_peaks().to_hex(); let mut rng = StdRng::seed_from_u64(0x92ca758afeec6d29); diff --git a/twenty-first/src/util_types/mmr/mmr_membership_proof.rs b/twenty-first/src/util_types/mmr/mmr_membership_proof.rs index 732f71d10..150e72948 100644 --- a/twenty-first/src/util_types/mmr/mmr_membership_proof.rs +++ b/twenty-first/src/util_types/mmr/mmr_membership_proof.rs @@ -636,11 +636,12 @@ mod tests { use proptest_arbitrary_interop::arb; use rand::Rng; use rand::random; - use test_strategy::proptest; use super::*; use crate::math::b_field_element::BFieldElement; use crate::math::other::random_elements; + use crate::tests::proptest; + use crate::tests::test; use crate::tip5::Digest; use crate::tip5::Tip5; use crate::util_types::mmr::archival_mmr::ArchivalMmr; @@ -648,7 +649,7 @@ mod tests { use crate::util_types::mmr::mmr_accumulator::util::mmra_with_mps; use crate::util_types::mmr::mmr_trait::Mmr; - #[test] + #[macro_rules_attr::apply(test)] fn equality_and_hash_test() { let mut rng = rand::rng(); let some_digest: Digest = rng.random(); @@ -686,7 +687,7 @@ mod tests { assert_ne!(Tip5::hash(&mp4), Tip5::hash(&mp5)); } - #[test] + #[macro_rules_attr::apply(test)] fn get_node_indices_simple_test() { const LEAF_INDEX: u64 = 4; let leaf_hashes: Vec = random_elements(8); @@ -702,7 +703,7 @@ mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] fn get_peak_index_simple_test() { let mut mmr_size = 7; let leaf_digests: Vec = random_elements(mmr_size); @@ -760,7 +761,7 @@ mod tests { } } - #[proptest(cases = 10)] + #[macro_rules_attr::apply(proptest(cases = 10))] fn mmr_verification_if_leaf_index_is_out_of_bounds( #[strategy(0..=121usize)] leaf_count: usize, ) { @@ -777,13 +778,13 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn mmr_verify_does_not_crash_on_too_short_peaks_list_unit() { let mmr_mp = MmrMembershipProof::new(vec![Default::default()]); assert!(!mmr_mp.verify(0, Default::default(), &[], 2)); } - #[proptest(cases = 10)] + #[macro_rules_attr::apply(proptest(cases = 10))] fn mmr_verification_with_wrong_length_of_peak_list( #[strategy(0..=1_000_000u64)] leaf_count: u64, #[strategy(0..=#leaf_count)] leaf_index: u64, @@ -806,7 +807,7 @@ mod tests { assert!(!mp.verify(leaf_index, leaf, &one_too_many, leaf_count)); } - #[test] + #[macro_rules_attr::apply(test)] fn update_batch_membership_proofs_from_leaf_mutations_new_test() { let total_leaf_count = 8; let leaf_hashes: Vec = random_elements(total_leaf_count); @@ -851,7 +852,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn batch_update_from_batch_leaf_mutation_total_replacement_test() { let total_leaf_count = 268; let leaf_hashes_init: Vec = random_elements(total_leaf_count); @@ -900,7 +901,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn batch_update_from_batch_leaf_mutation_test() { let total_leaf_count = 34; let mut leaf_hashes: Vec = random_elements(total_leaf_count); @@ -980,7 +981,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn batch_update_from_leaf_mutation_no_change_return_value_test() { // This test verifies that the return value indicating changed membership proofs is empty // even though the mutations affect the membership proofs. The reason it is empty is that @@ -1046,7 +1047,7 @@ mod tests { (leaf_hashes, membership_proofs) } - #[test] + #[macro_rules_attr::apply(test)] fn update_batch_membership_proofs_from_batch_leaf_mutations_test() { let total_leaf_count = 8; let leaf_hashes: Vec = random_elements(total_leaf_count); @@ -1092,7 +1093,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn update_membership_proof_from_leaf_mutation_test() { let leaf_hashes: Vec = random_elements(8); let new_leaf: Digest = Tip5::hash(&133337u64); @@ -1224,7 +1225,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn update_membership_proof_from_leaf_mutation_big_test() { // Build MMR from leaf count 0 to 22, and loop through *each* // leaf index for MMR, modifying its membership proof with a @@ -1288,7 +1289,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn update_membership_proof_from_append_simple_with_bit_mmra() { let original_leaf_count = (1 << 35) + (1 << 7) - 1; @@ -1334,7 +1335,7 @@ mod tests { assert!(all_proofs_are_valid); } - #[test] + #[macro_rules_attr::apply(test)] fn update_membership_proof_from_append_simple() { let leaf_count = 7; let leaf_hashes: Vec = random_elements(leaf_count); @@ -1377,7 +1378,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn update_membership_proof_from_append_big_tests() { for leaf_count in 0..68u64 { // 1. Build an ArchivalMmr with a variable amount of leafs @@ -1510,7 +1511,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn update_membership_proof_from_append_big_tip5() { // Build MMR from leaf count 0 to 9, and loop through *each* // leaf index for MMR, modifying its membership proof with an @@ -1562,7 +1563,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn serialization_test() { // You could argue that this test doesn't belong here, as it tests the behavior of // an imported library. I included it here, though, because the setup seems a bit clumsy @@ -1583,7 +1584,7 @@ mod tests { )); } - #[test] + #[macro_rules_attr::apply(test)] fn test_decode_mmr_membership_proof() { let mut rng = rand::rng(); for _ in 0..100 { @@ -1598,7 +1599,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "Lists must have same length. Got: 0 and 3")] fn test_diff_len_lists_batch_update_from_append() { // Checks that batch_update_from_append() panics when passed differing length lists @@ -1617,7 +1618,7 @@ mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "Lists must have same length. Got: 0 and 3")] fn test_diff_len_lists_batch_update_from_leaf_mutation() { // Checks that batch_update_from_leaf_mutation() panics when passed differing length lists @@ -1638,7 +1639,7 @@ mod tests { ); } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "Lists must have same length. Got: 0 and 3")] fn test_diff_len_lists_batch_update_from_batch_leaf_mutation() { // Checks that batch_update_from_batch_leaf_mutation() panics when passed diff --git a/twenty-first/src/util_types/mmr/mmr_successor_proof.rs b/twenty-first/src/util_types/mmr/mmr_successor_proof.rs index 5bac75d4f..453d3d721 100644 --- a/twenty-first/src/util_types/mmr/mmr_successor_proof.rs +++ b/twenty-first/src/util_types/mmr/mmr_successor_proof.rs @@ -261,9 +261,10 @@ mod tests { use itertools::Itertools; use proptest::prelude::*; use proptest_arbitrary_interop::arb; - use test_strategy::proptest; use super::*; + use crate::tests::proptest; + use crate::tests::test; use crate::tip5::digest::tests::DigestCorruptor; /// A type exclusive to testing. Simplifies construction and verification of @@ -319,21 +320,21 @@ mod tests { type Strategy = BoxedStrategy; } - #[test] + #[macro_rules_attr::apply(test)] fn append_nothing_to_empty_mmra() { let relation = MmrSuccessorRelation::new_with_numbered_leafs(0, 0); assert_eq!(0, relation.proof.paths.len()); relation.verify().unwrap(); } - #[test] + #[macro_rules_attr::apply(test)] fn append_one_thing_to_empty_mmra() { let relation = MmrSuccessorRelation::new_with_numbered_leafs(0, 1); assert_eq!(0, relation.proof.paths.len()); relation.verify().unwrap(); } - #[test] + #[macro_rules_attr::apply(test)] fn append_leafs_without_influence_on_existing_peaks() { let relation = MmrSuccessorRelation::new_with_numbered_leafs(1 << 3, 3); assert_eq!(0, relation.proof.paths.len()); @@ -356,7 +357,7 @@ mod tests { /// ╭─┴─╮ ╭─┴─╮ ╭─┴─╮ ╭─┴─╮ ╭─┴─╮ ╭─┴─╮ ·━┻━x ┏━┻━┓ /// ╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮ ╭┴╮ ┏┻┓ ┏┻┓ ┏┻┓ ┏┻┓ /// ``` - #[test] + #[macro_rules_attr::apply(test)] fn append_8_leafs_to_mmra_with_42_leafs() { let relation = MmrSuccessorRelation::new_with_numbered_leafs(42, 8); assert_eq!(2, relation.proof.paths.len()); @@ -372,7 +373,7 @@ mod tests { relation.verify().unwrap(); } - #[test] + #[macro_rules_attr::apply(test)] fn unit_tests() { for (n, m) in (0..18).cartesian_product(0..18) { dbg!((n, m)); @@ -381,12 +382,12 @@ mod tests { } } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn arbitrary_mmr_successor_relation_holds(relation: MmrSuccessorRelation) { relation.verify()?; } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn verification_fails_if_old_mmr_is_inconsistent( mut relation: MmrSuccessorRelation, wrong_num_leafs: u64, @@ -396,7 +397,7 @@ mod tests { prop_assert_eq!(Err(Error::InconsistentOldMmr), relation.verify()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn verification_fails_if_new_mmr_is_inconsistent( mut relation: MmrSuccessorRelation, wrong_num_leafs: u64, @@ -406,7 +407,7 @@ mod tests { prop_assert_eq!(Err(Error::InconsistentNewMmr), relation.verify()); } - #[proptest(cases = 50)] + #[macro_rules_attr::apply(proptest(cases = 50))] fn verification_fails_if_old_mmra_has_more_leafs_than_new_mmra( #[filter(#relation.old != #relation.new)] mut relation: MmrSuccessorRelation, ) { @@ -414,7 +415,7 @@ mod tests { prop_assert_eq!(Err(Error::OldHasMoreLeafsThanNew), relation.verify()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn verification_fails_if_old_mmra_has_swapped_peaks( #[filter(#relation.old.peaks().len() >= 2)] mut relation: MmrSuccessorRelation, #[strategy(0..#relation.old.peaks().len())] first_swap_idx: usize, @@ -435,7 +436,7 @@ mod tests { /// relation, it is impossible to swap arbitrary peaks and guarantee a /// verification failure. However, if the old MMRA is non-empty, the first /// peak of the new MMRA is always relevant. - #[proptest] + #[macro_rules_attr::apply(proptest)] fn verification_fails_if_new_mmra_has_first_peak_swapped_out( #[filter(#relation.old.num_leafs() > 0)] #[filter(#relation.new.peaks().len() >= 2)] @@ -452,7 +453,7 @@ mod tests { )); } - #[proptest(cases = 50)] + #[macro_rules_attr::apply(proptest(cases = 50))] fn verification_fails_if_authentication_path_is_corrupt( #[filter(!#relation.proof.paths.is_empty())] mut relation: MmrSuccessorRelation, #[strategy(0..#relation.proof.paths.len())] corruption_idx: usize, @@ -467,7 +468,7 @@ mod tests { )); } - #[proptest(cases = 50)] + #[macro_rules_attr::apply(proptest(cases = 50))] fn verification_fails_if_authentication_path_has_too_few_elements( #[filter(!#relation.proof.paths.is_empty())] mut relation: MmrSuccessorRelation, #[strategy(0..#relation.proof.paths.len())] deletion_idx: usize, @@ -476,7 +477,7 @@ mod tests { prop_assert_eq!(Err(Error::AuthenticationPathTooShort), relation.verify()); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn verification_fails_if_authentication_path_has_too_many_elements( mut relation: MmrSuccessorRelation, #[strategy(arb())] spurious_element: Digest, diff --git a/twenty-first/src/util_types/mmr/shared_advanced.rs b/twenty-first/src/util_types/mmr/shared_advanced.rs index 4a63c9c7e..611c7da26 100644 --- a/twenty-first/src/util_types/mmr/shared_advanced.rs +++ b/twenty-first/src/util_types/mmr/shared_advanced.rs @@ -282,13 +282,14 @@ mod tests { use proptest::prelude::Just; use proptest::prop_assert_eq; use rand::RngCore; - use test_strategy::proptest; use super::*; use crate::prelude::Digest; use crate::prelude::MmrMembershipProof; + use crate::tests::proptest; + use crate::tests::test; - #[test] + #[macro_rules_attr::apply(test)] fn leaf_index_to_node_index_test() { assert_eq!(1, leaf_index_to_node_index(0)); assert_eq!(2, leaf_index_to_node_index(1)); @@ -314,7 +315,7 @@ mod tests { assert_eq!(40, leaf_index_to_node_index(21)); } - #[test] + #[macro_rules_attr::apply(test)] fn node_indices_added_by_append_test() { assert_eq!(vec![1], node_indices_added_by_append(0)); assert_eq!(vec![2, 3], node_indices_added_by_append(1)); @@ -343,7 +344,7 @@ mod tests { assert_eq!(vec![64], node_indices_added_by_append(32)); } - #[test] + #[macro_rules_attr::apply(test)] fn leaf_index_node_index_pbt() { let mut rng = rand::rng(); for _ in 0..10_000 { @@ -354,7 +355,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn right_ancestor_count_test() { assert_eq!((0, 0), right_lineage_length_and_own_height(1)); // 0b1 => 0 assert_eq!((1, 0), right_lineage_length_and_own_height(2)); // 0b10 => 1 @@ -417,14 +418,14 @@ mod tests { assert_eq!((0, 62), right_lineage_length_and_own_height(u64::MAX / 2)); // 0b111...11 => 0 } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn right_lineage_length_property(#[strategy(0u64..(1<<63))] leaf_index: u64) { let rll = right_lineage_length_from_leaf_index(leaf_index); let rac = right_lineage_length_and_own_height(leaf_index_to_node_index(leaf_index)).0; prop_assert_eq!(rac, rll); } - #[test] + #[macro_rules_attr::apply(test)] fn leftmost_ancestor_test() { assert_eq!((1, 0), leftmost_ancestor(1)); assert_eq!((3, 1), leftmost_ancestor(2)); @@ -444,7 +445,7 @@ mod tests { assert_eq!((31, 4), leftmost_ancestor(16)); } - #[test] + #[macro_rules_attr::apply(test)] fn left_sibling_test() { assert_eq!(3, left_sibling(6, 1)); assert_eq!(1, left_sibling(2, 0)); @@ -454,7 +455,7 @@ mod tests { assert_eq!(7, left_sibling(14, 2)); } - #[test] + #[macro_rules_attr::apply(test)] fn node_index_to_leaf_index_test() { assert_eq!(Some(0), node_index_to_leaf_index(1)); assert_eq!(Some(1), node_index_to_leaf_index(2)); @@ -480,7 +481,7 @@ mod tests { assert_eq!(None, node_index_to_leaf_index(22)); } - #[test] + #[macro_rules_attr::apply(test)] fn leaf_count_to_node_count_test() { let node_counts: Vec = vec![ 0, 1, 3, 4, 7, 8, 10, 11, 15, 16, 18, 19, 22, 23, 25, 26, 31, 32, 34, 35, 38, 39, 41, @@ -491,7 +492,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn get_peak_heights_and_peak_node_indices_test() { type TestCase = (u64, (Vec, Vec)); let leaf_count_and_expected: Vec = vec![ @@ -526,7 +527,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn get_authentication_path_node_indices_test() { type Interval = (u64, u64, u64); type TestCase = (Interval, Option>); @@ -548,13 +549,13 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] #[should_panic(expected = "Leaf index out-of-bounds: 5/5")] fn auth_path_indices_out_of_bounds_unit_test() { auth_path_node_indices(5, 5); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn auth_path_indices_prop( #[strategy(0u64..(u64::MAX>>1))] _num_leafs: u64, #[strategy(0u64..(#_num_leafs))] leaf_index: u64, @@ -566,7 +567,7 @@ mod tests { prop_assert_eq!(mp.get_node_indices(leaf_index), node_indices); } - #[test] + #[macro_rules_attr::apply(test)] fn auth_path_indices_unit_test() { assert_eq!(vec![2, 6, 14, 30], auth_path_node_indices(16, 0)); assert_eq!(vec![1, 6, 14, 30], auth_path_node_indices(16, 1)); @@ -600,17 +601,17 @@ mod tests { } } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn right_lineage_length_from_node_index_does_not_crash(node_index: u64) { right_lineage_length_from_node_index(node_index); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn leftmost_ancestor_does_not_crash(node_index: u64) { leftmost_ancestor(node_index); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn right_lineage_length_and_own_height_does_not_crash(node_index: u64) { right_lineage_length_and_own_height(node_index); } diff --git a/twenty-first/src/util_types/mmr/shared_basic.rs b/twenty-first/src/util_types/mmr/shared_basic.rs index 8d0f4c8f0..c6fcaf910 100644 --- a/twenty-first/src/util_types/mmr/shared_basic.rs +++ b/twenty-first/src/util_types/mmr/shared_basic.rs @@ -142,11 +142,12 @@ pub fn calculate_new_peaks_from_leaf_mutation( mod tests { use proptest::collection::vec; use proptest_arbitrary_interop::arb; - use test_strategy::proptest; use super::*; + use crate::tests::proptest; + use crate::tests::test; - #[test] + #[macro_rules_attr::apply(test)] fn right_lineage_length_from_leaf_index_test() { assert_eq!(0, right_lineage_length_from_leaf_index(0)); assert_eq!(1, right_lineage_length_from_leaf_index(1)); @@ -163,7 +164,7 @@ mod tests { assert_eq!(63, right_lineage_length_from_leaf_index((1 << 63) - 1)); } - #[test] + #[macro_rules_attr::apply(test)] fn leaf_index_to_mt_index_test() { // 1 leaf assert_eq!((1, 0), leaf_index_to_mt_index_and_peak_index(0, 1)); @@ -268,7 +269,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn peak_index_test() { let peak_index = |leaf_index, num_leafs| { let (_, peak_index) = leaf_index_to_mt_index_and_peak_index(leaf_index, num_leafs); @@ -316,20 +317,20 @@ mod tests { assert_eq!(2, peak_index((0b0111 << 29) - 1, (0b1000 << 29) - 1)); } - #[test] + #[macro_rules_attr::apply(test)] fn merkle_tree_and_peak_indices_can_be_computed_for_large_mmrs() { leaf_index_to_mt_index_and_peak_index(0, u64::MAX); leaf_index_to_mt_index_and_peak_index(u64::MAX - 1, u64::MAX); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn right_lineage_length_from_leaf_index_does_not_crash( #[strategy(0..=u64::MAX >> 1)] leaf_index: u64, ) { right_lineage_length_from_leaf_index(leaf_index); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn calculate_new_peaks_from_append_does_not_crash( #[strategy(0..u64::MAX >> 1)] old_num_leafs: u64, #[strategy(vec(arb(), #old_num_leafs.count_ones() as usize))] old_peaks: Vec, @@ -338,7 +339,7 @@ mod tests { calculate_new_peaks_from_append(old_num_leafs, old_peaks, new_leaf); } - #[test] + #[macro_rules_attr::apply(test)] fn calculate_new_peaks_from_append_to_empty_mmra_does_not_crash() { calculate_new_peaks_from_append(0, vec![], rand::random()); } diff --git a/twenty-first/src/util_types/sponge.rs b/twenty-first/src/util_types/sponge.rs index 49ac52e83..26da194d2 100644 --- a/twenty-first/src/util_types/sponge.rs +++ b/twenty-first/src/util_types/sponge.rs @@ -62,12 +62,13 @@ mod tests { use rand::Rng; use rand::distr::Distribution; use rand::distr::StandardUniform; - use test_strategy::proptest; use super::*; use crate::math::x_field_element::EXTENSION_DEGREE; use crate::prelude::BFieldCodec; use crate::prelude::XFieldElement; + use crate::tests::proptest; + use crate::tests::test; use crate::tip5::Digest; use crate::tip5::Tip5; @@ -92,7 +93,7 @@ mod tests { } } - #[test] + #[macro_rules_attr::apply(test)] fn to_sequence_test() { // bool encode_prop(false, true); @@ -119,7 +120,7 @@ mod tests { encode_prop(0u128, u128::MAX); } - #[proptest] + #[macro_rules_attr::apply(proptest)] fn sample_indices(mut tip5: Tip5) { let cases = [ (2, 0), diff --git a/twenty-first/tests/bfield_codec_derive.rs b/twenty-first/tests/bfield_codec_derive.rs index d66881108..94da62c96 100644 --- a/twenty-first/tests/bfield_codec_derive.rs +++ b/twenty-first/tests/bfield_codec_derive.rs @@ -18,6 +18,7 @@ struct BFieldCodecTestStructA { } #[proptest] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn integration_test_struct_a(#[strategy(arb())] test_struct: BFieldCodecTestStructA) { let encoding = test_struct.encode(); let decoding = *BFieldCodecTestStructA::decode(&encoding).unwrap(); @@ -31,6 +32,7 @@ struct BFieldCodecTestStructB { } #[proptest] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn integration_test_struct_b(#[strategy(arb())] test_struct: BFieldCodecTestStructB) { let encoding = test_struct.encode(); let decoding = *BFieldCodecTestStructB::decode(&encoding).unwrap(); @@ -45,6 +47,7 @@ enum BFieldCodecTestEnumA { } #[proptest] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn integration_test_enum_a(#[strategy(arb())] test_enum: BFieldCodecTestEnumA) { let encoding = test_enum.encode(); let decoding = *BFieldCodecTestEnumA::decode(&encoding).unwrap(); @@ -59,6 +62,7 @@ enum BFieldCodecTestEnumB { } #[proptest] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn integration_test_enum_b(#[strategy(arb())] test_enum: BFieldCodecTestEnumB) { let encoding = test_enum.encode(); let decoding = *BFieldCodecTestEnumB::decode(&encoding).unwrap(); From 27f43da95b85e629e79b76ba4b87e59fcc42b7cd Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Thu, 15 Jan 2026 08:35:00 +0100 Subject: [PATCH 2/4] refactor: Inline `proptest-arbitrary-interop` The crate is unmaintained and, for the purpose of testing on wasm32, unusable. Because 1. the code has seen very little activity since its inception, 2. there are only a little more than 100 lines of code, and 3. it has worked perfectly well for all our needs so far, I think the risks associated with inlining are acceptable. --- twenty-first/Cargo.toml | 1 - twenty-first/src/lib.rs | 4 + twenty-first/src/math/b_field_element.rs | 2 +- twenty-first/src/math/bfield_codec.rs | 2 +- twenty-first/src/math/ntt.rs | 2 +- twenty-first/src/math/polynomial.rs | 2 +- twenty-first/src/math/x_field_element.rs | 2 +- twenty-first/src/math/zerofier_tree.rs | 2 +- .../src/proptest_arbitrary_interop.rs | 159 ++++++++++++++++++ twenty-first/src/tip5/digest.rs | 2 +- twenty-first/src/tip5/mod.rs | 2 +- twenty-first/src/util_types/merkle_tree.rs | 2 +- .../src/util_types/mmr/mmr_accumulator.rs | 2 +- .../util_types/mmr/mmr_membership_proof.rs | 2 +- .../src/util_types/mmr/mmr_successor_proof.rs | 2 +- .../src/util_types/mmr/shared_basic.rs | 2 +- twenty-first/tests/bfield_codec_derive.rs | 50 +++++- 17 files changed, 219 insertions(+), 21 deletions(-) create mode 100644 twenty-first/src/proptest_arbitrary_interop.rs diff --git a/twenty-first/Cargo.toml b/twenty-first/Cargo.toml index 4308aa84e..34ab85fb4 100644 --- a/twenty-first/Cargo.toml +++ b/twenty-first/Cargo.toml @@ -48,7 +48,6 @@ macro_rules_attr = "0.1.3" test-strategy = "=0.4.3" trybuild = "1.0" proptest = { version = "1.7.0", default-features = false, features = ["std"] } -proptest-arbitrary-interop = { git = "https://github.com/dan-da/proptest-arbitrary-interop.git", version = "0.1", rev = "d9fcf5b" } [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = { package = "codspeed-criterion-compat", version = "4.0", features = ["html_reports"] } diff --git a/twenty-first/src/lib.rs b/twenty-first/src/lib.rs index 2e2593448..14891cd93 100644 --- a/twenty-first/src/lib.rs +++ b/twenty-first/src/lib.rs @@ -20,6 +20,10 @@ pub mod prelude; pub mod tip5; pub mod util_types; +#[cfg(test)] +#[cfg_attr(coverage_nightly, feature(coverage_attribute))] +mod proptest_arbitrary_interop; + // This is needed for `#[derive(BFieldCodec)]` macro to work consistently across crates. // Specifically: // From inside the `twenty-first` crate, we need to refer to `twenty-first` by `crate`. diff --git a/twenty-first/src/math/b_field_element.rs b/twenty-first/src/math/b_field_element.rs index 963256eac..e3d028c07 100644 --- a/twenty-first/src/math/b_field_element.rs +++ b/twenty-first/src/math/b_field_element.rs @@ -820,12 +820,12 @@ mod tests { use itertools::izip; use proptest::prelude::*; - use proptest_arbitrary_interop::arb; use rand::random; use crate::math::b_field_element::*; use crate::math::other::random_elements; use crate::math::polynomial::Polynomial; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; diff --git a/twenty-first/src/math/bfield_codec.rs b/twenty-first/src/math/bfield_codec.rs index b7e67e323..93e1327ce 100644 --- a/twenty-first/src/math/bfield_codec.rs +++ b/twenty-first/src/math/bfield_codec.rs @@ -601,10 +601,10 @@ mod tests { use proptest::collection::vec; use proptest::prelude::*; use proptest::test_runner::TestCaseResult; - use proptest_arbitrary_interop::arb; use super::*; use crate::prelude::*; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; diff --git a/twenty-first/src/math/ntt.rs b/twenty-first/src/math/ntt.rs index 69a99ef7f..511682ffb 100644 --- a/twenty-first/src/math/ntt.rs +++ b/twenty-first/src/math/ntt.rs @@ -331,13 +331,13 @@ mod tests { use num_traits::Zero; use proptest::collection::vec; use proptest::prelude::*; - use proptest_arbitrary_interop::arb; use super::*; use crate::math::other::random_elements; use crate::math::traits::PrimitiveRootOfUnity; use crate::math::x_field_element::EXTENSION_DEGREE; use crate::prelude::*; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; use crate::xfe; diff --git a/twenty-first/src/math/polynomial.rs b/twenty-first/src/math/polynomial.rs index 4272ead0a..3f9095eaa 100644 --- a/twenty-first/src/math/polynomial.rs +++ b/twenty-first/src/math/polynomial.rs @@ -2707,10 +2707,10 @@ mod tests { use proptest::collection::size_range; use proptest::collection::vec; use proptest::prelude::*; - use proptest_arbitrary_interop::arb; use super::*; use crate::prelude::*; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; diff --git a/twenty-first/src/math/x_field_element.rs b/twenty-first/src/math/x_field_element.rs index 98f9c1a9e..1743f1332 100644 --- a/twenty-first/src/math/x_field_element.rs +++ b/twenty-first/src/math/x_field_element.rs @@ -669,7 +669,6 @@ mod tests { use num_traits::ConstOne; use proptest::collection::vec; use proptest::prelude::*; - use proptest_arbitrary_interop::arb; use super::*; use crate::bfe; @@ -677,6 +676,7 @@ mod tests { use crate::math::ntt::intt; use crate::math::ntt::ntt; use crate::math::other::random_elements; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; diff --git a/twenty-first/src/math/zerofier_tree.rs b/twenty-first/src/math/zerofier_tree.rs index f470df746..90441677b 100644 --- a/twenty-first/src/math/zerofier_tree.rs +++ b/twenty-first/src/math/zerofier_tree.rs @@ -106,11 +106,11 @@ mod tests { use num_traits::Zero; use proptest::collection::vec; use proptest::prop_assert_eq; - use proptest_arbitrary_interop::arb; use crate::math::zerofier_tree::ZerofierTree; use crate::prelude::BFieldElement; use crate::prelude::Polynomial; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; diff --git a/twenty-first/src/proptest_arbitrary_interop.rs b/twenty-first/src/proptest_arbitrary_interop.rs new file mode 100644 index 000000000..1c298ed0a --- /dev/null +++ b/twenty-first/src/proptest_arbitrary_interop.rs @@ -0,0 +1,159 @@ +//! Provides the necessary glue to reuse an implementation of +//! [`arbitrary::Arbitrary`] as a [`proptest::strategy::Strategy`]. +//! +//! The central method is [`arb`]. +//! +//! # Origin +//! +//! This code is a copy of the unmaintained crate +//! [`proptest-arbitrary-interop`][origin], with some additional improvements +//! from open pull requests of the original's repository. +//! +//! [origin]: https://crates.io/crates/proptest-arbitrary-interop +//! +//! # Caveats +//! +//! It only works with types that implement [`arbitrary::Arbitrary`] in a +//! particular fashion: those conforming to the requirements of [`ArbInterop`]. +//! These are roughly "types that, when randomly-generated, don't retain +//! pointers into the random-data buffer wrapped by the +//! [`arbitrary::Unstructured`] they are generated from". Many implementations +//! of [`arbitrary::Arbitrary`] will fit the bill, but certain kinds of +//! "zero-copy" implementations of [`arbitrary::Arbitrary`] will not work. This +//! requirement appears to be a necessary part of the semantic model of +//! [`proptest`] – generated values have to own their pointer graph, no +//! borrows. Patches welcome if you can figure out a way to not require it. + +use core::fmt::Debug; +use proptest::prelude::RngCore; +use proptest::test_runner::TestRunner; +use std::marker::PhantomData; + +/// The subset of possible [`arbitrary::Arbitrary`] implementations that this +/// crate works with. The main concern here is the `for<'a> Arbitrary<'a>` +/// business, which (in practice) decouples the generated `Arbitrary` value from +/// the lifetime of the random buffer it's fed; I can't actually explain how, +/// because Rust's type system is way over my head. +pub trait ArbInterop: for<'a> arbitrary::Arbitrary<'a> + 'static + Debug + Clone {} +impl ArbInterop for A where A: for<'a> arbitrary::Arbitrary<'a> + 'static + Debug + Clone {} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct ArbStrategy { + size: usize, + _ph: PhantomData, +} + +#[derive(Debug)] +pub struct ArbValueTree { + bytes: Vec, + curr: A, + prev: Option, + next: usize, +} + +impl proptest::strategy::ValueTree for ArbValueTree { + type Value = A; + + fn current(&self) -> Self::Value { + self.curr.clone() + } + + fn simplify(&mut self) -> bool { + if self.next == 0 { + return false; + } + self.next -= 1; + let Ok(simpler) = Self::gen_one_with_size(&self.bytes, self.next) else { + return false; + }; + + // Throw away the previous value and set the current value as prev. + // Advance the iterator and set the current value to the next one. + self.prev = Some(core::mem::replace(&mut self.curr, simpler)); + + true + } + + fn complicate(&mut self) -> bool { + // We can only complicate if we previously simplified. Complicating + // twice in a row without interleaved simplification is guaranteed to + // always yield false for the second call. + let Some(prev) = self.prev.take() else { + return false; + }; + + // Throw away the current value! + self.curr = prev; + + true + } +} + +impl ArbStrategy { + pub fn new(size: usize) -> Self { + Self { + size, + _ph: PhantomData, + } + } +} + +impl ArbValueTree { + fn gen_one_with_size(bytes: &[u8], size: usize) -> Result { + let mut unstructured = arbitrary::Unstructured::new(&bytes[0..size]); + + A::arbitrary(&mut unstructured) + } + + pub fn new(bytes: Vec) -> Result { + let next = bytes.len(); + let curr = Self::gen_one_with_size(&bytes, next)?; + + Ok(Self { + bytes, + prev: None, + curr, + next, + }) + } +} + +impl proptest::strategy::Strategy for ArbStrategy { + type Tree = ArbValueTree; + type Value = A; + + fn new_tree(&self, run: &mut TestRunner) -> proptest::strategy::NewTree { + loop { + let mut bytes = vec![0; self.size]; + run.rng().fill_bytes(&mut bytes); + match ArbValueTree::new(bytes) { + Ok(v) => return Ok(v), + + // If the Arbitrary impl cannot construct a value from the given + // bytes, try again. + Err(e @ arbitrary::Error::IncorrectFormat) => run.reject_local(format!("{e}"))?, + Err(e) => return Err(format!("{e}").into()), + } + } + } +} + +/// Constructs a [`proptest::strategy::Strategy`] for a given +/// [`arbitrary::Arbitrary`] type, generating `size` bytes of random data as +/// input to the [`arbitrary::Arbitrary`] type. +pub fn arb_sized(size: usize) -> ArbStrategy { + ArbStrategy::new(size) +} + +/// Calls [`arb_sized`](crate::arb_sized) with a best-effort guess for the size. +/// +/// In particular, if `A`'s [`size_hint`](Arbitrary::size_hint) is useful, the +/// hint is used; otherwise, a default size of 256 is used. +pub fn arb() -> ArbStrategy { + let (low, opt_high) = A::size_hint(0); + let Some(high) = opt_high else { + return arb_sized(256.max(2 * low)); + }; + + arb_sized(high) +} diff --git a/twenty-first/src/tip5/digest.rs b/twenty-first/src/tip5/digest.rs index 7b805e4e4..2dd995cda 100644 --- a/twenty-first/src/tip5/digest.rs +++ b/twenty-first/src/tip5/digest.rs @@ -279,11 +279,11 @@ pub(crate) mod tests { use proptest::collection::vec; use proptest::prelude::Arbitrary as ProptestArbitrary; use proptest::prelude::*; - use proptest_arbitrary_interop::arb; use super::*; use crate::error::ParseBFieldElementError; use crate::prelude::*; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; diff --git a/twenty-first/src/tip5/mod.rs b/twenty-first/src/tip5/mod.rs index d102a60d5..810c8a3dd 100644 --- a/twenty-first/src/tip5/mod.rs +++ b/twenty-first/src/tip5/mod.rs @@ -728,7 +728,6 @@ pub(crate) mod tests { use prop::sample::size_range; use proptest::prelude::*; - use proptest_arbitrary_interop::arb; use rand::RngCore; use rayon::prelude::IntoParallelIterator; use rayon::prelude::ParallelIterator; @@ -736,6 +735,7 @@ pub(crate) mod tests { use super::*; use crate::math::other::random_elements; use crate::math::x_field_element::XFieldElement; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index 09b269370..a742a5343 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -950,9 +950,9 @@ pub enum MerkleTreeError { pub(crate) mod tests { use proptest::collection::vec; use proptest::prelude::*; - use proptest_arbitrary_interop::arb; use super::*; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; use crate::tip5::digest::tests::DigestCorruptor; diff --git a/twenty-first/src/util_types/mmr/mmr_accumulator.rs b/twenty-first/src/util_types/mmr/mmr_accumulator.rs index 09f22b370..f9c5da917 100644 --- a/twenty-first/src/util_types/mmr/mmr_accumulator.rs +++ b/twenty-first/src/util_types/mmr/mmr_accumulator.rs @@ -555,13 +555,13 @@ mod tests { use num_traits::ConstZero; use proptest::collection::vec; use proptest::prop_assert_eq; - use proptest_arbitrary_interop::arb; use rand::distr::Uniform; use rand::prelude::*; use rand::random; use super::*; use crate::math::other::random_elements; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; use crate::util_types::mmr::archival_mmr::ArchivalMmr; diff --git a/twenty-first/src/util_types/mmr/mmr_membership_proof.rs b/twenty-first/src/util_types/mmr/mmr_membership_proof.rs index 150e72948..62f92f644 100644 --- a/twenty-first/src/util_types/mmr/mmr_membership_proof.rs +++ b/twenty-first/src/util_types/mmr/mmr_membership_proof.rs @@ -633,13 +633,13 @@ impl MmrMembershipProof { #[cfg_attr(coverage_nightly, coverage(off))] mod tests { use itertools::Itertools; - use proptest_arbitrary_interop::arb; use rand::Rng; use rand::random; use super::*; use crate::math::b_field_element::BFieldElement; use crate::math::other::random_elements; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; use crate::tip5::Digest; diff --git a/twenty-first/src/util_types/mmr/mmr_successor_proof.rs b/twenty-first/src/util_types/mmr/mmr_successor_proof.rs index 453d3d721..3fd932486 100644 --- a/twenty-first/src/util_types/mmr/mmr_successor_proof.rs +++ b/twenty-first/src/util_types/mmr/mmr_successor_proof.rs @@ -260,9 +260,9 @@ mod tests { use itertools::Itertools; use proptest::prelude::*; - use proptest_arbitrary_interop::arb; use super::*; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; use crate::tip5::digest::tests::DigestCorruptor; diff --git a/twenty-first/src/util_types/mmr/shared_basic.rs b/twenty-first/src/util_types/mmr/shared_basic.rs index c6fcaf910..a7639de53 100644 --- a/twenty-first/src/util_types/mmr/shared_basic.rs +++ b/twenty-first/src/util_types/mmr/shared_basic.rs @@ -141,9 +141,9 @@ pub fn calculate_new_peaks_from_leaf_mutation( #[cfg_attr(coverage_nightly, coverage(off))] mod tests { use proptest::collection::vec; - use proptest_arbitrary_interop::arb; use super::*; + use crate::proptest_arbitrary_interop::arb; use crate::tests::proptest; use crate::tests::test; diff --git a/twenty-first/tests/bfield_codec_derive.rs b/twenty-first/tests/bfield_codec_derive.rs index 94da62c96..e57899e69 100644 --- a/twenty-first/tests/bfield_codec_derive.rs +++ b/twenty-first/tests/bfield_codec_derive.rs @@ -1,6 +1,5 @@ -use arbitrary::Arbitrary; use proptest::prelude::*; -use proptest_arbitrary_interop::arb; +use test_strategy::Arbitrary; use test_strategy::proptest; // Required by the `BFieldCodec` derive macro. This is generally only needed // once per crate, at the top-level `lib.rs`. @@ -14,12 +13,14 @@ use twenty_first::prelude::XFieldElement; #[derive(Debug, Clone, PartialEq, Eq, BFieldCodec, Arbitrary)] struct BFieldCodecTestStructA { a: u32, + + #[strategy(bfe_strategy())] b: BFieldElement, } #[proptest] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] -fn integration_test_struct_a(#[strategy(arb())] test_struct: BFieldCodecTestStructA) { +fn integration_test_struct_a(test_struct: BFieldCodecTestStructA) { let encoding = test_struct.encode(); let decoding = *BFieldCodecTestStructA::decode(&encoding).unwrap(); prop_assert_eq!(test_struct, decoding); @@ -27,13 +28,16 @@ fn integration_test_struct_a(#[strategy(arb())] test_struct: BFieldCodecTestStru #[derive(Debug, Clone, PartialEq, Eq, BFieldCodec, Arbitrary)] struct BFieldCodecTestStructB { + #[strategy(xfe_strategy())] a: XFieldElement, + + #[strategy(prop::collection::vec((any::(), digest_strategy()), 0..50))] b: Vec<(u64, Digest)>, } #[proptest] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] -fn integration_test_struct_b(#[strategy(arb())] test_struct: BFieldCodecTestStructB) { +fn integration_test_struct_b(test_struct: BFieldCodecTestStructB) { let encoding = test_struct.encode(); let decoding = *BFieldCodecTestStructB::decode(&encoding).unwrap(); prop_assert_eq!(test_struct, decoding); @@ -48,22 +52,38 @@ enum BFieldCodecTestEnumA { #[proptest] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] -fn integration_test_enum_a(#[strategy(arb())] test_enum: BFieldCodecTestEnumA) { +fn integration_test_enum_a(test_enum: BFieldCodecTestEnumA) { let encoding = test_enum.encode(); let decoding = *BFieldCodecTestEnumA::decode(&encoding).unwrap(); prop_assert_eq!(test_enum, decoding); } -#[derive(Debug, Clone, PartialEq, Eq, BFieldCodec, Arbitrary)] +#[derive(Debug, Clone, PartialEq, Eq, BFieldCodec)] enum BFieldCodecTestEnumB { A(u32), B(XFieldElement), C(Vec<(u64, Digest)>), } +impl Arbitrary for BFieldCodecTestEnumB { + type Parameters = (); + + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + prop_oneof![ + any::().prop_map(BFieldCodecTestEnumB::A), + xfe_strategy().prop_map(BFieldCodecTestEnumB::B), + prop::collection::vec((any::(), digest_strategy()), 0..50) + .prop_map(BFieldCodecTestEnumB::C), + ] + .boxed() + } + + type Strategy = BoxedStrategy; +} + #[proptest] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] -fn integration_test_enum_b(#[strategy(arb())] test_enum: BFieldCodecTestEnumB) { +fn integration_test_enum_b(test_enum: BFieldCodecTestEnumB) { let encoding = test_enum.encode(); let decoding = *BFieldCodecTestEnumB::decode(&encoding).unwrap(); prop_assert_eq!(test_enum, decoding); @@ -76,3 +96,19 @@ fn try_build_various_failure_cases() { trybuild.compile_fail("trybuild/incorrect_field_attribute.rs"); trybuild.pass("trybuild/missing_field_attribute.rs"); } + +fn bfe_strategy() -> impl Strategy { + (0..=BFieldElement::MAX).prop_map(BFieldElement::new) +} + +fn xfe_strategy() -> impl Strategy { + let b = bfe_strategy; + + [b(), b(), b()].prop_map(XFieldElement::new) +} + +fn digest_strategy() -> impl Strategy { + let b = bfe_strategy; + + [b(), b(), b(), b(), b()].prop_map(Digest::new) +} From 03416bc9965e59b045d3be887d8e853a2ae7f576 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Wed, 14 Jan 2026 16:31:55 +0100 Subject: [PATCH 3/4] docs: add WebAssembly README file --- README.md | 45 ++++++++-------------- twenty-first/README-wasm32.md | 71 +++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 30 deletions(-) create mode 100644 twenty-first/README-wasm32.md diff --git a/README.md b/README.md index 32bef93c9..fe499db98 100644 --- a/README.md +++ b/README.md @@ -11,40 +11,25 @@ A collection of cryptography primitives written in Rust. This library contains primarily the following cryptographic primitives: - The Tip5 hash function - - [The Tip5 Hash Function for Recursive STARKs](https://eprint.iacr.org/2023/107) + - [The Tip5 Hash Function for Recursive STARKs](https://eprint.iacr.org/2023/107) - Lattice-crypto - - arithmetic for the quotient ring $\mathbb{F}_ p[X] / \langle X^{64} + 1 \rangle$ - - arithmetic for modules over this quotient ring - - a IND-CCA2-secure key encapsulation mechanism - - [Lattice-Based Cryptography in Miden VM](https://eprint.iacr.org/2022/1041) + - arithmetic for the quotient ring $\mathbb{F}_ p[X] / \langle X^{64} + 1 \rangle$ + - arithmetic for modules over this quotient ring + - a IND-CCA2-secure key encapsulation mechanism + - [Lattice-Based Cryptography in Miden VM](https://eprint.iacr.org/2022/1041) - `BFieldElement`, `XFieldElement` - - The prime-field type $\mathbb{F}_p$ where $p = 2^{64} - 2^{32} + 1$ - - The extension field $\mathbb{F}_p[x]/(x^3 - x + 1)$ - - A codec trait for encoding and decoding structs as `Vec`s of `BFieldElement` - - [An efficient prime for number-theoretic transforms](https://cp4space.hatsya.com/2021/09/01/an-efficient-prime-for-number-theoretic-transforms/) + - The prime-field type $\mathbb{F}_p$ where $p = 2^{64} - 2^{32} + 1$ + - The extension field $\mathbb{F}_p[x]/(x^3 - x + 1)$ + - A codec trait for encoding and decoding structs as `Vec`s of `BFieldElement` + - [An efficient prime for number-theoretic transforms](https://cp4space.hatsya.com/2021/09/01/an-efficient-prime-for-number-theoretic-transforms/) - NTT - - Number Theoretic Transform (discrete Fast Fourier Transform) - - [Anatomy of a STARK, Part 6: Speeding Things Up](https://neptune.cash/learn/stark-anatomy/faster/) -- Univariate and multivariate polynomials + - Number Theoretic Transform (discrete Fast Fourier Transform) + - [Anatomy of a STARK, Part 6: Speeding Things Up](https://neptune.cash/learn/stark-anatomy/faster/) +- Univariate polynomials - Merkle Trees - Merkle Mountain Ranges -## Release protocol +## Wasm support -While twenty-first's version is `0.x.y`, releasing a new version: - -1. Is the release backwards-compatible? - Then the new version is `0.x.y+1`. Otherwise the new version is `0.x+1.0`. -2. Checkout the last commit on Mjolnir, and run `make bench-publish`. Save the benchmark's result - and verify that there is no performance degradation. -3. Create a commit that increases `version = "0.x.y"` in twenty-first/Cargo.toml. - The commit message should give a one-line summary of each release change. Include the benchmark - result at the bottom. -4. Have a `v0.x.y` [git tag][tag] on this commit created. (`git tag v0.x.y [sha]`, `git push upstream --tags`) -5. Have this commit `cargo publish`ed on [crates.io][crates] and in GitHub [tags][tags]. - -[tag]: https://git-scm.com/book/en/v2/Git-Basics-Tagging -[tags]: https://github.com/Neptune-Crypto/twenty-first/tags -[crates]: https://crates.io/crates/twenty-first/versions - -If you do not have the privilege to create git tags or run `cargo publish`, submit a PR and the merger will take care of these. +The `twenty-first` library can be built for WebAssembly. See the [dedicated readme](twenty-first/README-wasm32.md) for +further information. \ No newline at end of file diff --git a/twenty-first/README-wasm32.md b/twenty-first/README-wasm32.md new file mode 100644 index 000000000..538792dc3 --- /dev/null +++ b/twenty-first/README-wasm32.md @@ -0,0 +1,71 @@ +# Building and Testing `twenty-first` for wasm32 + +This document provides instructions on how to build and test the `twenty-first` crate for the `wasm32-unknown-unknown` +target, which allows the library to run in WebAssembly environments. + +## What is wasm32? + +WebAssembly (Wasm) is a binary instruction format for a stack-based virtual machine. The `wasm32-unknown-unknown` target +allows Rust code to be compiled into Wasm, enabling high-performance applications to run directly in web browsers and +other Wasm-compatible environments. This is ideal for bringing computationally intensive tasks, like the cryptographic +operations in `twenty-first`, to the web without sacrificing performance. + +For more detailed information, see the official [WebAssembly website](https://webassembly.org/). + +## Required Tools and Setup + +To build and test for `wasm32`, you need to set up your Rust environment with the correct target and tooling. + +### Add the `wasm32` Target + +First, add the `wasm32-unknown-unknown` target to your Rust toolchain using `rustup`: + +```shell +rustup target add wasm32-unknown-unknown +``` + +### Install `wasm-pack` + +`wasm-pack` is the primary tool for building, testing, and publishing Rust-generated WebAssembly. It coordinates the +build process and handles the interaction with other tools like `wasm-bindgen`. + +Install `wasm-pack` using `cargo`: + +```shell +cargo install wasm-pack +``` + +### Install Node.js (for Testing) + +Running the `wasm32` test suite requires a JavaScript runtime. `wasm-pack` uses Node.js for this purpose. + +You must have Node.js v20 (LTS) or later installed. The `getrandom` crate, a dependency for our tests, requires the Web +Crypto API, which is stable and fully supported in all modern LTS releases of Node.js. + +You can download Node.js from the [official Node.js website](https://nodejs.org/) or install it using a version manager +like `nvm`. + +## Build and Test Commands + +With the environment configured, you can now build and test the crate. Make sure your current working directory is +`twenty-first/twenty-first` before executing any of the commands below. + +### Build the Crate + +To compile the `twenty-first` crate for WebAssembly, run the following command: + +```shell +wasm-pack build --target nodejs +``` + +This command compiles the crate and generates the necessary JavaScript bindings, placing the output in a `pkg/` +directory. + +### Run Tests + +To run the test suite for the `wasm32` target, use the `test` command from `wasm-pack`. This command will compile the +tests and execute them using your installed Node.js runtime. + +```shell +wasm-pack test --node +``` From f4951a1264d7aa397bd83dcaf8d9772ea963bedf Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Thu, 15 Jan 2026 10:50:32 +0100 Subject: [PATCH 4/4] ci: Build and test on `wasm32` target --- .github/workflows/wasm.yml | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/wasm.yml diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml new file mode 100644 index 000000000..30d764cf3 --- /dev/null +++ b/.github/workflows/wasm.yml @@ -0,0 +1,40 @@ +name: WASM + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + wasm: + name: Build, lint, test + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Install wasm-pack + uses: taiki-e/install-action@v2 + with: + tool: wasm-pack + + - name: Build with wasm-pack + working-directory: twenty-first + run: wasm-pack build --release --target nodejs + + - name: Run wasm-pack tests + working-directory: twenty-first + run: wasm-pack test --node