Skip to content

optimizations for prover & verifier#1

Merged
markosg04 merged 4 commits into
a16z:mainfrom
quangvdao:main
Nov 9, 2025
Merged

optimizations for prover & verifier#1
markosg04 merged 4 commits into
a16z:mainfrom
quangvdao:main

Conversation

@quangvdao
Copy link
Copy Markdown
Contributor

@quangvdao quangvdao commented Nov 8, 2025

PR title

Reduce verifier to O(nu) time and optimize first-round D2 pairings

Summary

  • Verifier no longer folds 2^nu-length tensors. It computes final s1, s2 via O(nu) accumulators using per-dimension coordinates.
  • Prover first round: compute D2L/D2R as MSM(G1, scalars) + one pairing with h2 instead of multi-pairing.
  • Correct round-to-dimension mapping on verifier (last dimension folds first).
  • Keep C+/C− unchanged, since v2 is no longer fixed-base after the first combine.
  • Cleanups: remove dead imports, no behavior change elsewhere.

Details

  • Verifier math
    • s1_final = ∏(y_t + α_t·(1 − y_t))
    • s2_final = ∏(x_t + α_t^{-1}·(1 − x_t))
    • Consume y_t, x_t in reverse of variable order, matching folding.
  • Implementation
    • DoryVerifierState now holds s1_acc, s2_acc and s1_coords, s2_coords. No tensor folding.
    • verify_evaluation_proof passes per-dimension coords; if sigma < nu, pad s2 coords with 1.
    • process_round derives index from nu and updates accumulators per round.
    • Prover state carries optional v2_scalars only for round 1; compute_first_message uses MSM+pairing for D2L/D2R when available; apply_first_challenge drops scalars.
  • Compatibility
    • Public API functions setup, prove, verify unchanged.
    • Internal DoryVerifierState::new and DoryProverState::new signatures changed; all internal call sites updated.
  • Performance impact
    • Verifier memory drops from O(2^nu) to O(1) and time per round becomes O(1) for s1/s2 folding.
    • Prover first round replaces two multi-pairings with two MSMs + two pairings.
  • Testing
    • Run: cargo test --features backends
    • The accumulator indexing uses idx = nu - 1 to match fold order.
  • Follow-ups
    • Add Criterion benches under benches/ to quantify improvements.
    • Cache affine bases in setup for faster MSMs.

Why this is correct

  • Folding a Kronecker product with per-round [α, 1] weights factorizes into a product of 2-term dot products, giving the O(nu) formulas above.
  • For round 1, v2[i] = h2·s_i, so ⟨Γ1', v2_half⟩ = e(Σ s_i·Γ1'[i], h2).

Copy link
Copy Markdown
Collaborator

@markosg04 markosg04 left a comment

Choose a reason for hiding this comment

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

Thanks a lot! Just bunch of nits

Comment thread src/evaluation_proof.rs Outdated
left_vec, // s2 = left_vec
row_commitments, // v1 = T_vec_prime (row commitments)
v2, // v2 = v_vec · g_fin
Some(v_vec.clone()), // v2_scalars available for first-round optimization
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can we avoid this clone? seems that prover_state is the last thing to consume v_vec

Comment thread src/evaluation_proof.rs Outdated
Comment on lines +257 to +264
// The verifier uses O(nu) accumulators derived from per-dimension coordinates.
// We take the first `nu` coordinates for s1, and the last `nu` coordinates for s2.
// If there are fewer than `nu` remaining for s2 (when sigma < nu), pad with ones (neutral).
let s1_coords: Vec<F> = point[..nu].to_vec();
let mut s2_coords: Vec<F> = point[nu..(nu + nu).min(point.len())].to_vec();
if s2_coords.len() < nu {
s2_coords.resize_with(nu, F::one);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

could we use sigma for the remainder? we are requiring square dimensions anyways. It might make the comments here clearer as well

Comment thread src/reduce_and_fold.rs Outdated
/// # Parameters
/// - `v1`: Initial G1 vector
/// - `v2`: Initial G2 vector
/// - `v2_scalars`: If v2 = h2 * scalars (first round), pass scalars for MSM+pair optimization
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// - `v2_scalars`: If v2 = h2 * scalars (first round), pass scalars for MSM+pair optimization
/// - `v2_scalars`: Use the scalars in the first round to avoid a multi-pairing by instead performing a (cheaper) MSM + Pairing

Comment thread src/reduce_and_fold.rs Outdated
Comment on lines +103 to +105
if let Some(ref sc) = v2_scalars {
debug_assert_eq!(sc.len(), v2.len(), "v2_scalars must match v2 length");
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if let Some(ref sc) = v2_scalars {
debug_assert_eq!(sc.len(), v2.len(), "v2_scalars must match v2 length");
}
if let Some(sc) = v2_scalars.as_ref() {
debug_assert_eq!(sc.len(), v2.len(), "v2_scalars must match v2 length");
}

ref is older rust and as_ref() is clearer anyways

Comment thread src/reduce_and_fold.rs Outdated
// D₂L = ⟨Γ₁', v₂L⟩, D₂R = ⟨Γ₁', v₂R⟩
let d2_left = E::multi_pair(g1_prime, v2_l);
let d2_right = E::multi_pair(g1_prime, v2_r);
// If v2 was constructed as h2 * scalars (first round), compute MSM(Γ₁', scalars) then one pairing.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// If v2 was constructed as h2 * scalars (first round), compute MSM(Γ₁', scalars) then one pairing.
// In the first round, v2 = h2 * scalars, so we can compute this as the Pairing of MSM(Γ₁', scalars)

Comment thread src/reduce_and_fold.rs Outdated
let d2_left = E::multi_pair(g1_prime, v2_l);
let d2_right = E::multi_pair(g1_prime, v2_r);
// If v2 was constructed as h2 * scalars (first round), compute MSM(Γ₁', scalars) then one pairing.
let (d2_left, d2_right) = if let Some(ref scalars) = self.v2_scalars {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

let's use as_ref() here as well

Comment thread src/reduce_and_fold.rs Outdated
self.v2[i] = self.v2[i] + self.setup.g2_vec[i].scale(&beta_inv);
}

// After first combine, v2 is no longer fixed-base; drop scalars to avoid misuse
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// After first combine, v2 is no longer fixed-base; drop scalars to avoid misuse
// After first combine, the `v2_scalars` optimization does not apply.

Comment thread src/reduce_and_fold.rs Outdated
Comment on lines +463 to +465
// Use accumulated folded scalars
let s1_final = self.s1_acc;
let s2_final = self.s2_acc;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

would it be possible to avoid having both s1/2_final and s1/2_acc ?

Comment thread src/reduce_and_fold.rs Outdated
}
self.s2_tensor.truncate(n2);
// Update folded scalars in O(1): s1_final *= (α·(1−y_t) + y_t), s2_final *= (α⁻¹·(1−x_t) + x_t)
// Determine current round index implicitly from remaining rounds (last dimension folds first).
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

question: (last dimension folds first) -> this is because of endian-ness?

@markosg04 markosg04 merged commit 83918cf into a16z:main Nov 9, 2025
3 checks passed
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