# Poseidon2 verify_batch: start_top_level is not constrained to happen only once during top level

Poseidon2 verify_batch AIR lacked a constraint forcing start_top_level to occur once, letting a malicious prover forge row hashes and inject arbitrary unconstrained inputs.

- Payout: Contest reward
- Severity: high
- Program: OpenVM
- Credited: cergyk
- Disclosed: 2025-03-24
- Write-up quality: 7/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_openvm_january2025.pdf#3.1.20
- Library page: https://bugbountylibrary.com/r/cantina%3Aopenvm_january2025%3A3.1.20
- Tags: domain:web3 impact:integrity kind:contest platform:cantina sev:high tech:rust technique:incomplete-fix technique:source-review vuln:misconfiguration

**Target:** Poseidon2 verify_batch AIR circuit (zkVM proof system)
**Root cause:** The AIR only constrained that top level has start_top_level=1 but never enforced it equals 0 on non-top-level rows nor that it occurs exactly once, so a prover can set it mid-process and alter the row_hash computation.
**Steps:**
1. Read poseidon2/air.rs#L460-L463: `row_hash = start_top_level*left_output[i] + (1-start_top_level)*right_input[i]`
2. Observe no constraint ties start_top_level to first row of top level or forces it to 0 afterwards
3. Set start_top_level=1 mid top-level processing so row_hash uses left_output[i] instead of right_input[i]
4. Supply the incorporated row as preimage of left_output[i] with matching opened_len
5. Depending on previous row's root_is_on_right, provide the fully unconstrained right_input or left_input arbitrarily
6. Produce a forged proof accepted by verify_batch
**How found:** Code review of the AIR constraints: noticed start_top_level is documented as occurring only once at top level but no constraint enforces that, then traced how its value feeds the row_hash selector and which inputs become unconstrained.
**Exploitation:** Malicious prover crafts a witness where start_top_level is set mid top-level, making the row hash depend on left_output instead of right_input; the unconstrained sibling input is chosen arbitrarily, yielding a valid-looking proof for a forged Merkle/row incorporation.
**Insight:** When a boolean flag selects between two hash inputs in an AIR, verify the flag's transition constraints (exactly-once, monotonic) — an unconstrained selector silently frees the non-selected input for arbitrary prover choice.
