Skip to content

feat: commit the R1CS witness with zook - #485

Merged
zkfriendly merged 13 commits into
mainfrom
ys/zook-witness-commitment
Sep 10, 2026
Merged

feat: commit the R1CS witness with zook#485
zkfriendly merged 13 commits into
mainfrom
ys/zook-witness-commitment

Conversation

@BornPsych

@BornPsych BornPsych commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Switches the R1CS witness commitment from plain WHIR to zook (RateSchedule::Adaptive, Mode::Standard), and bumps whir to 78e9460.

On the passport circuit this is ~37% off the R1CS proving core and ~17% off total prove time, for ~26% larger proofs.

SPARK query production works under zook: worldfnd/whir#274 makes zook's prove return its final evaluation point and RLC coefficients (ProverClaim), and the SPARK queries are assembled from it exactly as before the switch. Verified end-to-end (prepare --sparkprove --produce-spark-queryprove-sparkverify-spark) on a single-commitment circuit and a dual-commitment (range-check) circuit.

Notes:

@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
docs-provekit-org Ready Ready Preview Sep 9, 2026 12:12am UTC

Request Review

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown

CSP benchmarks

Metric Value
Workflow status [PASS] success
Commit e5603f35383c
Run #34293983888
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).

No baseline available yet — deltas will appear once this workflow has produced at least one successful main run.

Results
Circuit Constraints Witnesses Prover time Peak RSS Peak heap Verifier time Proof size PKP size
ecdsa_p256 143,321 258,215 1.57 s 237 MB 196 MB 110 ms 749 KB 811 KB
keccak_1024 788,814 1,490,812 4.19 s 929 MB 906 MB 467 ms 777 KB 5.88 MB
keccak_128 157,522 304,529 1.05 s 256 MB 210 MB 127 ms 744 KB 1.26 MB
keccak_2048 1,507,374 2,840,532 8.20 s 1.75 GB 1.74 GB 863 ms 894 KB 11.97 MB
keccak_256 247,782 474,010 1.23 s 291 MB 257 MB 170 ms 715 KB 1.88 MB
keccak_512 428,126 812,944 2.21 s 526 MB 471 MB 270 ms 743 KB 3.32 MB
poseidon2_12 479 563 40 ms 14.01 MB 4.02 MB 10 ms 227 KB 437 KB
poseidon2_16 556 719 40 ms 14.17 MB 4.24 MB 10 ms 227 KB 530 KB
poseidon2_2 231 278 40 ms 12.70 MB 3.42 MB 10 ms 226 KB 108 KB
poseidon2_4 529 535 30 ms 13.02 MB 3.61 MB 10 ms 228 KB 31.89 KB
poseidon2_8 363 423 40 ms 13.79 MB 3.88 MB 10 ms 229 KB 365 KB
poseidon_12 504 524 40 ms 14.19 MB 4.23 MB 10 ms 228 KB 569 KB
poseidon_16 609 633 50 ms 14.60 MB 4.51 MB 10 ms 227 KB 717 KB
poseidon_2 240 249 30 ms 12.70 MB 3.38 MB 10 ms 223 KB 59.40 KB
poseidon_4 297 309 40 ms 13.61 MB 3.81 MB 10 ms 231 KB 333 KB
poseidon_8 402 418 40 ms 13.53 MB 4.01 MB 10 ms 228 KB 448 KB
sha256_1024 196,940 339,764 1.14 s 283 MB 244 MB 180 ms 712 KB 1.89 MB
sha256_128 46,398 80,974 310 ms 75.87 MB 57.60 MB 60 ms 647 KB 506 KB
sha256_2048 345,399 612,724 2.18 s 500 MB 452 MB 317 ms 754 KB 3.02 MB
sha256_256 67,904 117,944 497 ms 125 MB 102 MB 80 ms 627 KB 706 KB
sha256_512 110,916 191,884 620 ms 151 MB 131 MB 110 ms 627 KB 1.11 MB

Base automatically changed from zkfr/integrate-buffer-abstraction to main August 26, 2026 10:32
Collects a slice of concrete covectors into the `Vec<&dyn LinearForm<F>>`
shape whir's opening APIs take. The prover and verifier both build this
vector by hand at several call sites; a shared helper removes the
repetition and keeps the trait-object cast in one place.
Moves the `whir` pin from 33fbecf to 8804e80 (worldfnd/whir, PR #270).
That rev replaces the `Messages` + separate-mask Reed-Solomon interface
with segmented `Polynomials`, and adds the `zook` protocol module the
following commits switch the witness commitment over to.

Also enables serde_json's `float_roundtrip` feature: zook's `KneeWeight`
and `ListSize` wrap `OrderedFloat<f64>`, and the default fast float
parser is off by one ULP on read-back, which breaks scheme JSON
roundtrips.

Cargo.lock additionally picks up a `zeroize` edge for whir and a batch of
incidental `windows-sys` version moves from re-resolving the graph; none
of those affect the build on our targets.
`WhirR1CSScheme::whir_witness` becomes a zook `ProtocolConfig` derived
from an explicit `SecuritySpec`/`TuningSpec` pair rather than a plain
`whir::Config`. The tuning knobs move to named constants: `Mode::Standard`
for the commitment mode, and `RateSchedule::Adaptive` at the default
0.5 knee, which plans per-round inverse rates against the prover-time /
proof-size pareto knee instead of stepping the rate unboundedly.

`ProtocolConfig::derive` is fallible, so `new_for_r1cs`,
`new_from_dimensions` and `new_witness_config_for_size` now return
`anyhow::Result`, and the two dimension invariants become `ensure!`
instead of `assert!` — library code should not panic on caller error.

The blinding commitment stays a plain `whir::Config`: the Spartan mask `g`
is extension-valued and does not go through the base-field zook path.
`WhirR1CSCommitment::witness` holds a zook `CommittedWitness`, and the
padded witness length now comes from the config's `tuning().vector_size`
rather than `initial_num_variables()`.

zook's `commit` consumes the buffer but the prove stage still needs the
message for the covector evaluations, so the padded witness is cloned;
an upstream `commit(&Buffer)` or a message accessor would remove that.

Both the single- and dual-commitment opening paths hand zook borrowed
linear forms via `linear_form_refs` instead of boxing each covector, and
the SPARK covector snapshots go away with them: zook's `prove` returns
`()`, so there is no final evaluation point to re-evaluate the alpha
weights at.
zook's `verify` takes the linear forms and their claimed evaluations up
front and returns a `FinalClaim` whose `verify` now takes a slice rather
than an iterator. Its sumcheck also draws extension-field randomness, so
the impl picks up a `Standard: Distribution<Ext<P>>` bound.

All three weight-reference sites switch to `linear_form_refs`.
A SPARK query needs the final WHIR evaluation point to re-evaluate the A,
B, C covectors at. zook's `prove` returns `()` and keeps that point
internal, so the query cannot be built. Rather than silently emitting
nothing, both the generic and the Mavros prove paths now fail fast with
an explanatory error, and `ProveFromAlphasCtx::spark_row` is documented
as reserved.

Restoring this needs a whir-side change that exposes the prover's final
claim; the call sites are marked with a TODO pointing at it.
Scheme construction can now fail while deriving the zook config, so the
Noir and Mavros compilers propagate the error and `MavrosSchemeBuilder`
returns `anyhow::Result<Self>`.

`assert_configs_secure` no longer reads a security level off the witness
config — zook owns that calculation. It runs zook's own `validate` and
asserts the target stays at 128 bits. The blinding half still checks
`security_level` directly, since it remains a plain whir config.
`prove_setup` returns `Result<ProveInputs<P>>` and the harness builders
propagate the scheme-construction error.
The gnark exporter serialises a plain whir `Config`; it has no way to
describe zook's `ProtocolConfig`, and the Go recursive verifier would
have to be updated in lockstep to consume one. Until that paired change
lands, both entry points — the `generate-gnark-inputs` CLI command and
the verifier server's parameter preparation — fail with an error that
says so, and `provekit-gnark` drops out of the two crates' dependencies
so `cargo machete` stays quiet.

Also drops two `return` statements from the verifier server's
cancellation branches; they are the last statement in their `select!`
arm and clippy flags them.
zook's `prove` now returns a `ProverClaim` carrying the final evaluation
point and RLC coefficients (whir #274), which is what SPARK query
production needs.
whir #274 makes zook's `prove` return a `ProverClaim` with the final
evaluation point, so the A, B, C covectors can again be re-evaluated at
the final WHIR point to form the SPARK queries. Restores the same query
assembly as the pre-zook prover (selector-prefixed columns in the dual-
commitment path) and drops the fail-fast guards from the generic and
Mavros prove paths.
@BornPsych
BornPsych force-pushed the ys/zook-witness-commitment branch from cefd4c8 to 4b58922 Compare August 27, 2026 15:53
@BornPsych
BornPsych marked this pull request as ready for review August 27, 2026 15:53
@BornPsych
BornPsych requested a review from zkfriendly August 27, 2026 15:57

@zkfriendly zkfriendly left a comment

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.

Changes look good and correct. One thing I'd like to discuss briefly before merging is the required Zook configs. Right now, this PR hardcodes the config as consts, but Zook takes it as user-provided input; should we also take it as input? Maybe via the cli arguments, and we could have the default return the currently hardcoded values?

Comment thread provekit/common/src/whir_r1cs.rs Outdated

/// Zook mode for the witness commitment; [`Mode::ZeroKnowledge`] makes it
/// hiding.
const ZOOK_MODE: Mode = Mode::Standard;

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.

Should we have ZOOK_MODE as a const or a dynamic as a cli flag or prover config? my question is do we need any other protocol change if we set ZOOK_MODE = ZeroKnowledge?

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.

Same question for the rate and security bits and other protocol params that whir takes as config

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

good suggestion, I’ve made ZK the default, with --witness-mode standard as the opt-out for smaller non-hiding proofs. The config is already serialized into the scheme/domain separator, so the verifier follows whatever mode prepare used.

The witness commitment mode was pinned to `Mode::Standard` by a private
`ZOOK_MODE` const, so a hiding commitment meant editing the source and
rebuilding. It is now a `WitnessCommitmentMode` argument carried
alongside `hash_config` from the frontends down to the zook
`SecuritySpec`, selected by `prepare --witness-mode standard|zk`.

The mode lands in the derived `ZookConfig`, which is already part of the
scheme and therefore of both key files and the Fiat-Shamir domain
separator, so `prove` and `verify` follow whatever `prepare` chose
without a flag of their own.

The rate schedule, security level and proof-of-work budget stay as
consts: they are the security floor rather than a per-circuit choice.

No behaviour change - the default is still `Mode::Standard`.
`Mode::ZeroKnowledge` round-trips on the pinned whir rev, so the witness
commitment is now hiding unless `prepare --witness-mode standard` asks
for the smaller non-hiding proofs.

Proofs grow. On the example circuits the hiding proof is roughly 5x
larger for `basic-2`, 3.3x for `basic` and 2.1x for `rangechecks`, where
the fixed mask overhead dominates. Larger circuits are not measured yet.

A hiding witness commitment is still not full proof ZK - the alpha,
public-input and challenge evaluations are sent in the clear.
@zkfriendly
zkfriendly merged commit 11fba77 into main Sep 10, 2026
16 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