2026-04-15 15:47:01 +08:00
|
|
|
#![no_main]
|
|
|
|
|
//! Fuzz target: encoding round-trip for all transaction types.
|
|
|
|
|
//!
|
2026-05-13 10:34:57 +08:00
|
|
|
//! Invariants exercised:
|
|
|
|
|
//!
|
|
|
|
|
//! 1. **Encode/decode stability** — `encode(decode(encode(tx))) == encode(tx)`.
|
|
|
|
|
//! 2. **Canonical encoding** — if raw fuzzer bytes decode successfully, re-encoding must
|
|
|
|
|
//! reproduce those exact bytes (`encode(decode(data)) == data`). This catches non-canonical
|
|
|
|
|
//! encodings that are accepted by the decoder but silently normalised on the way out.
|
2026-04-15 15:47:01 +08:00
|
|
|
//!
|
|
|
|
|
//! `PrivacyPreservingTransaction` is excluded because its ZK receipt cannot be
|
|
|
|
|
//! reconstructed in a fuzzing loop.
|
|
|
|
|
|
|
|
|
|
use arbitrary::{Arbitrary, Unstructured};
|
|
|
|
|
use fuzz_props::arbitrary_types::{ArbProgramDeploymentTransaction, ArbPublicTransaction};
|
|
|
|
|
use libfuzzer_sys::fuzz_target;
|
|
|
|
|
use nssa::{ProgramDeploymentTransaction, PublicTransaction};
|
|
|
|
|
|
|
|
|
|
fuzz_target!(|data: &[u8]| {
|
|
|
|
|
let mut u = Unstructured::new(data);
|
|
|
|
|
|
|
|
|
|
// ── Test 1: PublicTransaction round-trip ──────────────────────────────────
|
|
|
|
|
if let Ok(wrapped) = ArbPublicTransaction::arbitrary(&mut u) {
|
|
|
|
|
let tx = wrapped.0;
|
|
|
|
|
let encoded = tx.to_bytes();
|
|
|
|
|
let decoded = PublicTransaction::from_bytes(&encoded)
|
|
|
|
|
.expect("INVARIANT VIOLATION: PublicTransaction::to_bytes() produced un-decodable output");
|
|
|
|
|
let re_encoded = decoded.to_bytes();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
encoded,
|
|
|
|
|
re_encoded,
|
|
|
|
|
"INVARIANT VIOLATION: encode(decode(encode(tx))) != encode(tx) for PublicTransaction"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Test 2: ProgramDeploymentTransaction round-trip ───────────────────────
|
|
|
|
|
if let Ok(wrapped) = ArbProgramDeploymentTransaction::arbitrary(&mut u) {
|
|
|
|
|
let tx = wrapped.0;
|
|
|
|
|
let encoded = tx.to_bytes();
|
|
|
|
|
let decoded = ProgramDeploymentTransaction::from_bytes(&encoded)
|
|
|
|
|
.expect("INVARIANT VIOLATION: ProgramDeploymentTransaction::to_bytes() produced un-decodable output");
|
|
|
|
|
let re_encoded = decoded.to_bytes();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
encoded,
|
|
|
|
|
re_encoded,
|
|
|
|
|
"INVARIANT VIOLATION: encode(decode(encode(tx))) != encode(tx) for ProgramDeploymentTransaction"
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-05-13 10:34:57 +08:00
|
|
|
|
|
|
|
|
// ── Test 3: Canonical encoding — raw bytes that decode must re-encode identically ──
|
|
|
|
|
if let Ok(tx) = PublicTransaction::from_bytes(data) {
|
|
|
|
|
let re_encoded = tx.to_bytes();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
data,
|
|
|
|
|
re_encoded.as_slice(),
|
|
|
|
|
"INVARIANT VIOLATION: PublicTransaction decoded from raw fuzzer bytes but \
|
|
|
|
|
re-encoding differs from the original input (non-canonical encoding accepted)"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Test 4: Canonical encoding for ProgramDeploymentTransaction ──────────────────
|
|
|
|
|
if let Ok(tx) = ProgramDeploymentTransaction::from_bytes(data) {
|
|
|
|
|
let re_encoded = tx.to_bytes();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
data,
|
|
|
|
|
re_encoded.as_slice(),
|
|
|
|
|
"INVARIANT VIOLATION: ProgramDeploymentTransaction decoded from raw fuzzer bytes \
|
|
|
|
|
but re-encoding differs from the original input (non-canonical encoding accepted)"
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-04-15 15:47:01 +08:00
|
|
|
});
|