From 3370a394172065aac37f82c3ca76e5915dab6ccc Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 13 May 2026 10:34:57 +0800 Subject: [PATCH] fix: add canonical encoding check --- fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs | 30 ++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs b/fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs index 599a489..3f13302 100644 --- a/fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs +++ b/fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs @@ -1,8 +1,12 @@ #![no_main] //! Fuzz target: encoding round-trip for all transaction types. //! -//! Invariant: `decode(encode(tx)) == Ok(tx)` and `encode(decode(encode(tx))) == encode(tx)` -//! for every `PublicTransaction` and `ProgramDeploymentTransaction`. +//! 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. //! //! `PrivacyPreservingTransaction` is excluded because its ZK receipt cannot be //! reconstructed in a fuzzing loop. @@ -42,4 +46,26 @@ fuzz_target!(|data: &[u8]| { "INVARIANT VIOLATION: encode(decode(encode(tx))) != encode(tx) for ProgramDeploymentTransaction" ); } + + // ── 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)" + ); + } });