mirror of
https://github.com/logos-blockchain/lez-fuzzing.git
synced 2026-07-02 07:49:45 +00:00
fix: add negative test
This commit is contained in:
parent
ee13844b64
commit
119c867b89
@ -158,7 +158,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
// Non-signer public accounts at applied indices are set to their post-state.
|
||||
for (idx, id) in public_account_ids.iter().enumerate() {
|
||||
if idx >= public_post_states.len() {
|
||||
break; // zip in apply_state_diff truncates to the shorter vector
|
||||
break; // public_diff zips ids with post_states, truncating to the shorter vec
|
||||
}
|
||||
if signer_ids.contains(id) {
|
||||
continue; // signer accounts also get a nonce increment afterwards
|
||||
|
||||
@ -260,9 +260,14 @@ pub fn arb_privacy_preserving_tx(
|
||||
}
|
||||
|
||||
// ── new_nullifiers (unique — validator check 2b) ─────────────────────────────────
|
||||
// Check 6 additionally requires each digest to be a recognised commitment-set root.
|
||||
// Using the live root makes the success path reachable; a random digest drives the
|
||||
// check-6 rejection path.
|
||||
// Check 6 additionally requires each digest to be in the commitment set's `root_history`.
|
||||
// `root_history` starts *empty* on a fresh genesis state and is only seeded once a
|
||||
// commitment-bearing transaction applies (`CommitmentSet::extend` inserts the post-insert
|
||||
// root). So a nullifier digest set to the live root only passes check 6 on a *later*
|
||||
// transaction in the sequence — after an earlier tx grew the commitment set; against the
|
||||
// first tx (empty history) even the live root is rejected. We still use the live root half
|
||||
// the time so the success path becomes reachable once seeded; a random digest always drives
|
||||
// the check-6 rejection path.
|
||||
let n_null = (u8::arbitrary(u)? as usize) % 3;
|
||||
let live_root = state.commitment_set_digest();
|
||||
let mut new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)> = Vec::new();
|
||||
|
||||
@ -67,6 +67,59 @@ fn synthesized_proof_reaches_checks_5_6_and_applies() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Negative counterpart to the test above: the synthesised `FakeReceipt` is a forgery that
|
||||
/// must pass **only** under `RISC0_DEV_MODE`. With dev mode off, `Receipt::verify` runs the
|
||||
/// real integrity check, the fake fails it, and the executor must reject the transaction at
|
||||
/// check 4 — never reaching checks 5–6 or `apply_state_diff`.
|
||||
///
|
||||
/// This locks the dev-mode boundary in CI: it asserts the forgery is genuinely inert in a
|
||||
/// production-mode verifier, so `synthesize_passing_proof` can never be mistaken for a
|
||||
/// real-proof generator. It is the mirror of `synthesized_proof_reaches_checks_5_6_and_applies`
|
||||
/// — exactly one of the two runs in any given environment (a bare `cargo test` runs this one;
|
||||
/// `RISC0_DEV_MODE=1 cargo test` runs the other), so both directions are covered across CI.
|
||||
#[test]
|
||||
fn synthesized_proof_is_rejected_without_dev_mode() {
|
||||
let dev_mode = std::env::var("RISC0_DEV_MODE").is_ok_and(|v| v == "1" || v == "true");
|
||||
if dev_mode {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
|
||||
// Same well-formed message as the positive test: checks 1–3 are vacuous/trivially met, so a
|
||||
// rejection can only come from check 4 (proof verification) failing on the fake receipt.
|
||||
let aid = AccountId::new([7_u8; 32]);
|
||||
let commitment = Commitment::new(&aid, &Account::default());
|
||||
let message = PPMessage {
|
||||
public_account_ids: vec![],
|
||||
nonces: vec![],
|
||||
public_post_states: vec![],
|
||||
encrypted_private_post_states: vec![],
|
||||
new_commitments: vec![commitment.clone()],
|
||||
new_nullifiers: vec![],
|
||||
block_validity_window: BlockValidityWindow::new_unbounded(),
|
||||
timestamp_validity_window: TimestampValidityWindow::new_unbounded(),
|
||||
};
|
||||
|
||||
let proof = synthesize_passing_proof(&message, &state, &[]);
|
||||
let witness_set = PPWitnessSet::for_message(&message, proof, &[]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
assert!(
|
||||
state
|
||||
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
|
||||
.is_err(),
|
||||
"a synthesised fake receipt must be rejected at check 4 when RISC0_DEV_MODE is off - \
|
||||
the forgery must never verify in a production-mode verifier",
|
||||
);
|
||||
|
||||
// The rejection must also leave private state untouched (no commitment inserted).
|
||||
assert!(
|
||||
state.get_proof_for_commitment(&commitment).is_none(),
|
||||
"a rejected transaction must not insert its commitment into the set",
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Generator contract tests
|
||||
//
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user