Skip to content

Commit 32dbe90

Browse files
committed
feat(fuzz): session 55 — per-system VK fuzz harnesses
Adds 5 new fuzz harnesses targeting the verifying-key parser of each Phase-2 + Phase-3 system. Mirrors the session-54 proof-bytes harness pattern but flips the role of the libfuzzer input: the fuzz_target call swaps `data` into the VK slot while the proof and public-inputs slots use the scaffold fixtures. mosaic-fuzz/Cargo.toml — adds 5 new `[[bin]]` entries: fuzz_plonk_vk_bytes fuzz_hyperplonk_vk_bytes fuzz_halo2_vk_bytes fuzz_nova_vk_bytes fuzz_stark_vk_bytes 5 new fuzz_target files in `fuzz_targets/`, each pinning the same panic-free invariant on the system-specific VK parser: - fuzz_plonk_vk_bytes — 744-byte fixed envelope; non-matching lengths must surface as `Err(VerifyingKeyLengthMismatch)`. - fuzz_hyperplonk_vk_bytes — 744-byte fixed envelope; the cross- check `vk.num_variables == proof.sumcheck_rounds` adds a second dimension the harness can flip. - fuzz_halo2_vk_bytes — variable-length tail (fixed_commits ‖ permutation_commits). Empty IC equivalents (zero-length payload) and oversized payloads are both expected to be rejected. - fuzz_nova_vk_bytes — 235-byte fixed envelope plus the 3-way `FoldingVariant::from_byte` tag rejection for bytes ∉ {0, 1, 2}. - fuzz_stark_vk_bytes — 48-byte fixed envelope plus the `StarkFieldId::from_byte` 3-way tag rejection plus the structural cross-check against the scaffold proof shape. After session 55 the fuzz harness inventory is 13 targets total: Phase-1 (Groth16, 3 original) fuzz_groth16_proof_bytes, fuzz_vk_bytes, fuzz_public_inputs Phase-2 (KZG-PLONK, 2 new) fuzz_plonk_proof_bytes, fuzz_plonk_vk_bytes Phase-3 (HyperPlonk + Halo2 + Nova + STARK, 8 new) fuzz_hyperplonk_{proof,vk}_bytes fuzz_halo2_{proof,vk}_bytes fuzz_nova_{proof,vk}_bytes fuzz_stark_{proof,vk}_bytes Run any harness with: cargo +nightly fuzz run --fuzz-dir crates/mosaic-fuzz \ fuzz_<system>_<surface>_bytes -- -max_total_time=300 The remaining audit-pre-req fuzz gaps are: - Per-system public-input fuzzers (Phase-2 + Phase-3) — the groth16 one already exists; the others would be near-trivial follow-ups. - A combined "all three slots" fuzzer that splits the libfuzzer input into vk/proof/public_inputs by length-prefix — explores the cross-slot interaction surface that single-slot fuzzers can't reach. Local sanity: `cargo check -p mosaic-fuzz --lib` is clean (only pedantic missing-docs warnings on the fixture struct fields, inherited from session 54).
1 parent 9a4f04c commit 32dbe90

6 files changed

Lines changed: 144 additions & 0 deletions

File tree

crates/mosaic-fuzz/Cargo.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,39 @@ path = "fuzz_targets/fuzz_stark_proof_bytes.rs"
8282
test = false
8383
doc = false
8484

85+
# ───────────────────────────────────────────────────────────────────────
86+
# Session 55 — Per-system VK fuzz harnesses
87+
# ───────────────────────────────────────────────────────────────────────
88+
89+
[[bin]]
90+
name = "fuzz_plonk_vk_bytes"
91+
path = "fuzz_targets/fuzz_plonk_vk_bytes.rs"
92+
test = false
93+
doc = false
94+
95+
[[bin]]
96+
name = "fuzz_hyperplonk_vk_bytes"
97+
path = "fuzz_targets/fuzz_hyperplonk_vk_bytes.rs"
98+
test = false
99+
doc = false
100+
101+
[[bin]]
102+
name = "fuzz_halo2_vk_bytes"
103+
path = "fuzz_targets/fuzz_halo2_vk_bytes.rs"
104+
test = false
105+
doc = false
106+
107+
[[bin]]
108+
name = "fuzz_nova_vk_bytes"
109+
path = "fuzz_targets/fuzz_nova_vk_bytes.rs"
110+
test = false
111+
doc = false
112+
113+
[[bin]]
114+
name = "fuzz_stark_vk_bytes"
115+
path = "fuzz_targets/fuzz_stark_vk_bytes.rs"
116+
test = false
117+
doc = false
118+
85119
[lints]
86120
workspace = true
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//! Fuzz harness — arbitrary bytes treated as a Halo2-KZG VK.
2+
//!
3+
//! Session 55 — Phase-3 VK surface coverage. Halo2's VK has a
4+
//! variable-length tail (fixed_commits ‖ permutation_commits) so
5+
//! the harness exercises both the fixed-header parser and the
6+
//! length-prefixed payload bounds checks. Empty IC equivalents
7+
//! (zero-length payload) and oversized payloads are both expected
8+
//! to surface as `Err(VerifyingKeyLengthMismatch)`.
9+
10+
#![no_main]
11+
12+
use libfuzzer_sys::fuzz_target;
13+
use mosaic_core::{proof_system::ProofSystem, syscall::host::HostBackend};
14+
use mosaic_fuzz::Halo2Fixtures;
15+
use mosaic_halo2::Halo2KzgBn254;
16+
17+
fuzz_target!(|data: &[u8]| {
18+
let f = Halo2Fixtures::default();
19+
let backend = HostBackend::new();
20+
let v = Halo2KzgBn254::new(&backend);
21+
let _ = ProofSystem::verify(&v, data, &f.proof, &f.public_inputs);
22+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//! Fuzz harness — arbitrary bytes treated as a HyperPlonk-KZG VK.
2+
//!
3+
//! Session 55 — Phase-3 VK surface coverage. HyperPlonk's VK is a
4+
//! fixed 744-byte envelope (4 + 4 + 128 + 8·64 + 3·32). The harness
5+
//! pins the panic-free invariant on the VK parser plus the structural
6+
//! cross-check `vk.num_variables == proof.sumcheck_rounds`.
7+
8+
#![no_main]
9+
10+
use libfuzzer_sys::fuzz_target;
11+
use mosaic_core::{proof_system::ProofSystem, syscall::host::HostBackend};
12+
use mosaic_fuzz::HyperPlonkFixtures;
13+
use mosaic_hyperplonk::HyperPlonkKzgBn254;
14+
15+
fuzz_target!(|data: &[u8]| {
16+
let f = HyperPlonkFixtures::default();
17+
let backend = HostBackend::new();
18+
let v = HyperPlonkKzgBn254::new(&backend);
19+
let _ = ProofSystem::verify(&v, data, &f.proof, &f.public_inputs);
20+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//! Fuzz harness — arbitrary bytes treated as a Nova folding VK.
2+
//!
3+
//! Session 55 — Phase-3 VK surface coverage. Nova's VK is a fixed
4+
//! 235-byte envelope (1 + 2 + 4 + 128 + 3·64 + 32) with a 3-way
5+
//! variant tag at offset 0. The harness pins:
6+
//!
7+
//! - the `FoldingVariant::from_byte` rejection for tags ∉ {0, 1, 2}
8+
//! - the fixed-length envelope check
9+
//! - panic-free behaviour on every other adversarial input shape
10+
11+
#![no_main]
12+
13+
use libfuzzer_sys::fuzz_target;
14+
use mosaic_core::{proof_system::ProofSystem, syscall::host::HostBackend};
15+
use mosaic_fuzz::NovaFixtures;
16+
use mosaic_nova::NovaFolding;
17+
18+
fuzz_target!(|data: &[u8]| {
19+
let f = NovaFixtures::default();
20+
let backend = HostBackend::new();
21+
let v = NovaFolding::new(&backend);
22+
let _ = ProofSystem::verify(&v, data, &f.proof, &f.public_inputs);
23+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//! Fuzz harness — arbitrary bytes treated as a KZG-PLONK verifying key.
2+
//!
3+
//! Session 55 — Phase-2 VK surface coverage. Pins the panic-free
4+
//! invariant on PLONK's VK byte-layout parser: a 744-byte fixed
5+
//! envelope. Any input that doesn't exactly match must surface as
6+
//! `Err(VerifyingKeyLengthMismatch)` and never panic.
7+
8+
#![no_main]
9+
10+
use libfuzzer_sys::fuzz_target;
11+
use mosaic_core::{proof_system::ProofSystem, syscall::host::HostBackend};
12+
use mosaic_fuzz::PlonkFixtures;
13+
use mosaic_plonk::PlonkKzgBn254;
14+
15+
fuzz_target!(|data: &[u8]| {
16+
let f = PlonkFixtures::default();
17+
let backend = HostBackend::new();
18+
let v = PlonkKzgBn254::new(&backend);
19+
let _ = ProofSystem::verify(&v, data, &f.proof, &f.public_inputs);
20+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//! Fuzz harness — arbitrary bytes treated as a FRI-STARK VK.
2+
//!
3+
//! Session 55 — Phase-3 VK surface coverage. FRI-STARK's VK is a
4+
//! fixed 48-byte envelope (1 + 4 + 2 + 1 + 32 + 8) with a 3-way
5+
//! field-id tag at offset 0. The harness pins:
6+
//!
7+
//! - the `StarkFieldId::from_byte` rejection for tags ∉ {0, 1, 2}
8+
//! - the fixed 48-byte envelope check
9+
//! - the structural cross-check `vk.{trace_log_height,
10+
//! trace_width, log_blowup} == proof.*` against the bench's
11+
//! scaffold proof shape
12+
13+
#![no_main]
14+
15+
use libfuzzer_sys::fuzz_target;
16+
use mosaic_core::{proof_system::ProofSystem, syscall::host::HostBackend};
17+
use mosaic_fuzz::StarkFixtures;
18+
use mosaic_stark::FriStark;
19+
20+
fuzz_target!(|data: &[u8]| {
21+
let f = StarkFixtures::default();
22+
let backend = HostBackend::new();
23+
let v = FriStark::new(&backend);
24+
let _ = ProofSystem::verify(&v, data, &f.proof, &f.public_inputs);
25+
});

0 commit comments

Comments
 (0)