Skip to content

Conversation

@quangvdao
Copy link
Contributor

@quangvdao quangvdao commented Jan 20, 2026

Summary

Harden the Jolt verifier to return Result<_, ProofVerifyError> instead of panicking on malformed proofs or invalid inputs. This is critical for production use where verifier crashes could be exploited.

Status: ✅ Complete — all 395 jolt-core tests pass


Changes

Core verifier hardening (verifier.rs)

  • Add early validation in JoltVerifier::new() for trace_length, ram_K, bytecode_K
  • Use checked_next_power_of_two() to prevent overflow panics
  • Replace slice indexing with .get() + proper error returns
  • Replace zip_eq with length validation + zip
  • Replace HashMap::remove().unwrap() with ok_or_else()
  • Fix serialization methods to propagate errors

Transcripts (blake2b.rs, keccak.rs)

  • Make challenge_bytes() panic-free using iterator-only access
  • Use guarded split_at_mut() and safe zip() instead of indexing

Opening accumulator (opening_proof.rs)

  • Refactor OpeningAccumulator trait to return Result
  • All get_*_opening and append_* methods now return Result<_, ProofVerifyError>
  • Add typed error variants: MissingCommittedOpening, MissingVirtualOpening, MissingAdviceOpening

Sumcheck verification (sumcheck.rs, sumcheck_verifier.rs)

  • Add empty check before .max().unwrap() calls
  • Make SumcheckInstanceVerifier trait methods return Result
  • Propagate errors through verify() and cache_openings()

Claim reductions (claim_reductions/*.rs)

  • All verifier-side new() constructors return Result
  • Remove verifier-reachable unwraps on 2.inverse() and optional fields

Spartan/RAM/Registers verifiers

  • shift.rs: Remove try_into().unwrap() in ShiftSumcheckParams::new
  • ram/mod.rs: remap_address now returns Option instead of panicking
  • output_check.rs: Verifier returns errors instead of unwrapping remap_address

Dory PCS mitigation (external dependency)

  • Resample transcript challenges if zero (prevents inv().expect() panics)
  • Wrap dory::verify in catch_unwind to convert panics to ProofVerifyError::DoryError
  • ⚠️ Note: catch_unwind only works in panic=unwind builds

Error types (errors.rs)

  • Add typed variants: CountMismatch, SliceTooShort, ClaimMismatch, AddressRemapFailed, FieldArithmeticError, MissingProofComponent
  • Remove catch-all InvalidProofStructure(String)

Scope

  • Verifier code paths — fully hardened
  • ⏸️ Prover code paths — intentionally unchanged (out of scope)
  • ⚠️ External dory-pcs — mitigated via catch_unwind, but full panic-free guarantee in panic=abort builds would require patching upstream

Harden the verifier to return Result<_, ProofVerifyError> instead of panicking
on malformed proofs or invalid inputs. This is critical for production use
where verifier crashes could be exploited.

## Core verifier hardening (verifier.rs)
- Add early validation in JoltVerifier::new() for trace_length, ram_K, bytecode_K
- Use checked_next_power_of_two() to prevent overflow panics
- Replace slice indexing with .get() + proper error returns
- Replace zip_eq with length validation + zip
- Replace HashMap::remove().unwrap() with ok_or_else()
- Fix serialization methods to propagate errors

## Transcripts (blake2b.rs, keccak.rs)
- Make challenge_bytes() panic-free using iterator-only access
- Use guarded split_at_mut() and safe zip() instead of indexing

## Opening accumulator (opening_proof.rs)
- Refactor OpeningAccumulator trait to return Result
- All get_*_opening and append_* methods now return Result<_, ProofVerifyError>
- Add typed error variants: MissingCommittedOpening, MissingVirtualOpening, MissingAdviceOpening

## Sumcheck verification (sumcheck.rs, sumcheck_verifier.rs)
- Add empty check before .max().unwrap() calls
- Make SumcheckInstanceVerifier trait methods return Result
- Propagate errors through verify() and cache_openings()

## Claim reductions (claim_reductions/*.rs)
- All verifier-side new() constructors return Result
- Remove verifier-reachable unwraps on 2.inverse() and optional fields

## Spartan/RAM/Registers verifiers
- shift.rs: Remove try_into().unwrap() in ShiftSumcheckParams::new
- ram/mod.rs: remap_address now returns Option instead of panicking
- output_check.rs: Verifier returns errors instead of unwrapping remap_address

## Dory PCS mitigation (external dependency)
- Resample transcript challenges if zero (prevents inv().expect() panics)
- Wrap dory::verify in catch_unwind to convert panics to ProofVerifyError::DoryError
- Note: catch_unwind only works in panic=unwind builds

## Error types (errors.rs)
- Add typed variants: CountMismatch, SliceTooShort, ClaimMismatch,
  AddressRemapFailed, FieldArithmeticError, MissingProofComponent
- Remove catch-all InvalidProofStructure(String)

All 395 jolt-core tests pass. Prover-side panics intentionally unchanged (out of scope).
@quangvdao quangvdao changed the title feat: panic-free verifier (WIP) + cleanups feat: make Jolt verifier panic-free Jan 20, 2026
@0xAndoroid
Copy link
Collaborator

Code Review Finding

File: jolt-core/src/subprotocols/sumcheck.rs:285

assert_eq!(self.compressed_polys.len(), num_rounds);

This assert_eq! remains in SumcheckInstanceProof::verify() and will panic if a malformed proof contains the wrong number of round polynomials. This contradicts the PR's goal of making the verifier panic-free.

Suggested fix:

if self.compressed_polys.len() != num_rounds {
    return Err(ProofVerifyError::InvalidRoundCount {
        expected: num_rounds,
        actual: self.compressed_polys.len(),
    });
}

Note: The InvalidRoundCount error variant already exists in ProofVerifyError.

Copy link
Collaborator

@0xAndoroid 0xAndoroid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, except for some nits that I mentioned.
I'm okay with merging in right now, though it might cause quit e a bit of conflicts with ZK implementation. I think it shouldn't be too harsh tho

- Replace assert_eq! with error return in sumcheck verify
- Remove zero-resample loop in dory transcript wrapper
- Make challenge_bytes a trait default impl
- Remove section comments from ProofVerifyError enum
@quangvdao
Copy link
Contributor Author

I'm just putting this PR up for now, I think it still requires a fair bit of plumbing & cleaning up (esp. on panic freedom on Dory side).

I'm okay with letting this one stay up for a while until it's fully done. Can merge main into this occasionally and fix any problems.

@0xAndoroid
Copy link
Collaborator

Dory verifier is panic free (that's what @markosg04 said)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants