Skip to content

fix(groth16): remove the public unblinded prove API; deterministic prover becomes an unpublished test fixture (#50) - #57

Merged
satran004 merged 2 commits into
mainfrom
fix/50-gate-unblinded-prove-api
Sep 6, 2026
Merged

fix(groth16): remove the public unblinded prove API; deterministic prover becomes an unpublished test fixture (#50)#57
satran004 merged 2 commits into
mainfrom
fix/50-gate-unblinded-prove-api

Conversation

@satran004

@satran004 satran004 commented Sep 6, 2026

Copy link
Copy Markdown
Member

Summary

Closes #50 (consolidated triage C-01, Phase 1 of #54). Groth16ProverBLS381.proveUnblindedWithReaders(...) was a public main-source API that fixed the Groth16 blinders to r = s = 0 and therefore produced deterministic, non-zero-knowledge proofs from the ordinary prover surface. Both independent Groth16 review reports had wrongly stated that every unblinded path was package-private.

This PR implements ADR-0046 (docs/adr/0046-groth16-unblinded-proving-test-boundary.md, R3, status Proposed):

  • No fixed-blinder path in the product. proveUnblindedWithReaders, the unused heap-only proveUnblinded, and the private proveInternal are removed from Groth16ProverBLS381; the dead BN254 Groth16Prover.proveUnblinded goes too. The only seam through which a test can fix (r, s) is the package-private BlinderSource of proveBlinded, introduced by ADR-0045 for the forced-infinity tests.
  • Deterministic proving is an unpublished test fixture. Groth16UnblindedTestProver (new zeroj-crypto java-test-fixtures source set, same package) runs the public computeH and feeds (0, 0) exactly once through that seam, throwing on the second draw so the ADR-0045 P2 fail-closed behaviour is preserved. Both test-fixtures variants are skipped from the published component; a GenerateModuleMetadata guard (also run by check) and the ADR-0044 verifyDefaultModuleSurface task fail on a re-published variant or a runtime test-fixtures edge.
  • API-surface regression test. Groth16ProverApiSurfaceTest pins the exact public method sets of Groth16ProverBLS381, Groth16Keys, Groth16Pipeline, Groth16Prover and every public proof-returning method across the groth16/plonk packages (20), scans both packages for fixed-blinder names or BlinderSource parameters, asserts the fixture loads from a different code source than the prover, and checks behaviourally, against an independently computed unblinded A/B, that all eleven public BLS12-381 prove paths (and the BN254 pair) are blinded and randomized while the fixture is deterministic and still pairing-verifies.
  • Differential tests keep their oracle. The eight byte-equality gates (heap/mmap, dense/sparse store, pure-Java/blst, serial/parallel) use the fixture; expected values are bit-identical.
  • Docs. ADR-0046; ADR-0036 and ADR-0045 cross-references; Groth16 dev guide states that unblinded proofs are not zero-knowledge and that no public path exists; zeroj-crypto README; README ADR list.

Acceptance criteria (#50)

  • Normal public Groth16 APIs cannot create an unblinded proof accidentally (no fixed-blinder path in main; Z1–Z3).
  • Differential tests retain a supported deterministic path without widening the production API (unpublished test fixture).
  • API-surface/reflection test prevents an unblinded method from returning to the normal public facade (Groth16ProverApiSurfaceTest, 8 tests).
  • Documentation states that deterministic unblinded proofs do not provide the normal zero-knowledge guarantee (ADR-0046, prover Javadoc, fixture Javadoc, dev guide, README).
  • Pure-Java, mmap-reader, and blst differential tests continue to pass.

Verification

Gate Result
./gradlew build verifyDefaultModuleSurface publishToMavenLocal -PskipSigning=true 4076 tests / 19 modules / 0 failures
:zeroj-crypto:test, :zeroj-crypto-blst:test 1102 / 10 tests, 0 failures (8 differential gates re-run)
Published zeroj-crypto module metadata / POM / jar only apiElements, runtimeElements, javadocElements, sourcesElements; no -test-fixtures jar; no fixture class or fixed-blinder method in the jar
Metadata guard, skip() lines temporarily removed fails as intended
Module-surface guard, blst edge temporarily implementation testFixtures(...) fails as intended
zeroj-usecases (identity-kyc, personhood-airdrop, selective-disclosure, nft-ownership, private-voting, proof-of-reserves, digital-product-passport, account-ownership:cli) against 0.1.0-pre12-<sha>-SNAPSHOT 36 tests, 0 failures; DevKit / aor.e2e / circuit.measure gated tests skipped; external snarkjs ZkeyImportIT passed

An independent high-effort code review of the diff returned ten findings; they drove the seam consolidation (no r = s = 0 code in main), the proof-producer allowlist, the wider behavioural coverage, the module-surface extension, and the ADR wording corrections (Z2 now names the package-private PlonK proveUnblinded seams as out of scope under C-04; the ADR-0036 ingress list no longer credits proveWithHCoeffs with relation validation).

Remaining assumptions / for reviewer attention

  • SecureRandom is a CSPRNG on the deployment platform; blinder uniformity is argued (64 bytes reduced mod r), not measured.
  • The fixture and the prover form a split package on the class path (ZeroJ has no module-info); adopting JPMS would need a redesign, recorded in the ADR.
  • BN254 legacy proofs are checked for distinctness across calls only; PlonK blinding completeness stays under C-04.
  • Please read secureRandomBlinders / randomScalar / proveBlinded directly rather than relying on the tests alone, and agree the maintenance policy for the two allowlists (every new public prove method needs a deliberate update).
  • Source-incompatible removal of proveUnblindedWithReaders; no consumer in zeroj-usecases, zeroj-tools, or zeroj-integration-tests.

🤖 Generated with Claude Code

satran004 and others added 2 commits September 6, 2026 12:54
… becomes an unpublished test fixture (#50)

Groth16ProverBLS381.proveUnblindedWithReaders (public, r = s = 0) produced
deterministic, non-zero-knowledge proofs from the ordinary prover surface
(consolidated triage C-01, issue #50). ADR-0046 moves that capability out of
the product entirely:

- Groth16ProverBLS381 keeps no fixed-blinder path: proveUnblindedWithReaders,
  the unused heap-only proveUnblinded, and the private proveInternal are
  removed. The only seam that can fix (r, s) is the package-private
  BlinderSource of proveBlinded (ADR-0045). The dead BN254
  Groth16Prover.proveUnblinded goes too.
- Groth16UnblindedTestProver, in a new zeroj-crypto test-fixtures source set,
  runs the public computeH and feeds (0, 0) exactly once through that seam,
  throwing on the second draw so the ADR-0045 P2 fail-closed behaviour holds.
  Both test-fixtures variants are skipped from the published component; a
  GenerateModuleMetadata guard (also run by `check`) and the ADR-0044
  verifyDefaultModuleSurface task fail on any re-published variant or runtime
  test-fixtures edge.
- Groth16ProverApiSurfaceTest pins the exact public method sets of the four
  Groth16 facade classes and every public proof-returning method in the
  groth16/plonk packages, scans for fixed-blinder names or BlinderSource
  parameters, asserts the fixture ships from a different code source, and
  checks behaviourally that all eleven public BLS12-381 prove paths (and the
  BN254 pair) are blinded while the fixture reproduces the independently
  computed unblinded A and B and still verifies.
- The eight byte-equality differential tests (heap/mmap, dense/sparse store,
  pure-Java/blst, serial/parallel) now use the fixture; expected values are
  unchanged.
- Docs: ADR-0046 (new), ADR-0036 and ADR-0045 cross-references, Groth16 dev
  guide, zeroj-crypto README, README ADR list.

Verification: ./gradlew build verifyDefaultModuleSurface publishToMavenLocal
(4076 tests, 0 failures); published zeroj-crypto carries no test-fixtures
artifact or variant; both build guards verified to fail on a deliberate
violation; zeroj-usecases Groth16 projects build and test against the
snapshot (36 tests, 0 failures).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WRtUQPK45PkawH22huhT1K
…R-0046 arkworks/snarkjs references (#50 review)

PR #57 review, f88b50d:

- P2: the ADR-0044 module-surface task (which now also rejects runtime
  test-fixtures edges) was only invoked as a separate CI command, so a plain
  `./gradlew build` did not run the fixture-dependency guard ADR-0046 relies
  on. The root `check` now depends on it; `./gradlew build --dry-run` lists
  the task and `./gradlew check` executes both guards.
- P3: ADR-0046 wrongly stated that arkworks exposes no unblinded prover.
  arkworks groth16 v0.5.0 exposes create_proof_with_reduction_no_zk (r = s
  = 0) and caller-supplied-blinder variants as public functions. The
  reference is pinned to v0.5.0 and ZeroJ's stricter boundary is described
  as this ADR's own decision. The snarkjs reference is pinned to v0.7.6
  (`r`, `s` from `curve.Fr.random()`, no caller override).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WRtUQPK45PkawH22huhT1K
@satran004
satran004 merged commit 7bf569d into main Sep 6, 2026
5 checks passed
@satran004
satran004 deleted the fix/50-gate-unblinded-prove-api branch September 6, 2026 06:08
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.

[Groth16/Security] Remove or explicitly gate the public unblinded proving API

1 participant