Skip to content

Commit b3cdaa5

Browse files
committed
test: add security tests for private input tamper scenarios
Verify the verifier rejects proofs with tampered num_private_input_pages (zeroed, inflated, exceeds max) and tampered public_output when private input is present. Also confirm the proof struct does not carry raw private input bytes.
1 parent a27148c commit b3cdaa5

1 file changed

Lines changed: 134 additions & 0 deletions

File tree

prover/src/tests/prove_elfs_tests.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2039,6 +2039,140 @@ fn test_prove_ethrex_empty_block() {
20392039
assert_eq!(proof.public_output.len(), 160);
20402040
}
20412041

2042+
// =============================================================================
2043+
// Security: private-input tamper tests
2044+
// =============================================================================
2045+
2046+
/// Verifier must reject when num_private_input_pages is zeroed out.
2047+
/// The proof contains a non-preprocessed PAGE sub-proof for the private input,
2048+
/// but the verifier expects 0 such pages → proof count mismatch.
2049+
#[test]
2050+
fn test_verify_rejects_tampered_num_private_input_pages_zero() {
2051+
let elf_bytes = crate::test_utils::asm_elf_bytes("test_private_input_xpage");
2052+
let input: Vec<u8> = (0u8..16).collect();
2053+
let vm_proof = crate::prove_with_inputs(&elf_bytes, &input).expect("prove should succeed");
2054+
2055+
// Baseline: untampered proof must verify.
2056+
assert!(
2057+
crate::verify(&vm_proof, &elf_bytes).expect("verify should not error"),
2058+
"Baseline proof must verify before tampering"
2059+
);
2060+
assert!(
2061+
vm_proof.num_private_input_pages > 0,
2062+
"proof should have private pages"
2063+
);
2064+
2065+
// Tamper: zero out private input pages.
2066+
let tampered = crate::VmProof {
2067+
num_private_input_pages: 0,
2068+
..vm_proof
2069+
};
2070+
2071+
let result = crate::verify(&tampered, &elf_bytes);
2072+
assert!(
2073+
result.is_err() || result.unwrap() == false,
2074+
"Verifier must reject proof with num_private_input_pages zeroed out"
2075+
);
2076+
}
2077+
2078+
/// Verifier must reject when num_private_input_pages is inflated beyond actual.
2079+
/// The proof has 1 private page but we claim 2 → proof count mismatch.
2080+
#[test]
2081+
fn test_verify_rejects_inflated_num_private_input_pages() {
2082+
let elf_bytes = crate::test_utils::asm_elf_bytes("test_private_input_xpage");
2083+
let input: Vec<u8> = (0u8..16).collect();
2084+
let vm_proof = crate::prove_with_inputs(&elf_bytes, &input).expect("prove should succeed");
2085+
2086+
assert_eq!(
2087+
vm_proof.num_private_input_pages, 1,
2088+
"16 bytes fits in 1 page"
2089+
);
2090+
2091+
let tampered = crate::VmProof {
2092+
num_private_input_pages: 2,
2093+
..vm_proof
2094+
};
2095+
2096+
let result = crate::verify(&tampered, &elf_bytes);
2097+
assert!(
2098+
result.is_err() || result.unwrap() == false,
2099+
"Verifier must reject proof with inflated num_private_input_pages"
2100+
);
2101+
}
2102+
2103+
/// Verifier must reject num_private_input_pages that exceeds the max bound.
2104+
/// The early bounds check should catch this before constructing AIRs.
2105+
#[test]
2106+
fn test_verify_rejects_num_private_input_pages_exceeds_max() {
2107+
let elf_bytes = crate::test_utils::asm_elf_bytes("test_private_input_xpage");
2108+
let input: Vec<u8> = (0u8..16).collect();
2109+
let vm_proof = crate::prove_with_inputs(&elf_bytes, &input).expect("prove should succeed");
2110+
2111+
let tampered = crate::VmProof {
2112+
num_private_input_pages: 1000,
2113+
..vm_proof
2114+
};
2115+
2116+
assert!(
2117+
crate::verify(&tampered, &elf_bytes).is_err(),
2118+
"Verifier must error on num_private_input_pages exceeding max"
2119+
);
2120+
}
2121+
2122+
/// Verifier must reject tampered public_output when private input is used.
2123+
/// Ensures the COMMIT bus balance check still works with non-preprocessed pages.
2124+
#[test]
2125+
fn test_verify_rejects_private_input_with_tampered_public_output() {
2126+
let elf_bytes = crate::test_utils::asm_elf_bytes("test_private_input_xpage");
2127+
let input: Vec<u8> = (0u8..16).collect();
2128+
let vm_proof = crate::prove_with_inputs(&elf_bytes, &input).expect("prove should succeed");
2129+
2130+
assert!(
2131+
crate::verify(&vm_proof, &elf_bytes).expect("verify"),
2132+
"Baseline must verify"
2133+
);
2134+
2135+
let mut tampered_output = vm_proof.public_output.clone();
2136+
tampered_output[0] ^= 0x01;
2137+
let tampered = crate::VmProof {
2138+
public_output: tampered_output,
2139+
..vm_proof
2140+
};
2141+
2142+
let verified =
2143+
crate::verify(&tampered, &elf_bytes).expect("verify should not error on tampered output");
2144+
assert!(
2145+
!verified,
2146+
"Verifier must reject proof with tampered public_output (private input present)"
2147+
);
2148+
}
2149+
2150+
/// VmProof must not contain a field that stores the raw private input bytes.
2151+
/// This is a structural check: the proof struct should only carry
2152+
/// `num_private_input_pages`, not the actual input data.
2153+
#[test]
2154+
fn test_proof_does_not_contain_private_input_field() {
2155+
let elf_bytes = crate::test_utils::asm_elf_bytes("test_private_input_xpage");
2156+
let input: Vec<u8> = vec![
2157+
0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE,
2158+
0xF0,
2159+
];
2160+
let vm_proof = crate::prove_with_inputs(&elf_bytes, &input).expect("prove should succeed");
2161+
2162+
// The VmProof struct should only contain num_private_input_pages (a count),
2163+
// not the actual bytes. Verify the proof's public fields don't contain them.
2164+
assert_eq!(vm_proof.num_private_input_pages, 1);
2165+
// public_output is the committed output, NOT the private input.
2166+
// It should contain bytes [4..12] of the input (what the ASM program commits).
2167+
assert_eq!(vm_proof.public_output, input[4..12].to_vec());
2168+
// No `private_input` field exists — this is enforced by the type system,
2169+
// but explicitly document that the proof carries only the page count.
2170+
assert!(
2171+
vm_proof.num_private_input_pages <= 1,
2172+
"Only the page count is stored, not the bytes"
2173+
);
2174+
}
2175+
20422176
/// Regression test: addiw with negative immediate must verify.
20432177
/// arg2_sign_bit is the sign bit of rv2 (bit 31), not of arg2, per spec
20442178
/// constraint CPU-CE61: MSB16[arg2_sign_bit; rv2[1]].

0 commit comments

Comments
 (0)