# PLONK/Groth16 verifiers accept proofs with untrusted recursion vk root

SP1 PLONK/Groth16 final verifiers omit checking public vk_root against the trusted recursion vk allowlist, letting a prover certify proofs from unauthorized recursion circuits.

- Payout: Contest reward
- Severity: medium
- Program: Succinct
- Credited: Matte
- Disclosed: 2026-02-18
- Write-up quality: 6/10
- Original: https://code4rena.com/reports/2025-09-succinct#m-01
- Library page: https://bugbountylibrary.com/r/c4%3A2025-09-succinct%3AM-01
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:access-control vuln:signature-replay

**Target:** SP1 prover BN254 final verification helpers (verify_plonk_bn254 / verify_groth16_bn254)
**Root cause:** verify_plonk_bn254() and verify_groth16_bn254() parse vk_root from proof.public_inputs but only re-check the vkey hash and SP1 public values hash, never comparing vk_root to self.recursion_prover.recursion_vk_root.
**Steps:**
1. Read crates/prover/src/verify.rs L565-L624; note vk_root, proof_nonce parsed from proof.public_inputs.
2. Observe only verify_*_public_inputs() (vkey hash + public values hash) is re-validated after scheme verification.
3. Build a recursion Merkle tree with unauthorized vks (e.g. circuits omitting constraints) yielding an attacker-chosen vk_root.
4. Generate wrap + PLONK/Groth16 proofs referencing that vk_root; internal crypto checks stay consistent.
5. Submit to verifier; accepted since vk_root is never compared to trusted recursion_vk_root.
**How found:** Code review of the final BN254 verification helpers: noticed vk_root is a public input but the post-verification re-checks only cover vkey hash and public values hash, leaving the allowlist root unbound to a trusted reference.
**Exploitation:** A malicious prover constructs a recursion vk tree containing unauthorized verification keys, produces wrap and final PLONK/Groth16 proofs referencing the altered vk_root, and submits them; the verifier accepts, certifying arbitrary/invalid executions for onchain verification.
**Insight:** When a proof system carries an allowlist/root as a public input, check whether the final verifier actually binds it to a trusted local value—re-validating only hashes of other fields leaves the root attacker-controlled.
