From d31a02969370eb41b700802a38a9e52f8f1fd1e5 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 26 Jun 2026 17:41:55 +0800 Subject: [PATCH] fix: mutants harness --- .../fuzz_state_transition/seed_empty_tx | Bin 149 -> 149 bytes .../fuzz_stateless_verification/seed_empty_tx | Bin 149 -> 149 bytes .../fuzz_transaction_decoding/seed_empty_tx | Bin 149 -> 149 bytes fuzz_props/src/tests/privacy.rs | 67 +++++++++++++++--- 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/corpus/libfuzz/fuzz_state_transition/seed_empty_tx b/corpus/libfuzz/fuzz_state_transition/seed_empty_tx index 030f7189152c5c170affa85ab6c775ba8eda8e6e..1953f7e40386725d6ed8fe7ad07c5ac7be9b747a 100644 GIT binary patch delta 74 zcmV-Q0JZ;>0hIxeH9&>CM8cErjaieW^Jx9?Y6<`KK9ePXV7ZZ0hIxeH9#YU#Jn&lVx-FD?O&R^J+t&es=G>jfGaOn#4ldQ9Q-~%-y*Bb gV_tmmW;JGHbV{2yu8h11$0@GRO59C_cQ=uBARVY93IG5A diff --git a/corpus/libfuzz/fuzz_stateless_verification/seed_empty_tx b/corpus/libfuzz/fuzz_stateless_verification/seed_empty_tx index 030f7189152c5c170affa85ab6c775ba8eda8e6e..1953f7e40386725d6ed8fe7ad07c5ac7be9b747a 100644 GIT binary patch delta 74 zcmV-Q0JZ;>0hIxeH9&>CM8cErjaieW^Jx9?Y6<`KK9ePXV7ZZ0hIxeH9#YU#Jn&lVx-FD?O&R^J+t&es=G>jfGaOn#4ldQ9Q-~%-y*Bb gV_tmmW;JGHbV{2yu8h11$0@GRO59C_cQ=uBARVY93IG5A diff --git a/corpus/libfuzz/fuzz_transaction_decoding/seed_empty_tx b/corpus/libfuzz/fuzz_transaction_decoding/seed_empty_tx index 030f7189152c5c170affa85ab6c775ba8eda8e6e..1953f7e40386725d6ed8fe7ad07c5ac7be9b747a 100644 GIT binary patch delta 74 zcmV-Q0JZ;>0hIxeH9&>CM8cErjaieW^Jx9?Y6<`KK9ePXV7ZZ0hIxeH9#YU#Jn&lVx-FD?O&R^J+t&es=G>jfGaOn#4ldQ9Q-~%-y*Bb gV_tmmW;JGHbV{2yu8h11$0@GRO59C_cQ=uBARVY93IG5A diff --git a/fuzz_props/src/tests/privacy.rs b/fuzz_props/src/tests/privacy.rs index ad4a0d08..847c4392 100644 --- a/fuzz_props/src/tests/privacy.rs +++ b/fuzz_props/src/tests/privacy.rs @@ -257,6 +257,41 @@ fn arb_validity_window_bounds_use_modulo_8() { /// Two flavours of check run here: per-iteration upper bounds that must hold for /// *every* generated transaction, and end-of-run reachability checks that confirm /// the interesting shapes actually occur across the sampled inputs. +/// Which branches of the line-233 `if !accounts.is_empty() && bool::arbitrary(u)?` were +/// observable across a transaction's non-signer "extra" public-account ids. +#[derive(Default)] +struct ExtraKinds { + /// At least one extra (non-signer id) was appended at all. + any: bool, + /// An extra equal to a *known* fuzz-account id (the `&&`-true branch). + known: bool, + /// An extra that is a *random* id (the `else` branch). + random: bool, +} + +/// Classify a message's extras. A signer's public-account id is key-derived and independent +/// of `FuzzAccount.account_id`, so any non-signer id present in `public_account_ids` was +/// appended by the line-233 `if`; a *known* id can only come from its `&&`-true branch. +fn classify_extras( + public_account_ids: &[AccountId], + signer_ids: &[AccountId], + known_ids: &std::collections::HashSet, +) -> ExtraKinds { + let mut kinds = ExtraKinds::default(); + for id in public_account_ids { + if signer_ids.contains(id) { + continue; + } + kinds.any = true; + if known_ids.contains(id) { + kinds.known = true; + } else { + kinds.random = true; + } + } + kinds +} + #[test] fn arb_privacy_preserving_tx_generator_invariants() { let accounts: Vec = (1..=6_u8) @@ -270,6 +305,9 @@ fn arb_privacy_preserving_tx_generator_invariants() { accounts.iter().map(|a| (a.account_id, a.balance)).collect(); let state = V03State::new_with_genesis_accounts(&genesis, vec![], 0); + let known_ids: std::collections::HashSet = + accounts.iter().map(|a| a.account_id).collect(); + let mut rng = Rng::new(); let mut buf = vec![0_u8; 8192]; @@ -277,6 +315,8 @@ fn arb_privacy_preserving_tx_generator_invariants() { let mut max_signers = 0_usize; let mut saw_signer = false; let mut saw_extra = false; + let mut saw_known_extra = false; + let mut saw_random_extra = false; let mut max_commitments = 0_usize; let mut max_nullifiers = 0_usize; let mut saw_empty_comm_nonempty_null = false; @@ -344,14 +384,12 @@ fn arb_privacy_preserving_tx_generator_invariants() { msg.encrypted_private_post_states.len() ); - // An id that is not a signer can only be present because an extra was appended. - if msg - .public_account_ids - .iter() - .any(|id| !signer_ids.contains(id)) - { - saw_extra = true; - } + // Classify the non-signer "extras" by which branch of the line-233 `if` produced + // them — a *known* fuzz-account id, a *random* id, or both. + let extras = classify_extras(&msg.public_account_ids, &signer_ids, &known_ids); + saw_extra |= extras.any; + saw_known_extra |= extras.known; + saw_random_extra |= extras.random; max_commitments = max_commitments.max(msg.new_commitments.len()); max_nullifiers = max_nullifiers.max(msg.new_nullifiers.len()); @@ -393,6 +431,19 @@ fn arb_privacy_preserving_tx_generator_invariants() { saw_extra, "the generator never appended an extra public account id" ); + // Both branches `if !accounts.is_empty() && bool::arbitrary(u)?` must be + // reachable. The known-account branch must fire (else `delete !` — which short-circuits to + // the random branch when accounts are present — would be indistinguishable) + assert!( + saw_known_extra, + "the generator never appended a *known* fuzz-account id as an extra" + ); + // …and the random branch must fire (else `&&`→`||` — which short-circuits to the known + // branch when accounts are present — would be indistinguishable). + assert!( + saw_random_extra, + "the generator never appended a *random* id as an extra" + ); // Multiple distinct commitments must be reachable (the dedup must keep, not drop). assert!( max_commitments >= 2,