Skip to content

feat: commit the R1CS witness over the base field (proof format 2.0)#470

Open
BornPsych wants to merge 10 commits into
mainfrom
feat/base-field-witness-commit
Open

feat: commit the R1CS witness over the base field (proof format 2.0)#470
BornPsych wants to merge 10 commits into
mainfrom
feat/base-field-witness-commit

Conversation

@BornPsych

Copy link
Copy Markdown
Collaborator

Summary

Commits the R1CS witness over the base field and moves the Spartan blinding
polynomial g to a separate extension-field commitment, instead of committing the
whole witness (padded with the blinding coefficients) in the extension field. The proof
format is bumped to 2.0 and tagged per field so one field's proof is rejected by
another field's verifier. bn254 and Goldilocks both ride the new uniform non-ZK
base-commit path.

Builds on the field-generic spine + two-backend work (#458/#459/#460). Independent of the
CI guard PR (#469).

Why

Goldilocks is a 64-bit prime: its whole value is that committed data (witness, matrix
coefficients) stays in the base field Field64, with only challenges/sumcheck in the
degree-3 extension Field64_3. The old scheme committed the witness in the extension and
padded w1 with room for the blinding polynomial — that forces base data through
extension-width commitments and couples witness sizing to blinding room. Splitting the two
commitments lets the witness commit natively in the base field and sizes the blinding
commitment to just its 4 * m_0 cubic coefficients.

What changed

  • provekit-commonWhirR1CSScheme gains whir_witness: GenericWhirConfig<P::Embedding>
    (base-field, non-ZK) and a separate whir_blinding: GenericWhirConfig<Identity<Ext<P>>>;
    new new_witness_config_for_size / new_blinding_config_for_size / whir_protocol_params;
    the old 4 * m_0 w1-padding is gone. .np format magic is derived from
    ProofField::FIELD_ID (np_format); all format versions bump to 2.0 (major must match
    on read). Covector eval helpers are embedding-aware (base × ext).
  • provekit-prover — commits the base-field witness with whir_witness (no base→ext
    lift) and commits g with the separate whir_blinding; BlindingState carries g's own
    vector + WHIR witness; g is opened by a dedicated whir_blinding.prove after the witness
    opens.
  • provekit-verifier — receives the ext blinding commitment right after the witness
    commitment (matching prover transcript order), verifies the witness with the two-step
    whir::Config API, and verifies g by a dedicated open at the end.
  • Backends — bn254 drops the zkWHIR config for the same non-ZK path (Identity<Fr>,
    FIELD_ID 0, .np magic byte-identical); Goldilocks switches to Basefield<Field64_3>
    (FIELD_ID 1). Adds GoldilocksEfField (Identity<Field64_3>, FIELD_ID 2) as a stand-in
    for challenge-bearing fixtures.
  • Tooling / tests — cli + verifier-server pass whir_witness + whir_blinding to the
    gnark writer; r1cs-compiler security tests check the two new configs (dimension test drops
    to m = 13); fixtures route base vs challenge suites to the right field.

Caveats

  • GoldilocksEfField is a stand-in. A single Field64 challenge is only ~64-bit sound,
    so challenge-bearing fixtures run on the ext-leaf field until a base transcript codec +
    k-fold repetition land (see the TODO in goldilocks/src/field.rs).
  • Witness commitment is non-hiding — WHIR openings leak witness values, so this provides
    sumcheck ZK only, not witness ZK (whir_witness TODO).
  • Format 2.0 is a breaking change — proofs are not compatible with 1.x verifiers.

Out of scope (tracked separately)

This PR changes only the Rust WHIR proving/verifying path. It intentionally does not touch:

  • the Go recursive-verifier/ (which still parses the old fused zkWHIR commitment layout), or
  • the provekit-gnark bridge / Groth16 prover path.

Consequence: the WHIR → recursive-verifier (Go) → Groth16 on-chain route will not verify
2.0 proofs until a paired Go update lands, tracked as follow-up. The direct Groth16+BSB22
backend (#427 / #447)
is a separate crate and is unaffected by this change (the two only share
a few tooling files). This keeps the PR focused on the Rust proof-system change; the
recursive-verifier port is deliberately left for a coordinated follow-up.

Verification

  • cargo fmt --all --check, cargo clippy --all-targets --all-features (no new errors),
    cargo check --all-targets --all-features green.
  • Fixtures over both fields: bn254 7 roundtrip + 6 soundness, goldilocks 7 roundtrip +
    6 soundness — all pass.
  • provekit-r1cs-compiler 41 tests pass (security + dimension, split config).
  • Core libs: common 45, prover 8, verifier 83, bn254 45, goldilocks 8; doc tests pass.

Commits

9 authored commits (base-field split across common / prover / verifier / backends / tooling /
fixtures) plus one commit reconciling the base-sampling bound and rustfmt onto current main.

shreyas-londhe and others added 10 commits July 8, 2026 15:04
FIELD_ID tags each field's on-disk proof so one field's proof is rejected
by another field's verifier; bn254 reserves 0 for legacy byte-identity.
The 2.0 major bump makes an incompatible WhirR1CSScheme/WHIR payload fail
fast on read instead of mis-deserializing.
…per field

Commit the witness over the base field (whir_witness: GenericWhirConfig
<P::Embedding>) and move the Spartan blinding polynomial g to a separate
extension-field commitment (whir_blinding). g masks ext-valued sumcheck
rounds, so it needs ext randomness and cannot be embedded in a base
witness vector; sizing it to 4*m_0 coefficients drops the old m-padding.

Make the covector eval helpers embedding-aware (base×ext mixed arithmetic)
since the polynomial is now base while eval points are ext.

Fold ProofField::FIELD_ID into the .np magic (bn254 id 0 unchanged) so a
proof from one field fails another field's format check.
Mirror the scheme split: commit the base-field witness with the non-ZK
whir_witness config (no more base->ext lift) and commit the Spartan
blinding g with the separate ext whir_blinding config. BlindingState
carries g's flattened vector plus its own WHIR witness instead of an
offset into the witness padding.

The sumcheck sources blinding_eval from the standalone g vector (offset 0),
and g is opened by a dedicated whir_blinding.prove after all witness opens,
replacing the OffsetCovector-into-the-witness-open. Eval helpers take the
embedding for base x ext mixed products.
Receive the ext blinding commitment right after the witness commitment
(before challenge sampling), matching the prover's transcript order. Verify
the witness with the two-step whir::Config API (verify -> final-claim
check) and drop the blinding covector that was mixed into the witness open.
Verify g by a dedicated whir_blinding open at the end, against the power
covector at offset 0.
GoldilocksField switches its embedding from Identity<Field64_3> to
Basefield<Field64_3> (FIELD_ID 1): the witness commits in Field64, with
Field64_3 only for challenges and sumcheck — the base-commit path.

Add GoldilocksEfField (Identity<Field64_3>, FIELD_ID 2) as a stand-in for
challenge-bearing fixtures whose witness holds ext values a base witness
can't. Its distinct FIELD_ID reflects the different base byte layout, and
its TODO records the concrete removal path (base transcript codec + k-fold
repetition for 128-bit soundness). Factor the shared FieldHash glue into a
macro over both fields.
Drop the zkWHIR config (WhirZkConfig) — bn254 now uses the same non-ZK
base-commit path as every field, with Identity<Fr> so base == ext (no
actual base savings, no witness hiding). FIELD_ID 0 keeps the .np format
magic byte-identical. mavros_prove sources blinding_eval from the separate
g vector, matching the prover.
cli and verifier-server pass whir_witness + whir_blinding to the gnark
parameter writer instead of the removed zkWHIR blinded/blinding
commitments. The r1cs-compiler security tests check the two new configs
(new_witness_config_for_size + new_blinding_config_for_size), and the
dimension test drops to m=13 (the blinding-room padding bump is gone).
WhirR1CSProver now requires Standard: Distribution<Base<P>>; add it to the
harness runners that prove.
Split the roundtrip/soundness macros into base-compatible and
challenge-bearing suites. GoldilocksField runs the base suites (real
base-leaf commitment); GoldilocksEfField runs the challenge suites (ext
witness holds challenge values). bn254 runs both on itself. Thread the
base-sampling bound through the shared helpers.
Complete the base-sampling bound on `tampered_public_input_is_rejected`
with `Distribution<Base<P>>` — the merge left it with only the ext bound,
but the generic test body commits in the base field via `prove`. Rewrap
the `np_format` doc comment to satisfy rustfmt.

Reuse the `next_power_of_two` helper for `nv_blind` instead of a
re-implemented ceil-log2, and describe `GoldilocksEfField` by its purpose
(identity-embedding Goldilocks for challenge-bearing circuits).
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs-provekit-org Ready Ready Preview Jul 8, 2026 11:03am

Request Review

@BornPsych BornPsych changed the title feat: commit the R1CS witness over the base field with a separate extension-field blinding (proof format 2.0) feat: commit the R1CS witness over the base field (proof format 2.0) Jul 8, 2026
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

CSP benchmarks

Metric Value
Workflow status [PASS] success
Commit 0be51860833c
Run #28937725071
Circuits benchmarked 21
Iterations averaged per circuit 3

Prover time, peak RSS, peak heap, and verifier time are arithmetic means across the iterations. Peak heap comes from the largest peak memory entry in provekit-cli prove's tracing output; peak RSS is reported by /usr/bin/time -v (max-resident-set-size).

Each metric cell shows the current value followed by the percentage delta against the latest successful main run #28917281870. (new) marks circuits absent from the baseline.

Results
Circuit Constraints Witnesses Prover time Peak RSS Peak heap Verifier time Proof size PKP size
ecdsa_p256 143,321 (±0.0%) 258,215 (±0.0%) 1.76 s (-33.0%) 226 MB (-11.8%) 188 MB (-16.5%) 90 ms (-73.8%) 307 KB (-89.3%) 811 KB (±0.0%)
keccak_1024 788,814 (±0.0%) 1,490,812 (±0.0%) 5.11 s (-19.1%) 897 MB (-7.9%) 874 MB (-6.9%) 447 ms (-46.0%) 370 KB (-88.5%) 5.88 MB (±0.0%)
keccak_128 157,522 (±0.0%) 304,529 (±0.0%) 1.23 s (-42.0%) 235 MB (-13.9%) 202 MB (-15.5%) 110 ms (-69.4%) 310 KB (-89.1%) 1.26 MB (±0.0%)
keccak_2048 1,507,374 (±0.0%) 2,840,532 (±0.0%) 10.15 s (-13.1%) 1.70 GB (-4.5%) 1.68 GB (-5.3%) 850 ms (-40.0%) 391 KB (-88.3%) 11.97 MB (±0.0%)
keccak_256 247,782 (±0.0%) 474,010 (±0.0%) 1.41 s (-39.0%) 293 MB (-10.2%) 249 MB (-13.1%) 150 ms (-62.8%) 330 KB (-88.6%) 1.88 MB (±0.0%)
keccak_512 428,126 (±0.0%) 812,944 (±0.0%) 2.63 s (-26.9%) 541 MB (-6.9%) 455 MB (-9.5%) 247 ms (-54.0%) 350 KB (-88.5%) 3.32 MB (±0.0%)
poseidon2_12 479 (±0.0%) 563 (±0.0%) 40 ms (-88.7%) 12.57 MB (-48.6%) 3.26 MB (-77.8%) 10 ms (-90.3%) 73.27 KB (-93.1%) 436 KB (±0.0%)
poseidon2_16 556 (±0.0%) 719 (±0.0%) 40 ms (-88.6%) 12.93 MB (-47.9%) 3.46 MB (-76.7%) 10 ms (-90.0%) 75.50 KB (-93.0%) 530 KB (±0.0%)
poseidon2_2 231 (±0.0%) 278 (±0.0%) 30 ms (-91.3%) 11.53 MB (-51.3%) 2.72 MB (-80.7%) 68.66 KB (-93.7%) 108 KB (±0.0%)
poseidon2_4 529 (±0.0%) 535 (±0.0%) 30 ms (-91.2%) 11.74 MB (-50.8%) 2.86 MB (-80.0%) 74.13 KB (-93.1%) 31.66 KB (±0.0%)
poseidon2_8 363 (±0.0%) 423 (±0.0%) 33 ms (-90.6%) 12.51 MB (-49.4%) 3.15 MB (-78.3%) 71.09 KB (-93.2%) 365 KB (±0.0%)
poseidon_12 504 (±0.0%) 524 (±0.0%) 40 ms (-88.8%) 12.96 MB (-47.7%) 3.53 MB (-76.3%) 10 ms (-90.0%) 72.67 KB (-93.3%) 569 KB (±0.0%)
poseidon_16 609 (±0.0%) 633 (±0.0%) 40 ms (-88.9%) 13.41 MB (-46.2%) 3.98 MB (-73.8%) 10 ms (-90.0%) 73.03 KB (-93.0%) 717 KB (±0.0%)
poseidon_2 240 (±0.0%) 249 (±0.0%) 30 ms (-91.3%) 11.57 MB (-50.8%) 2.68 MB (-80.9%) 67.29 KB (-93.6%) 59.18 KB (±0.0%)
poseidon_4 297 (±0.0%) 309 (±0.0%) 30 ms (-91.5%) 12.21 MB (-49.6%) 3.10 MB (-78.6%) 69.97 KB (-93.4%) 332 KB (±0.0%)
poseidon_8 402 (±0.0%) 418 (±0.0%) 30 ms (-91.4%) 12.43 MB (-49.1%) 3.29 MB (-77.6%) 10 ms (-90.0%) 71.43 KB (-93.2%) 448 KB (±0.0%)
sha256_1024 196,940 (±0.0%) 339,764 (±0.0%) 1.32 s (-40.3%) 267 MB (-13.6%) 236 MB (-13.8%) 160 ms (-61.6%) 315 KB (-89.1%) 1.89 MB (+2.0%)
sha256_128 46,398 (±0.0%) 80,974 (±0.0%) 323 ms (-70.3%) 75.27 MB (-25.7%) 55.50 MB (-33.6%) 40 ms (-84.6%) 297 KB (-88.6%) 508 KB (+0.5%)
sha256_2048 345,399 (±0.0%) 612,724 (±0.0%) 2.59 s (-28.1%) 515 MB (-6.0%) 436 MB (-9.9%) 307 ms (-49.2%) 342 KB (-88.6%) 3.04 MB (+0.2%)
sha256_256 67,904 (±0.0%) 117,944 (±0.0%) 570 ms (-59.0%) 122 MB (-18.9%) 98.23 MB (-24.6%) 60 ms (-79.3%) 277 KB (-89.8%) 717 KB (+0.8%)
sha256_512 110,916 (±0.0%) 191,884 (±0.0%) 697 ms (-54.1%) 152 MB (-15.2%) 126 MB (-20.2%) 90 ms (-72.5%) 296 KB (-89.2%) 1.10 MB (-0.8%)

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