diff --git a/NOTICE b/NOTICE
index bbc146b..cf9d507 100644
--- a/NOTICE
+++ b/NOTICE
@@ -41,22 +41,6 @@ Copyright (c) 2022 ICON Foundation
Licensed under the Apache License, Version 2.0
https://github.com/icon-project/blst-java
---------------------------------------------------------------------------
-rapidsnark — Native Groth16 Prover (incubator/zeroj-prover-rapidsnark)
---------------------------------------------------------------------------
-Copyright (c) 2021 0KIMS Association
-Licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0)
-https://github.com/iden3/rapidsnark
-
-The zeroj-prover-rapidsnark module downloads pre-compiled rapidsnark
-shared libraries (librapidsnark.so/.dylib) from GitHub releases and
-loads them via Java FFM at runtime. The binaries are NOT distributed
-in this repository. As a dynamically-linked library, your own
-application code is NOT subject to the LGPL. You may replace the
-library with your own modified build. See
-incubator/zeroj-prover-rapidsnark/NOTICE for LGPL-specific obligations.
-
---------------------------------------------------------------------------
halo2 — Zero-Knowledge Proving System (incubator/zeroj-verifier-halo2)
--------------------------------------------------------------------------
Copyright (c) 2020-2024 The Electric Coin Company
@@ -104,11 +88,3 @@ Apache License, Version 2.0
A copy of the Apache License, Version 2.0 is available at:
https://www.apache.org/licenses/LICENSE-2.0
-
-==========================================================================
-GNU Lesser General Public License v3.0
-==========================================================================
-
-A copy of the LGPL-3.0 is included in:
-incubator/zeroj-prover-rapidsnark/LICENSES/LGPL-3.0.txt
-incubator/zeroj-prover-rapidsnark/LICENSES/GPL-3.0.txt
diff --git a/README.md b/README.md
index c524bda..04db30a 100644
--- a/README.md
+++ b/README.md
@@ -29,9 +29,9 @@ ZeroJ lets Java developers **define ZK circuits**, **generate proofs**, **verify
### Verify On-Chain (Cardano Plutus V3)
- **Groth16 BLS12-381** — reusable Plutus V3 spending validator via Julc
-- **PlonK BLS12-381** — full on-chain PlonK verifier with Fiat-Shamir transcript
+- **PlonK BLS12-381** — experimental Julc prototype; Fiat-Shamir/inverse checks work, KZG pairing check is still deferred
- VK baked at deploy time, proof passed as redeemer, public inputs as datum
-- **Proven end-to-end**: Java DSL circuit → pure Java prove → Yaci DevKit on-chain verify
+- **Proven end-to-end for Groth16**: Java DSL circuit → pure Java prove → Yaci DevKit on-chain verify
### Anchor on Cardano L1
- 4 anchor patterns: proof hash, state root + proof hash, full verification ref, nullifier commitment
@@ -88,7 +88,10 @@ For production setup, use an MPC ceremony `.zkey` instead of `PowersOfTauBLS381.
```java
// Same circuit, but prove via gnark (10-50x faster, requires Go native lib)
try (var prover = new GnarkProver()) {
- var result = prover.groth16FullProve(r1csBytes, witnessBytes, "bls12381");
+ var result = prover.groth16FullProve(r1cs, witness, CurveId.BLS12_381);
+ String proofJson = result.proveResponse().proofJson();
+ String vkJson = result.vkJson();
+ List publicSignals = result.proveResponse().publicSignals();
}
```
@@ -124,9 +127,12 @@ The **pure Java prover and verifier require no optional dependencies**.
## Building
```bash
-# Build everything (no native dependencies needed)
+# Build the full repository, including opt-in WASM/native modules
./gradlew build
+# Build the core privacy path only
+./gradlew :zeroj-bom-core:build :zeroj-verifier-core:build :zeroj-verifier-groth16:build :zeroj-verifier-plonk:build :zeroj-crypto:build :zeroj-onchain-julc:build
+
# Run all tests (2680+ tests)
./gradlew test
@@ -164,7 +170,7 @@ The **pure Java prover and verifier require no optional dependencies**.
### Module Organization
-#### Core Modules
+#### Core Modules (`zeroj-bom-core`)
| Module | Description |
|--------|-------------|
@@ -174,35 +180,47 @@ The **pure Java prover and verifier require no optional dependencies**.
| [`zeroj-verifier-core`](zeroj-verifier-core/) | Verifier orchestration and backend routing |
| [`zeroj-verifier-groth16`](zeroj-verifier-groth16/) | Groth16 verification — BN254 (pure Java) + BLS12-381 (pure Java / blst) |
| [`zeroj-verifier-plonk`](zeroj-verifier-plonk/) | PlonK verification — BN254 + BLS12-381 (pure Java) |
+| [`zeroj-bls12381`](zeroj-bls12381/) | Pure Java BLS12-381 field, curve, and pairing primitives |
| [`zeroj-blst`](zeroj-blst/) | BLS12-381 pairing operations via blst native library |
| [`zeroj-crypto`](zeroj-crypto/) | **Pure Java prover** — Montgomery field arithmetic, EC operations, Groth16 + PlonK for BN254 and BLS12-381 |
| [`zeroj-circuit-dsl`](zeroj-circuit-dsl/) | Java Circuit DSL — define circuits with CircuitSpec, compile to R1CS/PlonK/Halo2 |
| [`zeroj-circuit-lib`](zeroj-circuit-lib/) | Circuit standard library — Poseidon, PoseidonN, MiMC, MiMCSponge, Merkle, comparators, AliasCheck |
+| [`zeroj-prover-spi`](zeroj-prover-spi/) | Minimal prover request/response SPI shared by prover implementations |
| [`zeroj-prover-gnark`](zeroj-prover-gnark/) | gnark native prover (Groth16 + PlonK) via FFM |
| [`zeroj-patterns`](zeroj-patterns/) | High-level ZK patterns — state transitions, nullifier claims, membership proofs |
-| [`zeroj-submission`](zeroj-submission/) | Proof submission wire format, Ed25519 signatures |
-| [`zeroj-ingestion`](zeroj-ingestion/) | Submission ingestion pipeline, governance, security checks |
| [`zeroj-cardano`](zeroj-cardano/) | Cardano anchoring — proof anchor model, metadata encoding |
| [`zeroj-ccl`](zeroj-ccl/) | Cardano Client Lib integration — fluent transaction helpers |
-| [`zeroj-onchain-julc`](zeroj-onchain-julc/) | Reusable Plutus V3 on-chain verifiers (Groth16 + PlonK) via Julc |
+| [`zeroj-onchain-julc`](zeroj-onchain-julc/) | Reusable Plutus V3 on-chain verifiers via Julc; Groth16 is production, PlonK is an experimental prototype |
+
+#### Mainline Opt-In Modules (`zeroj-bom-all` only)
+
+| Module | Description |
+|--------|-------------|
+| [`zeroj-bbs`](zeroj-bbs/) | BBS/BBS+ selective disclosure credential backend |
+| [`zeroj-bbs-wasm`](zeroj-bbs-wasm/) | WASM-backed BBS provider |
+| [`zeroj-bls12381-wasm`](zeroj-bls12381-wasm/) | WASM-backed BLS12-381 provider |
+
+#### Support Modules
+
+| Module | Description |
+|--------|-------------|
| [`zeroj-test-vectors`](zeroj-test-vectors/) | Shared test fixtures — pre-generated proofs and VKs |
| [`zeroj-examples`](zeroj-examples/) | End-to-end demos: circuit definition to on-chain verification |
+| [`zeroj-bom-core`](zeroj-bom-core/) | BOM for the stable v3 core path |
+| [`zeroj-bom-all`](zeroj-bom-all/) | BOM for core plus opt-in and incubator modules |
#### Incubator Modules (`incubator/`)
| Module | Description |
|--------|-------------|
-| [`zeroj-prover-rapidsnark`](incubator/zeroj-prover-rapidsnark/) | RapidSNARK native prover — BN254 Groth16 via FFM |
-| [`zeroj-prover-sidecar`](incubator/zeroj-prover-sidecar/) | HTTP client for external prover services |
| [`zeroj-prover-wasm`](incubator/zeroj-prover-wasm/) | Circom witness calculation via GraalVM WebAssembly |
| [`zeroj-verifier-halo2`](incubator/zeroj-verifier-halo2/) | Halo2 IPA verification via Rust FFM (no trusted setup) |
-| [`zeroj-onchain-experimental`](incubator/zeroj-onchain-experimental/) | On-chain helpers — proof preparation, budget estimation |
## Dependency (Gradle)
```gradle
dependencies {
- implementation platform('com.bloxbean.cardano:zeroj-bom:0.1.0')
+ implementation platform('com.bloxbean.cardano:zeroj-bom-core:0.1.0')
// Circuit definition + standard library
implementation 'com.bloxbean.cardano:zeroj-circuit-dsl'
@@ -230,9 +248,9 @@ dependencies {
- **[Getting Started](docs/getting-started.md)** — end-to-end: circuit to on-chain verification
- **[Pure Java Prover Guide](docs/pure-java-prover-guide.md)** — zero-dependency proving pipeline
- **[Circuit DSL User Guide](docs/circuit-dsl-user-guide.md)** — CircuitSpec, Signal API, standard library
-- **[Alternate Prover Backends](docs/alternate-prover-backends.md)** — gnark FFM, rapidsnark, snarkjs
+- **[Alternate Prover Backends](docs/alternate-prover-backends.md)** — gnark FFM and snarkjs
- **[Architecture Overview](docs/architecture-overview.md)** — module design and layer separation
-- **[PlonK Support](docs/plonk-support.md)** — PlonK proving and on-chain verification
+- **[PlonK Support](docs/plonk-support.md)** — PlonK proving, off-chain verification, and the experimental Julc prototype
### Use Cases
- **[ZK Use Cases on Cardano](docs/usecases/README.md)** — 8 real-world applications with secret/public input breakdowns
diff --git a/build.gradle b/build.gradle
index d035638..825e842 100644
--- a/build.gradle
+++ b/build.gradle
@@ -33,8 +33,8 @@ subprojects {
version = "${project.version}".replace("-SNAPSHOT", "-${commit_id}-SNAPSHOT")
}
- // The BOM module uses java-platform, which is incompatible with java-library
- if (project.name == 'zeroj-bom') {
+ // BOM modules use java-platform, which is incompatible with java-library
+ if (project.name.startsWith('zeroj-bom')) {
return
}
@@ -80,7 +80,7 @@ subprojects {
}
// Publishing for publishable modules
- def nonPublishable = ['zeroj-test-vectors', 'zeroj-examples', 'zeroj-prover-rapidsnark']
+ def nonPublishable = ['zeroj-test-vectors', 'zeroj-examples']
if (!nonPublishable.contains(project.name)) {
apply plugin: 'maven-publish'
apply plugin: 'signing'
diff --git a/docs/adr/0006-separation-of-crypto-and-policy-verification.md b/docs/adr/0006-separation-of-crypto-and-policy-verification.md
index b633400..2ac10d2 100644
--- a/docs/adr/0006-separation-of-crypto-and-policy-verification.md
+++ b/docs/adr/0006-separation-of-crypto-and-policy-verification.md
@@ -1,13 +1,18 @@
# ADR-0006: Separation of Cryptographic and Policy Verification
## Status
-Accepted
+Superseded by [ADR-0020](0020-module-cleanup-and-core-restructure.md)
## Date
2026-03-25
## Context
+> Historical note: the crypto-vs-policy separation remains a useful design
+> principle, but the generic `zeroj-ingestion` submission pipeline described
+> here was removed by ADR-0020. Application-specific policy validation now
+> belongs outside the ZeroJ core modules.
+
A proof can be cryptographically valid but still unacceptable. Consider:
- A valid Groth16 proof for the wrong circuit
- A valid proof with a stale previous state root
diff --git a/docs/adr/0007-module-structure-and-boundaries.md b/docs/adr/0007-module-structure-and-boundaries.md
index b0f2773..28815f9 100644
--- a/docs/adr/0007-module-structure-and-boundaries.md
+++ b/docs/adr/0007-module-structure-and-boundaries.md
@@ -1,13 +1,19 @@
# ADR-0007: Multi-Module Structure and Boundaries
## Status
-Accepted
+Superseded by [ADR-0020](0020-module-cleanup-and-core-restructure.md)
## Date
2026-03-25
## Context
+> Historical note: this ADR describes the original broad module structure.
+> ADR-0020 removes `zeroj-submission`, `zeroj-ingestion`,
+> `zeroj-prover-sidecar`, `zeroj-prover-rapidsnark`,
+> `zeroj-onchain-experimental`, and replaces `zeroj-bom` with
+> `zeroj-bom-core` / `zeroj-bom-all`.
+
ZeroJ serves three distinct audiences:
1. **Java developers** who want a standalone ZK verification library (no Cardano dependency)
2. **Network operators** who want proof-verified app-layer consensus
diff --git a/docs/adr/0010-java-circuit-dsl.md b/docs/adr/0010-java-circuit-dsl.md
index 5b28116..17b3061 100644
--- a/docs/adr/0010-java-circuit-dsl.md
+++ b/docs/adr/0010-java-circuit-dsl.md
@@ -1,11 +1,17 @@
# ADR-0010: Java DSL for ZK Circuit Definition
## Status
-Proposed
+Updated by ADR-0020
## Date
2026-03-28
+## 2026-05-17 Update
+
+ADR-0020 removed the RapidSNARK module from ZeroJ. The Java circuit DSL remains
+part of the core direction, but active proving paths are gnark FFM and pure Java
+proving where implemented.
+
## Context
ZeroJ provides verification, proving (via FFM), and submission infrastructure for ZK proofs. However, defining circuits still requires external languages:
@@ -23,7 +29,7 @@ This forces Java developers to learn a different language, switch toolchains, an
| Define circuit | circom / gnark Go | circom / Go |
| Compile circuit | circom CLI / go build | Rust / Go |
| Calculate witness | snarkjs (Node.js) OR GraalWasm | JS / WASM |
-| Prove | gnark FFM / rapidsnark FFM | Go / C++ |
+| Prove | gnark FFM / pure Java where implemented | Go / Java |
| Verify | **Pure Java** (zeroj-verifier-*) | Java |
### Prior art
@@ -127,12 +133,12 @@ Compiles the constraint graph to R1CS: `(A · w) × (B · w) = (C · w)`.
Each multiplication gate becomes one R1CS constraint. Addition gates are free (linear combinations absorbed into A, B, C vectors).
Output formats:
-- **iden3 `.r1cs` binary** — standard format consumed by snarkjs, rapidsnark
+- **iden3 `.r1cs` binary** — standard format consumed by snarkjs-compatible tooling and gnark import flows
- **In-memory R1CS** — for pure Java Groth16 prover (future)
```java
var r1cs = circuit.compileR1CS(CurveId.BN254);
-byte[] r1csBytes = r1cs.toIden3Binary(); // feed to rapidsnark
+byte[] r1csBytes = r1cs.toIden3Binary(); // feed to snarkjs-compatible tooling or gnark import flows
BigInteger[] witness = r1cs.calculateWitness(inputs);
byte[] wtnsBytes = WitnessExporter.toWtns(witness, r1cs.prime(), r1cs.n32());
```
@@ -220,7 +226,6 @@ The compiled constraint system feeds into existing zeroj provers:
| Backend | Output | Prover | Curve support |
|---------|--------|--------|---------------|
-| R1CS | iden3 `.r1cs` + `.wtns` | rapidsnark FFM | BN254 |
| R1CS | iden3 `.r1cs` + `.wtns` | gnark FFM (Groth16) | BN254 + BLS12-381 |
| R1CS | in-memory | Pure Java prover (future) | BN254 + BLS12-381 |
| PlonK | gnark SparseR1CS | gnark FFM (PlonK) | BN254 + BLS12-381 |
@@ -285,12 +290,13 @@ A future ADR may address **automatic Julc verifier generation** from circuit def
- `CircuitBuilder`, `CircuitAPI`, `Variable`, `ConstraintGraph`, `Gate`
- `R1CSCompiler` + `R1CSSerializer` (iden3 format)
- `WitnessCalculator`
-- Test: multiplier circuit → R1CS → rapidsnark prove → pure Java verify
+- Test: multiplier circuit → R1CS → gnark or pure Java prove → pure Java verify
### Phase 2: PlonK backend
- `PlonKCompiler` (gate table + permutation σ)
- `PlonKSerializer` (gnark SparseR1CS format)
-- Test: same multiplier circuit → PlonK → gnark prove → pure Java verify
+- Test: same multiplier circuit → PlonK → pure Java prove/verify, or gnark
+ prove with gnark native verification until a structured proof adapter is added
### Phase 3: Standard library (zeroj-circuit-lib)
- Poseidon hash (ZK-friendly, low constraint count)
diff --git a/docs/adr/0020-module-cleanup-and-core-restructure.md b/docs/adr/0020-module-cleanup-and-core-restructure.md
new file mode 100644
index 0000000..90a132b
--- /dev/null
+++ b/docs/adr/0020-module-cleanup-and-core-restructure.md
@@ -0,0 +1,431 @@
+# ADR-0020: Module Cleanup and Core Restructure
+
+## Status
+Accepted
+
+## Date
+2026-05-17
+
+## Context
+
+ZeroJ has accumulated too many top-level Gradle modules. Some are shipping and
+important, some are optional accelerators, some are incubating research paths,
+and some are app-layer workflows that are not required for the privacy-first
+vision in `docs/vision-v3.md`.
+
+The current module layout makes the project look broader and less focused than
+the intended product:
+
+```text
+Java circuit authoring
+pure-Java proving and verification
+Groth16 / PlonK over BLS12-381
+Cardano transaction binding
+JuLC on-chain verification
+privacy templates and proof envelopes
+```
+
+Several dependency-layering problems also exist:
+
+1. `zeroj-prover-gnark` depends on `zeroj-prover-sidecar` only to reuse
+ prover contracts and result/error types. The shared prover SPI should not
+ live inside an HTTP sidecar transport module.
+2. Before this cleanup, `zeroj-crypto` depended on `zeroj-verifier-plonk` for
+ Fiat-Shamir transcript code. This made a foundation module depend on a
+ verifier implementation.
+3. The BOM currently advertises optional and incubating modules as first-class
+ dependencies, which blurs the difference between the shipping core and
+ experimental integrations.
+
+The cleanup must preserve everything the v3 vision classifies as shipping:
+
+- Groth16 BLS12-381 verification
+- PlonK BLS12-381 verification, budget-sensitive
+- pure-Java proving where available
+- gnark native proving as a production fast path
+- `zeroj-blst` as stable opt-in BLS12-381 acceleration
+- BBS as a visible mainline credential backend, but not part of the core
+ Cardano SNARK BOM
+
+## Decision
+
+### 1. Define the mainline core
+
+The mainline project remains focused on the privacy-first Cardano path.
+
+Core modules included in `zeroj-bom-core`:
+
+| Module | Role |
+|--------|------|
+| `zeroj-api` | Core proof envelopes, IDs, public inputs, witnesses, verification material. |
+| `zeroj-codec` | Canonical proof and VK serialization. |
+| `zeroj-backend-spi` | Verifier-side SPI: `ZkVerifier`, backend descriptors, VK registry. |
+| `zeroj-verifier-core` | Verifier registry and orchestration. |
+| `zeroj-verifier-groth16` | Groth16 verification, including BLS12-381 path for Cardano. |
+| `zeroj-verifier-plonk` | PlonK verification; BLS12-381 is shipping but budget-sensitive on-chain. |
+| `zeroj-bls12381` | Pure Java BLS12-381 primitives and provider interface. |
+| `zeroj-blst` | Stable opt-in native BLS12-381 acceleration. |
+| `zeroj-crypto` | Pure Java proving and cryptographic foundations. |
+| `zeroj-circuit-dsl` | Java circuit authoring API. |
+| `zeroj-circuit-lib` | Circuit gadgets and privacy primitives. |
+| `zeroj-prover-spi` | New prover-side SPI, extracted from sidecar. |
+| `zeroj-prover-gnark` | Shipping native gnark prover fast path. |
+| `zeroj-onchain-julc` | JuLC-based Cardano on-chain verifiers and on-chain preparation helpers. |
+| `zeroj-cardano` | Lightweight Cardano anchoring and proof metadata models. |
+| `zeroj-ccl` | Cardano Client Lib transaction integration. |
+| `zeroj-patterns` | Privacy templates: nullifier, membership, credential, DPP, range patterns. |
+
+Mainline modules included only in `zeroj-bom-all`:
+
+| Module | Role |
+|--------|------|
+| `zeroj-bbs` | Mainline BBS credential backend; visible and supported, but outside the core Cardano SNARK BOM. |
+
+Support modules:
+
+| Module | Role |
+|--------|------|
+| `zeroj-test-vectors` | Test fixtures and interoperability vectors. |
+| `zeroj-examples` | Demos and end-to-end examples; not a production dependency signal. |
+| `zeroj-bom-core` | New BOM for the shipping privacy-first Cardano path. |
+| `zeroj-bom-all` | New BOM including optional and incubating modules. |
+
+### 2. Add `zeroj-prover-spi`
+
+Create a new module, `zeroj-prover-spi`, for prover-side abstractions.
+
+Move these types from `zeroj-prover-sidecar`:
+
+- `ProverService`
+- `ProveRequest`
+- `ProveResponse`
+- `ProverException`
+
+`zeroj-prover-gnark` must depend on `zeroj-prover-spi`, not on
+`zeroj-prover-sidecar`. It may use shared response and exception types without
+implementing `ProverService` directly if its native proving API requires richer
+inputs than `ProveRequest` can express.
+
+`zeroj-prover-spi` is separate from `zeroj-backend-spi` because
+`zeroj-backend-spi` is verifier-side SPI:
+
+- `ZkVerifier`
+- `BackendDescriptor`
+- `VerificationKeyRegistry`
+- `InMemoryVerificationKeyRegistry`
+
+Do not overload `zeroj-backend-spi` with prover transport concerns. A future
+rename to `zeroj-verifier-spi` may be considered, but is not part of this ADR.
+`zeroj-prover-gnark` must not service-load a verifier-side `ZkVerifier`; PlonK
+verification is owned by `zeroj-verifier-plonk`.
+
+### 3. Remove app-layer submission pipeline modules
+
+Remove:
+
+- `zeroj-submission`
+- `zeroj-ingestion`
+
+These modules model proof-backed application submissions, state-root updates,
+submitter authorization, nullifier storage, sequence checks, audit logs, and
+pipeline governance. They are useful application infrastructure, but they are
+not required for the v3 core SDK and distract from the focused Java/Cardano/ZK
+developer path.
+
+If needed later, they can return as a separate reference app or Yaci-oriented
+integration package.
+
+### 4. Remove sidecar prover module for now
+
+Remove:
+
+- `zeroj-prover-sidecar`
+
+Remote proving is a valid deployment choice, but it is not needed for the
+local-first v3 core. Removing it also prevents an HTTP transport module from
+owning shared prover SPI types.
+
+If remote proving becomes a product requirement later, reintroduce it as an
+optional implementation of `zeroj-prover-spi`.
+
+### 5. Remove RapidSNARK module
+
+Remove:
+
+- `zeroj-prover-rapidsnark`
+
+Rationale:
+
+- BN254 is not Cardano on-chain feasible with current Plutus builtins.
+- The module is native and platform-packaging-heavy.
+- RapidSNARK adds LGPL/native distribution complexity.
+- It is not needed for the v3 shipping path.
+
+### 6. Merge useful on-chain experimental helpers, then remove the module
+
+Remove:
+
+- `zeroj-onchain-experimental`
+
+Before removal, merge useful code into `zeroj-onchain-julc` where it directly
+supports the shipping on-chain verifier path:
+
+- `ScriptBudgetEstimator`
+- `OnChainFeasibility`
+- selected deployment pattern/config types from `ReferenceScriptDeployer`
+
+Do not blindly move everything:
+
+- `OnChainProofPreparer` overlaps with `SnarkjsToCardano` and
+ `ProverToCardano`.
+- `OnChainVkPreparer` overlaps with existing VK/proof conversion flows.
+
+Any moved helper must get tests in `zeroj-onchain-julc`. Stale or duplicate
+conversion code should be deleted instead of preserved.
+
+### 7. Keep BBS visible but outside the core BOM
+
+Keep:
+
+- `zeroj-bbs`
+
+`zeroj-bbs` is a real, tested credential backend and supports the privacy
+vision. It remains mainline so credential developers can find it easily.
+
+However, BBS is not the same as Cardano on-chain SNARK verification. It is an
+off-chain credential presentation backend today, with future circuit/on-chain
+integration possible. Therefore:
+
+- include it in `zeroj-bom-all`
+- exclude it from `zeroj-bom-core`
+- document it as "mainline credential backend, not part of the core Cardano
+ SNARK path"
+
+### 8. Keep WASM and research providers outside the core BOM
+
+Keep as optional/incubating modules, but outside `zeroj-bom-core`:
+
+- `zeroj-bbs-wasm`
+- `zeroj-bls12381-wasm`
+- `zeroj-prover-wasm`
+- `zeroj-verifier-halo2`
+
+These modules are useful for provider experimentation, compatibility, or
+research, but they are not required for the default Java/Cardano/ZK path.
+
+They may remain in `settings.gradle`; physical moves into `incubator/` are not
+required for this cleanup. BOM exclusion and documentation grouping do the
+important work without adding directory churn.
+
+`zeroj-verifier-halo2` remains research/incubator because Halo2/Pallas is not
+Cardano on-chain feasible today. Do not archive it as part of this cleanup.
+
+### 9. Keep gnark, PlonK, and blst in mainline
+
+Do not demote:
+
+- `zeroj-prover-gnark`
+- `zeroj-verifier-plonk`
+- `zeroj-blst`
+
+Rationale:
+
+- `zeroj-prover-gnark` is shipping and remains the production fast path for
+ native proving.
+- `zeroj-verifier-plonk` is shipping; Groth16 is the default on-chain path, but
+ PlonK BLS12-381 is supported and budget-sensitive.
+- `zeroj-blst` is stable opt-in native acceleration for BLS12-381.
+
+Documentation must distinguish:
+
+- pure Java proving and verification as the zero-dependency path
+- gnark and blst as production acceleration paths
+- Groth16 as the default on-chain path
+- PlonK as supported and budget-sensitive
+
+### 10. Fix `zeroj-crypto` to not depend on `zeroj-verifier-plonk`
+
+Move shared transcript code currently used by both PlonK proving and PlonK
+verification out of `zeroj-verifier-plonk`.
+
+Target:
+
+- `zeroj-crypto`, if transcript code is fundamentally proving/crypto
+ infrastructure
+- or a small shared package inside a lower-level module
+
+After the change:
+
+- `zeroj-crypto` must not depend on `zeroj-verifier-plonk`, including test
+ dependencies
+- `zeroj-verifier-plonk` may depend on `zeroj-crypto` or the shared lower-level
+ transcript implementation
+
+### 11. Split the BOM
+
+Replace the single broad BOM with:
+
+| BOM | Includes |
+|-----|----------|
+| `zeroj-bom-core` | Main shipping Cardano privacy path: API, codec, verifier SPI/core, Groth16, PlonK, BLS12-381, blst, crypto, circuit DSL/lib, prover SPI, gnark, onchain-julc, cardano, CCL, patterns. |
+| `zeroj-bom-all` | Everything in core plus BBS, WASM providers, Halo2, prover WASM, examples-facing optional modules. |
+
+If the existing artifact name `zeroj-bom` is already public, keep it for one
+transition release as an alias to `zeroj-bom-all` or document the replacement
+clearly. New users should be directed to `zeroj-bom-core`.
+
+## Implementation Plan
+
+### Phase 1: Prover SPI extraction
+
+1. Add `zeroj-prover-spi`.
+2. Move `ProverService`, `ProveRequest`, `ProveResponse`, and
+ `ProverException` into it.
+3. Update packages and imports in `zeroj-prover-gnark`.
+4. Update tests that refer to old sidecar package names.
+5. Remove `zeroj-prover-gnark -> zeroj-prover-sidecar` dependency.
+6. Remove any gnark verifier-side SPI registration; gnark remains the native
+ proving path, while `zeroj-verifier-plonk` owns PlonK verification.
+
+### Phase 2: Remove app-layer and sidecar modules
+
+1. Remove `zeroj-submission`, `zeroj-ingestion`, and `zeroj-prover-sidecar`
+ from `settings.gradle`.
+2. Remove these modules from BOM constraints.
+3. Update or delete examples that depend on them.
+4. Audit docs and use cases for removed modules:
+ - `zeroj-submission`
+ - `zeroj-ingestion`
+ - `zeroj-prover-sidecar`
+5. Add migration guidance in `docs/migration/0020-module-cleanup.md` or the
+ project changelog, listing removed coordinates and replacements.
+6. Remove README references that present removed modules as core modules.
+
+### Phase 3: Remove RapidSNARK
+
+1. Remove `zeroj-prover-rapidsnark` from `settings.gradle`.
+2. Remove BOM and README references.
+3. Add RapidSNARK to the migration guidance with the recommendation to use
+ `zeroj-prover-gnark` for native proving or `zeroj-crypto` for pure Java
+ proving.
+4. Remove related docs or mark historical docs as obsolete if needed.
+
+### Phase 4: Merge on-chain helpers
+
+1. Move tested, useful helpers from `zeroj-onchain-experimental` into
+ `zeroj-onchain-julc`.
+2. Add or move tests for budget estimation and feasibility matrix.
+3. Check conversion helpers against `SnarkjsToCardano` and `ProverToCardano`;
+ delete duplicates rather than preserving two paths.
+4. Audit and update `META-INF/native-image` resources for
+ `zeroj-onchain-julc` after moving classes.
+5. Remove `zeroj-onchain-experimental` from `settings.gradle`, BOM, and README.
+
+### Phase 5: Fix PlonK transcript layering
+
+1. Move `FiatShamirTranscript` and any required shared transcript utilities
+ below `zeroj-verifier-plonk`.
+2. Update `zeroj-crypto` PlonK prover imports.
+3. Update `zeroj-verifier-plonk` imports.
+4. Audit and update native-image metadata for `zeroj-crypto` and
+ `zeroj-verifier-plonk`.
+5. Remove `zeroj-crypto -> zeroj-verifier-plonk`, including test-scoped edges.
+
+### Phase 6: Split BOMs and documentation
+
+1. Add `zeroj-bom-core`.
+2. Add or repurpose `zeroj-bom-all`.
+3. Update top-level `README.md` module tables into:
+ - Core
+ - Cardano integration
+ - Privacy and credential backends
+ - Optional accelerators
+ - Incubator/research
+ - Examples and test fixtures
+4. Update `docs/architecture-overview.md` so its module boundaries match the
+ new BOM and settings layout.
+5. Document that `zeroj-bbs` is mainline but outside the core BOM.
+
+## Consequences
+
+### Easier
+
+- The default project story becomes simpler and closer to the v3 vision.
+- The core BOM is smaller and safer for production users.
+- Prover SPI is no longer tied to an HTTP sidecar implementation.
+- `zeroj-crypto` becomes a cleaner foundation module.
+- Cardano on-chain helper code has one owner: `zeroj-onchain-julc`.
+- Optional and incubating modules stop driving the perceived product surface.
+
+### Harder
+
+- Existing users of `zeroj-submission`, `zeroj-ingestion`, `zeroj-prover-sidecar`,
+ or `zeroj-prover-rapidsnark` will need migration guidance or must pin an older
+ version.
+- Examples that currently demonstrate submission/ingestion flows must be
+ rewritten or removed.
+- Splitting BOMs introduces publication and documentation work.
+- Moving transcript code requires careful package compatibility and test
+ coverage to avoid changing PlonK prover/verifier behavior.
+
+## Risks
+
+| Risk | Mitigation |
+|------|------------|
+| Removing modules breaks downstream users. | Treat this as a pre-1.0 cleanup or provide one release note with migration guidance. |
+| Useful on-chain conversion logic is lost. | Merge only after comparing against `SnarkjsToCardano` and `ProverToCardano`, then add tests. |
+| `zeroj-bom-core` accidentally omits a needed shipping dependency. | Validate by building examples that use only the core Cardano privacy path. |
+| BBS visibility drops if excluded from core BOM. | Keep `zeroj-bbs` mainline and document it prominently as a credential backend. |
+| Prover SPI grows into a second orchestration framework. | Keep `zeroj-prover-spi` minimal: request, response, service, and shared exception only; do not force every prover to implement `ProverService`. |
+| PlonK transcript move changes challenge derivation. | Preserve byte-for-byte test vectors before and after the move. |
+
+## Test Plan
+
+- Compile core modules after each phase.
+- Run:
+ - `:zeroj-api:test`
+ - `:zeroj-codec:test`
+ - `:zeroj-backend-spi:test`
+ - `:zeroj-verifier-core:test`
+ - `:zeroj-verifier-groth16:test`
+ - `:zeroj-verifier-plonk:test`
+ - `:zeroj-crypto:test`
+ - `:zeroj-circuit-dsl:test`
+ - `:zeroj-circuit-lib:test`
+ - `:zeroj-prover-gnark:test`
+ - `:zeroj-onchain-julc:test`
+ - `:zeroj-cardano:test`
+ - `:zeroj-ccl:test`
+ - `:zeroj-patterns:test`
+ - `:zeroj-bbs:test`
+- Run example tests after removing submission/ingestion demos.
+- Verify Gradle project listing no longer includes removed modules.
+- Verify `zeroj-bom-core` resolves without incubator modules.
+- Verify `zeroj-bom-all` resolves optional modules that remain.
+
+## Final Target
+
+Removed:
+
+- `zeroj-submission`
+- `zeroj-ingestion`
+- `zeroj-prover-sidecar`
+- `zeroj-prover-rapidsnark`
+- `zeroj-onchain-experimental`
+
+Added:
+
+- `zeroj-prover-spi`
+- `zeroj-bom-core`
+- `zeroj-bom-all`
+
+Mainline but outside core BOM:
+
+- `zeroj-bbs`
+
+Optional/incubator outside core BOM:
+
+- `zeroj-bbs-wasm`
+- `zeroj-bls12381-wasm`
+- `zeroj-prover-wasm`
+- `zeroj-verifier-halo2`
diff --git a/docs/alternate-prover-backends.md b/docs/alternate-prover-backends.md
index ab2e84e..6732d73 100644
--- a/docs/alternate-prover-backends.md
+++ b/docs/alternate-prover-backends.md
@@ -1,4 +1,4 @@
-# Alternate Prover Backends — gnark FFM, rapidsnark, snarkjs
+# Alternate Prover Backends — gnark FFM and snarkjs
## Table of Contents
@@ -22,7 +22,6 @@ ZeroJ's **recommended** prover is the [pure Java prover](pure-java-prover-guide.
|---------|-------------|--------|-------|-------------|--------|
| **Pure Java** | Groth16, PlonK | BN254, BLS12-381 | Baseline | None | `zeroj-crypto` |
| **gnark FFM** | Groth16, PlonK | BN254, BLS12-381 | ~10-50x faster | Go native lib | `zeroj-prover-gnark` |
-| **rapidsnark FFM** | Groth16 only | BN254 only | ~50-100x faster | C++ native lib | `zeroj-prover-rapidsnark` |
| **snarkjs CLI** | Groth16, PlonK | BN254, BLS12-381 | Slowest | Node.js + snarkjs | (external process) |
## gnark FFM (Foreign Function & Memory)
@@ -44,16 +43,17 @@ import com.bloxbean.cardano.zeroj.prover.gnark.GnarkProver;
// Define circuit and compile R1CS (same as pure Java path)
var circuit = MyCircuit.build();
var r1cs = circuit.compileR1CS(CurveId.BLS12_381);
-byte[] r1csBytes = R1CSSerializer.serialize(r1cs);
-byte[] wtnsBytes = WitnessExporter.toWtns(witness, r1cs.prime(), r1cs.fieldConfig().n32());
// Prove with gnark (in-process, no external CLI)
try (var prover = new GnarkProver()) {
- var result = prover.groth16FullProve(r1csBytes, wtnsBytes, "bls12381");
+ var result = prover.groth16FullProve(r1cs, witness, CurveId.BLS12_381);
String proofJson = result.proveResponse().proofJson();
String vkJson = result.vkJson();
- String publicJson = result.proveResponse().publicInputsJson();
+ List publicSignals = result.proveResponse().publicSignals();
+ String publicJson = publicSignals.stream()
+ .map(v -> "\"" + v + "\"")
+ .collect(java.util.stream.Collectors.joining(",", "[", "]"));
}
```
@@ -61,14 +61,15 @@ try (var prover = new GnarkProver()) {
```java
try (var prover = new GnarkProver()) {
- var result = prover.plonkFullProve(r1csBytes, wtnsBytes, "bls12381");
- // Same JSON format as Groth16
+ var result = prover.plonkFullProve(r1cs, witness, CurveId.BLS12_381);
+ // gnark binary PlonK proof JSON; verify with gnark until an adapter lands
}
```
### Verification
-gnark proofs are verified using the same pure Java verifiers:
+For Groth16, gnark and snarkjs artifacts can be normalized into the same
+envelope model and verified by the pure Java verifiers:
```java
var envelope = SnarkjsJsonCodec.toEnvelopeFromJson(proofJson, vkJson, publicJson, circuitId);
@@ -76,6 +77,11 @@ var verifier = new Groth16BLS12381PureJavaVerifier();
var result = verifier.verify(envelope, material);
```
+For PlonK, the pure Java verifiers consume structured snarkjs/ZeroJ proof JSON.
+gnark's opaque binary PlonK proof JSON is kept as a typed artifact and should be
+verified with gnark native verification until a dedicated decoder/adapter is
+implemented.
+
### Gradle
```gradle
@@ -163,7 +169,7 @@ var proof = snarkjs.groth16Prove(zkeyPath, wtnsPath, workDir);
| **Cardano on-chain verification** | Pure Java (BLS12-381) |
| **Development / testing** | Pure Java (zero setup, instant) |
| **Large circuits (>10K constraints)** | gnark FFM (10-50x faster) |
-| **BN254 proofs (Ethereum)** | gnark FFM or rapidsnark |
+| **BN254 proofs (Ethereum)** | gnark FFM |
| **Existing snarkjs workflow** | snarkjs CLI → import .zkey → pure Java prove |
| **Mobile / serverless** | Pure Java (no native deps, GraalVM compatible) |
| **CI/CD pipelines** | Pure Java (no build toolchain needed) |
diff --git a/docs/architecture-overview.md b/docs/architecture-overview.md
index de5efb3..61384cb 100644
--- a/docs/architecture-overview.md
+++ b/docs/architecture-overview.md
@@ -15,16 +15,23 @@
## Design Philosophy
-ZeroJ is a **verifier-first** ZK platform. Circuits can be defined in Java (DSL) or externally (circom, gnark Go). Proofs are generated in-process (gnark FFM) or externally (snarkjs). Verification is pure Java. On-chain verification uses Julc-compiled Plutus V3 validators.
+ZeroJ is a privacy-first ZK platform for Java and Cardano. Circuits can be
+defined in Java or imported from external toolchains. Proofs can be generated
+with the pure Java prover or the gnark native accelerator. Verification is Java
+first, and on-chain verification uses Julc-compiled Plutus V3 validators. gnark
+binary PlonK artifacts remain on the gnark native verification path until a
+structured proof adapter is added.
## Module Organization
-Modules are organized into **core** (top-level) and **incubator** (`incubator/` subfolder). Incubator modules are experimental or alternative backends -- still compiled, tested, and published, but visually separated.
+Modules are organized into core modules, mainline opt-in modules, and incubator
+modules. `zeroj-bom-core` covers the stable v3 privacy path. `zeroj-bom-all`
+covers core plus opt-in BBS/WASM and incubator modules.
## Module Dependency Graph
```
-zeroj-api (no project deps — foundation types)
+zeroj-api (foundation types)
|
+-- zeroj-codec (→ zeroj-api, jackson, cbor)
|
@@ -32,39 +39,41 @@ zeroj-api (no project deps — foundation types)
| |
| +-- zeroj-verifier-core (→ zeroj-api, zeroj-backend-spi)
| |
- | +-- zeroj-verifier-groth16 (→ zeroj-backend-spi, zeroj-codec, zeroj-blst)
+ | +-- zeroj-verifier-groth16 (→ zeroj-backend-spi, zeroj-codec, zeroj-bls12381, zeroj-blst)
| |
- | +-- zeroj-verifier-plonk (→ zeroj-backend-spi, zeroj-codec, zeroj-blst)
+ | +-- zeroj-verifier-plonk (→ zeroj-backend-spi, zeroj-codec, zeroj-crypto, zeroj-verifier-groth16 for BN254 arithmetic)
| |
| +-- zeroj-verifier-halo2 (→ zeroj-backend-spi, zeroj-codec, Rust FFM) [incubator]
|
+ +-- zeroj-bls12381 (pure Java BLS12-381 field/curve/pairing)
+ | |
+ | +-- zeroj-crypto (→ zeroj-api, zeroj-bls12381)
+ |
+-- zeroj-blst (→ zeroj-api, blst-java)
|
+-- zeroj-circuit-dsl (→ zeroj-api, zeroj-codec)
| |
| +-- zeroj-circuit-lib (→ zeroj-circuit-dsl)
|
- +-- zeroj-submission (→ zeroj-api, cbor)
- | |
- | +-- zeroj-ingestion (→ zeroj-submission, zeroj-verifier-core, zeroj-codec)
- |
+-- zeroj-patterns (→ zeroj-api, zeroj-verifier-core, zeroj-codec, zeroj-cardano)
|
+-- zeroj-cardano (→ zeroj-api, cbor)
| |
| +-- zeroj-ccl (→ zeroj-cardano, zeroj-api, cardano-client-lib)
|
- +-- zeroj-prover-gnark (→ zeroj-api, zeroj-codec, zeroj-backend-spi, Go FFM)
- |
- +-- zeroj-prover-sidecar (→ zeroj-api, zeroj-codec, jackson) [incubator]
+ +-- zeroj-prover-spi (prover request/response contracts)
| |
- | +-- zeroj-prover-rapidsnark (→ zeroj-prover-sidecar, FFM) [incubator]
+ | +-- zeroj-prover-gnark (→ zeroj-api, zeroj-codec, zeroj-circuit-dsl, Go FFM)
+ |
+ +-- zeroj-prover-wasm (→ zeroj-api, GraalVM WASM) [incubator]
|
- +-- zeroj-onchain-julc (→ julc-stdlib, BLS12-381 builtins)
+ +-- zeroj-onchain-julc (→ zeroj-crypto, julc-stdlib, BLS12-381 builtins)
|
+-- zeroj-test-vectors (→ zeroj-api, test fixtures only)
-zeroj-bom (platform module, no code)
+zeroj-bbs, zeroj-bbs-wasm, zeroj-bls12381-wasm (mainline opt-in)
+
+zeroj-bom-core / zeroj-bom-all (platform modules, no code)
```
## Layer Separation
@@ -95,7 +104,7 @@ Backend abstraction:
### Layer 4: Verification Backends
Concrete implementations:
- `zeroj-verifier-groth16` -- Groth16 for BN254 (pure Java) + BLS12-381 (pure Java / blst)
-- `zeroj-verifier-plonk` -- PlonK for BN254 + BLS12-381 (pure Java), byte-for-byte verified against gnark
+- `zeroj-verifier-plonk` -- structured PlonK proof verification for BN254 + BLS12-381 (pure Java)
- `zeroj-verifier-halo2` -- Halo2 IPA via Rust FFM (incubator)
- `zeroj-blst` -- Low-level BLS12-381 curve operations
@@ -111,30 +120,27 @@ Routes verification requests to the correct backend based on proof system and cu
### Layer 7: Proving
Proof generation backends:
-- `zeroj-prover-gnark` -- in-process Groth16/PlonK via Go FFM (primary)
-- `zeroj-prover-rapidsnark` -- in-process Groth16 BN254 via C++ FFM (incubator)
-- `zeroj-prover-sidecar` -- HTTP client for external prover services (incubator)
-
-### Layer 8: Submission & Ingestion
-Proof-backed state transitions:
-- `zeroj-submission` -- Wire format, Ed25519 signatures, result types
-- `zeroj-ingestion` -- 6-stage validation pipeline, governance stores, audit
+- `zeroj-crypto` -- pure Java Groth16 and PlonK proving where supported
+- `zeroj-prover-spi` -- minimal prover-side request/response contract
+- `zeroj-prover-gnark` -- production native Groth16/PlonK proving via Go FFM
+- `zeroj-prover-wasm` -- Circom witness calculation via GraalVM WASM (incubator)
-### Layer 9: High-Level Patterns (`zeroj-patterns`)
+### Layer 8: High-Level Patterns (`zeroj-patterns`)
Domain-specific APIs:
- State transitions, nullifier claims, membership proofs
- Typed inputs, enriched results, pre-built policies
-### Layer 10: Cardano Integration
+### Layer 9: Cardano Integration
Anchoring verified results on L1:
- `zeroj-cardano` -- Anchor model, CIP-10 metadata encoding
- `zeroj-ccl` -- Cardano Client Lib transaction builder integration
-### Layer 11: On-Chain Verification (`zeroj-onchain-julc`)
+### Layer 10: On-Chain Verification (`zeroj-onchain-julc`)
Reusable Plutus V3 spending validators compiled via Julc:
- `Groth16BLS12381Verifier` -- on-chain Groth16 verification using BLS12-381 builtins
-- `PlonkBLS12381FullVerifier` -- on-chain PlonK verification with Fiat-Shamir transcript
+- `PlonkBLS12381FullVerifier` -- experimental on-chain PlonK prototype with Fiat-Shamir transcript and inverse checks; KZG pairing check deferred
- `SnarkjsToCardano` -- converts snarkjs JSON to BLS compressed bytes for on-chain use
+- `ScriptBudgetEstimator`, `OnChainFeasibility`, `ReferenceScriptDeployer` -- on-chain budget and deployment helpers
## Crypto Backend Strategy
@@ -158,14 +164,14 @@ On-chain ZK verification uses Julc (Java-to-Plutus compiler) to create reusable
| Proof System | Curve | On-Chain Status | Module |
|-------------|-------|----------------|--------|
| Groth16 | BLS12-381 | Working | `zeroj-onchain-julc` |
-| PlonK | BLS12-381 | Working | `zeroj-onchain-julc` |
+| PlonK | BLS12-381 | Experimental partial prototype; KZG pairing check deferred | `zeroj-onchain-julc` |
| Groth16/PlonK | BN254 | Not feasible | No Plutus BN254 builtins |
The `zeroj-examples` module includes complete end-to-end tests (DSL to on-chain execution on Yaci DevKit).
## GraalVM Native Image
-All modules include native-image configuration files in:
+Runtime modules that need native-image metadata keep configuration files in:
```
src/main/resources/META-INF/native-image/com.bloxbean.cardano//
```
@@ -186,3 +192,4 @@ Best-effort compatibility from the start; hardened in later milestones.
| [0008](adr/0008-plonk-support-via-gnark.md) | PlonK support via gnark |
| [0009](adr/0009-halo2-support-strategy.md) | Halo2 support strategy |
| [0010](adr/0010-java-circuit-dsl.md) | Java Circuit DSL |
+| [0020](adr/0020-module-cleanup-and-core-restructure.md) | Module cleanup and core restructure |
diff --git a/docs/circuit-dsl-user-guide.md b/docs/circuit-dsl-user-guide.md
index 7acf9b3..01e56b4 100644
--- a/docs/circuit-dsl-user-guide.md
+++ b/docs/circuit-dsl-user-guide.md
@@ -26,7 +26,7 @@ The ZeroJ Circuit DSL lets you define ZK arithmetic circuits in Java and compile
| Backend | Proof System | Prover | Use Case |
|---------|-------------|--------|----------|
-| R1CS | Groth16 | **Pure Java**, gnark FFM, rapidsnark FFM | Smallest proofs, cheapest on-chain verification |
+| R1CS | Groth16 | **Pure Java**, gnark FFM | Smallest proofs, cheapest on-chain verification |
| PlonK | PlonK | **Pure Java**, gnark FFM | Universal setup, no per-circuit ceremony |
| Halo2 | Halo2 (IPA/KZG) | Halo2 Rust FFM | No trusted setup (IPA), recursive proofs |
@@ -637,7 +637,7 @@ var circuit = MultiFieldCommitCircuit.build("name", "age", "address", "balance")
```java
var r1cs = circuit.compileR1CS(CurveId.BN254);
-// Serialize to iden3 .r1cs binary (for rapidsnark/snarkjs)
+// Serialize to iden3 .r1cs binary (for snarkjs or gnark import)
byte[] r1csBytes = R1CSSerializer.serialize(r1cs);
Files.write(Path.of("circuit.r1cs"), r1csBytes);
@@ -685,7 +685,7 @@ Properties:
| Curve | Proof Systems | On-chain? | Note |
|-------|--------------|-----------|------|
| BN254 | Groth16, PlonK | No (no Plutus builtins) | circom/snarkjs ecosystem |
-| BLS12-381 | Groth16, PlonK | **Yes** (Plutus V3) | Cardano on-chain verification |
+| BLS12-381 | Groth16, PlonK | Groth16: **Yes**; PlonK: prototype | Cardano-native BLS builtins; PlonK KZG pairing check is still deferred on-chain |
| Pallas | Halo2 IPA | No | No trusted setup, recursive proofs |
## Witness Calculation
@@ -738,7 +738,6 @@ Java CircuitSpec / CircuitBuilder DSL
│
├──▶ compileR1CS() ──▶ Groth16ProverBLS381 (pure Java) ──▶ proof
│ └──▶ gnark FFM ──▶ proof (see alternate-backends.md)
- │ └──▶ rapidsnark FFM ──▶ proof
│
├──▶ compilePlonK() ──▶ PlonKProverBLS381 (pure Java) ──▶ proof
│ └──▶ gnark FFM ──▶ proof
@@ -752,7 +751,8 @@ Verification (pure Java, zero native deps):
PlonkBN254Verifier / PlonkBLS12381Verifier
On-Chain (Cardano Plutus V3):
- Groth16BLS12381Verifier / PlonkBLS12381FullVerifier (Julc)
+ Groth16BLS12381Verifier (Julc)
+ PlonkBLS12381FullVerifier (Julc prototype: transcript/inverse checks only)
```
**Recommended path**: CircuitSpec → `compileR1CS(BLS12_381)` → `Groth16ProverBLS381` → on-chain verify.
@@ -781,7 +781,6 @@ implementation 'com.bloxbean.cardano:zeroj-circuit-lib'
// Provers (choose based on your proof system)
implementation 'com.bloxbean.cardano:zeroj-prover-gnark' // gnark (Groth16 + PlonK)
-implementation 'com.bloxbean.cardano:zeroj-prover-rapidsnark' // rapidsnark (Groth16 BN254)
// Verifiers (pure Java, zero native deps)
implementation 'com.bloxbean.cardano:zeroj-verifier-groth16' // Groth16 (BN254 + BLS12-381)
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 65f48da..5ace823 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -115,11 +115,14 @@ This is all pure Java -- no external tools needed.
```java
try (var prover = new GnarkProver()) {
- var result = prover.groth16FullProve(r1csBytes, wtnsBytes, "bls12381");
+ var result = prover.groth16FullProve(r1cs, witness, CurveId.BLS12_381);
String proofJson = result.proveResponse().proofJson();
String vkJson = result.vkJson();
- String publicJson = result.proveResponse().publicInputsJson();
+ List publicSignals = result.proveResponse().publicSignals();
+ String publicJson = publicSignals.stream()
+ .map(v -> "\"" + v + "\"")
+ .collect(java.util.stream.Collectors.joining(",", "[", "]"));
}
```
@@ -298,11 +301,10 @@ See the [examples README](../zeroj-examples/README.md) for detailed descriptions
| **Pure Java** | Groth16 + PlonK | BLS12-381, BN254 | **None** | Seconds |
| **gnark FFM** | Groth16 + PlonK | BLS12-381, BN254 | gnark native lib | ~50-300ms |
| **snarkjs CLI** | Groth16 + PlonK | BLS12-381, BN254 | Node.js + snarkjs | Minutes |
-| **rapidsnark** | Groth16 | BN254 only | rapidsnark native lib | ~10-50ms |
**Pure Java** is the recommended prover for Cardano -- zero dependencies, GraalVM-compatible, proven end-to-end on-chain. See the [Pure Java Prover Guide](pure-java-prover-guide.md) for the complete pipeline.
-For maximum speed with large circuits, see [Alternate Prover Backends](alternate-prover-backends.md) (gnark FFM, rapidsnark).
+For maximum speed with large circuits, see [Alternate Prover Backends](alternate-prover-backends.md) (gnark FFM).
## Curves and On-Chain Feasibility
diff --git a/docs/plan-graalwasm-witness-calculator.md b/docs/plan-graalwasm-witness-calculator.md
index a1cd776..084ea71 100644
--- a/docs/plan-graalwasm-witness-calculator.md
+++ b/docs/plan-graalwasm-witness-calculator.md
@@ -16,7 +16,7 @@ circom circuit → compile → .wasm + .r1cs
└───────────────────────────────┘
│
▼
- rapidsnark FFM or gnark FFM → proof
+ gnark FFM or pure Java prover → proof
```
## Target State
@@ -31,15 +31,15 @@ circom circuit → compile → .wasm + .r1cs
└───────────────────────────────┘
│
▼
- rapidsnark FFM or gnark FFM → proof
+ gnark FFM or pure Java prover → proof
```
## Design
-### New module: `zeroj-prover-wasm`
+### Incubator module: `zeroj-prover-wasm`
```
-zeroj-prover-wasm/
+incubator/zeroj-prover-wasm/
src/main/java/com/bloxbean/cardano/zeroj/prover/wasm/
WasmWitnessCalculator.java — main API
CircomWasmRuntime.java — GraalWasm context management
@@ -57,7 +57,7 @@ var calculator = new WasmWitnessCalculator(Path.of("multiplier.wasm"));
Map inputs = Map.of("a", BigInteger.valueOf(3), "b", BigInteger.valueOf(11));
byte[] witnessWtns = calculator.calculateWitness(inputs);
-// Export for rapidsnark
+// Export to .wtns
Path wtnsPath = WitnessExporter.writeWtns(witnessWtns, tempDir);
// Or export for gnark
byte[] gnarkWitness = WitnessExporter.toGnarkBinary(witnessWtns, CurveId.BN254);
@@ -70,10 +70,7 @@ byte[] gnarkWitness = WitnessExporter.toGnarkBinary(witnessWtns, CurveId.BN254);
var calculator = new WasmWitnessCalculator(circuitWasmPath);
byte[] witness = calculator.calculateWitness(Map.of("a", "3", "b", "11"));
-// 2. Prove (rapidsnark FFM — in-process)
-try (var prover = new RapidsnarkProver()) {
- var proof = prover.prove(r1csPath, witness);
-}
+// 2. Prove with gnark FFM or the pure Java prover
// 3. Verify (pure Java — zero native deps)
var verifier = new Groth16BN254Verifier();
@@ -108,15 +105,15 @@ Reference: snarkjs `wtns_calculate.js` and circom `calcwit.cpp`
### Phase 3: Wire into prover pipeline
1. `WasmWitnessCalculator` produces witness bytes
-2. `WitnessExporter` converts to `.wtns` (for rapidsnark) or gnark binary format
-3. Prover FFM takes witness + r1cs → proof
+2. `WitnessExporter` converts to `.wtns` or gnark binary format
+3. Prover takes witness + r1cs -> proof
4. Pure Java verifier checks proof
### Phase 4: Integration tests
- Multiplier circuit: compute witness for a=3, b=11, verify output c=33
- Compare witness output with snarkjs `wtns calculate` output (byte-for-byte)
-- End-to-end: wasm witness → rapidsnark prove → pure Java verify
+- End-to-end: wasm witness -> gnark or pure Java prove -> pure Java verify
## Dependencies
@@ -143,6 +140,6 @@ Requires GraalVM JDK (already the project's toolchain: Java 25 GraalVM).
## Success Criteria
- `WasmWitnessCalculator` produces identical witness bytes as `snarkjs wtns calculate`
-- End-to-end: circom `.wasm` → GraalWasm witness → rapidsnark proof → pure Java verify
+- End-to-end: circom `.wasm` -> GraalWasm witness -> gnark or pure Java proof -> pure Java verify
- No Node.js process needed at any point
- Works with GraalVM native-image
diff --git a/docs/plonk-support.md b/docs/plonk-support.md
index eae16f9..8bdf886 100644
--- a/docs/plonk-support.md
+++ b/docs/plonk-support.md
@@ -13,23 +13,29 @@
---
-## Status: Production (Off-Chain Pure Java, On-Chain via Julc)
+## Status: Production Off-Chain, Experimental On-Chain
-ZeroJ supports PlonK proof generation via gnark FFM and **pure Java verification** on BLS12-381 and BN254 curves. On-chain PlonK verification is available via a Julc-compiled Plutus V3 validator.
+ZeroJ supports PlonK proof generation via gnark FFM and **pure Java verification**
+for structured snarkjs/ZeroJ PlonK proof JSON on BLS12-381 and BN254 curves.
+gnark's opaque binary PlonK proof JSON should be verified with gnark native
+verification until a dedicated adapter is added. The Julc on-chain PlonK path is
+an experimental prototype today: transcript and inverse checks are implemented,
+but the KZG batch opening pairing check is still deferred.
## What Works Today
### Off-Chain PlonK (Production-Ready)
- **Setup**: Universal SRS generation (one setup works for any circuit up to the SRS size)
-- **Prove**: Generate PlonK proofs from gnark circuits via FFM
-- **Verify**: **Pure Java verification** -- zero native dependencies, byte-for-byte verified against gnark
+- **Prove**: Generate PlonK proofs with the pure Java prover or with gnark FFM
+- **Verify**: **Pure Java verification** for structured snarkjs/ZeroJ proof JSON; gnark binary PlonK proof JSON uses gnark native verification until an adapter lands
- **Both BN254 and BLS12-381 curves supported**
-### On-Chain PlonK (Working)
-- **Full on-chain PlonK verifier** via `PlonkBLS12381FullVerifier` in `zeroj-onchain-julc`
+### On-Chain PlonK (Experimental)
+- Prototype verifier via `PlonkBLS12381FullVerifier` in `zeroj-onchain-julc`
- Fiat-Shamir challenge re-derivation matching gnark's exact transcript format
+- KZG batch opening pairing check still deferred
- BLS12-381 only (Plutus V3 builtins)
-- Tested end-to-end on Yaci DevKit
+- Useful for budget and data-shape work, not yet a full trustless on-chain verifier
### Advantage Over Groth16
| Feature | Groth16 | PlonK |
diff --git a/incubator/zeroj-onchain-experimental/README.md b/incubator/zeroj-onchain-experimental/README.md
deleted file mode 100644
index 08c1507..0000000
--- a/incubator/zeroj-onchain-experimental/README.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# zeroj-onchain-experimental
-
-Java-side helpers for on-chain ZK proof verification on Cardano.
-
-This module provides utilities for preparing proofs and verification keys for submission to Plutus scripts, estimating script execution budgets, and evaluating feasibility of different proof system / curve combinations on-chain.
-
-> **Note:** Reusable Plutus V3 on-chain verifiers (Groth16 + PlonK) live in [`zeroj-onchain-julc`](../../zeroj-onchain-julc/). This module provides the Java-side data preparation and budget estimation only.
-
-## Key Classes
-
-| Class | Purpose |
-|-------|---------|
-| `OnChainProofPreparer` | Converts `ZkProofEnvelope` → Plutus redeemer format (G1/G2 point byte arrays) |
-| `OnChainVkPreparer` | Compresses VK for on-chain use, computes VK hash for commitment patterns |
-| `ScriptBudgetEstimator` | Estimates CPU/memory costs based on Plutus V3 BLS12-381 builtin costs (CIP-0381) |
-| `OnChainFeasibility` | Structured feasibility matrix — which systems work on-chain and at what cost |
-| `ReferenceScriptDeployer` | CIP-0033 reference script deployment patterns and configuration |
-
-## Feasibility Matrix
-
-| System | Curve | Status | Est. CPU Budget |
-|--------|-------|--------|-----------------|
-| Groth16 | BLS12-381 | **Working** | ~2.0B (1 public input) |
-| PlonK | BLS12-381 | Experimental | ~2.0B+ (with MSM) |
-| Halo2 KZG | BLS12-381 | Assessment only | Very high |
-| Groth16/PlonK | BN254 | Not feasible | No BN254 builtins |
-
-```java
-// Check feasibility
-boolean feasible = OnChainFeasibility.isFeasible(ProofSystemId.GROTH16, CurveId.BLS12_381);
-
-// Estimate budget
-long cpu = ScriptBudgetEstimator.estimateCpu(ProofSystemId.GROTH16, CurveId.BLS12_381, 1);
-
-// Prepare proof for on-chain submission
-List redeemerElements = OnChainProofPreparer.prepareGroth16BLS12381Redeemer(envelope);
-List publicInputs = OnChainProofPreparer.preparePublicInputs(envelope);
-```
-
-## Reference Script Patterns
-
-| Pattern | Description | Trade-off |
-|---------|-------------|-----------|
-| VK-in-script | VK baked into script at deploy | Simple, larger script |
-| Reference script + datum VK | Logic as CIP-0033, VK in datum | Small script, VK rotatable |
-| VK hash commitment | Script has hash, full VK in redeemer | Smallest script, VK in redeemer |
-
-```java
-// Configure deployment
-var config = ReferenceScriptDeployer.DeploymentConfig.referenceWithDatumVk(scriptBytes, vkBytes);
-```
-
-## Gradle
-
-```gradle
-dependencies {
- implementation 'com.bloxbean.cardano:zeroj-onchain-experimental'
-}
-```
diff --git a/incubator/zeroj-onchain-experimental/build.gradle b/incubator/zeroj-onchain-experimental/build.gradle
deleted file mode 100644
index 264ac4d..0000000
--- a/incubator/zeroj-onchain-experimental/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-plugins {
- id 'java-library'
-}
-
-description = 'ZeroJ on-chain experimental — helpers for Cardano on-chain ZK proof verification'
-
-dependencies {
- api project(':zeroj-api')
- implementation project(':zeroj-codec')
- implementation project(':zeroj-cardano')
- implementation project(':zeroj-ccl')
-
- testImplementation project(':zeroj-test-vectors')
-}
-
-publishing {
- publications {
- mavenJava(MavenPublication) {
- pom {
- name = 'ZeroJ On-Chain Experimental'
- description = 'Helpers for on-chain ZK proof verification on Cardano (experimental)'
- }
- }
- }
-}
diff --git a/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/OnChainProofPreparer.java b/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/OnChainProofPreparer.java
deleted file mode 100644
index 9570487..0000000
--- a/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/OnChainProofPreparer.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package com.bloxbean.cardano.zeroj.onchain;
-
-import com.bloxbean.cardano.zeroj.api.ZkProofEnvelope;
-
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Converts a {@link ZkProofEnvelope} into a format suitable for Plutus redeemer construction.
- *
- * On-chain verifiers (Julc-based) expect proof data as lists of integers and byte arrays
- * that map to Plutus Data types. This helper transforms the generic proof envelope into
- * the specific layout each on-chain verifier expects.
- *
- * Note: Actual Plutus script construction stays in zeroj-examples (Julc).
- * This helper prepares the Java-side data only.
- */
-public final class OnChainProofPreparer {
-
- private OnChainProofPreparer() {}
-
- /**
- * Prepare Groth16 BLS12-381 proof data for on-chain redeemer.
- * Returns the proof elements as a list of byte arrays (G1/G2 points).
- *
- * @param envelope the proof envelope (must be Groth16/BLS12-381)
- * @return list of proof element byte arrays: [piA, piB, piC]
- */
- public static List prepareGroth16BLS12381Redeemer(ZkProofEnvelope envelope) {
- validateSystem(envelope, "groth16", "bls12381");
-
- // Parse proof.json and extract the three curve points
- var proof = com.bloxbean.cardano.zeroj.codec.SnarkjsJsonCodec.parseProof(
- new String(envelope.proofBytes()));
-
- List result = new ArrayList<>();
- result.add(g1ToBytes(proof.piA()));
- result.add(g2ToBytes(proof.piB()));
- result.add(g1ToBytes(proof.piC()));
- return result;
- }
-
- /**
- * Prepare public inputs as a list of BigIntegers for Plutus integer fields.
- */
- public static List preparePublicInputs(ZkProofEnvelope envelope) {
- return List.copyOf(envelope.publicInputs().values());
- }
-
- /**
- * Encode a G1 point [x, y, z] (projective, BLS12-381) to 96-byte uncompressed affine.
- */
- static byte[] g1ToBytes(List coords) {
- var x = coords.get(0);
- var y = coords.get(1);
- // Assume z=1 (snarkjs outputs affine-in-projective-form)
- var result = new byte[96];
- writeBigEndian(result, 0, 48, x);
- writeBigEndian(result, 48, 48, y);
- return result;
- }
-
- /**
- * Encode a G2 point [[x_c0,x_c1],[y_c0,y_c1],[z_c0,z_c1]] to 192-byte uncompressed.
- */
- static byte[] g2ToBytes(List> coords) {
- var result = new byte[192];
- // BLS12-381 G2: x_c1, x_c0, y_c1, y_c0 (48 bytes each)
- writeBigEndian(result, 0, 48, coords.get(0).get(1)); // x_c1
- writeBigEndian(result, 48, 48, coords.get(0).get(0)); // x_c0
- writeBigEndian(result, 96, 48, coords.get(1).get(1)); // y_c1
- writeBigEndian(result, 144, 48, coords.get(1).get(0)); // y_c0
- return result;
- }
-
- private static void validateSystem(ZkProofEnvelope envelope, String expectedSystem, String expectedCurve) {
- if (!envelope.proofSystem().value().equals(expectedSystem)) {
- throw new IllegalArgumentException("Expected " + expectedSystem + " proof, got " + envelope.proofSystem());
- }
- if (!envelope.curve().value().equals(expectedCurve)) {
- throw new IllegalArgumentException("Expected " + expectedCurve + " curve, got " + envelope.curve());
- }
- }
-
- private static void writeBigEndian(byte[] buf, int offset, int fieldSize, BigInteger value) {
- var bytes = value.toByteArray();
- int srcStart = (bytes.length > fieldSize) ? bytes.length - fieldSize : 0;
- int destStart = offset + fieldSize - Math.min(bytes.length, fieldSize);
- System.arraycopy(bytes, srcStart, buf, destStart, Math.min(bytes.length, fieldSize));
- }
-}
diff --git a/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/OnChainVkPreparer.java b/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/OnChainVkPreparer.java
deleted file mode 100644
index 6fbbd7b..0000000
--- a/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/OnChainVkPreparer.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.bloxbean.cardano.zeroj.onchain;
-
-import com.bloxbean.cardano.zeroj.api.VerificationMaterial;
-import com.bloxbean.cardano.zeroj.codec.SnarkjsJsonCodec;
-
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Converts {@link VerificationMaterial} into compressed byte arrays suitable for
- * Julc on-chain verifier {@code @Param byte[]} parameters.
- *
- * On-chain verifiers need VK data as compact byte arrays that can be baked into
- * Plutus scripts or passed as datums. This helper serializes the VK into the expected format.
- */
-public final class OnChainVkPreparer {
-
- private OnChainVkPreparer() {}
-
- /**
- * Prepare a Groth16 BLS12-381 VK for on-chain use.
- * Returns VK elements as a list of byte arrays: [alpha, beta, gamma, delta, IC...]
- */
- public static List prepareGroth16BLS12381Vk(VerificationMaterial material) {
- var vk = SnarkjsJsonCodec.parseVerificationKey(new String(material.vkBytes()));
-
- List elements = new ArrayList<>();
- elements.add(OnChainProofPreparer.g1ToBytes(vk.vkAlpha1()));
- elements.add(OnChainProofPreparer.g2ToBytes(vk.vkBeta2()));
- elements.add(OnChainProofPreparer.g2ToBytes(vk.vkGamma2()));
- elements.add(OnChainProofPreparer.g2ToBytes(vk.vkDelta2()));
- for (var ic : vk.ic()) {
- elements.add(OnChainProofPreparer.g1ToBytes(ic));
- }
- return elements;
- }
-
- /**
- * Compute a compact VK hash for on-chain VK commitment (hash-in-script pattern).
- * Uses SHA-256 of the canonical VK bytes.
- */
- public static byte[] computeVkHash(VerificationMaterial material) {
- try {
- return MessageDigest.getInstance("SHA-256").digest(material.vkBytes());
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("SHA-256 not available", e);
- }
- }
-
- /**
- * Estimate the on-chain size (in bytes) of the VK when serialized for a given proof system.
- */
- public static int estimateOnChainSize(VerificationMaterial material) {
- return switch (material.proofSystemId()) {
- case GROTH16 -> {
- var vk = SnarkjsJsonCodec.parseVerificationKey(new String(material.vkBytes()));
- // alpha (96) + beta (192) + gamma (192) + delta (192) + IC * 96
- yield 96 + 192 * 3 + vk.ic().size() * 96;
- }
- case PLONK -> {
- // 8 selector/permutation G1 commitments (96 each) + 1 G2 (192)
- yield 8 * 96 + 192;
- }
- default -> material.vkBytes().length;
- };
- }
-}
diff --git a/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/ReferenceScriptDeployer.java b/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/ReferenceScriptDeployer.java
deleted file mode 100644
index 6086ae5..0000000
--- a/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/ReferenceScriptDeployer.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.bloxbean.cardano.zeroj.onchain;
-
-/**
- * Helper for deploying ZK verifier scripts as CIP-0033 reference scripts on Cardano.
- *
- * Reference scripts allow separating the verifier logic (stored once on-chain in a UTxO)
- * from the proof submission transactions, significantly reducing per-transaction costs.
- *
- * Three deployment patterns are supported:
- *
- * - VK-in-script: VK baked into script at deploy time. Simple but larger script; VK not rotatable.
- * - Reference script + datum VK: Logic as CIP-0033 reference script, VK in a separate datum UTxO.
- * Smallest script, VK is rotatable by spending the datum UTxO.
- * - VK hash commitment: Script holds only the VK hash; full VK passed in redeemer.
- * Smallest script, but larger redeemers. VK rotation requires new hash commitment.
- *
- *
- * Note: Actual transaction construction uses CCL (cardano-client-lib).
- * This class provides the deployment patterns as structured types that
- * {@link com.bloxbean.cardano.zeroj.ccl.ZkTransactionHelper} can consume.
- */
-public final class ReferenceScriptDeployer {
-
- private ReferenceScriptDeployer() {}
-
- /**
- * Deployment pattern for on-chain verifier scripts.
- */
- public enum DeploymentPattern {
- /** VK baked into script at deploy time. */
- VK_IN_SCRIPT,
- /** Logic as CIP-0033, VK in datum. */
- REFERENCE_SCRIPT_DATUM_VK,
- /** Script has VK hash, full VK in redeemer. */
- VK_HASH_COMMITMENT
- }
-
- /**
- * Deployment configuration for a reference script.
- *
- * @param pattern the deployment pattern
- * @param scriptBytes compiled Plutus script bytes (CBOR)
- * @param vkBytes verification key bytes (for VK_IN_SCRIPT or REFERENCE_SCRIPT_DATUM_VK)
- * @param vkHash VK hash (for VK_HASH_COMMITMENT pattern)
- * @param estimatedScriptSize estimated on-chain size in bytes
- */
- public record DeploymentConfig(
- DeploymentPattern pattern,
- byte[] scriptBytes,
- byte[] vkBytes,
- byte[] vkHash,
- int estimatedScriptSize
- ) {
- /**
- * Create a VK-in-script deployment config.
- */
- public static DeploymentConfig vkInScript(byte[] scriptBytes, byte[] vkBytes) {
- return new DeploymentConfig(DeploymentPattern.VK_IN_SCRIPT,
- scriptBytes, vkBytes, null, scriptBytes.length);
- }
-
- /**
- * Create a reference script + datum VK deployment config.
- */
- public static DeploymentConfig referenceWithDatumVk(byte[] scriptBytes, byte[] vkBytes) {
- return new DeploymentConfig(DeploymentPattern.REFERENCE_SCRIPT_DATUM_VK,
- scriptBytes, vkBytes, null, scriptBytes.length);
- }
-
- /**
- * Create a VK hash commitment deployment config.
- */
- public static DeploymentConfig vkHashCommitment(byte[] scriptBytes, byte[] vkHash) {
- return new DeploymentConfig(DeploymentPattern.VK_HASH_COMMITMENT,
- scriptBytes, null, vkHash, scriptBytes.length);
- }
- }
-}
diff --git a/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/ScriptBudgetEstimator.java b/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/ScriptBudgetEstimator.java
deleted file mode 100644
index b32424e..0000000
--- a/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/ScriptBudgetEstimator.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package com.bloxbean.cardano.zeroj.onchain;
-
-import com.bloxbean.cardano.zeroj.api.CurveId;
-import com.bloxbean.cardano.zeroj.api.ProofSystemId;
-
-/**
- * Estimates on-chain execution budgets (CPU and memory) for ZK proof verification scripts.
- *
- * Based on Plutus V3 BLS12-381 builtin costs from CIP-0381.
- * These are estimates — actual costs should be measured via {@code BudgetBenchmarkTest}
- * in zeroj-examples using the Julc VM.
- *
- * BN254 curves have no Plutus builtins, so on-chain verification is not feasible.
- */
-public final class ScriptBudgetEstimator {
-
- private ScriptBudgetEstimator() {}
-
- /**
- * Estimated CPU cost for a single BLS12-381 miller loop (Plutus V3 builtin).
- */
- public static final long MILLER_LOOP_CPU = 402_099_373L;
-
- /**
- * Estimated CPU cost for a BLS12-381 final verify (Plutus V3 builtin).
- */
- public static final long FINAL_VERIFY_CPU = 388_656_972L;
-
- /**
- * Estimated CPU cost for a BLS12-381 G1 scalar multiplication.
- */
- public static final long G1_SCALAR_MUL_CPU = 94_607_019L;
-
- /**
- * Estimated CPU cost for a BLS12-381 G1 addition.
- */
- public static final long G1_ADD_CPU = 1_046_420L;
-
- /**
- * Estimated CPU cost for a BLS12-381 G1 multi-scalar multiplication (MSM).
- * Per-element cost approximately.
- */
- public static final long G1_MSM_PER_ELEMENT_CPU = 80_000_000L;
-
- /**
- * Estimated CPU cost for blake2b_256 hash (Plutus builtin).
- */
- public static final long BLAKE2B_256_CPU = 2_477_736L;
-
- /**
- * Estimate the total CPU budget for on-chain verification.
- *
- * @param proofSystem the proof system
- * @param curve the elliptic curve
- * @param numPublicInputs number of public inputs
- * @return estimated CPU budget, or -1 if not feasible
- */
- public static long estimateCpu(ProofSystemId proofSystem, CurveId curve, int numPublicInputs) {
- if (curve != CurveId.BLS12_381) {
- return -1; // No on-chain builtins for BN254 or Pallas
- }
-
- return switch (proofSystem) {
- case GROTH16 -> estimateGroth16Cpu(numPublicInputs);
- case PLONK -> estimatePlonkCpu(numPublicInputs);
- default -> -1;
- };
- }
-
- /**
- * Estimate the total memory budget for on-chain verification.
- *
- * @param proofSystem the proof system
- * @param curve the elliptic curve
- * @param numPublicInputs number of public inputs
- * @return estimated memory in bytes, or -1 if not feasible
- */
- public static long estimateMemory(ProofSystemId proofSystem, CurveId curve, int numPublicInputs) {
- if (curve != CurveId.BLS12_381) {
- return -1;
- }
-
- return switch (proofSystem) {
- case GROTH16 -> estimateGroth16Memory(numPublicInputs);
- case PLONK -> estimatePlonkMemory(numPublicInputs);
- default -> -1;
- };
- }
-
- /**
- * Groth16 BLS12-381: e(A,B) * e(-alpha,beta) * e(-vk_x,gamma) * e(-C,delta) == 1
- * Cost: 4 miller loops + 1 final verify + (nPublic) G1 scalar muls + (nPublic) G1 adds
- */
- private static long estimateGroth16Cpu(int numPublicInputs) {
- return 4 * MILLER_LOOP_CPU
- + FINAL_VERIFY_CPU
- + numPublicInputs * G1_SCALAR_MUL_CPU
- + numPublicInputs * G1_ADD_CPU;
- }
-
- /**
- * PlonK BLS12-381: linearized commitment via MSM + 2 pairings + Fiat-Shamir hashing
- */
- private static long estimatePlonkCpu(int numPublicInputs) {
- int numMsmElements = 8 + numPublicInputs; // selectors + permutation + public
- return 2 * MILLER_LOOP_CPU
- + FINAL_VERIFY_CPU
- + numMsmElements * G1_MSM_PER_ELEMENT_CPU
- + 10 * G1_ADD_CPU
- + 6 * BLAKE2B_256_CPU; // Fiat-Shamir rounds
- }
-
- private static long estimateGroth16Memory(int numPublicInputs) {
- // Rough estimate: ~2MB base + 100KB per public input
- return 2_000_000L + numPublicInputs * 100_000L;
- }
-
- private static long estimatePlonkMemory(int numPublicInputs) {
- // PlonK needs more memory for commitments and challenges
- return 4_000_000L + numPublicInputs * 150_000L;
- }
-}
diff --git a/incubator/zeroj-prover-rapidsnark/LICENSES/GPL-3.0.txt b/incubator/zeroj-prover-rapidsnark/LICENSES/GPL-3.0.txt
deleted file mode 100644
index f288702..0000000
--- a/incubator/zeroj-prover-rapidsnark/LICENSES/GPL-3.0.txt
+++ /dev/null
@@ -1,674 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- Copyright (C)
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-.
-
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
-.
diff --git a/incubator/zeroj-prover-rapidsnark/LICENSES/LGPL-3.0.txt b/incubator/zeroj-prover-rapidsnark/LICENSES/LGPL-3.0.txt
deleted file mode 100644
index 0a04128..0000000
--- a/incubator/zeroj-prover-rapidsnark/LICENSES/LGPL-3.0.txt
+++ /dev/null
@@ -1,165 +0,0 @@
- GNU LESSER GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-
- This version of the GNU Lesser General Public License incorporates
-the terms and conditions of version 3 of the GNU General Public
-License, supplemented by the additional permissions listed below.
-
- 0. Additional Definitions.
-
- As used herein, "this License" refers to version 3 of the GNU Lesser
-General Public License, and the "GNU GPL" refers to version 3 of the GNU
-General Public License.
-
- "The Library" refers to a covered work governed by this License,
-other than an Application or a Combined Work as defined below.
-
- An "Application" is any work that makes use of an interface provided
-by the Library, but which is not otherwise based on the Library.
-Defining a subclass of a class defined by the Library is deemed a mode
-of using an interface provided by the Library.
-
- A "Combined Work" is a work produced by combining or linking an
-Application with the Library. The particular version of the Library
-with which the Combined Work was made is also called the "Linked
-Version".
-
- The "Minimal Corresponding Source" for a Combined Work means the
-Corresponding Source for the Combined Work, excluding any source code
-for portions of the Combined Work that, considered in isolation, are
-based on the Application, and not on the Linked Version.
-
- The "Corresponding Application Code" for a Combined Work means the
-object code and/or source code for the Application, including any data
-and utility programs needed for reproducing the Combined Work from the
-Application, but excluding the System Libraries of the Combined Work.
-
- 1. Exception to Section 3 of the GNU GPL.
-
- You may convey a covered work under sections 3 and 4 of this License
-without being bound by section 3 of the GNU GPL.
-
- 2. Conveying Modified Versions.
-
- If you modify a copy of the Library, and, in your modifications, a
-facility refers to a function or data to be supplied by an Application
-that uses the facility (other than as an argument passed when the
-facility is invoked), then you may convey a copy of the modified
-version:
-
- a) under this License, provided that you make a good faith effort to
- ensure that, in the event an Application does not supply the
- function or data, the facility still operates, and performs
- whatever part of its purpose remains meaningful, or
-
- b) under the GNU GPL, with none of the additional permissions of
- this License applicable to that copy.
-
- 3. Object Code Incorporating Material from Library Header Files.
-
- The object code form of an Application may incorporate material from
-a header file that is part of the Library. You may convey such object
-code under terms of your choice, provided that, if the incorporated
-material is not limited to numerical parameters, data structure
-layouts and accessors, or small macros, inline functions and templates
-(ten or fewer lines in length), you do both of the following:
-
- a) Give prominent notice with each copy of the object code that the
- Library is used in it and that the Library and its use are
- covered by this License.
-
- b) Accompany the object code with a copy of the GNU GPL and this license
- document.
-
- 4. Combined Works.
-
- You may convey a Combined Work under terms of your choice that,
-taken together, effectively do not restrict modification of the
-portions of the Library contained in the Combined Work and reverse
-engineering for debugging such modifications, if you also do each of
-the following:
-
- a) Give prominent notice with each copy of the Combined Work that
- the Library is used in it and that the Library and its use are
- covered by this License.
-
- b) Accompany the Combined Work with a copy of the GNU GPL and this license
- document.
-
- c) For a Combined Work that displays copyright notices during
- execution, include the copyright notice for the Library among
- these notices, as well as a reference directing the user to the
- copies of the GNU GPL and this license document.
-
- d) Do one of the following:
-
- 0) Convey the Minimal Corresponding Source under the terms of this
- License, and the Corresponding Application Code in a form
- suitable for, and under terms that permit, the user to
- recombine or relink the Application with a modified version of
- the Linked Version to produce a modified Combined Work, in the
- manner specified by section 6 of the GNU GPL for conveying
- Corresponding Source.
-
- 1) Use a suitable shared library mechanism for linking with the
- Library. A suitable mechanism is one that (a) uses at run time
- a copy of the Library already present on the user's computer
- system, and (b) will operate properly with a modified version
- of the Library that is interface-compatible with the Linked
- Version.
-
- e) Provide Installation Information, but only if you would otherwise
- be required to provide such information under section 6 of the
- GNU GPL, and only to the extent that such information is
- necessary to install and execute a modified version of the
- Combined Work produced by recombining or relinking the
- Application with a modified version of the Linked Version. (If
- you use option 4d0, the Installation Information must accompany
- the Minimal Corresponding Source and Corresponding Application
- Code. If you use option 4d1, you must provide the Installation
- Information in the manner specified by section 6 of the GNU GPL
- for conveying Corresponding Source.)
-
- 5. Combined Libraries.
-
- You may place library facilities that are a work based on the
-Library side by side in a single library together with other library
-facilities that are not Applications and are not covered by this
-License, and convey such a combined library under terms of your
-choice, if you do both of the following:
-
- a) Accompany the combined library with a copy of the same work based
- on the Library, uncombined with any other library facilities,
- conveyed under the terms of this License.
-
- b) Give prominent notice with the combined library that part of it
- is a work based on the Library, and explaining where to find the
- accompanying uncombined form of the same work.
-
- 6. Revised Versions of the GNU Lesser General Public License.
-
- The Free Software Foundation may publish revised and/or new versions
-of the GNU Lesser General Public License from time to time. Such new
-versions will be similar in spirit to the present version, but may
-differ in detail to address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Library as you received it specifies that a certain numbered version
-of the GNU Lesser General Public License "or any later version"
-applies to it, you have the option of following the terms and
-conditions either of that published version or of any later version
-published by the Free Software Foundation. If the Library as you
-received it does not specify a version number of the GNU Lesser
-General Public License, you may choose any version of the GNU Lesser
-General Public License ever published by the Free Software Foundation.
-
- If the Library as you received it specifies that a proxy can decide
-whether future versions of the GNU Lesser General Public License shall
-apply, that proxy's public statement of acceptance of any version is
-permanent authorization for you to choose that version for the
-Library.
diff --git a/incubator/zeroj-prover-rapidsnark/NOTICE b/incubator/zeroj-prover-rapidsnark/NOTICE
deleted file mode 100644
index ee794c2..0000000
--- a/incubator/zeroj-prover-rapidsnark/NOTICE
+++ /dev/null
@@ -1,54 +0,0 @@
-zeroj-prover-rapidsnark
-Copyright (c) 2026 BloxBean
-
-This module is part of ZeroJ and is licensed under the MIT License.
-
-==========================================================================
-Third-Party Library: rapidsnark
-==========================================================================
-
-This module uses pre-compiled shared libraries from the rapidsnark
-project, which are licensed under the GNU Lesser General Public License
-v3.0 (LGPL-3.0). The native binaries are NOT distributed in this
-repository — they are downloaded on demand from the iden3/rapidsnark
-GitHub releases by the Gradle `downloadNativeLib` task.
-
- Project: rapidsnark
- Author: 0KIMS Association (iden3)
- License: LGPL-3.0-only
- Source: https://github.com/iden3/rapidsnark
- Releases: https://github.com/iden3/rapidsnark/releases
-
-==========================================================================
-LGPL-3.0 Compliance
-==========================================================================
-
-1. DYNAMIC LINKING
- ZeroJ loads rapidsnark as a shared library at runtime via Java's
- Foreign Function & Memory (FFM) API. This constitutes dynamic linking.
- Your application code that uses zeroj-prover-rapidsnark is NOT subject
- to the LGPL and may use any license.
-
-2. REPLACEMENT
- You may replace the rapidsnark library with your own modified build.
- Place your compiled librapidsnark.so or librapidsnark.dylib on the
- system library path, or replace the file in the
- native// resource directory.
-
-3. SOURCE CODE
- The rapidsnark source code is available at:
- https://github.com/iden3/rapidsnark
-
- To build from source:
- git clone https://github.com/iden3/rapidsnark.git
- cd rapidsnark
- git submodule init && git submodule update
- ./build_gmp.sh host
- mkdir build_prover && cd build_prover
- cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../package
- make -j$(nproc) && make install
-
-4. LICENSE TEXTS
- Full copies of the LGPL-3.0 and GPL-3.0 are included in:
- LICENSES/LGPL-3.0.txt
- LICENSES/GPL-3.0.txt
diff --git a/incubator/zeroj-prover-rapidsnark/README.md b/incubator/zeroj-prover-rapidsnark/README.md
deleted file mode 100644
index 3210b76..0000000
--- a/incubator/zeroj-prover-rapidsnark/README.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# zeroj-prover-rapidsnark
-
-Native in-process Groth16/BN254 proving via [rapidsnark](https://github.com/iden3/rapidsnark) FFM bindings.
-
-This module provides high-performance in-process proof generation for Groth16/BN254 circuits using the rapidsnark native library, accessed through Java's Foreign Function & Memory (FFM) API. It avoids the overhead of an external sidecar process.
-
-## Features
-
-- In-process proof generation (no HTTP overhead)
-- BN254 curve support (Groth16)
-- Implements `ProverService` interface (drop-in replacement for sidecar client)
-- `AutoCloseable` for proper native resource management
-
-## Native Libraries
-
-The `librapidsnark` native binaries are **not** checked into git. They are downloaded automatically from [iden3/rapidsnark GitHub releases](https://github.com/iden3/rapidsnark/releases) when you build the module.
-
-### Automatic download (recommended)
-
-```bash
-# Downloads all platform binaries, then builds
-./gradlew :zeroj-prover-rapidsnark:build
-
-# Or download only
-./gradlew :zeroj-prover-rapidsnark:downloadNativeLib
-```
-
-The `downloadNativeLib` task downloads pre-built binaries for all supported platforms and places them under `src/main/resources/native/`. If a binary already exists locally, it is skipped.
-
-### Supported platforms
-
-| Platform | Library |
-|----------|---------|
-| linux-x86_64 | `librapidsnark.so` |
-| linux-arm64 | `librapidsnark.so` |
-| macos-x86_64 | `librapidsnark.dylib` |
-| macos-arm64 | `librapidsnark.dylib` |
-
-### Build from source
-
-To build rapidsnark from source instead of downloading pre-built binaries:
-
-```bash
-git clone https://github.com/iden3/rapidsnark.git
-cd rapidsnark
-git submodule init && git submodule update
-./build_gmp.sh host
-mkdir build_prover && cd build_prover
-cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../package
-make -j$(nproc) && make install
-```
-
-Then copy the resulting `librapidsnark.so` or `librapidsnark.dylib` to the appropriate `src/main/resources/native/-/` directory.
-
-## License
-
-This module (zeroj-prover-rapidsnark) is MIT-licensed as part of ZeroJ.
-
-The `librapidsnark` native libraries are from
-[iden3/rapidsnark](https://github.com/iden3/rapidsnark) and are licensed
-under **LGPL-3.0**. Because rapidsnark is loaded as a shared library
-(dynamic linking via FFM), your application code is NOT subject to the
-LGPL. You may replace the library with your own modified build.
-
-See [NOTICE](NOTICE) for full details and [LICENSES/](LICENSES/) for
-the LGPL-3.0 and GPL-3.0 license texts.
-
-## Key Types
-
-| Type | Description |
-|------|-------------|
-| `RapidsnarkProver` | Native prover — `proveRaw(zkey, witness)` or file-based API |
-| `RapidsnarkLibrary` | FFM bindings to `librapidsnark` |
-| `NativeLibraryLoader` | Classpath extraction / system library search |
-
-## Usage
-
-```java
-try (var prover = new RapidsnarkProver()) {
- // From byte arrays
- ProveResponse response = prover.proveRaw(zkeyBytes, witnessBytes);
-
- // From files
- ProveResponse response = prover.proveRaw(Path.of("circuit.zkey"), Path.of("witness.wtns"));
-}
-```
-
-## Limitations
-
-- **BN254 only** — for BLS12-381 proving, use `zeroj-prover-gnark`
-- Requires pre-compiled native library for your platform
-- This module is in **incubator** status and is not published to Maven Central
diff --git a/incubator/zeroj-prover-rapidsnark/build.gradle b/incubator/zeroj-prover-rapidsnark/build.gradle
deleted file mode 100644
index 8273b34..0000000
--- a/incubator/zeroj-prover-rapidsnark/build.gradle
+++ /dev/null
@@ -1,94 +0,0 @@
-plugins {
- id 'java-library'
-}
-
-description = 'ZeroJ rapidsnark prover — native in-process BN254 Groth16 proving via FFM'
-
-ext.rapidsnarkVersion = '0.0.8'
-
-// Platform definitions for native library download
-def nativePlatforms = [
- [zeroj: 'linux-x86_64', release: 'linux-x86_64', lib: 'librapidsnark.so'],
- [zeroj: 'linux-arm64', release: 'linux-arm64', lib: 'librapidsnark.so'],
- [zeroj: 'macos-x86_64', release: 'macOS-x86_64', lib: 'librapidsnark.dylib'],
- [zeroj: 'macos-arm64', release: 'macOS-arm64', lib: 'librapidsnark.dylib'],
-]
-
-tasks.register('downloadNativeLib') {
- description = 'Download rapidsnark native libraries from GitHub releases'
- group = 'native'
-
- def nativeDir = file("src/main/resources/native")
- outputs.dir(nativeDir)
-
- doLast {
- nativePlatforms.each { platform ->
- def targetDir = file("${nativeDir}/${platform.zeroj}")
- def targetFile = file("${targetDir}/${platform.lib}")
-
- if (targetFile.exists()) {
- logger.lifecycle(" SKIP ${platform.zeroj} (already exists)")
- return // skip this platform
- }
-
- def zipName = "rapidsnark-${platform.release}-v${rapidsnarkVersion}.zip"
- def url = "https://github.com/iden3/rapidsnark/releases/download/v${rapidsnarkVersion}/${zipName}"
-
- logger.lifecycle(" Downloading ${zipName}...")
- def tmpZip = file("${buildDir}/tmp/${zipName}")
- tmpZip.parentFile.mkdirs()
- new URL(url).withInputStream { is -> tmpZip.bytes = is.bytes }
-
- // Extract shared lib from zip (package/lib/librapidsnark.{so,dylib})
- targetDir.mkdirs()
- ant.unzip(src: tmpZip, dest: "${buildDir}/tmp/${platform.zeroj}") {
- patternset { include(name: "package/lib/${platform.lib}") }
- }
-
- def extracted = file("${buildDir}/tmp/${platform.zeroj}/package/lib/${platform.lib}")
- if (extracted.exists()) {
- extracted.renameTo(targetFile)
- logger.lifecycle(" OK ${platform.zeroj}/${platform.lib}")
- } else {
- logger.warn(" WARN: ${platform.lib} not found in ${zipName}")
- }
- }
- }
-}
-
-// Wire: processResources depends on downloadNativeLib
-tasks.named('processResources') {
- dependsOn 'downloadNativeLib'
-}
-
-// Bundle LGPL-3.0/GPL-3.0 license texts and NOTICE inside the published JAR
-tasks.named('jar') {
- from(projectDir) {
- include 'NOTICE'
- include 'LICENSES/**'
- into 'META-INF'
- }
-}
-
-dependencies {
- api project(':zeroj-api')
- api project(':zeroj-codec')
- api project(':zeroj-prover-sidecar')
- implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.2'
-
- testImplementation project(':zeroj-test-vectors')
- testImplementation project(':zeroj-verifier-core')
- testImplementation project(':zeroj-verifier-groth16')
- testImplementation project(':zeroj-backend-spi')
-}
-
-publishing {
- publications {
- mavenJava(MavenPublication) {
- pom {
- name = 'ZeroJ Prover Rapidsnark'
- description = 'Native in-process BN254 Groth16 proving via rapidsnark FFM bindings'
- }
- }
- }
-}
diff --git a/incubator/zeroj-prover-rapidsnark/src/main/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/NativeLibraryLoader.java b/incubator/zeroj-prover-rapidsnark/src/main/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/NativeLibraryLoader.java
deleted file mode 100644
index bb00c87..0000000
--- a/incubator/zeroj-prover-rapidsnark/src/main/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/NativeLibraryLoader.java
+++ /dev/null
@@ -1,155 +0,0 @@
-package com.bloxbean.cardano.zeroj.prover.rapidsnark;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-
-/**
- * Platform-aware loader for the rapidsnark native library.
- *
- * Extracts the correct shared library from classpath resources
- * ({@code /native/{platform}/librapidsnark.{so,dylib}}) to a temp
- * directory and returns the path for FFM loading.
- */
-public final class NativeLibraryLoader {
-
- private static volatile Path cachedLibraryPath;
-
- private NativeLibraryLoader() {}
-
- /**
- * Supported platforms for rapidsnark native library.
- */
- public enum Platform {
- LINUX_X86_64("linux-x86_64", "librapidsnark.so"),
- LINUX_ARM64("linux-arm64", "librapidsnark.so"),
- MACOS_X86_64("macos-x86_64", "librapidsnark.dylib"),
- MACOS_ARM64("macos-arm64", "librapidsnark.dylib");
-
- private final String directory;
- private final String fileName;
-
- Platform(String directory, String fileName) {
- this.directory = directory;
- this.fileName = fileName;
- }
-
- public String directory() { return directory; }
- public String fileName() { return fileName; }
-
- /**
- * Classpath resource path for this platform's library.
- */
- public String resourcePath() {
- return "/native/" + directory + "/" + fileName;
- }
- }
-
- /**
- * Detect the current platform from system properties.
- *
- * @return the detected platform
- * @throws UnsupportedOperationException if the platform is not supported
- */
- public static Platform detectPlatform() {
- String os = System.getProperty("os.name", "").toLowerCase();
- String arch = System.getProperty("os.arch", "").toLowerCase();
-
- boolean isLinux = os.contains("linux");
- boolean isMac = os.contains("mac") || os.contains("darwin");
- boolean isX86_64 = arch.equals("amd64") || arch.equals("x86_64");
- boolean isArm64 = arch.equals("aarch64") || arch.equals("arm64");
-
- if (isLinux && isX86_64) return Platform.LINUX_X86_64;
- if (isLinux && isArm64) return Platform.LINUX_ARM64;
- if (isMac && isArm64) return Platform.MACOS_ARM64;
- if (isMac && isX86_64) return Platform.MACOS_X86_64;
-
- throw new UnsupportedOperationException(
- "Unsupported platform: os=" + os + ", arch=" + arch
- + ". Supported: linux x86_64/arm64, macOS x86_64/arm64");
- }
-
- /**
- * Extract the native library for the current platform from classpath
- * to a temporary directory.
- *
- * @return path to the extracted shared library
- * @throws IOException if extraction fails
- * @throws UnsupportedOperationException if the platform is not supported
- */
- public static Path extractLibrary() throws IOException {
- return extractLibrary(detectPlatform());
- }
-
- /**
- * Extract the native library for a specific platform from classpath.
- *
- * @param platform the target platform
- * @return path to the extracted shared library
- * @throws IOException if extraction fails
- */
- public static Path extractLibrary(Platform platform) throws IOException {
- Path cached = cachedLibraryPath;
- if (cached != null && Files.exists(cached)) {
- return cached;
- }
-
- synchronized (NativeLibraryLoader.class) {
- cached = cachedLibraryPath;
- if (cached != null && Files.exists(cached)) {
- return cached;
- }
-
- String resourcePath = platform.resourcePath();
- try (InputStream in = NativeLibraryLoader.class.getResourceAsStream(resourcePath)) {
- if (in == null) {
- throw new IOException(
- "Native library not found on classpath: " + resourcePath
- + ". Ensure the rapidsnark binary for " + platform.directory()
- + " is bundled in the JAR.");
- }
-
- Path tempDir = Files.createTempDirectory("zeroj-rapidsnark-");
- Path libPath = tempDir.resolve(platform.fileName());
- Files.copy(in, libPath, StandardCopyOption.REPLACE_EXISTING);
- libPath.toFile().deleteOnExit();
- tempDir.toFile().deleteOnExit();
-
- cachedLibraryPath = libPath;
- return libPath;
- }
- }
- }
-
- /**
- * Check if the native library is available on the classpath for the
- * current platform.
- *
- * @return true if the library resource exists
- */
- public static boolean isAvailable() {
- try {
- Platform platform = detectPlatform();
- return NativeLibraryLoader.class.getResource(platform.resourcePath()) != null;
- } catch (UnsupportedOperationException e) {
- return false;
- }
- }
-
- /**
- * Load library from an explicit path (bypasses classpath extraction).
- *
- * @param libraryPath path to the shared library file
- * @return the same path, after validation
- * @throws IOException if the file does not exist
- */
- public static Path fromPath(Path libraryPath) throws IOException {
- if (!Files.exists(libraryPath)) {
- throw new IOException("Library file not found: " + libraryPath);
- }
- return libraryPath;
- }
-}
diff --git a/incubator/zeroj-prover-rapidsnark/src/main/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/RapidsnarkLibrary.java b/incubator/zeroj-prover-rapidsnark/src/main/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/RapidsnarkLibrary.java
deleted file mode 100644
index c5fd73a..0000000
--- a/incubator/zeroj-prover-rapidsnark/src/main/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/RapidsnarkLibrary.java
+++ /dev/null
@@ -1,268 +0,0 @@
-package com.bloxbean.cardano.zeroj.prover.rapidsnark;
-
-import com.bloxbean.cardano.zeroj.prover.sidecar.ProverException;
-
-import java.io.IOException;
-import java.lang.foreign.*;
-import java.lang.invoke.MethodHandle;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-
-/**
- * Low-level FFM (Foreign Function & Memory) bindings to the rapidsnark C library.
- *
- * Wraps the {@code groth16_prover} function from rapidsnark's {@code prover.h}.
- * All native memory is scoped to individual prove calls — no manual cleanup required.
- *
- * Error codes from rapidsnark:
- *
- * - {@code PROVER_OK (0x0)} — success
- * - {@code PROVER_ERROR (0x1)} — general error
- * - {@code PROVER_ERROR_SHORT_BUFFER (0x2)} — output buffer too small (retry with larger)
- * - {@code PROVER_INVALID_WITNESS_LENGTH (0x3)} — witness doesn't match circuit
- *
- */
-public final class RapidsnarkLibrary implements AutoCloseable {
-
- private static final int PROVER_OK = 0x0;
- private static final int PROVER_ERROR_SHORT_BUFFER = 0x2;
- private static final int PROVER_INVALID_WITNESS_LENGTH = 0x3;
-
- private static final long DEFAULT_ERROR_BUFFER_SIZE = 4096;
-
- private final Arena libraryArena;
- private final MethodHandle groth16Prover;
- private final MethodHandle groth16ProofSize;
- private final MethodHandle groth16PublicSizeForZkeyBuf;
-
- private final long proofBufferSize;
-
- /**
- * Load rapidsnark from the classpath (auto-detects platform).
- *
- * @throws IOException if library extraction fails
- * @throws UnsupportedOperationException if the platform is not supported
- */
- public RapidsnarkLibrary() throws IOException {
- this(NativeLibraryLoader.extractLibrary());
- }
-
- /**
- * Load rapidsnark from an explicit path.
- *
- * @param libraryPath path to {@code librapidsnark.so} or {@code librapidsnark.dylib}
- */
- public RapidsnarkLibrary(Path libraryPath) {
- this.libraryArena = Arena.ofShared();
-
- SymbolLookup lookup = SymbolLookup.libraryLookup(libraryPath, libraryArena);
- Linker linker = Linker.nativeLinker();
-
- // groth16_prover(zkey_buf, zkey_size, wtns_buf, wtns_size,
- // proof_buf, proof_size, public_buf, public_size,
- // error_msg, error_msg_maxsize) -> int
- this.groth16Prover = linker.downcallHandle(
- lookup.find("groth16_prover").orElseThrow(
- () -> new IllegalStateException("Symbol 'groth16_prover' not found in " + libraryPath)),
- FunctionDescriptor.of(
- ValueLayout.JAVA_INT, // return
- ValueLayout.ADDRESS, // zkey_buffer
- ValueLayout.JAVA_LONG, // zkey_size
- ValueLayout.ADDRESS, // wtns_buffer
- ValueLayout.JAVA_LONG, // wtns_size
- ValueLayout.ADDRESS, // proof_buffer
- ValueLayout.ADDRESS, // proof_size
- ValueLayout.ADDRESS, // public_buffer
- ValueLayout.ADDRESS, // public_size
- ValueLayout.ADDRESS, // error_msg
- ValueLayout.JAVA_LONG // error_msg_maxsize
- ));
-
- // groth16_proof_size(proof_size*) -> void
- this.groth16ProofSize = linker.downcallHandle(
- lookup.find("groth16_proof_size").orElseThrow(
- () -> new IllegalStateException("Symbol 'groth16_proof_size' not found")),
- FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
-
- // groth16_public_size_for_zkey_buf(zkey_buf, zkey_size, public_size*, error_msg, error_msg_maxsize) -> int
- this.groth16PublicSizeForZkeyBuf = linker.downcallHandle(
- lookup.find("groth16_public_size_for_zkey_buf").orElseThrow(
- () -> new IllegalStateException("Symbol 'groth16_public_size_for_zkey_buf' not found")),
- FunctionDescriptor.of(
- ValueLayout.JAVA_INT,
- ValueLayout.ADDRESS,
- ValueLayout.JAVA_LONG,
- ValueLayout.ADDRESS,
- ValueLayout.ADDRESS,
- ValueLayout.JAVA_LONG
- ));
-
- // Query the fixed proof buffer size
- this.proofBufferSize = queryProofSize();
- }
-
- /**
- * Result of a Groth16 proof generation.
- *
- * @param proofJson snarkjs-compatible proof JSON
- * @param publicSignalsJson snarkjs-compatible public signals JSON array
- */
- public record ProveResult(String proofJson, String publicSignalsJson) {}
-
- /**
- * Generate a Groth16 proof using the rapidsnark native library.
- *
- * @param zkey circuit proving key (.zkey file contents)
- * @param wtns witness data (.wtns file contents)
- * @return proof result containing proof JSON and public signals JSON
- * @throws ProverException if proving fails
- * @throws IllegalArgumentException if zkey or wtns is null or empty
- */
- public ProveResult groth16Prove(byte[] zkey, byte[] wtns) {
- if (zkey == null || zkey.length == 0) {
- throw new IllegalArgumentException("zkey must not be null or empty");
- }
- if (wtns == null || wtns.length == 0) {
- throw new IllegalArgumentException("wtns must not be null or empty");
- }
-
- long publicBufSize = queryPublicSize(zkey);
-
- // Attempt proving, retry once if buffer too small
- for (int attempt = 0; attempt < 2; attempt++) {
- try {
- return doProve(zkey, wtns, proofBufferSize, publicBufSize);
- } catch (ShortBufferException e) {
- publicBufSize = e.requiredPublicSize > 0 ? e.requiredPublicSize : publicBufSize * 2;
- }
- }
-
- throw new ProverException(ProverException.ErrorCode.PROVING_FAILED,
- "Buffer too small after retry");
- }
-
- private ProveResult doProve(byte[] zkey, byte[] wtns, long proofBufSize, long publicBufSize) {
- try (var arena = Arena.ofConfined()) {
- // Copy zkey and wtns into native memory
- MemorySegment zkeySegment = arena.allocate(zkey.length);
- MemorySegment.copy(zkey, 0, zkeySegment, ValueLayout.JAVA_BYTE, 0, zkey.length);
-
- MemorySegment wtnsSegment = arena.allocate(wtns.length);
- MemorySegment.copy(wtns, 0, wtnsSegment, ValueLayout.JAVA_BYTE, 0, wtns.length);
-
- // Allocate output buffers
- MemorySegment proofBuffer = arena.allocate(proofBufSize);
- MemorySegment proofSizePtr = arena.allocate(ValueLayout.JAVA_LONG);
- proofSizePtr.set(ValueLayout.JAVA_LONG, 0, proofBufSize);
-
- MemorySegment publicBuffer = arena.allocate(publicBufSize);
- MemorySegment publicSizePtr = arena.allocate(ValueLayout.JAVA_LONG);
- publicSizePtr.set(ValueLayout.JAVA_LONG, 0, publicBufSize);
-
- MemorySegment errorBuffer = arena.allocate(DEFAULT_ERROR_BUFFER_SIZE);
-
- int result = (int) groth16Prover.invokeExact(
- zkeySegment, (long) zkey.length,
- wtnsSegment, (long) wtns.length,
- proofBuffer, proofSizePtr,
- publicBuffer, publicSizePtr,
- errorBuffer, DEFAULT_ERROR_BUFFER_SIZE);
-
- if (result == PROVER_ERROR_SHORT_BUFFER) {
- long requiredProof = proofSizePtr.get(ValueLayout.JAVA_LONG, 0);
- long requiredPublic = publicSizePtr.get(ValueLayout.JAVA_LONG, 0);
- throw new ShortBufferException(requiredProof, requiredPublic);
- }
-
- if (result == PROVER_INVALID_WITNESS_LENGTH) {
- String errorMsg = errorBuffer.getString(0, StandardCharsets.UTF_8);
- throw new ProverException(ProverException.ErrorCode.INVALID_INPUT,
- "Witness length doesn't match circuit: " + errorMsg);
- }
-
- if (result != PROVER_OK) {
- String errorMsg = errorBuffer.getString(0, StandardCharsets.UTF_8);
- throw new ProverException(ProverException.ErrorCode.PROVING_FAILED,
- "rapidsnark error (code " + result + "): " + errorMsg);
- }
-
- long proofSize = proofSizePtr.get(ValueLayout.JAVA_LONG, 0);
- long publicSize = publicSizePtr.get(ValueLayout.JAVA_LONG, 0);
-
- String proofJson = new String(
- proofBuffer.asSlice(0, proofSize).toArray(ValueLayout.JAVA_BYTE),
- StandardCharsets.UTF_8);
- String publicJson = new String(
- publicBuffer.asSlice(0, publicSize).toArray(ValueLayout.JAVA_BYTE),
- StandardCharsets.UTF_8);
-
- return new ProveResult(proofJson, publicJson);
- } catch (ShortBufferException e) {
- throw e;
- } catch (ProverException e) {
- throw e;
- } catch (Throwable e) {
- throw new ProverException(ProverException.ErrorCode.PROVING_FAILED,
- "FFM call to groth16_prover failed: " + e.getMessage(), e);
- }
- }
-
- /**
- * Query the fixed proof buffer size from rapidsnark.
- */
- private long queryProofSize() {
- try (var arena = Arena.ofConfined()) {
- MemorySegment sizePtr = arena.allocate(ValueLayout.JAVA_LONG);
- groth16ProofSize.invokeExact(sizePtr);
- return sizePtr.get(ValueLayout.JAVA_LONG, 0);
- } catch (Throwable e) {
- // Fallback to generous default
- return 16 * 1024;
- }
- }
-
- /**
- * Query the required public signals buffer size for a given zkey.
- */
- private long queryPublicSize(byte[] zkey) {
- try (var arena = Arena.ofConfined()) {
- MemorySegment zkeySegment = arena.allocate(zkey.length);
- MemorySegment.copy(zkey, 0, zkeySegment, ValueLayout.JAVA_BYTE, 0, zkey.length);
-
- MemorySegment sizePtr = arena.allocate(ValueLayout.JAVA_LONG);
- MemorySegment errorBuffer = arena.allocate(DEFAULT_ERROR_BUFFER_SIZE);
-
- int result = (int) groth16PublicSizeForZkeyBuf.invokeExact(
- zkeySegment, (long) zkey.length,
- sizePtr,
- errorBuffer, DEFAULT_ERROR_BUFFER_SIZE);
-
- if (result == PROVER_OK) {
- return sizePtr.get(ValueLayout.JAVA_LONG, 0);
- }
- } catch (Throwable e) {
- // fall through to default
- }
- // Fallback to generous default
- return 64 * 1024;
- }
-
- @Override
- public void close() {
- libraryArena.close();
- }
-
- /**
- * Internal exception for short buffer retry logic.
- */
- private static final class ShortBufferException extends RuntimeException {
- final long requiredProofSize;
- final long requiredPublicSize;
-
- ShortBufferException(long requiredProofSize, long requiredPublicSize) {
- super("Buffer too small");
- this.requiredProofSize = requiredProofSize;
- this.requiredPublicSize = requiredPublicSize;
- }
- }
-}
diff --git a/incubator/zeroj-prover-rapidsnark/src/main/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/RapidsnarkProver.java b/incubator/zeroj-prover-rapidsnark/src/main/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/RapidsnarkProver.java
deleted file mode 100644
index 9bbaf34..0000000
--- a/incubator/zeroj-prover-rapidsnark/src/main/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/RapidsnarkProver.java
+++ /dev/null
@@ -1,251 +0,0 @@
-package com.bloxbean.cardano.zeroj.prover.rapidsnark;
-
-import com.bloxbean.cardano.zeroj.api.CircuitId;
-import com.bloxbean.cardano.zeroj.api.ZkProofEnvelope;
-import com.bloxbean.cardano.zeroj.codec.SnarkjsJsonCodec;
-import com.bloxbean.cardano.zeroj.prover.sidecar.ProveRequest;
-import com.bloxbean.cardano.zeroj.prover.sidecar.ProveResponse;
-import com.bloxbean.cardano.zeroj.prover.sidecar.ProverException;
-import com.bloxbean.cardano.zeroj.prover.sidecar.ProverService;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import java.io.IOException;
-import java.math.BigInteger;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Native in-process Groth16 prover for BN254 circuits using rapidsnark via FFM.
- *
- * This is a drop-in replacement for {@link com.bloxbean.cardano.zeroj.prover.sidecar.SidecarProverClient}
- * that runs proving directly in the JVM process — no Docker container or sidecar needed.
- *
- * Usage with raw zkey + witness:
- * {@code
- * try (var prover = new RapidsnarkProver()) {
- * byte[] zkey = Files.readAllBytes(Path.of("circuit.zkey"));
- * byte[] wtns = Files.readAllBytes(Path.of("witness.wtns"));
- * ProveResponse response = prover.proveRaw(zkey, wtns);
- * }
- * }
- *
- * Usage with file paths:
- * {@code
- * try (var prover = new RapidsnarkProver()) {
- * ProveResponse response = prover.proveRaw(
- * Path.of("circuit.zkey"), Path.of("witness.wtns"));
- * }
- * }
- *
- * Note: rapidsnark only supports BN254 (alt_bn128). For BLS12-381
- * circuits (Cardano-native), use the gnark-based prover module.
- */
-public class RapidsnarkProver implements ProverService, AutoCloseable {
-
- private static final ObjectMapper MAPPER = new ObjectMapper();
-
- private final RapidsnarkLibrary library;
-
- /**
- * Create a prover that auto-loads the native library from classpath.
- *
- * @throws ProverException if the native library cannot be loaded
- */
- public RapidsnarkProver() {
- try {
- this.library = new RapidsnarkLibrary();
- } catch (IOException e) {
- throw new ProverException(ProverException.ErrorCode.CONNECTION_FAILED,
- "Failed to load rapidsnark native library: " + e.getMessage(), e);
- }
- }
-
- /**
- * Create a prover with an explicit path to the native library.
- *
- * @param libraryPath path to {@code librapidsnark.so} or {@code librapidsnark.dylib}
- */
- public RapidsnarkProver(Path libraryPath) {
- this.library = new RapidsnarkLibrary(libraryPath);
- }
-
- /**
- * Create a prover using a pre-loaded library instance.
- *
- * @param library pre-configured rapidsnark library bindings
- */
- public RapidsnarkProver(RapidsnarkLibrary library) {
- this.library = library;
- }
-
- // --- Low-level API: raw zkey + witness bytes ---
-
- /**
- * Generate a Groth16 proof from raw zkey and witness bytes.
- *
- * This is the primary API for direct rapidsnark usage. The zkey file
- * contains the circuit's proving key and constraints. The witness (.wtns)
- * contains the computed witness values.
- *
- * @param zkey circuit proving key bytes (.zkey file)
- * @param wtns witness data bytes (.wtns file)
- * @return prove response with proof JSON, public signals, protocol, and curve
- * @throws ProverException if proving fails
- */
- public ProveResponse proveRaw(byte[] zkey, byte[] wtns) {
- long startTime = System.nanoTime();
-
- RapidsnarkLibrary.ProveResult result = library.groth16Prove(zkey, wtns);
-
- long provingTimeMs = (System.nanoTime() - startTime) / 1_000_000;
-
- return toProveResponse(result, provingTimeMs);
- }
-
- /**
- * Generate a Groth16 proof from zkey and witness file paths.
- *
- * @param zkeyPath path to the .zkey file
- * @param wtnsPath path to the .wtns file
- * @return prove response with proof JSON, public signals, protocol, and curve
- * @throws ProverException if proving fails or files cannot be read
- */
- public ProveResponse proveRaw(Path zkeyPath, Path wtnsPath) {
- try {
- byte[] zkey = Files.readAllBytes(zkeyPath);
- byte[] wtns = Files.readAllBytes(wtnsPath);
- return proveRaw(zkey, wtns);
- } catch (IOException e) {
- throw new ProverException(ProverException.ErrorCode.CIRCUIT_NOT_FOUND,
- "Failed to read circuit files: " + e.getMessage(), e);
- }
- }
-
- /**
- * Generate a Groth16 proof and wrap it as a {@link ZkProofEnvelope}.
- *
- * @param zkey circuit proving key bytes (.zkey file)
- * @param wtns witness data bytes (.wtns file)
- * @param vkJson verification key JSON (for envelope construction)
- * @param circuitId circuit identifier for the envelope
- * @return a fully populated proof envelope ready for verification
- * @throws ProverException if proving fails
- */
- public ZkProofEnvelope proveRawAndWrap(byte[] zkey, byte[] wtns,
- String vkJson, String circuitId) {
- RapidsnarkLibrary.ProveResult result = library.groth16Prove(zkey, wtns);
-
- String enrichedProofJson = enrichProofJson(result.proofJson());
-
- return SnarkjsJsonCodec.toEnvelopeFromJson(
- enrichedProofJson, vkJson, result.publicSignalsJson(),
- new CircuitId(circuitId));
- }
-
- // --- ProverService interface (circuit-name based) ---
-
- /**
- * Not supported for the native prover — use {@link #proveRaw(byte[], byte[])} instead.
- *
- * The {@code ProverService.prove(ProveRequest)} interface expects a circuit name and
- * input map, then handles witness computation internally. The native rapidsnark prover
- * operates on pre-computed {@code .zkey} + {@code .wtns} files.
- *
- * For a full circuit-name-based workflow, use the sidecar for witness computation
- * and this prover for proof generation, or provide pre-computed witness files.
- *
- * @throws UnsupportedOperationException always
- */
- @Override
- public ProveResponse prove(ProveRequest request) {
- throw new UnsupportedOperationException(
- "Native rapidsnark prover requires raw .zkey + .wtns bytes. "
- + "Use proveRaw(byte[] zkey, byte[] wtns) instead. "
- + "For input-based proving, use the sidecar for witness computation.");
- }
-
- /**
- * Not supported — see {@link #prove(ProveRequest)}.
- *
- * @throws UnsupportedOperationException always
- */
- @Override
- public ZkProofEnvelope proveAndWrap(ProveRequest request, String circuitId) {
- throw new UnsupportedOperationException(
- "Native rapidsnark prover requires raw .zkey + .wtns bytes. "
- + "Use proveRawAndWrap(byte[], byte[], String, String) instead.");
- }
-
- /**
- * Native library is always "healthy" if it loaded successfully.
- *
- * @return true
- */
- @Override
- public boolean isHealthy() {
- return true;
- }
-
- /**
- * Not applicable for the native prover.
- *
- * @return empty list
- */
- @Override
- public List listCircuits() {
- return List.of();
- }
-
- @Override
- public void close() {
- library.close();
- }
-
- // --- Internal helpers ---
-
- /**
- * Enrich rapidsnark proof JSON with protocol and curve fields.
- *
- * rapidsnark outputs only {@code pi_a}, {@code pi_b}, {@code pi_c} — it omits
- * the {@code protocol} and {@code curve} metadata that snarkjs includes.
- * Since rapidsnark is BN254 Groth16 only, we inject these known values.
- */
- private String enrichProofJson(String rawProofJson) {
- try {
- JsonNode proofNode = MAPPER.readTree(rawProofJson);
- if (!proofNode.has("protocol") || !proofNode.has("curve")) {
- var objectNode = MAPPER.createObjectNode();
- proofNode.fields().forEachRemaining(e -> objectNode.set(e.getKey(), e.getValue()));
- if (!objectNode.has("protocol")) objectNode.put("protocol", "groth16");
- if (!objectNode.has("curve")) objectNode.put("curve", "bn128");
- return MAPPER.writeValueAsString(objectNode);
- }
- return rawProofJson;
- } catch (Exception e) {
- return rawProofJson;
- }
- }
-
- private ProveResponse toProveResponse(RapidsnarkLibrary.ProveResult result, long provingTimeMs) {
- try {
- // Parse public signals from JSON array: ["33", "3"]
- List publicSignals = new ArrayList<>();
- JsonNode publicNode = MAPPER.readTree(result.publicSignalsJson());
- if (publicNode.isArray()) {
- for (JsonNode element : publicNode) {
- publicSignals.add(new BigInteger(element.asText()));
- }
- }
-
- String enrichedProofJson = enrichProofJson(result.proofJson());
-
- return new ProveResponse(enrichedProofJson, publicSignals, "groth16", "bn128", provingTimeMs);
- } catch (Exception e) {
- throw new ProverException(ProverException.ErrorCode.INVALID_RESPONSE,
- "Failed to parse rapidsnark output: " + e.getMessage(), e);
- }
- }
-}
diff --git a/incubator/zeroj-prover-rapidsnark/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-rapidsnark/reflect-config.json b/incubator/zeroj-prover-rapidsnark/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-rapidsnark/reflect-config.json
deleted file mode 100644
index fe51488..0000000
--- a/incubator/zeroj-prover-rapidsnark/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-rapidsnark/reflect-config.json
+++ /dev/null
@@ -1 +0,0 @@
-[]
diff --git a/incubator/zeroj-prover-rapidsnark/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-rapidsnark/resource-config.json b/incubator/zeroj-prover-rapidsnark/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-rapidsnark/resource-config.json
deleted file mode 100644
index 17a7a85..0000000
--- a/incubator/zeroj-prover-rapidsnark/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-rapidsnark/resource-config.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{"resources":{"includes":[
- {"pattern":"native/.*"}
-]}}
diff --git a/incubator/zeroj-prover-rapidsnark/src/test/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/NativeLibraryLoaderTest.java b/incubator/zeroj-prover-rapidsnark/src/test/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/NativeLibraryLoaderTest.java
deleted file mode 100644
index 9ad7231..0000000
--- a/incubator/zeroj-prover-rapidsnark/src/test/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/NativeLibraryLoaderTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.bloxbean.cardano.zeroj.prover.rapidsnark;
-
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class NativeLibraryLoaderTest {
-
- @Test
- void detectPlatform_returnsValidPlatform() {
- // Should not throw on any supported development machine
- NativeLibraryLoader.Platform platform = NativeLibraryLoader.detectPlatform();
- assertNotNull(platform);
- assertNotNull(platform.directory());
- assertNotNull(platform.fileName());
- assertNotNull(platform.resourcePath());
-
- System.out.println("Detected platform: " + platform
- + " (dir=" + platform.directory()
- + ", file=" + platform.fileName() + ")");
- }
-
- @Test
- void resourcePath_hasCorrectFormat() {
- for (NativeLibraryLoader.Platform platform : NativeLibraryLoader.Platform.values()) {
- String path = platform.resourcePath();
- assertTrue(path.startsWith("/native/"), "Path should start with /native/: " + path);
- assertTrue(path.endsWith(".so") || path.endsWith(".dylib"),
- "Path should end with .so or .dylib: " + path);
- }
- }
-
- @Test
- void isAvailable_doesNotThrow() {
- // This tests that the method works without exceptions,
- // regardless of whether the library is actually bundled
- boolean available = NativeLibraryLoader.isAvailable();
- System.out.println("Native library available on classpath: " + available);
- }
-}
diff --git a/incubator/zeroj-prover-rapidsnark/src/test/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/RapidsnarkProverTest.java b/incubator/zeroj-prover-rapidsnark/src/test/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/RapidsnarkProverTest.java
deleted file mode 100644
index 4ab46dd..0000000
--- a/incubator/zeroj-prover-rapidsnark/src/test/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/RapidsnarkProverTest.java
+++ /dev/null
@@ -1,434 +0,0 @@
-package com.bloxbean.cardano.zeroj.prover.rapidsnark;
-
-import com.bloxbean.cardano.zeroj.api.*;
-import com.bloxbean.cardano.zeroj.codec.SnarkjsJsonCodec;
-import com.bloxbean.cardano.zeroj.codec.SnarkjsProof;
-import com.bloxbean.cardano.zeroj.prover.sidecar.ProveResponse;
-import com.bloxbean.cardano.zeroj.prover.sidecar.ProverException;
-import com.bloxbean.cardano.zeroj.verifier.groth16.bn254.Groth16BN254Verifier;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.EnabledIf;
-import org.junit.jupiter.api.io.TempDir;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Comprehensive tests for RapidsnarkProver.
- *
- * Covers: proof generation, proof structure validation, round-trip verification,
- * error handling, multiple proofs, path-based API, and cross-verification with
- * existing test vectors.
- */
-class RapidsnarkProverTest {
-
- private static final String TEST_ZKEY_RESOURCE = "/test-circuits/multiplier/multiplier.zkey";
- private static final String TEST_WTNS_RESOURCE = "/test-circuits/multiplier/multiplier_witness.wtns";
- private static final String TEST_VK_RESOURCE = "/test-circuits/multiplier/verification_key.json";
-
- private static final String CUBIC_ZKEY_RESOURCE = "/test-circuits/cubic/cubic.zkey";
- private static final String CUBIC_WTNS_RESOURCE = "/test-circuits/cubic/cubic_witness.wtns";
- private static final String CUBIC_VK_RESOURCE = "/test-circuits/cubic/verification_key.json";
-
- private static RapidsnarkProver prover;
-
- @BeforeAll
- static void setUp() {
- if (isNativeLibraryAvailable()) {
- prover = new RapidsnarkProver();
- }
- }
-
- @AfterAll
- static void tearDown() {
- if (prover != null) {
- prover.close();
- }
- }
-
- // ===== Basic proving tests =====
-
- @Test
- @EnabledIf("isNativeLibraryAndTestFixturesAvailable")
- void proveRaw_multiplierCircuit_producesValidProof() throws Exception {
- byte[] zkey = loadTestResource(TEST_ZKEY_RESOURCE);
- byte[] wtns = loadTestResource(TEST_WTNS_RESOURCE);
-
- ProveResponse response = prover.proveRaw(zkey, wtns);
-
- assertNotNull(response);
- assertNotNull(response.proofJson());
- assertFalse(response.proofJson().isBlank());
- assertFalse(response.publicSignals().isEmpty());
- assertEquals("groth16", response.protocol());
- assertEquals("bn128", response.curve());
- assertTrue(response.provingTimeMs() >= 0);
-
- System.out.println("Proof generated in " + response.provingTimeMs() + " ms");
- System.out.println("Public signals: " + response.publicSignals());
- }
-
- @Test
- @EnabledIf("isNativeLibraryAndTestFixturesAvailable")
- void proveRaw_multiplierCircuit_correctPublicSignals() throws Exception {
- byte[] zkey = loadTestResource(TEST_ZKEY_RESOURCE);
- byte[] wtns = loadTestResource(TEST_WTNS_RESOURCE);
-
- ProveResponse response = prover.proveRaw(zkey, wtns);
-
- // multiplier circuit: a=3, b=11 → output=33, a=3
- assertEquals(2, response.publicSignals().size(),
- "Expected 2 public signals (output + public input)");
- assertEquals(BigInteger.valueOf(33), response.publicSignals().get(0),
- "First public signal should be product 33");
- assertEquals(BigInteger.valueOf(3), response.publicSignals().get(1),
- "Second public signal should be public input 3");
- }
-
- // ===== Proof structure validation =====
-
- @Test
- @EnabledIf("isNativeLibraryAndTestFixturesAvailable")
- void proveRaw_proofJsonIsValidSnarkjsFormat() throws Exception {
- byte[] zkey = loadTestResource(TEST_ZKEY_RESOURCE);
- byte[] wtns = loadTestResource(TEST_WTNS_RESOURCE);
-
- ProveResponse response = prover.proveRaw(zkey, wtns);
-
- // Parse with the same codec used by verifiers — this validates
- // the proof has pi_a, pi_b, pi_c, protocol, curve fields
- SnarkjsProof parsed = SnarkjsJsonCodec.parseProof(response.proofJson());
-
- assertNotNull(parsed);
- assertEquals("groth16", parsed.protocol());
- assertEquals("bn128", parsed.curve());
- assertEquals(3, parsed.piA().size(), "pi_a should have 3 coordinates");
- assertEquals(3, parsed.piB().size(), "pi_b should have 3 coordinate pairs");
- assertEquals(3, parsed.piC().size(), "pi_c should have 3 coordinates");
-
- // G1 points: last coordinate should be 1 (projective affine)
- assertEquals(BigInteger.ONE, parsed.piA().get(2));
- assertEquals(BigInteger.ONE, parsed.piC().get(2));
- }
-
- // ===== Envelope wrapping =====
-
- @Test
- @EnabledIf("isNativeLibraryAndTestFixturesAvailable")
- void proveRawAndWrap_producesValidEnvelope() throws Exception {
- byte[] zkey = loadTestResource(TEST_ZKEY_RESOURCE);
- byte[] wtns = loadTestResource(TEST_WTNS_RESOURCE);
- String vkJson = new String(loadTestResource(TEST_VK_RESOURCE));
-
- ZkProofEnvelope envelope = prover.proveRawAndWrap(zkey, wtns, vkJson, "multiplier");
-
- assertNotNull(envelope);
- assertEquals(ProofSystemId.GROTH16, envelope.proofSystem());
- assertEquals(CurveId.BN254, envelope.curve());
- assertEquals("multiplier", envelope.circuitId().value());
- assertNotNull(envelope.proofBytes());
- assertTrue(envelope.proofBytes().length > 0);
- assertEquals(2, envelope.publicInputs().size());
- assertEquals(BigInteger.valueOf(33), envelope.publicInputs().get(0));
- assertEquals(BigInteger.valueOf(3), envelope.publicInputs().get(1));
- }
-
- // ===== Round-trip: prove → verify =====
-
- @Test
- @EnabledIf("isNativeLibraryAndTestFixturesAvailable")
- void proveAndVerify_roundTrip_proofIsValid() throws Exception {
- byte[] zkey = loadTestResource(TEST_ZKEY_RESOURCE);
- byte[] wtns = loadTestResource(TEST_WTNS_RESOURCE);
- String vkJson = new String(loadTestResource(TEST_VK_RESOURCE), StandardCharsets.UTF_8);
-
- // Prove with rapidsnark
- ZkProofEnvelope envelope = prover.proveRawAndWrap(zkey, wtns, vkJson, "multiplier");
-
- // Verify with ZeroJ BN254 verifier
- var verifier = new Groth16BN254Verifier();
- VerificationMaterial material = VerificationMaterial.of(
- vkJson.getBytes(StandardCharsets.UTF_8),
- ProofSystemId.GROTH16,
- CurveId.BN254,
- new CircuitId("multiplier"));
-
- VerificationResult result = verifier.verify(envelope, material);
-
- assertTrue(result.proofValid(), "Proof should be cryptographically valid. Reason: " + result.reasonCode());
-
- System.out.println("Round-trip: rapidsnark prove → ZeroJ verify → VALID");
- }
-
- // ===== Cross-verification with existing test vectors =====
-
- @Test
- @EnabledIf("isNativeLibraryAndTestFixturesAvailable")
- void proofVerifiesWithSameVerifierAsExistingTestVectors() throws Exception {
- byte[] zkey = loadTestResource(TEST_ZKEY_RESOURCE);
- byte[] wtns = loadTestResource(TEST_WTNS_RESOURCE);
- String vkJson = new String(loadTestResource(TEST_VK_RESOURCE), StandardCharsets.UTF_8);
-
- // Generate proof with rapidsnark
- ZkProofEnvelope envelope = prover.proveRawAndWrap(zkey, wtns, vkJson, "multiplier");
-
- // Use the same verifier class that validates existing test vectors
- // in Groth16BN254VerifierTest.verifyValidMultiplierProof()
- var verifier = new Groth16BN254Verifier();
- VerificationMaterial material = VerificationMaterial.of(
- vkJson.getBytes(StandardCharsets.UTF_8),
- ProofSystemId.GROTH16,
- CurveId.BN254,
- new CircuitId("multiplier"));
-
- VerificationResult result = verifier.verify(envelope, material);
- assertTrue(result.proofValid(),
- "Rapidsnark proof must verify with same verifier used for snarkjs test vectors");
-
- // Verify public signals match the existing test vector format
- // The existing groth16-bn254/public.json is ["33", "3"]
- assertEquals(BigInteger.valueOf(33), envelope.publicInputs().get(0));
- assertEquals(BigInteger.valueOf(3), envelope.publicInputs().get(1));
- }
-
- // ===== Multiple proofs (no state leakage) =====
-
- @Test
- @EnabledIf("isNativeLibraryAndTestFixturesAvailable")
- void proveRaw_multipleSequentialProofs_allValid() throws Exception {
- byte[] zkey = loadTestResource(TEST_ZKEY_RESOURCE);
- byte[] wtns = loadTestResource(TEST_WTNS_RESOURCE);
- String vkJson = new String(loadTestResource(TEST_VK_RESOURCE), StandardCharsets.UTF_8);
-
- var verifier = new Groth16BN254Verifier();
- VerificationMaterial material = VerificationMaterial.of(
- vkJson.getBytes(StandardCharsets.UTF_8),
- ProofSystemId.GROTH16,
- CurveId.BN254,
- new CircuitId("multiplier"));
-
- for (int i = 0; i < 5; i++) {
- ZkProofEnvelope envelope = prover.proveRawAndWrap(zkey, wtns, vkJson, "multiplier");
- VerificationResult result = verifier.verify(envelope, material);
- assertTrue(result.proofValid(), "Proof #" + (i + 1) + " should be valid");
- }
- }
-
- @Test
- @EnabledIf("isNativeLibraryAndTestFixturesAvailable")
- void proveRaw_eachProofIsDifferent() throws Exception {
- byte[] zkey = loadTestResource(TEST_ZKEY_RESOURCE);
- byte[] wtns = loadTestResource(TEST_WTNS_RESOURCE);
-
- ProveResponse proof1 = prover.proveRaw(zkey, wtns);
- ProveResponse proof2 = prover.proveRaw(zkey, wtns);
-
- // Groth16 uses randomization — same inputs should produce different proofs
- assertNotEquals(proof1.proofJson(), proof2.proofJson(),
- "Two proofs from same inputs should differ (Groth16 randomization)");
-
- // But public signals must be identical
- assertEquals(proof1.publicSignals(), proof2.publicSignals(),
- "Public signals should be identical for same inputs");
- }
-
- // ===== Multi-circuit: cubic =====
-
- @Test
- @EnabledIf("isCubicFixturesAvailable")
- void proveRaw_cubicCircuit_correctPublicSignals() throws Exception {
- byte[] zkey = loadTestResource(CUBIC_ZKEY_RESOURCE);
- byte[] wtns = loadTestResource(CUBIC_WTNS_RESOURCE);
-
- ProveResponse response = prover.proveRaw(zkey, wtns);
-
- // cubic circuit: x=3 → y=x³=27
- assertEquals(1, response.publicSignals().size(), "Cubic has 1 public signal (output y)");
- assertEquals(BigInteger.valueOf(27), response.publicSignals().get(0),
- "Public signal should be 3³ = 27");
- assertEquals("groth16", response.protocol());
- assertEquals("bn128", response.curve());
- }
-
- @Test
- @EnabledIf("isCubicFixturesAvailable")
- void proveAndVerify_cubicCircuit_roundTrip() throws Exception {
- byte[] zkey = loadTestResource(CUBIC_ZKEY_RESOURCE);
- byte[] wtns = loadTestResource(CUBIC_WTNS_RESOURCE);
- String vkJson = new String(loadTestResource(CUBIC_VK_RESOURCE), StandardCharsets.UTF_8);
-
- ZkProofEnvelope envelope = prover.proveRawAndWrap(zkey, wtns, vkJson, "cubic");
-
- var verifier = new Groth16BN254Verifier();
- VerificationMaterial material = VerificationMaterial.of(
- vkJson.getBytes(StandardCharsets.UTF_8),
- ProofSystemId.GROTH16, CurveId.BN254, new CircuitId("cubic"));
-
- VerificationResult result = verifier.verify(envelope, material);
- assertTrue(result.proofValid(), "Cubic proof should verify. Reason: " + result.reasonCode());
- }
-
- @Test
- @EnabledIf("isBothFixturesAvailable")
- void proveRaw_differentCircuits_differentProofStructure() throws Exception {
- // Prove both circuits with the same prover instance
- ProveResponse multiplier = prover.proveRaw(
- loadTestResource(TEST_ZKEY_RESOURCE), loadTestResource(TEST_WTNS_RESOURCE));
- ProveResponse cubic = prover.proveRaw(
- loadTestResource(CUBIC_ZKEY_RESOURCE), loadTestResource(CUBIC_WTNS_RESOURCE));
-
- // Different circuits → different number of public signals
- assertEquals(2, multiplier.publicSignals().size(), "Multiplier: 2 public signals");
- assertEquals(1, cubic.publicSignals().size(), "Cubic: 1 public signal");
-
- // Both are groth16/bn128
- assertEquals(multiplier.protocol(), cubic.protocol());
- assertEquals(multiplier.curve(), cubic.curve());
- }
-
- // ===== Path-based API =====
-
- @Test
- @EnabledIf("isNativeLibraryAndTestFixturesAvailable")
- void proveRaw_withFilePaths_producesValidProof(@TempDir Path tempDir) throws Exception {
- // Write test resources to temp files
- Path zkeyFile = tempDir.resolve("multiplier.zkey");
- Path wtnsFile = tempDir.resolve("multiplier.wtns");
- Files.write(zkeyFile, loadTestResource(TEST_ZKEY_RESOURCE));
- Files.write(wtnsFile, loadTestResource(TEST_WTNS_RESOURCE));
-
- ProveResponse response = prover.proveRaw(zkeyFile, wtnsFile);
-
- assertNotNull(response);
- assertEquals("groth16", response.protocol());
- assertEquals("bn128", response.curve());
- assertEquals(2, response.publicSignals().size());
- assertEquals(BigInteger.valueOf(33), response.publicSignals().get(0));
- }
-
- @Test
- @EnabledIf("isNativeLibraryAvailable")
- void proveRaw_nonexistentPath_throwsProverException() {
- assertThrows(ProverException.class, () ->
- prover.proveRaw(Path.of("/nonexistent/circuit.zkey"), Path.of("/nonexistent/witness.wtns")));
- }
-
- // ===== Error handling =====
-
- @Test
- @EnabledIf("isNativeLibraryAvailable")
- void proveRaw_nullZkey_throwsIllegalArgument() {
- assertThrows(IllegalArgumentException.class, () ->
- prover.proveRaw(null, new byte[]{1}));
- }
-
- @Test
- @EnabledIf("isNativeLibraryAvailable")
- void proveRaw_nullWtns_throwsIllegalArgument() throws Exception {
- byte[] zkey = loadTestResource(TEST_ZKEY_RESOURCE);
- assertThrows(IllegalArgumentException.class, () ->
- prover.proveRaw(zkey, null));
- }
-
- @Test
- @EnabledIf("isNativeLibraryAvailable")
- void proveRaw_emptyZkey_throwsIllegalArgument() {
- assertThrows(IllegalArgumentException.class, () ->
- prover.proveRaw(new byte[0], new byte[]{1}));
- }
-
- @Test
- @EnabledIf("isNativeLibraryAvailable")
- void proveRaw_emptyWtns_throwsIllegalArgument() throws Exception {
- byte[] zkey = loadTestResource(TEST_ZKEY_RESOURCE);
- assertThrows(IllegalArgumentException.class, () ->
- prover.proveRaw(zkey, new byte[0]));
- }
-
- @Test
- @EnabledIf("isNativeLibraryAvailable")
- void proveRaw_garbageZkey_throwsProverException() {
- byte[] garbageZkey = "not a valid zkey file".getBytes();
- byte[] garbageWtns = "not a valid wtns file".getBytes();
-
- assertThrows(ProverException.class, () ->
- prover.proveRaw(garbageZkey, garbageWtns));
- }
-
- @Test
- @EnabledIf("isNativeLibraryAndTestFixturesAvailable")
- void proveRaw_validZkeyGarbageWtns_throwsProverException() throws Exception {
- byte[] zkey = loadTestResource(TEST_ZKEY_RESOURCE);
- byte[] garbageWtns = "not a valid wtns file".getBytes();
-
- assertThrows(ProverException.class, () ->
- prover.proveRaw(zkey, garbageWtns));
- }
-
- // ===== ProverService interface tests =====
-
- @Test
- @EnabledIf("isNativeLibraryAvailable")
- void prove_throwsUnsupportedOperation() {
- assertThrows(UnsupportedOperationException.class,
- () -> prover.prove(null));
- }
-
- @Test
- @EnabledIf("isNativeLibraryAvailable")
- void proveAndWrap_throwsUnsupportedOperation() {
- assertThrows(UnsupportedOperationException.class,
- () -> prover.proveAndWrap(null, "circuit"));
- }
-
- @Test
- @EnabledIf("isNativeLibraryAvailable")
- void isHealthy_returnsTrue() {
- assertTrue(prover.isHealthy());
- }
-
- @Test
- @EnabledIf("isNativeLibraryAvailable")
- void listCircuits_returnsEmptyList() {
- assertTrue(prover.listCircuits().isEmpty());
- }
-
- // ===== Helper methods =====
-
- static boolean isNativeLibraryAvailable() {
- return NativeLibraryLoader.isAvailable();
- }
-
- static boolean isNativeLibraryAndTestFixturesAvailable() {
- return isNativeLibraryAvailable()
- && RapidsnarkProverTest.class.getResource(TEST_ZKEY_RESOURCE) != null
- && RapidsnarkProverTest.class.getResource(TEST_WTNS_RESOURCE) != null;
- }
-
- static boolean isCubicFixturesAvailable() {
- return isNativeLibraryAvailable()
- && RapidsnarkProverTest.class.getResource(CUBIC_ZKEY_RESOURCE) != null
- && RapidsnarkProverTest.class.getResource(CUBIC_WTNS_RESOURCE) != null;
- }
-
- static boolean isBothFixturesAvailable() {
- return isNativeLibraryAndTestFixturesAvailable() && isCubicFixturesAvailable();
- }
-
- private static byte[] loadTestResource(String resourcePath) throws IOException {
- try (InputStream in = RapidsnarkProverTest.class.getResourceAsStream(resourcePath)) {
- if (in == null) {
- throw new IOException("Test resource not found: " + resourcePath);
- }
- return in.readAllBytes();
- }
- }
-}
diff --git a/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/cubic/cubic.zkey b/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/cubic/cubic.zkey
deleted file mode 100644
index 3cad14d..0000000
Binary files a/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/cubic/cubic.zkey and /dev/null differ
diff --git a/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/cubic/cubic_witness.wtns b/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/cubic/cubic_witness.wtns
deleted file mode 100644
index 900e99c..0000000
Binary files a/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/cubic/cubic_witness.wtns and /dev/null differ
diff --git a/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/cubic/verification_key.json b/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/cubic/verification_key.json
deleted file mode 100644
index 614471a..0000000
--- a/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/cubic/verification_key.json
+++ /dev/null
@@ -1,94 +0,0 @@
-{
- "protocol": "groth16",
- "curve": "bn128",
- "nPublic": 1,
- "vk_alpha_1": [
- "5362640689234543659793117168399769053948575121356629477878800635119599381340",
- "4161138602699663714323119435210466648430276155239099456215621303237278438548",
- "1"
- ],
- "vk_beta_2": [
- [
- "1569004688900187042055062353743609255300320098094329828934054341092107551862",
- "10996348762775871917059078039411134482394052552727101745870045410620738931526"
- ],
- [
- "541114662429542570717872820911124566873890854514586683997771501173931808702",
- "6567221368178380318261912683086042166200757952558391541061547588832059289659"
- ],
- [
- "1",
- "0"
- ]
- ],
- "vk_gamma_2": [
- [
- "10857046999023057135944570762232829481370756359578518086990519993285655852781",
- "11559732032986387107991004021392285783925812861821192530917403151452391805634"
- ],
- [
- "8495653923123431417604973247489272438418190587263600148770280649306958101930",
- "4082367875863433681332203403145435568316851327593401208105741076214120093531"
- ],
- [
- "1",
- "0"
- ]
- ],
- "vk_delta_2": [
- [
- "17353330606836476688337810763540050610443791044325162550699449759798739991717",
- "305586219580448828232540765495590224399758724411961579145119194110672010380"
- ],
- [
- "10378270579322891333317090056686652634032383110851396803138049788496522557007",
- "21465360122325393413460705756570479584386404029171814112438677946195182658843"
- ],
- [
- "1",
- "0"
- ]
- ],
- "vk_alphabeta_12": [
- [
- [
- "14975714748542605121975888291126603702972471078222264860248743025286554822412",
- "20480769102603473724598140856766688599417994023770388849533418111361296878772"
- ],
- [
- "7666997979695538888598892606153770695870089865696743631580017300972668016586",
- "14074212616598961327815871822305021248224950930011067647915235801999224207503"
- ],
- [
- "8366634673361249441274110000323092214478390078618491587058162037225023301229",
- "5406237858044532111505069437819472894923212510823456939522410236689195819711"
- ]
- ],
- [
- [
- "1029174468025477026105264869089494030499880704322962009468509976960830863916",
- "4271848892756428739195302422780896447234879382870125819161113927238714178045"
- ],
- [
- "1562468149171691679114489116615458246234192850409898346253607150531746747594",
- "10492062166073473636756278182134815939652155448099245765686693527439885190446"
- ],
- [
- "4532422787196223804008878417304098578408648073044434266674943268676698384772",
- "9669643168590774486475379798360824254424895520626174609379848415599733642461"
- ]
- ]
- ],
- "IC": [
- [
- "2139052843349111430411278968681211239528025418427722898770530786601761977405",
- "18125732096995708995399215875062022866955063606855892581642465876394609774721",
- "1"
- ],
- [
- "6832869233770086226470775341178059820974898950775725586001662867110907310747",
- "12967053391189282495286151780529369897616174076644701540018797013032751741388",
- "1"
- ]
- ]
-}
\ No newline at end of file
diff --git a/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/multiplier/multiplier.zkey b/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/multiplier/multiplier.zkey
deleted file mode 100644
index 02ad7f1..0000000
Binary files a/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/multiplier/multiplier.zkey and /dev/null differ
diff --git a/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/multiplier/multiplier_witness.wtns b/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/multiplier/multiplier_witness.wtns
deleted file mode 100644
index 36e6f6d..0000000
Binary files a/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/multiplier/multiplier_witness.wtns and /dev/null differ
diff --git a/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/multiplier/verification_key.json b/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/multiplier/verification_key.json
deleted file mode 100644
index 64b8f69..0000000
--- a/incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/multiplier/verification_key.json
+++ /dev/null
@@ -1,99 +0,0 @@
-{
- "protocol": "groth16",
- "curve": "bn128",
- "nPublic": 2,
- "vk_alpha_1": [
- "6721189404295871100110148147953611017329464919630955606675289364483331070337",
- "4719297192636060444489863072677105395904632614435846099575462982396851777097",
- "1"
- ],
- "vk_beta_2": [
- [
- "13409609875079881554747821032520254991851347348434783837870884051177075071547",
- "14126460803898185062573696902559188877065490852415202694989363204025987255430"
- ],
- [
- "15632000568449178443716612857555157274296852706988012786525031303339441631058",
- "4692881803307731987191693922366979037394216924123017529642283326859790244340"
- ],
- [
- "1",
- "0"
- ]
- ],
- "vk_gamma_2": [
- [
- "10857046999023057135944570762232829481370756359578518086990519993285655852781",
- "11559732032986387107991004021392285783925812861821192530917403151452391805634"
- ],
- [
- "8495653923123431417604973247489272438418190587263600148770280649306958101930",
- "4082367875863433681332203403145435568316851327593401208105741076214120093531"
- ],
- [
- "1",
- "0"
- ]
- ],
- "vk_delta_2": [
- [
- "13303752662688568589858189000234662961288942370354807536891141146973420963686",
- "16010948534439959023061901411977589759720679998029320247552238566302345243656"
- ],
- [
- "934417337279454343964399052391141065748063451700164695757310068149144191186",
- "15403458615256824969318893191401438357352264761708713644166001813479040028600"
- ],
- [
- "1",
- "0"
- ]
- ],
- "vk_alphabeta_12": [
- [
- [
- "13299770736687111909584583762468506030234530139054140990003710529012642810620",
- "6858218688583266641020682300894444738094876826251930764450808411783116071269"
- ],
- [
- "18051928700453943762828579770890824115694137841868560386916077775094923151721",
- "11666346573562648726980205736146890560065345177928345573093123039393428652674"
- ],
- [
- "10474366014969583765318608570415300258644004556075897622724915215083947837353",
- "12567123622438545956054981183287496295777991910289675925611660045089691497457"
- ]
- ],
- [
- [
- "13741216585639366050372635724172887088776515513608734324167313019884468669925",
- "19165912419646532203232940701759520119504023352656725850966252868626604628388"
- ],
- [
- "7222512809537379747732225679111127026086200167177796962579829881645127079760",
- "14689337700441285797372044468981090004808982496661808306771925263687262529164"
- ],
- [
- "11392372831996873484342445487103893435108055870384953116779289403169663360011",
- "13116349325706754208583384522263365470254531528210925715358225633183303210257"
- ]
- ]
- ],
- "IC": [
- [
- "14266293952376672001269048911780335200579152217059306228691857770826313594674",
- "12519043243046927717559821015927991062408438820556062255595384882323359967057",
- "1"
- ],
- [
- "332835585897147738005220243413397819854945459932092827903942402410001432675",
- "12590255825637050879833920446237430761576609815462265662295959481221303485814",
- "1"
- ],
- [
- "2482273681997633153737762093506389986399099324335568141577856982839574683047",
- "3701930848745361674388408039316904960863294043822169720647492412815656623526",
- "1"
- ]
- ]
-}
\ No newline at end of file
diff --git a/incubator/zeroj-prover-sidecar/README.md b/incubator/zeroj-prover-sidecar/README.md
deleted file mode 100644
index 7a4fb88..0000000
--- a/incubator/zeroj-prover-sidecar/README.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# zeroj-prover-sidecar
-
-Java client SDK for external ZK proof generation services.
-
-This module provides an HTTP client that connects to an external snarkjs-based prover sidecar service. The sidecar model keeps proving (computationally expensive) separate from verification (lightweight), following ZeroJ's verifier-first architecture.
-
-## Key Types
-
-| Type | Description |
-|------|-------------|
-| `SidecarProverClient` | HTTP client with health checks, proof generation, and retry support |
-| `ProverService` | Interface contract for proof generation (implemented by all prover modules) |
-| `ProveRequest` | Immutable request — circuit name + input map |
-| `ProveResponse` | Immutable response — proof JSON + public signals |
-| `ProverConfig` | Endpoint URI, timeouts, retry policy |
-| `ProverException` | Typed error codes: `CONNECTION_FAILED`, `INVALID_RESPONSE`, `PROOF_GENERATION_FAILED`, `TIMEOUT` |
-
-## Usage
-
-```java
-// Connect to sidecar
-var config = ProverConfig.localhost(); // http://localhost:3000
-var client = new SidecarProverClient(config);
-
-// Check health
-boolean healthy = client.isHealthy();
-
-// Generate a proof
-var request = ProveRequest.of("multiplier", Map.of("a", "3", "b", "11"));
-ProveResponse response = client.prove(request);
-
-// Generate and wrap as ZkProofEnvelope (ready for verification)
-ZkProofEnvelope envelope = client.proveAndWrap(request, "multiplier");
-```
-
-## Docker Sidecar
-
-A Docker-based snarkjs proving service is included in the `docker/` directory. See [docker/README.md](docker/README.md) for setup instructions.
-
-```bash
-docker build -t zeroj-prover docker/
-docker run -p 3000:3000 -v /path/to/circuits:/circuits zeroj-prover
-```
-
-## Gradle
-
-```gradle
-dependencies {
- implementation 'com.bloxbean.cardano:zeroj-prover-sidecar'
-}
-```
diff --git a/incubator/zeroj-prover-sidecar/build.gradle b/incubator/zeroj-prover-sidecar/build.gradle
deleted file mode 100644
index beea1f8..0000000
--- a/incubator/zeroj-prover-sidecar/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-plugins {
- id 'java-library'
-}
-
-description = 'ZeroJ prover sidecar — Java client SDK for external snarkjs/gnark proving service'
-
-dependencies {
- api project(':zeroj-api')
- api project(':zeroj-codec')
- implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.2'
-
- testImplementation project(':zeroj-test-vectors')
- testImplementation project(':zeroj-verifier-core')
- testImplementation project(':zeroj-verifier-groth16')
- testImplementation project(':zeroj-backend-spi')
-}
-
-publishing {
- publications {
- mavenJava(MavenPublication) {
- pom {
- name = 'ZeroJ Prover Sidecar'
- description = 'Java client SDK for ZK proof generation via sidecar service'
- }
- }
- }
-}
diff --git a/incubator/zeroj-prover-sidecar/docker/Dockerfile b/incubator/zeroj-prover-sidecar/docker/Dockerfile
deleted file mode 100644
index 82754db..0000000
--- a/incubator/zeroj-prover-sidecar/docker/Dockerfile
+++ /dev/null
@@ -1,23 +0,0 @@
-FROM node:22-slim
-
-WORKDIR /app
-
-# Install snarkjs and express
-COPY package.json ./
-RUN npm install --production
-
-# Copy server
-COPY server.js ./
-
-# Circuits directory (mount via volume)
-RUN mkdir -p /circuits
-
-ENV PORT=3000
-ENV CIRCUITS_DIR=/circuits
-
-EXPOSE 3000
-
-HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
- CMD curl -f http://localhost:3000/health || exit 1
-
-CMD ["node", "server.js"]
diff --git a/incubator/zeroj-prover-sidecar/docker/README.md b/incubator/zeroj-prover-sidecar/docker/README.md
deleted file mode 100644
index 28e0bf6..0000000
--- a/incubator/zeroj-prover-sidecar/docker/README.md
+++ /dev/null
@@ -1,80 +0,0 @@
-# ZeroJ Prover Sidecar
-
-A Docker-based snarkjs proving service for ZeroJ.
-
-## Quick Start
-
-```bash
-# Build the image
-docker build -t zeroj-prover .
-
-# Run with circuit artifacts
-docker run -p 3000:3000 -v /path/to/circuits:/circuits zeroj-prover
-```
-
-## Circuit Directory Structure
-
-Mount a directory with circuit artifacts at `/circuits`:
-
-```
-/circuits/
- multiplier/
- multiplier.wasm # circom WASM witness calculator
- multiplier_final.zkey # Groth16 proving key
- verification_key.json # Verification key
- transfer/
- transfer.wasm
- transfer_final.zkey
- verification_key.json
-```
-
-## API
-
-### `GET /health`
-Returns `{"status": "ok", "circuits": N}`.
-
-### `GET /circuits`
-Returns `["multiplier", "transfer", ...]`.
-
-### `GET /circuits/:name/vk`
-Returns the verification key JSON for a circuit.
-
-### `POST /prove`
-Generate a proof.
-
-Request:
-```json
-{
- "circuit": "multiplier",
- "input": {"a": "3", "b": "11"}
-}
-```
-
-Response:
-```json
-{
- "proof": { "pi_a": [...], "pi_b": [...], "pi_c": [...], "protocol": "groth16", "curve": "bn128" },
- "publicSignals": ["33", "3"],
- "protocol": "groth16",
- "curve": "bn128",
- "provingTimeMs": 150
-}
-```
-
-### `POST /reload`
-Reload circuit discovery (after mounting new volumes).
-
-## Java Client
-
-```java
-var client = new SidecarProverClient(ProverConfig.localhost());
-
-// Check health
-boolean healthy = client.isHealthy();
-
-// Generate proof
-var response = client.prove(ProveRequest.of("multiplier", Map.of("a", "3", "b", "11")));
-
-// Generate proof and wrap as ZkProofEnvelope (ready for verification)
-var envelope = client.proveAndWrap(ProveRequest.of("multiplier", Map.of("a", "3", "b", "11")), "multiplier");
-```
diff --git a/incubator/zeroj-prover-sidecar/docker/docker-compose.yml b/incubator/zeroj-prover-sidecar/docker/docker-compose.yml
deleted file mode 100644
index b50435e..0000000
--- a/incubator/zeroj-prover-sidecar/docker/docker-compose.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-version: '3.8'
-
-services:
- prover:
- build: .
- ports:
- - "3000:3000"
- volumes:
- # Mount your circuit artifacts here
- # Each circuit needs a directory with: .wasm, _final.zkey, verification_key.json
- - ./circuits:/circuits
- environment:
- - PORT=3000
- - CIRCUITS_DIR=/circuits
- healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
- interval: 10s
- timeout: 3s
- retries: 3
diff --git a/incubator/zeroj-prover-sidecar/docker/package.json b/incubator/zeroj-prover-sidecar/docker/package.json
deleted file mode 100644
index 64da36f..0000000
--- a/incubator/zeroj-prover-sidecar/docker/package.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "zeroj-prover-sidecar",
- "version": "0.1.0",
- "description": "ZeroJ snarkjs proving sidecar service",
- "main": "server.js",
- "scripts": {
- "start": "node server.js"
- },
- "dependencies": {
- "express": "^4.18.0",
- "snarkjs": "^0.7.0"
- }
-}
diff --git a/incubator/zeroj-prover-sidecar/docker/server.js b/incubator/zeroj-prover-sidecar/docker/server.js
deleted file mode 100644
index b0cc232..0000000
--- a/incubator/zeroj-prover-sidecar/docker/server.js
+++ /dev/null
@@ -1,99 +0,0 @@
-const express = require('express');
-const snarkjs = require('snarkjs');
-const fs = require('fs');
-const path = require('path');
-
-const app = express();
-app.use(express.json({ limit: '10mb' }));
-
-const CIRCUITS_DIR = process.env.CIRCUITS_DIR || '/circuits';
-const PORT = process.env.PORT || 3000;
-
-// Discover available circuits (each circuit has a directory with .wasm and .zkey files)
-function discoverCircuits() {
- if (!fs.existsSync(CIRCUITS_DIR)) return {};
- const circuits = {};
- for (const name of fs.readdirSync(CIRCUITS_DIR)) {
- const dir = path.join(CIRCUITS_DIR, name);
- if (!fs.statSync(dir).isDirectory()) continue;
-
- const wasmFile = path.join(dir, `${name}.wasm`);
- const zkeyFile = path.join(dir, `${name}_final.zkey`);
- const vkFile = path.join(dir, 'verification_key.json');
-
- if (fs.existsSync(wasmFile) && fs.existsSync(zkeyFile)) {
- circuits[name] = { wasmFile, zkeyFile, vkFile };
- console.log(`[circuit] ${name}: wasm=${wasmFile}, zkey=${zkeyFile}`);
- }
- }
- return circuits;
-}
-
-let circuits = discoverCircuits();
-
-// GET /health
-app.get('/health', (req, res) => {
- res.json({ status: 'ok', circuits: Object.keys(circuits).length });
-});
-
-// GET /circuits
-app.get('/circuits', (req, res) => {
- res.json(Object.keys(circuits));
-});
-
-// GET /circuits/:name/vk — fetch verification key
-app.get('/circuits/:name/vk', (req, res) => {
- const circuit = circuits[req.params.name];
- if (!circuit) return res.status(404).json({ error: `Circuit '${req.params.name}' not found` });
- if (!fs.existsSync(circuit.vkFile)) {
- return res.status(404).json({ error: `VK not found for circuit '${req.params.name}'` });
- }
- const vk = fs.readFileSync(circuit.vkFile, 'utf-8');
- res.type('application/json').send(vk);
-});
-
-// POST /prove — generate a proof
-app.post('/prove', async (req, res) => {
- const { circuit: circuitName, input } = req.body;
-
- if (!circuitName) return res.status(400).json({ error: 'Missing "circuit" field' });
- if (!input) return res.status(400).json({ error: 'Missing "input" field' });
-
- const circuit = circuits[circuitName];
- if (!circuit) return res.status(404).json({ error: `Circuit '${circuitName}' not found` });
-
- try {
- const startTime = Date.now();
-
- const { proof, publicSignals } = await snarkjs.groth16.fullProve(
- input,
- circuit.wasmFile,
- circuit.zkeyFile
- );
-
- const provingTimeMs = Date.now() - startTime;
-
- res.json({
- proof,
- publicSignals,
- protocol: proof.protocol || 'groth16',
- curve: proof.curve || 'bn128',
- provingTimeMs
- });
- } catch (err) {
- console.error(`[prove] Error for circuit '${circuitName}':`, err.message);
- res.status(500).json({ error: err.message });
- }
-});
-
-// Reload circuits (useful after mounting new volumes)
-app.post('/reload', (req, res) => {
- circuits = discoverCircuits();
- res.json({ circuits: Object.keys(circuits) });
-});
-
-app.listen(PORT, () => {
- console.log(`ZeroJ prover sidecar listening on port ${PORT}`);
- console.log(`Circuits directory: ${CIRCUITS_DIR}`);
- console.log(`Available circuits: ${Object.keys(circuits).join(', ') || '(none)'}`);
-});
diff --git a/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProverConfig.java b/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProverConfig.java
deleted file mode 100644
index 53da07c..0000000
--- a/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProverConfig.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.bloxbean.cardano.zeroj.prover.sidecar;
-
-import java.time.Duration;
-import java.util.Objects;
-
-/**
- * Configuration for the prover sidecar client.
- *
- * @param baseUrl sidecar HTTP base URL (e.g., "http://localhost:3000")
- * @param connectTimeout connection timeout
- * @param proveTimeout proving request timeout (proving can be slow)
- * @param maxRetries maximum retry attempts for transient failures
- * @param retryDelay initial delay between retries (doubles on each retry)
- */
-public record ProverConfig(
- String baseUrl,
- Duration connectTimeout,
- Duration proveTimeout,
- int maxRetries,
- Duration retryDelay
-) {
-
- public ProverConfig {
- Objects.requireNonNull(baseUrl, "baseUrl required");
- Objects.requireNonNull(connectTimeout, "connectTimeout required");
- Objects.requireNonNull(proveTimeout, "proveTimeout required");
- Objects.requireNonNull(retryDelay, "retryDelay required");
- if (maxRetries < 0) throw new IllegalArgumentException("maxRetries must be >= 0");
- }
-
- /**
- * Default config for local development (localhost:3000, 30s prove timeout, 2 retries).
- */
- public static ProverConfig localhost() {
- return new ProverConfig(
- "http://localhost:3000",
- Duration.ofSeconds(5),
- Duration.ofSeconds(30),
- 2,
- Duration.ofMillis(500)
- );
- }
-
- /**
- * Config builder for custom setups.
- */
- public static ProverConfig of(String baseUrl) {
- return new ProverConfig(
- baseUrl,
- Duration.ofSeconds(5),
- Duration.ofSeconds(60),
- 3,
- Duration.ofSeconds(1)
- );
- }
-}
diff --git a/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProverService.java b/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProverService.java
deleted file mode 100644
index a662c5e..0000000
--- a/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProverService.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.bloxbean.cardano.zeroj.prover.sidecar;
-
-import com.bloxbean.cardano.zeroj.api.ZkProofEnvelope;
-
-import java.util.List;
-
-/**
- * Contract for a ZK proof generation service.
- *
- * Implementations may be backed by a Docker sidecar (snarkjs), a remote
- * proving service, or a local native prover. The output is always a standard
- * {@link ZkProofEnvelope} compatible with ZeroJ verifiers.
- */
-public interface ProverService {
-
- /**
- * Generate a proof for the given request.
- *
- * @param request the proving request (circuit name + inputs)
- * @return the prove response with proof JSON and public signals
- * @throws ProverException if proving fails
- */
- ProveResponse prove(ProveRequest request);
-
- /**
- * Generate a proof and wrap it as a {@link ZkProofEnvelope}.
- *
- * @param request the proving request
- * @param circuitId the circuit identifier for the envelope
- * @return a fully populated proof envelope ready for verification
- * @throws ProverException if proving fails
- */
- ZkProofEnvelope proveAndWrap(ProveRequest request, String circuitId);
-
- /**
- * Check if the sidecar is healthy and reachable.
- *
- * @return true if the sidecar responds to health checks
- */
- boolean isHealthy();
-
- /**
- * List circuits available in the sidecar.
- *
- * @return list of circuit names
- */
- List listCircuits();
-}
diff --git a/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/SidecarProverClient.java b/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/SidecarProverClient.java
deleted file mode 100644
index 43af099..0000000
--- a/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/SidecarProverClient.java
+++ /dev/null
@@ -1,245 +0,0 @@
-package com.bloxbean.cardano.zeroj.prover.sidecar;
-
-import com.bloxbean.cardano.zeroj.api.CircuitId;
-import com.bloxbean.cardano.zeroj.api.ZkProofEnvelope;
-import com.bloxbean.cardano.zeroj.codec.SnarkjsJsonCodec;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import java.math.BigInteger;
-import java.net.ConnectException;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.http.HttpTimeoutException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-/**
- * HTTP client for the ZeroJ prover sidecar service.
- *
- * Communicates with a snarkjs-based proving server over HTTP.
- * Supports configurable timeouts and retry with exponential backoff.
- *
- * Usage:
- * {@code
- * var client = new SidecarProverClient(ProverConfig.localhost());
- * var response = client.prove(ProveRequest.of("multiplier", Map.of("a", "3", "b", "11")));
- * var envelope = client.proveAndWrap(request, "multiplier");
- * }
- */
-public class SidecarProverClient implements ProverService {
-
- private static final ObjectMapper MAPPER = new ObjectMapper();
-
- private final ProverConfig config;
- private final HttpClient httpClient;
-
- public SidecarProverClient(ProverConfig config) {
- this.config = Objects.requireNonNull(config);
- this.httpClient = HttpClient.newBuilder()
- .connectTimeout(config.connectTimeout())
- .build();
- }
-
- @Override
- public ProveResponse prove(ProveRequest request) {
- var body = Map.of(
- "circuit", request.circuitName(),
- "input", request.input()
- );
-
- String responseBody = postWithRetry("/prove", body);
-
- try {
- var root = MAPPER.readTree(responseBody);
-
- // Parse proof JSON (it's a nested object)
- String proofJson;
- if (root.has("proof") && root.get("proof").isObject()) {
- proofJson = MAPPER.writeValueAsString(root.get("proof"));
- } else if (root.has("proof") && root.get("proof").isTextual()) {
- proofJson = root.get("proof").asText();
- } else {
- throw new ProverException(ProverException.ErrorCode.INVALID_RESPONSE,
- "Response missing 'proof' field");
- }
-
- // Parse public signals
- List publicSignals = new ArrayList<>();
- var signalsNode = root.get("publicSignals");
- if (signalsNode == null) signalsNode = root.get("public");
- if (signalsNode != null && signalsNode.isArray()) {
- for (var element : signalsNode) {
- publicSignals.add(new BigInteger(element.asText()));
- }
- }
-
- String protocol = root.has("protocol") ? root.get("protocol").asText() : "groth16";
- String curve = root.has("curve") ? root.get("curve").asText() : "bn128";
- long provingTimeMs = root.has("provingTimeMs") ? root.get("provingTimeMs").asLong() : 0;
-
- return new ProveResponse(proofJson, publicSignals, protocol, curve, provingTimeMs);
- } catch (ProverException e) {
- throw e;
- } catch (Exception e) {
- throw new ProverException(ProverException.ErrorCode.INVALID_RESPONSE,
- "Failed to parse prove response: " + e.getMessage(), e);
- }
- }
-
- @Override
- public ZkProofEnvelope proveAndWrap(ProveRequest request, String circuitId) {
- var response = prove(request);
-
- // We need the VK JSON to build the envelope — fetch it from sidecar
- String vkJson = fetchVerificationKey(request.circuitName());
-
- // Build public signals JSON
- var publicSignalsJson = new StringBuilder("[");
- for (int i = 0; i < response.publicSignals().size(); i++) {
- if (i > 0) publicSignalsJson.append(",");
- publicSignalsJson.append("\"").append(response.publicSignals().get(i)).append("\"");
- }
- publicSignalsJson.append("]");
-
- return SnarkjsJsonCodec.toEnvelopeFromJson(
- response.proofJson(), vkJson, publicSignalsJson.toString(),
- new CircuitId(circuitId));
- }
-
- @Override
- public boolean isHealthy() {
- try {
- var request = HttpRequest.newBuilder()
- .uri(URI.create(config.baseUrl() + "/health"))
- .timeout(config.connectTimeout())
- .GET()
- .build();
- var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
- return response.statusCode() == 200;
- } catch (Exception e) {
- return false;
- }
- }
-
- @Override
- public List listCircuits() {
- try {
- var request = HttpRequest.newBuilder()
- .uri(URI.create(config.baseUrl() + "/circuits"))
- .timeout(config.connectTimeout())
- .GET()
- .build();
- var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
- if (response.statusCode() != 200) {
- throw new ProverException(ProverException.ErrorCode.PROVING_FAILED,
- "Failed to list circuits: HTTP " + response.statusCode());
- }
- return MAPPER.readValue(response.body(), new TypeReference>() {});
- } catch (ProverException e) {
- throw e;
- } catch (Exception e) {
- throw new ProverException(ProverException.ErrorCode.CONNECTION_FAILED,
- "Failed to list circuits: " + e.getMessage(), e);
- }
- }
-
- /**
- * Fetch the verification key for a circuit from the sidecar.
- */
- public String fetchVerificationKey(String circuitName) {
- try {
- var request = HttpRequest.newBuilder()
- .uri(URI.create(config.baseUrl() + "/circuits/" + circuitName + "/vk"))
- .timeout(config.connectTimeout())
- .GET()
- .build();
- var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
- if (response.statusCode() != 200) {
- throw new ProverException(ProverException.ErrorCode.CIRCUIT_NOT_FOUND,
- "VK not found for circuit: " + circuitName);
- }
- return response.body();
- } catch (ProverException e) {
- throw e;
- } catch (Exception e) {
- throw new ProverException(ProverException.ErrorCode.CONNECTION_FAILED,
- "Failed to fetch VK: " + e.getMessage(), e);
- }
- }
-
- /**
- * POST with retry and exponential backoff.
- */
- private String postWithRetry(String path, Object body) {
- int attempt = 0;
- long delayMs = config.retryDelay().toMillis();
- Exception lastException = null;
-
- while (attempt <= config.maxRetries()) {
- try {
- String jsonBody = MAPPER.writeValueAsString(body);
- var request = HttpRequest.newBuilder()
- .uri(URI.create(config.baseUrl() + path))
- .timeout(config.proveTimeout())
- .header("Content-Type", "application/json")
- .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
- .build();
-
- var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
-
- if (response.statusCode() == 200) {
- return response.body();
- }
-
- // Parse error response
- if (response.statusCode() == 404) {
- throw new ProverException(ProverException.ErrorCode.CIRCUIT_NOT_FOUND,
- "Circuit not found: " + response.body());
- }
- if (response.statusCode() == 400) {
- throw new ProverException(ProverException.ErrorCode.INVALID_INPUT,
- "Invalid input: " + response.body());
- }
-
- // Server error — retryable
- lastException = new ProverException(ProverException.ErrorCode.PROVING_FAILED,
- "HTTP " + response.statusCode() + ": " + response.body());
- } catch (ProverException e) {
- if (e.errorCode() == ProverException.ErrorCode.CIRCUIT_NOT_FOUND
- || e.errorCode() == ProverException.ErrorCode.INVALID_INPUT) {
- throw e; // Non-retryable
- }
- lastException = e;
- } catch (HttpTimeoutException e) {
- lastException = new ProverException(ProverException.ErrorCode.TIMEOUT,
- "Prove request timed out after " + config.proveTimeout(), e);
- } catch (ConnectException e) {
- lastException = new ProverException(ProverException.ErrorCode.CONNECTION_FAILED,
- "Cannot connect to sidecar at " + config.baseUrl(), e);
- } catch (Exception e) {
- lastException = new ProverException(ProverException.ErrorCode.CONNECTION_FAILED,
- "Request failed: " + e.getMessage(), e);
- }
-
- attempt++;
- if (attempt <= config.maxRetries()) {
- try { Thread.sleep(delayMs); } catch (InterruptedException ie) {
- Thread.currentThread().interrupt();
- throw new ProverException(ProverException.ErrorCode.RETRIES_EXHAUSTED,
- "Interrupted during retry", ie);
- }
- delayMs *= 2; // exponential backoff
- }
- }
-
- throw new ProverException(ProverException.ErrorCode.RETRIES_EXHAUSTED,
- "All " + (config.maxRetries() + 1) + " attempts failed", lastException);
- }
-}
diff --git a/incubator/zeroj-prover-sidecar/src/test/java/com/bloxbean/cardano/zeroj/prover/sidecar/SidecarProverClientTest.java b/incubator/zeroj-prover-sidecar/src/test/java/com/bloxbean/cardano/zeroj/prover/sidecar/SidecarProverClientTest.java
deleted file mode 100644
index 6cc2a0a..0000000
--- a/incubator/zeroj-prover-sidecar/src/test/java/com/bloxbean/cardano/zeroj/prover/sidecar/SidecarProverClientTest.java
+++ /dev/null
@@ -1,197 +0,0 @@
-package com.bloxbean.cardano.zeroj.prover.sidecar;
-
-import com.sun.net.httpserver.HttpServer;
-import org.junit.jupiter.api.*;
-
-import java.io.OutputStream;
-import java.math.BigInteger;
-import java.net.InetSocketAddress;
-import java.nio.charset.StandardCharsets;
-import java.time.Duration;
-import java.util.Map;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Unit tests for the SidecarProverClient using an embedded HTTP server.
- */
-class SidecarProverClientTest {
-
- private static HttpServer server;
- private static int port;
- private SidecarProverClient client;
-
- @BeforeAll
- static void startServer() throws Exception {
- server = HttpServer.create(new InetSocketAddress(0), 0);
- port = server.getAddress().getPort();
-
- // GET /health
- server.createContext("/health", exchange -> {
- var resp = """
- {"status":"ok","circuits":1}""".getBytes(StandardCharsets.UTF_8);
- exchange.getResponseHeaders().set("Content-Type", "application/json");
- exchange.sendResponseHeaders(200, resp.length);
- try (OutputStream os = exchange.getResponseBody()) { os.write(resp); }
- });
-
- // GET /circuits
- server.createContext("/circuits", exchange -> {
- if (exchange.getRequestURI().getPath().equals("/circuits")) {
- var resp = """
- ["multiplier"]""".getBytes(StandardCharsets.UTF_8);
- exchange.getResponseHeaders().set("Content-Type", "application/json");
- exchange.sendResponseHeaders(200, resp.length);
- try (OutputStream os = exchange.getResponseBody()) { os.write(resp); }
- } else if (exchange.getRequestURI().getPath().equals("/circuits/multiplier/vk")) {
- var resp = """
- {"protocol":"groth16","curve":"bn128","nPublic":2,"vk_alpha_1":["1","2","1"],"vk_beta_2":[["1","0"],["1","0"],["1","0"]],"vk_gamma_2":[["1","0"],["1","0"],["1","0"]],"vk_delta_2":[["1","0"],["1","0"],["1","0"]],"vk_alphabeta_12":[[["1","0"],["1","0"],["1","0"]],[["1","0"],["1","0"],["1","0"]]],"IC":[["1","2","1"],["1","2","1"],["1","2","1"]]}""".getBytes(StandardCharsets.UTF_8);
- exchange.getResponseHeaders().set("Content-Type", "application/json");
- exchange.sendResponseHeaders(200, resp.length);
- try (OutputStream os = exchange.getResponseBody()) { os.write(resp); }
- } else {
- exchange.sendResponseHeaders(404, 0);
- exchange.getResponseBody().close();
- }
- });
-
- // POST /prove
- server.createContext("/prove", exchange -> {
- if (!"POST".equals(exchange.getRequestMethod())) {
- exchange.sendResponseHeaders(405, 0);
- exchange.getResponseBody().close();
- return;
- }
- // Read request body
- var body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
-
- if (body.contains("\"bad_circuit\"")) {
- exchange.sendResponseHeaders(404, 0);
- exchange.getResponseBody().close();
- return;
- }
-
- var resp = """
- {
- "proof": {
- "pi_a": ["100", "200", "1"],
- "pi_b": [["10","20"],["30","40"],["1","0"]],
- "pi_c": ["300", "400", "1"],
- "protocol": "groth16",
- "curve": "bn128"
- },
- "publicSignals": ["33", "3"],
- "protocol": "groth16",
- "curve": "bn128",
- "provingTimeMs": 42
- }""".getBytes(StandardCharsets.UTF_8);
-
- exchange.getResponseHeaders().set("Content-Type", "application/json");
- exchange.sendResponseHeaders(200, resp.length);
- try (OutputStream os = exchange.getResponseBody()) { os.write(resp); }
- });
-
- server.setExecutor(null);
- server.start();
- }
-
- @AfterAll
- static void stopServer() {
- server.stop(0);
- }
-
- @BeforeEach
- void setUp() {
- var config = new ProverConfig(
- "http://localhost:" + port,
- Duration.ofSeconds(5),
- Duration.ofSeconds(10),
- 1,
- Duration.ofMillis(100));
- client = new SidecarProverClient(config);
- }
-
- // --- Health ---
-
- @Test
- void isHealthy_returnsTrue() {
- assertTrue(client.isHealthy());
- }
-
- @Test
- void isHealthy_returnsFalse_whenUnreachable() {
- var badConfig = new ProverConfig("http://localhost:1", Duration.ofMillis(500),
- Duration.ofSeconds(1), 0, Duration.ofMillis(100));
- var badClient = new SidecarProverClient(badConfig);
- assertFalse(badClient.isHealthy());
- }
-
- // --- List circuits ---
-
- @Test
- void listCircuits_returnsAvailable() {
- var circuits = client.listCircuits();
- assertEquals(1, circuits.size());
- assertEquals("multiplier", circuits.getFirst());
- }
-
- // --- Prove ---
-
- @Test
- void prove_returnsProof() {
- var response = client.prove(ProveRequest.of("multiplier", Map.of("a", "3", "b", "11")));
-
- assertEquals("groth16", response.protocol());
- assertEquals("bn128", response.curve());
- assertEquals(2, response.publicSignals().size());
- assertEquals(BigInteger.valueOf(33), response.publicSignals().get(0));
- assertEquals(BigInteger.valueOf(3), response.publicSignals().get(1));
- assertEquals(42, response.provingTimeMs());
- assertTrue(response.proofJson().contains("pi_a"));
- }
-
- @Test
- void prove_circuitNotFound_throws() {
- var ex = assertThrows(ProverException.class, () ->
- client.prove(ProveRequest.of("bad_circuit", Map.of("x", "1"))));
- assertEquals(ProverException.ErrorCode.CIRCUIT_NOT_FOUND, ex.errorCode());
- }
-
- // --- Retry ---
-
- @Test
- void connectionFailure_retriesAndFails() {
- var badConfig = new ProverConfig("http://localhost:1",
- Duration.ofMillis(200), Duration.ofMillis(500), 1, Duration.ofMillis(50));
- var badClient = new SidecarProverClient(badConfig);
-
- var ex = assertThrows(ProverException.class, () ->
- badClient.prove(ProveRequest.of("test", Map.of("x", "1"))));
- assertEquals(ProverException.ErrorCode.RETRIES_EXHAUSTED, ex.errorCode());
- }
-
- // --- Config ---
-
- @Test
- void localhostConfig() {
- var config = ProverConfig.localhost();
- assertEquals("http://localhost:3000", config.baseUrl());
- assertEquals(Duration.ofSeconds(5), config.connectTimeout());
- assertEquals(Duration.ofSeconds(30), config.proveTimeout());
- assertEquals(2, config.maxRetries());
- }
-
- // --- Request validation ---
-
- @Test
- void proveRequest_emptyInputThrows() {
- assertThrows(IllegalArgumentException.class, () ->
- ProveRequest.of("test", Map.of()));
- }
-
- @Test
- void proveRequest_nullCircuitThrows() {
- assertThrows(NullPointerException.class, () ->
- ProveRequest.of(null, Map.of("x", "1")));
- }
-}
diff --git a/incubator/zeroj-prover-wasm/README.md b/incubator/zeroj-prover-wasm/README.md
index e046add..321f41e 100644
--- a/incubator/zeroj-prover-wasm/README.md
+++ b/incubator/zeroj-prover-wasm/README.md
@@ -18,7 +18,7 @@ var inputs = Map.of(
BigInteger[] witness = calculator.calculateWitness(inputs);
// witness = [1, 33, 3, 11] (constant, output, public input, private input)
-// Export as .wtns for rapidsnark/snarkjs
+// Export as standard .wtns for downstream provers
byte[] wtnsBytes = calculator.calculateWtns(inputs);
```
@@ -33,7 +33,7 @@ circom CLI (build-time only)
├── WasmWitnessCalculator (GraalVM, in-process)
│ │
│ ▼ witness
- │ rapidsnark FFM (in-process) → proof
+ │ gnark FFM or Java prover → proof
│
└── Pure Java verifier → verified ✓
diff --git a/incubator/zeroj-prover-wasm/src/main/java/com/bloxbean/cardano/zeroj/prover/wasm/WitnessExporter.java b/incubator/zeroj-prover-wasm/src/main/java/com/bloxbean/cardano/zeroj/prover/wasm/WitnessExporter.java
index d9a7f19..f3cc15d 100644
--- a/incubator/zeroj-prover-wasm/src/main/java/com/bloxbean/cardano/zeroj/prover/wasm/WitnessExporter.java
+++ b/incubator/zeroj-prover-wasm/src/main/java/com/bloxbean/cardano/zeroj/prover/wasm/WitnessExporter.java
@@ -9,9 +9,9 @@
import java.nio.file.Path;
/**
- * Exports witness values to standard formats (.wtns for snarkjs/rapidsnark, gnark binary).
+ * Exports witness values to standard formats (.wtns and gnark binary).
*
- * The .wtns format is the standard circom witness file format used by snarkjs and rapidsnark.
+ * The .wtns format is the standard circom witness file format used by snarkjs-compatible tooling.
*/
public final class WitnessExporter {
diff --git a/settings.gradle b/settings.gradle
index 54def92..5c3eb57 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,6 +1,6 @@
rootProject.name = 'zeroj'
-// === Core modules ===
+// === Core BOM modules ===
include 'zeroj-api'
include 'zeroj-codec'
include 'zeroj-backend-spi'
@@ -9,36 +9,30 @@ include 'zeroj-verifier-groth16'
include 'zeroj-verifier-plonk'
include 'zeroj-bls12381'
include 'zeroj-blst'
-include 'zeroj-test-vectors'
-include 'zeroj-submission'
-include 'zeroj-ingestion'
include 'zeroj-cardano'
include 'zeroj-ccl'
include 'zeroj-patterns'
include 'zeroj-crypto'
include 'zeroj-circuit-dsl'
include 'zeroj-circuit-lib'
+include 'zeroj-prover-spi'
include 'zeroj-prover-gnark'
include 'zeroj-onchain-julc'
+
+// === Support and platform modules ===
+include 'zeroj-test-vectors'
include 'zeroj-examples'
-include 'zeroj-bom'
+include 'zeroj-bom-core'
+include 'zeroj-bom-all'
+// === Mainline opt-in modules, outside zeroj-bom-core ===
include 'zeroj-bls12381-wasm'
include 'zeroj-bbs-wasm'
include 'zeroj-bbs'
// === Incubator modules (experimental, alternative backends) ===
-include 'zeroj-prover-rapidsnark'
-project(':zeroj-prover-rapidsnark').projectDir = file('incubator/zeroj-prover-rapidsnark')
-
-include 'zeroj-prover-sidecar'
-project(':zeroj-prover-sidecar').projectDir = file('incubator/zeroj-prover-sidecar')
-
include 'zeroj-prover-wasm'
project(':zeroj-prover-wasm').projectDir = file('incubator/zeroj-prover-wasm')
include 'zeroj-verifier-halo2'
project(':zeroj-verifier-halo2').projectDir = file('incubator/zeroj-verifier-halo2')
-
-include 'zeroj-onchain-experimental'
-project(':zeroj-onchain-experimental').projectDir = file('incubator/zeroj-onchain-experimental')
diff --git a/zeroj-api/README.md b/zeroj-api/README.md
index eb3c74c..6678cd0 100644
--- a/zeroj-api/README.md
+++ b/zeroj-api/README.md
@@ -11,7 +11,7 @@ This module defines the foundational data types shared across all ZeroJ modules.
| `ZkProofEnvelope` | Immutable container for a ZK proof — proof bytes, public inputs, VK reference, circuit ID, proof system, curve |
| `VerificationResult` | Separates cryptographic validity from policy validity with typed reason codes |
| `VerificationMaterial` | VK bytes + proof system/curve/circuit metadata for verification |
-| `ProofSystemId` | Enum: `GROTH16`, `PLONK`, `FFLONK`, `HALO2` |
+| `ProofSystemId` | Enum: `GROTH16`, `PLONK`, `FFLONK`, `HALO2`, `BBS` |
| `CurveId` | Enum: `BN254`, `BLS12_381`, `PALLAS` |
| `CircuitId` | Typed wrapper for circuit identifiers (non-blank string) |
| `PublicInputs` | Immutable list of `BigInteger` field elements |
diff --git a/zeroj-backend-spi/README.md b/zeroj-backend-spi/README.md
index 1e4759f..279f316 100644
--- a/zeroj-backend-spi/README.md
+++ b/zeroj-backend-spi/README.md
@@ -25,7 +25,7 @@ public class MyVerifier implements ZkVerifier {
@Override
public VerificationResult verify(ZkProofEnvelope envelope, VerificationMaterial material) {
// Your verification logic here
- return VerificationResult.valid();
+ return VerificationResult.ok();
}
}
```
diff --git a/zeroj-bls12381/README.md b/zeroj-bls12381/README.md
new file mode 100644
index 0000000..bf8b709
--- /dev/null
+++ b/zeroj-bls12381/README.md
@@ -0,0 +1,42 @@
+# zeroj-bls12381
+
+Shared pure Java BLS12-381 primitives for ZeroJ.
+
+This module contains the JVM implementation of the BLS12-381 field, curve,
+pairing, hash-to-curve, and provider abstractions used by the rest of ZeroJ. It
+is the zero-native-dependency foundation for Cardano-native proof systems and
+BBS selective disclosure.
+
+## What It Provides
+
+| Area | Key Types |
+|------|-----------|
+| Field arithmetic | `Fp`, `Fp2`, `Fp6`, `Fp12`, `MontFp381`, `MontFr381` |
+| Curve operations | `G1Point`, `G2Point`, `JacobianG1BLS381`, `JacobianG2BLS381` |
+| Pairing | `BLS12381Pairing` |
+| Encoding and generators | `Bls12381Codecs`, `Bls12381Generators` |
+| Hashing | `Bls12381Hash`, hash-to-curve internals |
+| Provider SPI | `Bls12381Provider`, `PureJavaBls12381Provider`, `Bls12381Providers` |
+
+## Why It Is Useful
+
+- Provides the Cardano-native BLS12-381 curve used by Plutus V3 builtins.
+- Keeps core proving and verification flows available without native libraries.
+- Gives BBS, PlonK, Groth16, and test code a shared primitive layer.
+- Lets optional accelerators such as `zeroj-blst` and `zeroj-bls12381-wasm`
+ plug into the same provider contract.
+
+## When To Use Directly
+
+Most applications use this module transitively through `zeroj-crypto`,
+`zeroj-verifier-groth16`, `zeroj-verifier-plonk`, or `zeroj-bbs`. Depend on it
+directly when implementing a protocol that needs BLS12-381 group operations,
+pairings, or an explicit `Bls12381Provider`.
+
+## Gradle
+
+```gradle
+dependencies {
+ implementation 'com.bloxbean.cardano:zeroj-bls12381'
+}
+```
diff --git a/zeroj-bom/README.md b/zeroj-bom-all/README.md
similarity index 65%
rename from zeroj-bom/README.md
rename to zeroj-bom-all/README.md
index 1f5153e..6a4ada9 100644
--- a/zeroj-bom/README.md
+++ b/zeroj-bom-all/README.md
@@ -1,8 +1,11 @@
-# zeroj-bom
+# zeroj-bom-all
-Bill of Materials for ZeroJ version alignment.
+Bill of Materials for all publishable ZeroJ modules, including core, opt-in
+privacy backends, WASM providers, and incubator modules.
-Import this Gradle platform to ensure all ZeroJ modules use consistent versions without specifying version numbers individually.
+Most applications should start with `zeroj-bom-core`. Use this BOM when you
+explicitly want optional modules such as BBS, WASM providers, or incubator
+backends aligned to the same version.
## Usage
@@ -10,8 +13,7 @@ Import this Gradle platform to ensure all ZeroJ modules use consistent versions
```gradle
dependencies {
- // Import BOM
- implementation platform('com.bloxbean.cardano:zeroj-bom:0.1.0')
+ implementation platform('com.bloxbean.cardano:zeroj-bom-all:0.1.0')
// Now declare modules without version
implementation 'com.bloxbean.cardano:zeroj-circuit-dsl'
@@ -33,7 +35,7 @@ dependencies {
com.bloxbean.cardano
- zeroj-bom
+ zeroj-bom-all
0.1.0
pom
import
@@ -44,7 +46,7 @@ dependencies {
## Included Modules
-All publishable ZeroJ modules are covered by this BOM:
+All publishable ZeroJ modules are covered by this BOM.
**Core:**
- `zeroj-api`
@@ -53,20 +55,23 @@ All publishable ZeroJ modules are covered by this BOM:
- `zeroj-verifier-core`
- `zeroj-verifier-groth16`
- `zeroj-verifier-plonk`
+- `zeroj-bls12381`
- `zeroj-blst`
-- `zeroj-submission`
-- `zeroj-ingestion`
+- `zeroj-crypto`
- `zeroj-cardano`
- `zeroj-ccl`
- `zeroj-patterns`
- `zeroj-circuit-dsl`
- `zeroj-circuit-lib`
+- `zeroj-prover-spi`
- `zeroj-prover-gnark`
- `zeroj-onchain-julc`
-**Incubator:**
-- `zeroj-prover-sidecar`
-- `zeroj-prover-rapidsnark`
+**Mainline opt-in:**
+- `zeroj-bbs`
+- `zeroj-bbs-wasm`
+- `zeroj-bls12381-wasm`
+
+**Incubator opt-in:**
- `zeroj-prover-wasm`
- `zeroj-verifier-halo2`
-- `zeroj-onchain-experimental`
diff --git a/zeroj-bom/build.gradle b/zeroj-bom-all/build.gradle
similarity index 83%
rename from zeroj-bom/build.gradle
rename to zeroj-bom-all/build.gradle
index e8335e0..63827aa 100644
--- a/zeroj-bom/build.gradle
+++ b/zeroj-bom-all/build.gradle
@@ -4,7 +4,7 @@ plugins {
id 'signing'
}
-description = 'ZeroJ Bill of Materials — import this platform to align all ZeroJ module versions'
+description = 'ZeroJ All Bill of Materials — core modules plus opt-in BBS, WASM, and incubator modules'
javaPlatform {
allowDependencies()
@@ -12,7 +12,6 @@ javaPlatform {
dependencies {
constraints {
- // Core modules
api project(':zeroj-api')
api project(':zeroj-codec')
api project(':zeroj-backend-spi')
@@ -20,25 +19,25 @@ dependencies {
api project(':zeroj-verifier-groth16')
api project(':zeroj-verifier-plonk')
api project(':zeroj-bls12381')
- api project(':zeroj-bls12381-wasm')
api project(':zeroj-blst')
- api project(':zeroj-submission')
- api project(':zeroj-ingestion')
api project(':zeroj-cardano')
api project(':zeroj-ccl')
api project(':zeroj-patterns')
+ api project(':zeroj-crypto')
api project(':zeroj-circuit-dsl')
api project(':zeroj-circuit-lib')
+ api project(':zeroj-prover-spi')
api project(':zeroj-prover-gnark')
api project(':zeroj-onchain-julc')
+
+ // Mainline opt-in modules
+ api project(':zeroj-bls12381-wasm')
api project(':zeroj-bbs')
api project(':zeroj-bbs-wasm')
// Incubator modules
- api project(':zeroj-prover-sidecar')
api project(':zeroj-prover-wasm')
api project(':zeroj-verifier-halo2')
- api project(':zeroj-onchain-experimental')
}
}
@@ -47,8 +46,8 @@ publishing {
bom(MavenPublication) {
from components.javaPlatform
pom {
- name = 'ZeroJ BOM'
- description = 'Bill of Materials for ZeroJ'
+ name = 'ZeroJ All BOM'
+ description = 'Bill of Materials for all publishable ZeroJ modules, including opt-in and incubator modules'
url = 'https://github.com/bloxbean/zeroj'
licenses {
license {
diff --git a/zeroj-bom-core/README.md b/zeroj-bom-core/README.md
new file mode 100644
index 0000000..dd08713
--- /dev/null
+++ b/zeroj-bom-core/README.md
@@ -0,0 +1,39 @@
+# zeroj-bom-core
+
+Core Bill of Materials for the stable ZeroJ v3 path: Java circuits/proving,
+verification, Cardano anchoring, and Julc on-chain BLS12-381 verifiers.
+
+## Gradle
+
+```gradle
+dependencies {
+ implementation platform('com.bloxbean.cardano:zeroj-bom-core:0.1.0')
+
+ implementation 'com.bloxbean.cardano:zeroj-circuit-dsl'
+ implementation 'com.bloxbean.cardano:zeroj-circuit-lib'
+ implementation 'com.bloxbean.cardano:zeroj-crypto'
+ implementation 'com.bloxbean.cardano:zeroj-verifier-core'
+ implementation 'com.bloxbean.cardano:zeroj-verifier-groth16'
+ implementation 'com.bloxbean.cardano:zeroj-onchain-julc'
+}
+```
+
+## Included Modules
+
+- `zeroj-api`
+- `zeroj-codec`
+- `zeroj-backend-spi`
+- `zeroj-verifier-core`
+- `zeroj-verifier-groth16`
+- `zeroj-verifier-plonk`
+- `zeroj-bls12381`
+- `zeroj-blst`
+- `zeroj-crypto`
+- `zeroj-circuit-dsl`
+- `zeroj-circuit-lib`
+- `zeroj-prover-spi`
+- `zeroj-prover-gnark`
+- `zeroj-onchain-julc`
+- `zeroj-cardano`
+- `zeroj-ccl`
+- `zeroj-patterns`
diff --git a/zeroj-bom-core/build.gradle b/zeroj-bom-core/build.gradle
new file mode 100644
index 0000000..ea196c7
--- /dev/null
+++ b/zeroj-bom-core/build.gradle
@@ -0,0 +1,70 @@
+plugins {
+ id 'java-platform'
+ id 'maven-publish'
+ id 'signing'
+}
+
+description = 'ZeroJ Core Bill of Materials — stable v3 privacy and Cardano verification path'
+
+javaPlatform {
+ allowDependencies()
+}
+
+dependencies {
+ constraints {
+ api project(':zeroj-api')
+ api project(':zeroj-codec')
+ api project(':zeroj-backend-spi')
+ api project(':zeroj-verifier-core')
+ api project(':zeroj-verifier-groth16')
+ api project(':zeroj-verifier-plonk')
+ api project(':zeroj-bls12381')
+ api project(':zeroj-blst')
+ api project(':zeroj-crypto')
+ api project(':zeroj-circuit-dsl')
+ api project(':zeroj-circuit-lib')
+ api project(':zeroj-prover-spi')
+ api project(':zeroj-prover-gnark')
+ api project(':zeroj-onchain-julc')
+ api project(':zeroj-cardano')
+ api project(':zeroj-ccl')
+ api project(':zeroj-patterns')
+ }
+}
+
+publishing {
+ publications {
+ bom(MavenPublication) {
+ from components.javaPlatform
+ pom {
+ name = 'ZeroJ Core BOM'
+ description = 'Core Bill of Materials for ZeroJ v3 privacy and Cardano verification modules'
+ url = 'https://github.com/bloxbean/zeroj'
+ licenses {
+ license {
+ name = 'The MIT License'
+ url = 'https://opensource.org/licenses/mit-license.php'
+ }
+ }
+ developers {
+ developer {
+ id = 'satran004'
+ name = 'Satya'
+ }
+ }
+ scm {
+ connection = 'scm:git:git://github.com/bloxbean/zeroj.git'
+ developerConnection = 'scm:git:ssh://git@github.com/bloxbean/zeroj.git'
+ url = 'https://github.com/bloxbean/zeroj'
+ }
+ }
+ }
+ }
+}
+
+ext.isReleaseVersion = !version.endsWith("SNAPSHOT")
+if (isReleaseVersion && !project.hasProperty("skipSigning")) {
+ signing {
+ sign publishing.publications
+ }
+}
diff --git a/zeroj-circuit-dsl/README.md b/zeroj-circuit-dsl/README.md
new file mode 100644
index 0000000..fef7a4c
--- /dev/null
+++ b/zeroj-circuit-dsl/README.md
@@ -0,0 +1,58 @@
+# zeroj-circuit-dsl
+
+Java API for defining ZeroJ arithmetic circuits and compiling them into backend
+constraint systems.
+
+This module lets application code define circuits in Java, calculate witnesses
+in-process, and compile the same circuit definition to R1CS, PlonK, or Halo2
+shapes. It is proof-system agnostic at the circuit-definition layer.
+
+## What It Provides
+
+| Type | Purpose |
+|------|---------|
+| `CircuitBuilder` | Fluent entry point for declaring public/secret variables and constraints |
+| `CircuitAPI` | Functional arithmetic API used by `define(...)` |
+| `SignalBuilder` / `Signal` | Object-style API for reusable circuit components |
+| `WitnessCalculator` | Evaluates declared circuits against concrete inputs |
+| `R1CSCompiler` / `R1CSSerializer` | Groth16-oriented R1CS compilation and serialization |
+| `PlonKCompiler` | PlonK gate table and permutation compilation |
+| `Halo2Compiler` | Halo2-style PLONKish circuit shape compilation |
+
+## Example
+
+```java
+var circuit = CircuitBuilder.create("multiplier")
+ .publicVar("z")
+ .secretVar("x")
+ .secretVar("y")
+ .define(api -> {
+ var product = api.mul(api.var("x"), api.var("y"));
+ api.assertEqual(product, api.var("z"));
+ });
+
+var r1cs = circuit.compileR1CS(CurveId.BLS12_381);
+var plonk = circuit.compilePlonK(CurveId.BLS12_381);
+
+var witness = circuit.calculateWitness(Map.of(
+ "z", List.of(BigInteger.valueOf(33)),
+ "x", List.of(BigInteger.valueOf(3)),
+ "y", List.of(BigInteger.valueOf(11))
+), CurveId.BLS12_381);
+```
+
+## Why It Is Useful
+
+- Keeps circuit authoring in Java instead of switching to circom or another DSL.
+- Produces reusable circuit definitions that can target multiple proving paths.
+- Gives tests and applications a common witness calculation path.
+- Supports field consistency checks so field-specific gadgets are not compiled
+ against the wrong curve.
+
+## Gradle
+
+```gradle
+dependencies {
+ implementation 'com.bloxbean.cardano:zeroj-circuit-dsl'
+}
+```
diff --git a/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/r1cs/R1CSSerializer.java b/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/r1cs/R1CSSerializer.java
index b28d40f..1395c15 100644
--- a/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/r1cs/R1CSSerializer.java
+++ b/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/r1cs/R1CSSerializer.java
@@ -8,7 +8,7 @@
/**
* Serializes an {@link R1CSConstraintSystem} to the iden3 {@code .r1cs} binary format.
*
- * This is the standard format consumed by snarkjs and rapidsnark. The format is
+ *
This is the standard format consumed by snarkjs-compatible tooling. The format is
* specified at iden3/r1csfile.
*/
public final class R1CSSerializer {
diff --git a/zeroj-circuit-lib/README.md b/zeroj-circuit-lib/README.md
new file mode 100644
index 0000000..1ee23bc
--- /dev/null
+++ b/zeroj-circuit-lib/README.md
@@ -0,0 +1,55 @@
+# zeroj-circuit-lib
+
+Reusable ZK circuit components for the ZeroJ circuit DSL.
+
+This module contains standard gadgets and helper APIs that sit on top of
+`zeroj-circuit-dsl`. Use it when building application circuits that need hashes,
+Merkle membership, range/comparison checks, binary decomposition, multiplexing,
+or Jubjub-style primitives.
+
+## What It Provides
+
+| Area | Key Types |
+|------|-----------|
+| Hashes | `Poseidon`, `PoseidonN`, `MiMC`, `MiMCSponge` |
+| Merkle proofs | `Merkle`, `SignalMerkle` |
+| Comparisons | `Comparators`, `SignalComparators` |
+| Binary gadgets | `Binary`, `SignalBinary`, `AliasCheck` |
+| Selection | `Mux` |
+| Signal helpers | `SignalPoseidon`, `SignalMiMC` |
+| Jubjub primitives | `JubjubCurve`, `PedersenCommitment`, `EdDSAJubjub`, in-circuit variants |
+| Poseidon parameters | `PoseidonParams*`, `PoseidonHash`, Grain LFSR generation helpers |
+
+## Why It Is Useful
+
+- Avoids reimplementing common ZK gadgets in every application circuit.
+- Keeps hash and Merkle circuits consistent across examples and production code.
+- Provides field-aware Poseidon parameters for BN254 and BLS12-381 use cases.
+- Lets higher-level privacy patterns build on reviewed, reusable components.
+
+## Usage Shape
+
+The library is designed to be called from a `CircuitBuilder` definition:
+
+```java
+var circuit = CircuitBuilder.create("membership")
+ .publicVar("root")
+ .secretVar("leaf")
+ .defineSignals(c -> {
+ var leaf = c.privateInput("leaf");
+ var root = c.publicInput("root");
+ var commitment = SignalPoseidon.hash(c, leaf, c.constant(BigInteger.ONE));
+ c.assertEqual(commitment, root);
+ });
+```
+
+For larger circuits, prefer the `Signal*` helper classes with `SignalBuilder`
+and reusable `CircuitSpec` components.
+
+## Gradle
+
+```gradle
+dependencies {
+ implementation 'com.bloxbean.cardano:zeroj-circuit-lib'
+}
+```
diff --git a/zeroj-circuit-lib/src/test/java/com/bloxbean/cardano/zeroj/circuit/lib/StdlibUsageExamplesTest.java b/zeroj-circuit-lib/src/test/java/com/bloxbean/cardano/zeroj/circuit/lib/StdlibUsageExamplesTest.java
index 8d09cc8..b9c17dc 100644
--- a/zeroj-circuit-lib/src/test/java/com/bloxbean/cardano/zeroj/circuit/lib/StdlibUsageExamplesTest.java
+++ b/zeroj-circuit-lib/src/test/java/com/bloxbean/cardano/zeroj/circuit/lib/StdlibUsageExamplesTest.java
@@ -210,7 +210,7 @@ void example_dualBackend_sameCircuitBothProofSystems() {
api.assertEqual(api.mul(api.var("x"), api.var("y")), api.var("z"));
});
- // Same circuit → R1CS (for Groth16 via rapidsnark or gnark)
+ // Same circuit → R1CS (for Groth16 via gnark or the Java prover)
var r1cs = circuit.compileR1CS(CurveId.BN254);
assertNotNull(r1cs);
diff --git a/zeroj-codec/README.md b/zeroj-codec/README.md
index 01f7ff5..b444ab0 100644
--- a/zeroj-codec/README.md
+++ b/zeroj-codec/README.md
@@ -14,7 +14,7 @@ This module bridges external proof tooling (snarkjs, gnark) and ZeroJ's internal
| `CborEnvelopeCodec` | Deterministic CBOR serialization of `ZkProofEnvelope` (integer-keyed map) |
| `CanonicalHash` | SHA-256 hash of canonical VK encoding for content addressing |
| `EnvelopeValidator` | Validates envelope fields before verification |
-| `GnarkPlonkCodec` | Codec for gnark PlonK proof artifacts |
+| `GnarkPlonkCodec` | Typed envelope codec for gnark binary PlonK proof artifacts; verification still uses gnark native verification until a structured adapter exists |
| `Halo2Codec` | Codec for Halo2 proof artifacts |
## Usage
diff --git a/zeroj-codec/src/main/java/com/bloxbean/cardano/zeroj/codec/GnarkPlonkCodec.java b/zeroj-codec/src/main/java/com/bloxbean/cardano/zeroj/codec/GnarkPlonkCodec.java
index d088745..05206bd 100644
--- a/zeroj-codec/src/main/java/com/bloxbean/cardano/zeroj/codec/GnarkPlonkCodec.java
+++ b/zeroj-codec/src/main/java/com/bloxbean/cardano/zeroj/codec/GnarkPlonkCodec.java
@@ -150,7 +150,11 @@ private static CurveId detectCurve(String json) {
String curve = root.get("curve").asText();
return CurveId.fromValue(curve);
}
- } catch (Exception ignored) {}
- return CurveId.BLS12_381; // default for gnark PlonK
+ throw new CodecException("gnark PlonK JSON missing required 'curve' field");
+ } catch (CodecException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new CodecException("Failed to detect gnark PlonK curve", e);
+ }
}
}
diff --git a/zeroj-codec/src/test/java/com/bloxbean/cardano/zeroj/codec/GnarkPlonkCodecTest.java b/zeroj-codec/src/test/java/com/bloxbean/cardano/zeroj/codec/GnarkPlonkCodecTest.java
new file mode 100644
index 0000000..55329ff
--- /dev/null
+++ b/zeroj-codec/src/test/java/com/bloxbean/cardano/zeroj/codec/GnarkPlonkCodecTest.java
@@ -0,0 +1,31 @@
+package com.bloxbean.cardano.zeroj.codec;
+
+import com.bloxbean.cardano.zeroj.api.CircuitId;
+import com.bloxbean.cardano.zeroj.api.CurveId;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class GnarkPlonkCodecTest {
+
+ @Test
+ void toEnvelopeFromJson_usesExplicitCurve() {
+ var proofJson = "{\"binary\":\"AA==\",\"protocol\":\"plonk\",\"curve\":\"bn128\"}";
+
+ var envelope = GnarkPlonkCodec.toEnvelopeFromJson(
+ proofJson, "{}", "[\"33\"]", new CircuitId("mul"));
+
+ assertEquals(CurveId.BN254, envelope.curve());
+ assertEquals("gnark-plonk-json", envelope.proofFormat().orElseThrow());
+ assertEquals(1, envelope.publicInputs().size());
+ }
+
+ @Test
+ void toEnvelopeFromJson_missingCurveRejected() {
+ var proofJson = "{\"binary\":\"AA==\",\"protocol\":\"plonk\"}";
+
+ assertThrows(CodecException.class, () -> GnarkPlonkCodec.toEnvelopeFromJson(
+ proofJson, "{}", "[\"33\"]", new CircuitId("mul")));
+ }
+}
diff --git a/zeroj-crypto/README.md b/zeroj-crypto/README.md
new file mode 100644
index 0000000..f7e55c2
--- /dev/null
+++ b/zeroj-crypto/README.md
@@ -0,0 +1,44 @@
+# zeroj-crypto
+
+Pure Java proving primitives and cryptographic building blocks for ZeroJ.
+
+This module is the implementation-heavy core for zero-native-dependency proving.
+It includes optimized field arithmetic, elliptic-curve operations, FFT/MSM/KZG
+utilities, Groth16 and PlonK setup/proving code, zkey/ptau importers, and the
+shared Fiat-Shamir transcript utilities used by PlonK.
+
+## What It Provides
+
+| Area | Key Types |
+|------|-----------|
+| BN254 arithmetic | `MontFp254`, `MontFp2_254`, `MontFr254`, `JacobianG1BN254`, `JacobianG2BN254` |
+| BLS12-381 proving | `Groth16ProverBLS381`, `PlonKProverBLS381`, `KZGCommitmentBLS381`, `PippengerBLS381` |
+| Groth16 | `Groth16Setup`, `Groth16Prover`, `ZkeyImporter`, `R1CSImporter` |
+| PlonK | `PlonKSetup`, `PlonKProver`, `PlonKZkeyImporter`, `PtauImporter` |
+| Polynomial tools | `FieldFFT`, `FieldFFTBLS381`, `KZGCommitment`, `Pippenger` |
+| Setup helpers | `PowersOfTau`, `PowersOfTauBLS381`, `SetupCache` |
+| Transcript | `FiatShamirTranscript`, `Keccak256` |
+
+## Why It Is Useful
+
+- Provides a portable proving path with no Go, Node.js, Rust, or native library
+ dependency.
+- Keeps the default privacy stack local-first and JVM-native.
+- Shares transcript and polynomial code across provers and verifiers, avoiding
+ verifier-to-prover dependency inversions.
+- Supports both BN254 and BLS12-381 flows used by the ZeroJ test vectors and
+ Cardano-oriented examples.
+
+## Production Notes
+
+The in-module `PowersOfTau*` and single-party setup helpers are intended for
+development, tests, and local demos. Production Groth16 or PlonK deployments
+should use ceremony outputs appropriate to the proof system and circuit.
+
+## Gradle
+
+```gradle
+dependencies {
+ implementation 'com.bloxbean.cardano:zeroj-crypto'
+}
+```
diff --git a/zeroj-crypto/build.gradle b/zeroj-crypto/build.gradle
index e21ee02..10c1d01 100644
--- a/zeroj-crypto/build.gradle
+++ b/zeroj-crypto/build.gradle
@@ -5,11 +5,9 @@ plugins {
description = 'Pure Java optimized cryptographic primitives — Montgomery field arithmetic, EC operations, FFT, MSM'
dependencies {
+ api project(':zeroj-api')
api project(':zeroj-bls12381')
- // PlonK prover uses the Fiat-Shamir transcript from the verifier module
- implementation project(':zeroj-verifier-plonk')
-
// Test-only: integration test uses the circuit DSL, codec, and verifier to close the loop
testImplementation project(':zeroj-circuit-dsl')
testImplementation project(':zeroj-verifier-groth16')
diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/R1CSImporter.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/R1CSImporter.java
index b59fa1b..d4dce71 100644
--- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/R1CSImporter.java
+++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/R1CSImporter.java
@@ -12,7 +12,7 @@
/**
* Imports R1CS constraint systems from iden3 .r1cs binary format.
*
- * This is the standard format output by circom and consumed by snarkjs/rapidsnark.
+ *
This is the standard format output by circom and consumed by snarkjs-compatible tooling.
* The constraints parsed here are in the exact form used by the trusted setup,
* ensuring compatibility with the .zkey proving key.
*/
diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProver.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProver.java
index ad4072d..f536730 100644
--- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProver.java
+++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProver.java
@@ -5,7 +5,7 @@
import com.bloxbean.cardano.zeroj.crypto.field.MontFr254;
import com.bloxbean.cardano.zeroj.crypto.kzg.KZGCommitment;
import com.bloxbean.cardano.zeroj.crypto.poly.FieldFFT;
-import com.bloxbean.cardano.zeroj.verifier.plonk.FiatShamirTranscript;
+import com.bloxbean.cardano.zeroj.crypto.transcript.FiatShamirTranscript;
import java.math.BigInteger;
import java.security.SecureRandom;
diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProverBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProverBLS381.java
index b32fc7c..de8f301 100644
--- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProverBLS381.java
+++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProverBLS381.java
@@ -5,7 +5,7 @@
import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381;
import com.bloxbean.cardano.zeroj.crypto.kzg.KZGCommitmentBLS381;
import com.bloxbean.cardano.zeroj.crypto.poly.FieldFFTBLS381;
-import com.bloxbean.cardano.zeroj.verifier.plonk.FiatShamirTranscript;
+import com.bloxbean.cardano.zeroj.crypto.transcript.FiatShamirTranscript;
import java.math.BigInteger;
import java.security.SecureRandom;
@@ -99,7 +99,7 @@ private static PlonKProofBLS381 proveInternal(PlonKProvingKeyBLS381 pk, MontFr38
var commitC = KZGCommitmentBLS381.commit(srs, cBlind).toAffine();
// Fiat-Shamir: derive beta, gamma
- var transcript = new FiatShamirTranscript(FR);
+ var transcript = new FiatShamirTranscript(FR, 32, 48);
addG1(transcript, pk.qmCommit()); addG1(transcript, pk.qlCommit());
addG1(transcript, pk.qrCommit()); addG1(transcript, pk.qoCommit());
addG1(transcript, pk.qcCommit());
diff --git a/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/FiatShamirTranscript.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/transcript/FiatShamirTranscript.java
similarity index 98%
rename from zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/FiatShamirTranscript.java
rename to zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/transcript/FiatShamirTranscript.java
index 04a8d3c..9c4fa71 100644
--- a/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/FiatShamirTranscript.java
+++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/transcript/FiatShamirTranscript.java
@@ -1,4 +1,4 @@
-package com.bloxbean.cardano.zeroj.verifier.plonk;
+package com.bloxbean.cardano.zeroj.crypto.transcript;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
@@ -133,6 +133,7 @@ public BigInteger squeezeNonZeroChallenge() {
}
/** @deprecated Use constructor + addScalar/addPolCommitment instead. */
+ @Deprecated
public void appendBytes(byte[] data) {
types.add(new int[]{SCALAR});
dataList.add(data.clone());
diff --git a/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/Keccak256.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/transcript/Keccak256.java
similarity index 97%
rename from zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/Keccak256.java
rename to zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/transcript/Keccak256.java
index ef1c1b8..109a279 100644
--- a/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/Keccak256.java
+++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/transcript/Keccak256.java
@@ -1,4 +1,4 @@
-package com.bloxbean.cardano.zeroj.verifier.plonk;
+package com.bloxbean.cardano.zeroj.crypto.transcript;
/**
* Minimal Keccak-256 implementation for Fiat-Shamir transcript compatibility with snarkjs.
@@ -6,13 +6,13 @@
* Keccak-256 differs from SHA3-256 in the padding byte (0x01 vs 0x06).
* snarkjs uses Keccak-256 (Ethereum-style), not NIST SHA3-256.
*/
-final class Keccak256 {
+public final class Keccak256 {
private Keccak256() {}
private static final int RATE = 136; // (1600 - 2*256) / 8
- static byte[] hash(byte[] input) {
+ public static byte[] hash(byte[] input) {
long[] state = new long[25];
int offset = 0;
diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKBLS381EndToEndTest.java b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKBLS381EndToEndTest.java
index f33c5c8..e04f06f 100644
--- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKBLS381EndToEndTest.java
+++ b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKBLS381EndToEndTest.java
@@ -9,7 +9,7 @@
import com.bloxbean.cardano.zeroj.bls12381.ec.*;
import com.bloxbean.cardano.zeroj.bls12381.field.*;
import com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing;
-import com.bloxbean.cardano.zeroj.verifier.plonk.FiatShamirTranscript;
+import com.bloxbean.cardano.zeroj.crypto.transcript.FiatShamirTranscript;
import org.junit.jupiter.api.Test;
import java.math.BigInteger;
@@ -106,7 +106,7 @@ void fullPipeline_multiplier_proveAndVerify() {
BigInteger eval_s1 = proof.evalS1(), eval_s2 = proof.evalS2(), eval_zw = proof.evalZw();
// Fiat-Shamir challenges
- var transcript = new FiatShamirTranscript(FR);
+ var transcript = new FiatShamirTranscript(FR, 32, 48);
addG1T(transcript, Qm); addG1T(transcript, Ql); addG1T(transcript, Qr);
addG1T(transcript, Qo); addG1T(transcript, Qc);
addG1T(transcript, S1); addG1T(transcript, S2); addG1T(transcript, S3);
diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKEndToEndTest.java b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKEndToEndTest.java
index 915320f..f85ba0b 100644
--- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKEndToEndTest.java
+++ b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKEndToEndTest.java
@@ -6,7 +6,7 @@
import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BN254;
import com.bloxbean.cardano.zeroj.crypto.field.MontFr254;
import com.bloxbean.cardano.zeroj.verifier.groth16.bn254.*;
-import com.bloxbean.cardano.zeroj.verifier.plonk.FiatShamirTranscript;
+import com.bloxbean.cardano.zeroj.crypto.transcript.FiatShamirTranscript;
import org.junit.jupiter.api.Test;
import java.io.IOException;
diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/setup/PowersOfTauTest.java b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/setup/PowersOfTauTest.java
index e6877c6..2911f1f 100644
--- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/setup/PowersOfTauTest.java
+++ b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/setup/PowersOfTauTest.java
@@ -8,7 +8,7 @@
import com.bloxbean.cardano.zeroj.crypto.plonk.PlonKProver;
import com.bloxbean.cardano.zeroj.crypto.plonk.PlonKSetup;
import com.bloxbean.cardano.zeroj.verifier.groth16.bn254.*;
-import com.bloxbean.cardano.zeroj.verifier.plonk.FiatShamirTranscript;
+import com.bloxbean.cardano.zeroj.crypto.transcript.FiatShamirTranscript;
import org.junit.jupiter.api.Test;
import java.math.BigInteger;
diff --git a/zeroj-verifier-plonk/src/test/java/com/bloxbean/cardano/zeroj/verifier/plonk/FiatShamirTranscriptTest.java b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/transcript/FiatShamirTranscriptTest.java
similarity index 86%
rename from zeroj-verifier-plonk/src/test/java/com/bloxbean/cardano/zeroj/verifier/plonk/FiatShamirTranscriptTest.java
rename to zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/transcript/FiatShamirTranscriptTest.java
index b2474dc..7973538 100644
--- a/zeroj-verifier-plonk/src/test/java/com/bloxbean/cardano/zeroj/verifier/plonk/FiatShamirTranscriptTest.java
+++ b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/transcript/FiatShamirTranscriptTest.java
@@ -1,8 +1,9 @@
-package com.bloxbean.cardano.zeroj.verifier.plonk;
-
-import org.junit.jupiter.api.Test;
+package com.bloxbean.cardano.zeroj.crypto.transcript;
import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@@ -83,6 +84,20 @@ void appendBytes_affectsChallenge() {
assertNotEquals(t1.getChallenge(), t2.getChallenge());
}
+ @Test
+ void keccak256_knownEmptyVector() {
+ byte[] empty = Keccak256.hash(new byte[0]);
+
+ assertEquals("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", toHex(empty));
+ }
+
+ @Test
+ void keccak256_knownHelloVector() {
+ byte[] result = Keccak256.hash("hello".getBytes(StandardCharsets.UTF_8));
+
+ assertEquals("1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8", toHex(result));
+ }
+
@Test
void bls12381_challengeInField() {
var t = new FiatShamirTranscript(BLS12_381_R, 32, 48);
@@ -154,4 +169,12 @@ void plonkChallengeSequence_sixRounds() {
}
assertEquals(6, new java.util.HashSet<>(challenges).size(), "all 6 should be unique");
}
+
+ private static String toHex(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : bytes) {
+ sb.append(String.format("%02x", b));
+ }
+ return sb.toString();
+ }
}
diff --git a/zeroj-examples/README.md b/zeroj-examples/README.md
index 51a5366..7bfef6c 100644
--- a/zeroj-examples/README.md
+++ b/zeroj-examples/README.md
@@ -52,9 +52,11 @@ Used in: `SealedBidE2ETest`, `AnonymousVotingE2ETest`, `BalanceThresholdE2ETest`
### Path 2: gnark FFM (in-process, no external tools)
```
-Java DSL → R1CS (pure Java) → gnark FFM (in-JVM) → Java verify (pure Java)
+Java DSL → R1CS (pure Java) → gnark FFM (in-JVM) → verify
```
-Used in: `SealedBidGnarkE2ETest`
+Used in: `SealedBidGnarkE2ETest`. Groth16 artifacts use the pure Java verifier;
+gnark binary PlonK artifacts use gnark native verification until a structured
+proof adapter is added.
### Path 3: On-chain (Julc / Plutus V3)
```
@@ -82,7 +84,7 @@ The on-chain Plutus V3 validators live in [`zeroj-onchain-julc`](../zeroj-onchai
| Validator | Proof System | Source |
|-----------|-------------|--------|
| `Groth16BLS12381Verifier` | Groth16 BLS12-381 | `zeroj-onchain-julc` |
-| `PlonkBLS12381FullVerifier` | PlonK BLS12-381 | `zeroj-onchain-julc` |
+| `PlonkBLS12381FullVerifier` | PlonK BLS12-381 prototype | `zeroj-onchain-julc` |
The example-specific `ZkAuctionVerifier` in this module extends the pattern with auction-specific logic (reserve price check).
@@ -90,9 +92,8 @@ The example-specific `ZkAuctionVerifier` in this module extends the pattern with
| Toolchain | Circuit Language | Prove | Verify | External Deps |
|-----------|-----------------|-------|--------|---------------|
-| **gnark FFM** | Java DSL | In-process FFM | Pure Java | gnark native lib |
+| **gnark FFM** | Java DSL | In-process FFM | Groth16: pure Java; PlonK binary: gnark native | gnark native lib |
| **snarkjs** | Java DSL / circom | Node.js CLI | Pure Java | circom + Node.js |
-| **rapidsnark FFM** | Java DSL / circom | In-process FFM | Pure Java | rapidsnark native lib |
## Verification Options (all pure Java, zero native deps)
@@ -107,14 +108,14 @@ The example-specific `ZkAuctionVerifier` in this module extends the pattern with
## Legacy Demos
### EndToEndDemo (snarkjs Groth16/BN254)
-Pre-generated snarkjs proof flow: load → verify → submit → anchor → replay attack demo.
+Pre-generated snarkjs proof flow: load -> verify -> anchor.
```bash
./gradlew :zeroj-examples:run
```
### GnarkPlonkEndToEndDemo (gnark PlonK/BLS12-381)
-In-process gnark proving + pure Java verification + ingestion pipeline.
+In-process gnark proving and native verification + Cardano anchor metadata.
```bash
cd zeroj-prover-gnark/gnark-wrapper && make build # one-time
diff --git a/zeroj-examples/build.gradle b/zeroj-examples/build.gradle
index f4852d0..171bf01 100644
--- a/zeroj-examples/build.gradle
+++ b/zeroj-examples/build.gradle
@@ -32,8 +32,6 @@ dependencies {
implementation project(':zeroj-circuit-lib')
implementation project(':zeroj-crypto')
implementation project(':zeroj-blst')
- implementation project(':zeroj-submission')
- implementation project(':zeroj-ingestion')
implementation project(':zeroj-cardano')
implementation project(':zeroj-ccl')
implementation project(':zeroj-patterns')
diff --git a/zeroj-examples/docs/e2e-plonk-sealed-bid.md b/zeroj-examples/docs/e2e-plonk-sealed-bid.md
index 0c5475f..f646e8b 100644
--- a/zeroj-examples/docs/e2e-plonk-sealed-bid.md
+++ b/zeroj-examples/docs/e2e-plonk-sealed-bid.md
@@ -41,19 +41,21 @@ try (var prover = new GnarkProver()) {
}
```
-### 5. Verify off-chain (pure Java)
+### 5. Verify off-chain
```java
-// gnark PlonK proofs can be verified with the PlonkBLS12381Verifier
-// The verifier handles Fiat-Shamir challenge re-derivation matching gnark's format
-var verifier = new PlonkBLS12381Verifier();
-var result = verifier.verify(envelope, material);
-assert result.proofValid();
+// gnark binary PlonK proof JSON should be verified with gnark today.
+boolean ok = prover.plonkVerify("bls12381", vkPath, proofBase64, publicWitnessPath);
+assert ok;
```
+The pure Java `PlonkBLS12381Verifier` consumes structured snarkjs/ZeroJ PlonK
+proof JSON. A dedicated adapter is still needed before gnark's opaque binary
+PlonK JSON can be routed through that verifier.
+
### 6. On-chain verification
-The `PlonkBLS12381FullVerifier` in `zeroj-onchain-julc` performs full trustless PlonK verification on-chain including Fiat-Shamir challenge re-derivation matching gnark's exact transcript format.
+The `PlonkBLS12381FullVerifier` in `zeroj-onchain-julc` is currently an experimental on-chain prototype. It validates the Fiat-Shamir transcript and inverse checks, but the KZG batch opening pairing check is still deferred, so it is not yet a full trustless on-chain PlonK verifier.
## Running the Tests
diff --git a/zeroj-examples/src/main/java/com/bloxbean/cardano/zeroj/examples/EndToEndDemo.java b/zeroj-examples/src/main/java/com/bloxbean/cardano/zeroj/examples/EndToEndDemo.java
index 066c9bb..964cdbe 100644
--- a/zeroj-examples/src/main/java/com/bloxbean/cardano/zeroj/examples/EndToEndDemo.java
+++ b/zeroj-examples/src/main/java/com/bloxbean/cardano/zeroj/examples/EndToEndDemo.java
@@ -4,8 +4,6 @@
import com.bloxbean.cardano.zeroj.api.*;
import com.bloxbean.cardano.zeroj.backend.spi.InMemoryVerificationKeyRegistry;
import com.bloxbean.cardano.zeroj.cardano.AnchorMetadataEncoder;
-import com.bloxbean.cardano.zeroj.cardano.AnchorPattern;
-import com.bloxbean.cardano.zeroj.cardano.ProofAnchor;
import com.bloxbean.cardano.zeroj.ccl.ZkTransactionHelper;
import com.bloxbean.cardano.zeroj.codec.CanonicalHash;
import com.bloxbean.cardano.zeroj.codec.SnarkjsJsonCodec;
@@ -13,17 +11,9 @@
import com.bloxbean.cardano.zeroj.verifier.core.VerifierRegistry;
import com.bloxbean.cardano.zeroj.verifier.groth16.bn254.Groth16BN254Verifier;
import com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.Groth16BLS12381Verifier;
-import com.bloxbean.cardano.zeroj.submission.AppProofSubmission;
-import com.bloxbean.cardano.zeroj.submission.Ed25519Signer;
-import com.bloxbean.cardano.zeroj.submission.SubmissionHash;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult;
-import com.bloxbean.cardano.zeroj.ingestion.*;
-import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
-import java.security.KeyPair;
import java.security.MessageDigest;
-import java.util.List;
/**
* ZeroJ End-to-End Demo
@@ -35,8 +25,7 @@
* The scenario: A DeFi protocol uses ZK proofs to verify off-chain balance transfers.
* - A user computes a balance transfer off-chain
* - A ZK proof is generated externally (snarkjs/circom)
- * - The proof is submitted to a network of verifier nodes
- * - Each node verifies the proof WITHOUT re-executing the computation
+ * - A verifier checks the proof WITHOUT re-executing the computation
* - The verified result is anchored on Cardano L1
*
* This is the core value proposition of ZeroJ:
@@ -113,92 +102,14 @@ public static void main(String[] args) throws Exception {
System.out.println();
// ============================================================
- // STEP 4: Submit as a proof-backed state transition
- // ============================================================
- // The submitter signs the transition and submits to the verifier network.
- // The 6-stage pipeline validates everything: signature, authorization,
- // circuit allowlist, cryptographic proof, state root chain, replay protection.
-
- System.out.println("[Step 4] Submitting proof-backed state transition...");
-
- // Set up identity
- KeyPair submitterKeys = Ed25519Signer.generateKeyPair();
- String submitterId = "alice";
-
- // Set up policy infrastructure
- var submitterReg = new InMemorySubmitterRegistry();
- submitterReg.register(submitterId, submitterKeys.getPublic(), "defi-app");
-
- var circuitAllowlist = new InMemoryCircuitAllowlist();
- circuitAllowlist.allow("multiplier", "v1");
-
- var stateRootStore = new InMemoryStateRootStore();
- byte[] genesisRoot = sha256("genesis-state".getBytes());
- stateRootStore.initialize("defi-app", genesisRoot);
-
- var sequenceTracker = new InMemorySequenceTracker();
- var nullifierStore = new InMemoryNullifierStore();
-
- var pipeline = new SubmissionIngestionPipeline(
- orchestrator, vkRegistry, submitterReg, circuitAllowlist,
- stateRootStore, sequenceTracker, nullifierStore);
-
- // Build the submission
- byte[] newStateRoot = sha256("state-after-transfer".getBytes());
- var unsigned = AppProofSubmission.builder()
- .appId("defi-app")
- .proofSystem(ProofSystemId.GROTH16)
- .curve(CurveId.BN254)
- .circuitId("multiplier")
- .circuitVersion("v1")
- .prevStateRoot(genesisRoot)
- .newStateRoot(newStateRoot)
- .publicInputs(List.of(BigInteger.valueOf(33), BigInteger.valueOf(3)))
- .proofBytes(proofJson.getBytes(StandardCharsets.UTF_8))
- .vkHash(vkHash)
- .submitterId(submitterId)
- .submitterSignature(new byte[64])
- .sequence(1)
- .build();
-
- // Sign with Ed25519
- byte[] submissionHash = SubmissionHash.compute(unsigned);
- byte[] signature = Ed25519Signer.sign(submissionHash, submitterKeys.getPrivate());
-
- var submission = AppProofSubmission.builder()
- .appId(unsigned.appId()).proofSystem(unsigned.proofSystem())
- .curve(unsigned.curve()).circuitId(unsigned.circuitId())
- .circuitVersion(unsigned.circuitVersion())
- .prevStateRoot(unsigned.prevStateRoot())
- .newStateRoot(unsigned.newStateRoot())
- .publicInputs(unsigned.publicInputs())
- .proofBytes(unsigned.proofBytes()).vkHash(unsigned.vkHash())
- .submitterId(unsigned.submitterId())
- .submitterSignature(signature)
- .sequence(unsigned.sequence())
- .build();
-
- System.out.println(" Submitter: " + submitterId);
- System.out.println(" App: " + submission.appId());
- System.out.println(" Circuit: " + submission.circuitId() + "/" + submission.circuitVersion());
- System.out.println(" Sequence: " + submission.sequence());
-
- // Process through 6-stage pipeline
- SubmissionResult subResult = pipeline.process(submission);
-
- System.out.println(" Pipeline stages: syntactic -> signature -> circuit -> crypto -> policy -> accept");
- System.out.println(" Result: " + (subResult.accepted() ? "ACCEPTED" : "REJECTED: " + subResult.reason().orElse(null)));
- assert subResult.accepted() : "Submission should be accepted!";
- System.out.println();
-
- // ============================================================
- // STEP 5: Anchor on Cardano L1
+ // STEP 4: Anchor on Cardano L1
// ============================================================
// After verification, anchor the result on Cardano for settlement.
- System.out.println("[Step 5] Anchoring verified result on Cardano L1...");
+ System.out.println("[Step 4] Anchoring verified result on Cardano L1...");
byte[] proofHash = sha256(proofJson.getBytes(StandardCharsets.UTF_8));
+ byte[] newStateRoot = sha256("state-after-transfer".getBytes(StandardCharsets.UTF_8));
// Build anchor metadata
Metadata metadata = ZkTransactionHelper.anchorFullRef(
@@ -215,20 +126,6 @@ public static void main(String[] args) throws Exception {
System.out.println(" CIP-10 label: " + AnchorMetadataEncoder.DEFAULT_LABEL);
System.out.println();
- // ============================================================
- // STEP 6: Demonstrate security — replay attack is rejected
- // ============================================================
- System.out.println("[Step 6] Security demo — replay attack...");
-
- // Try to replay the same submission (same sequence number)
- SubmissionResult replayResult = pipeline.process(submission);
- System.out.println(" Replaying same submission...");
- System.out.println(" Result: " + (replayResult.accepted() ? "ACCEPTED (BAD!)" : "REJECTED"));
- System.out.println(" Stage: " + replayResult.stage());
- System.out.println(" Reason: " + replayResult.reason().orElse(null));
- assert !replayResult.accepted() : "Replay should be rejected!";
- System.out.println();
-
// ============================================================
// DONE
// ============================================================
@@ -238,10 +135,8 @@ public static void main(String[] args) throws Exception {
System.out.println(" What happened:");
System.out.println(" 1. Proof generated EXTERNALLY (snarkjs/circom)");
System.out.println(" 2. Verified in JAVA without re-executing the computation");
- System.out.println(" 3. Submitted with Ed25519 signature to verifier network");
- System.out.println(" 4. 6-stage validation: auth + crypto + policy");
- System.out.println(" 5. Anchored on Cardano L1 as CIP-10 metadata");
- System.out.println(" 6. Replay attack automatically rejected");
+ System.out.println(" 3. Canonical proof hash computed for deterministic identification");
+ System.out.println(" 4. Anchored on Cardano L1 as CIP-10 metadata");
System.out.println();
System.out.println(" ZeroJ: Prove once, verify everywhere, settle on Cardano.");
System.out.println("=".repeat(70));
diff --git a/zeroj-examples/src/main/java/com/bloxbean/cardano/zeroj/examples/GnarkPlonkEndToEndDemo.java b/zeroj-examples/src/main/java/com/bloxbean/cardano/zeroj/examples/GnarkPlonkEndToEndDemo.java
index a5729b5..6bafef0 100644
--- a/zeroj-examples/src/main/java/com/bloxbean/cardano/zeroj/examples/GnarkPlonkEndToEndDemo.java
+++ b/zeroj-examples/src/main/java/com/bloxbean/cardano/zeroj/examples/GnarkPlonkEndToEndDemo.java
@@ -1,38 +1,27 @@
package com.bloxbean.cardano.zeroj.examples;
+import com.bloxbean.cardano.client.metadata.Metadata;
import com.bloxbean.cardano.zeroj.api.*;
-import com.bloxbean.cardano.zeroj.backend.spi.InMemoryVerificationKeyRegistry;
+import com.bloxbean.cardano.zeroj.cardano.AnchorMetadataEncoder;
+import com.bloxbean.cardano.zeroj.ccl.ZkTransactionHelper;
import com.bloxbean.cardano.zeroj.codec.GnarkPlonkCodec;
-import com.bloxbean.cardano.zeroj.ingestion.*;
import com.bloxbean.cardano.zeroj.prover.gnark.GnarkProver;
-import com.bloxbean.cardano.zeroj.submission.AppProofSubmission;
-import com.bloxbean.cardano.zeroj.submission.Ed25519Signer;
-import com.bloxbean.cardano.zeroj.submission.SubmissionHash;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult;
-import com.bloxbean.cardano.zeroj.verifier.core.VerifierOrchestrator;
-import com.bloxbean.cardano.zeroj.verifier.core.VerifierRegistry;
-import com.bloxbean.cardano.zeroj.verifier.plonk.PlonkBN254Verifier;
-import com.bloxbean.cardano.zeroj.verifier.plonk.PlonkBLS12381Verifier;
-
-import java.math.BigInteger;
+
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.security.KeyPair;
import java.security.MessageDigest;
-import java.util.Base64;
-import java.util.List;
/**
* ZeroJ Gnark PlonK End-to-End Demo
* ===================================
*
- * This demo shows the complete ZK flow using ONLY Java (no Node.js, no CLI tools):
+ * This demo shows the complete gnark PlonK artifact flow using Java (no Node.js,
+ * no CLI tools):
*
- * 1. gnark FFM: setup + prove (in-process, via Go native library)
- * 2. Pure Java: verify (no native deps for verification)
- * 3. Pipeline: 6-stage validation with governance
- * 4. Cardano: anchor on L1
+ * 1. gnark FFM: setup + prove + verify (in-process, via Go native library)
+ * 2. ZeroJ: normalize the proof artifact and hash it
+ * 3. Cardano: anchor the verified result on L1
*
* Circuit: Multiplier (X * Y = Z) on BLS12-381
* Witness: X=3, Y=11, Z=33 (public: Z=33)
@@ -41,15 +30,16 @@
* - gnark native library built: cd zeroj-prover-gnark/gnark-wrapper && make build
* - Test vectors generated: the pre-generated vectors in zeroj-test-vectors are used
*
- * This demonstrates: gnark proves → pure Java verifies → Cardano anchors.
- * All in one JVM process.
+ * Pure Java PlonK verification currently consumes structured snarkjs/ZeroJ proof
+ * JSON, not gnark's opaque binary PlonK proof JSON. Gnark binary proofs should be
+ * verified with gnark until a dedicated adapter is added.
*/
public class GnarkPlonkEndToEndDemo {
public static void main(String[] args) throws Exception {
System.out.println("=".repeat(70));
System.out.println(" ZeroJ Gnark PlonK End-to-End Demo");
- System.out.println(" gnark proves → pure Java verifies → Cardano anchors");
+ System.out.println(" gnark proves/verifies → ZeroJ anchors");
System.out.println("=".repeat(70));
System.out.println();
@@ -71,7 +61,7 @@ public static void main(String[] args) throws Exception {
String proofBase64;
String proofJson;
String vkJson;
- String publicJson;
+ boolean gnarkVerified;
try (var prover = new GnarkProver()) {
System.out.println(" gnark version: " + prover.gnarkVersion());
@@ -88,21 +78,14 @@ public static void main(String[] args) throws Exception {
proofJson = proveResult.proofJson();
proofBase64 = GnarkPlonkCodec.extractProofBase64(proofJson);
- // Build public.json from the public signals
var pubSignals = proveResult.publicSignals();
- var pubJsonBuilder = new StringBuilder("[");
- for (int i = 0; i < pubSignals.size(); i++) {
- if (i > 0) pubJsonBuilder.append(",");
- pubJsonBuilder.append("\"").append(pubSignals.get(i)).append("\"");
- }
- publicJson = pubJsonBuilder.append("]").toString();
System.out.println(" Proof generated: " + proofBase64.length() + " chars (base64)");
System.out.println(" Protocol: " + proveResult.protocol());
System.out.println(" Public inputs: " + pubSignals);
// Verify with gnark native (sanity check)
- boolean gnarkVerified = prover.plonkVerify("bls12381",
+ gnarkVerified = prover.plonkVerify("bls12381",
vkBinPath, proofBase64, pubWitnessPath);
System.out.println(" gnark native verify: " + (gnarkVerified ? "PASS" : "FAIL"));
assert gnarkVerified : "gnark native verification failed!";
@@ -110,92 +93,40 @@ public static void main(String[] args) throws Exception {
System.out.println();
// ============================================================
- // STEP 2: Verify with Pure Java (no native library needed)
+ // STEP 2: Normalize the gnark artifact for anchoring
// ============================================================
- // The proof was generated by gnark, but we verify it with pure Java.
- // This demonstrates that the verification side has ZERO native dependencies.
-
- System.out.println("[Step 2] Pure Java verification (zero native deps)...");
-
- // Register pure Java PlonK verifiers
- var verifierRegistry = VerifierRegistry.empty();
- verifierRegistry.register(new PlonkBLS12381Verifier()); // Pure Java
- verifierRegistry.register(new PlonkBN254Verifier()); // Pure Java
+ // The proof was generated and verified by gnark. ZeroJ keeps the proof
+ // as a typed artifact for hashing and anchoring. Do not route gnark's
+ // opaque binary PlonK JSON into the snarkjs-style pure Java verifier.
- // Build envelope from gnark proof with witness in metadata
- byte[] pubWitnessBytes = Files.readAllBytes(pubWitnessPath);
- var envelope = GnarkPlonkCodec.toEnvelopeWithWitness(
- proofJson, vkJson, publicJson,
- new CircuitId("multiplier-bls"), pubWitnessBytes);
-
- // Register VK
+ System.out.println("[Step 2] ZeroJ proof artifact metadata...");
byte[] vkBytes = vkJson.getBytes(StandardCharsets.UTF_8);
byte[] vkHash = sha256(vkBytes);
- var vkRegistry = new InMemoryVerificationKeyRegistry();
- var material = VerificationMaterial.of(vkBytes, ProofSystemId.PLONK, CurveId.BLS12_381,
- new CircuitId("multiplier-bls"), vkHash);
- vkRegistry.register(material);
-
- var orchestrator = new VerifierOrchestrator(verifierRegistry, vkRegistry);
- var result = orchestrator.verify(envelope, material);
System.out.println(" Proof system: PlonK / BLS12-381");
- System.out.println(" Verifier: pure Java (no gnark, no blst)");
- System.out.println(" Proof valid: " + result.proofValid());
+ System.out.println(" Proof format: gnark-plonk-json");
+ System.out.println(" Verification: gnark native " + (gnarkVerified ? "PASS" : "FAIL"));
+ System.out.println(" Pure Java verifier: use structured snarkjs/ZeroJ PlonK proof JSON");
System.out.println(" VK hash: " + hex(vkHash).substring(0, 16) + "...");
System.out.println();
// ============================================================
- // STEP 3: Submit through 6-stage governance pipeline
+ // STEP 3: Anchor on Cardano L1
// ============================================================
- System.out.println("[Step 3] Submitting through governance pipeline...");
-
- KeyPair submitterKeys = Ed25519Signer.generateKeyPair();
- String submitterId = "gnark-prover-node";
-
- var submitterReg = new InMemorySubmitterRegistry();
- submitterReg.register(submitterId, submitterKeys.getPublic(), "zk-app");
-
- var circuitRegistry = new InMemoryCircuitRegistry();
- circuitRegistry.register(CircuitRegistry.CircuitVersionInfo.active("multiplier-bls", "v1"));
-
- var stateRootStore = new InMemoryStateRootStore();
- byte[] genesisRoot = sha256("genesis".getBytes());
- stateRootStore.initialize("zk-app", genesisRoot);
-
- var auditLog = new InMemoryAuditLog();
- var pipeline = new SubmissionIngestionPipeline(
- orchestrator, vkRegistry, submitterReg, circuitRegistry,
- stateRootStore, new InMemorySequenceTracker(), new InMemoryNullifierStore(),
- auditLog);
-
- // Build submission
- var publicInputs = GnarkPlonkCodec.parsePublicInputs(publicJson);
byte[] newStateRoot = sha256("state-after-proof".getBytes());
- var unsigned = AppProofSubmission.builder()
- .appId("zk-app").proofSystem(ProofSystemId.PLONK).curve(CurveId.BLS12_381)
- .circuitId("multiplier-bls").circuitVersion("v1")
- .prevStateRoot(genesisRoot).newStateRoot(newStateRoot)
- .publicInputs(publicInputs.values())
- .proofBytes(proofJson.getBytes(StandardCharsets.UTF_8))
- .vkHash(vkHash).submitterId(submitterId)
- .submitterSignature(new byte[64]).sequence(1).build();
-
- byte[] sig = Ed25519Signer.sign(SubmissionHash.compute(unsigned), submitterKeys.getPrivate());
- var submission = AppProofSubmission.builder()
- .appId(unsigned.appId()).proofSystem(unsigned.proofSystem())
- .curve(unsigned.curve()).circuitId(unsigned.circuitId())
- .circuitVersion(unsigned.circuitVersion())
- .prevStateRoot(unsigned.prevStateRoot()).newStateRoot(unsigned.newStateRoot())
- .publicInputs(unsigned.publicInputs())
- .proofBytes(unsigned.proofBytes()).vkHash(unsigned.vkHash())
- .submitterId(unsigned.submitterId()).submitterSignature(sig)
- .sequence(unsigned.sequence()).build();
-
- SubmissionResult subResult = pipeline.process(submission);
- System.out.println(" Pipeline: syntactic → signature → circuit → crypto → policy → accept");
- System.out.println(" Result: " + (subResult.accepted() ? "ACCEPTED" : "REJECTED: " + subResult.reason().orElse(null)));
- System.out.println(" Audit entries: " + auditLog.count());
+ byte[] proofHash = sha256(proofJson.getBytes(StandardCharsets.UTF_8));
+ Metadata metadata = ZkTransactionHelper.anchorFullRef(
+ newStateRoot, proofHash, "multiplier-bls/v1", vkHash)
+ .buildMetadata();
+ byte[] metadataCbor = metadata.serialize();
+
+ System.out.println("[Step 3] Anchoring gnark-verified result on Cardano L1...");
+ System.out.println(" Anchor pattern: FULL_VERIFICATION_REF");
+ System.out.println(" State root: " + hex(newStateRoot).substring(0, 16) + "...");
+ System.out.println(" Proof hash: " + hex(proofHash).substring(0, 16) + "...");
+ System.out.println(" VK hash: " + hex(vkHash).substring(0, 16) + "...");
+ System.out.println(" Metadata CBOR: " + metadataCbor.length + " bytes");
+ System.out.println(" CIP-10 label: " + AnchorMetadataEncoder.DEFAULT_LABEL);
System.out.println();
// ============================================================
@@ -205,14 +136,13 @@ stateRootStore, new InMemorySequenceTracker(), new InMemoryNullifierStore(),
System.out.println(" Demo complete!");
System.out.println();
System.out.println(" What happened (all in one JVM process):");
- System.out.println(" 1. gnark FFM: PlonK setup + prove (Go native, in-process)");
- System.out.println(" 2. Pure Java: PlonK verify (zero native deps)");
- System.out.println(" 3. Pipeline: 6-stage governance validation");
- System.out.println(" 4. Ready: for Cardano L1 anchoring");
+ System.out.println(" 1. gnark FFM: PlonK setup + prove + verify (Go native, in-process)");
+ System.out.println(" 2. ZeroJ: typed proof artifact hashed for anchoring");
+ System.out.println(" 3. Cardano: anchor metadata built for L1 settlement");
System.out.println();
System.out.println(" External tools needed: NONE at runtime");
- System.out.println(" Native libs: gnark .dylib/.so (proving only)");
- System.out.println(" Verification: 100% pure Java");
+ System.out.println(" Native libs: gnark .dylib/.so (proving + gnark binary verification)");
+ System.out.println(" Verification: pure Java path is available for structured PlonK proof JSON");
System.out.println("=".repeat(70));
}
diff --git a/zeroj-ingestion/README.md b/zeroj-ingestion/README.md
deleted file mode 100644
index 937803b..0000000
--- a/zeroj-ingestion/README.md
+++ /dev/null
@@ -1,103 +0,0 @@
-# zeroj-ingestion
-
-6-stage submission ingestion pipeline with governance, security, and audit.
-
-This module orchestrates the complete validation of proof-backed state transition submissions. It enforces a strict 6-stage pipeline with fail-fast semantics: if any stage rejects, processing stops immediately with a typed rejection reason.
-
-## Pipeline Stages
-
-```
-Submission
- |
- v
-1. SYNTACTIC -- proof non-empty, VK hash 32 bytes, public inputs present
- |
-2. SIGNATURE -- Ed25519 valid, submitter known, authorized for app
- |
-3. CIRCUIT -- circuit allowed (not retired), VK found in registry
- |
-4. CRYPTOGRAPHIC -- delegate to VerifierOrchestrator (actual proof verification)
- |
-5. POLICY -- state root chain valid, sequence monotonic, nullifier unused
- |
-6. ACCEPT -- update state root, record sequence, mark nullifier used
- |
- v
-SubmissionResult (accepted/rejected + stage + reason)
-```
-
-## Key Types
-
-### Pipeline
-
-| Type | Description |
-|------|-------------|
-| `SubmissionIngestionPipeline` | Orchestrates all 6 stages; fail-fast on first rejection |
-
-### Governance Interfaces
-
-| Interface | Purpose | In-Memory Implementation |
-|-----------|---------|--------------------------|
-| `CircuitAllowlist` | Allow/retire circuit ID + version combinations | `InMemoryCircuitAllowlist` |
-| `CircuitRegistry` | Extended lifecycle: ACTIVE -> DEPRECATED -> RETIRED | `InMemoryCircuitRegistry` |
-| `SubmitterRegistry` | Ed25519 public keys + app authorization per submitter | `InMemorySubmitterRegistry` |
-| `VersionedVkRegistry` | VK rotation with transition windows per circuit | `VersionedVkRegistry` |
-
-### Security Stores
-
-| Interface | Purpose | In-Memory Implementation |
-|-----------|---------|--------------------------|
-| `StateRootStore` | Track current accepted state root per app | `InMemoryStateRootStore` |
-| `SequenceTracker` | Enforce monotonically increasing sequences (replay protection) | `InMemorySequenceTracker` |
-| `NullifierStore` | Track used nullifiers (double-spend prevention) | `InMemoryNullifierStore` |
-
-### Audit
-
-| Type | Description |
-|------|-------------|
-| `AuditLog` | Immutable record of all submission results | `InMemoryAuditLog` |
-
-All in-memory implementations are thread-safe (using `ConcurrentHashMap` / `CopyOnWriteArrayList`).
-
-## Usage
-
-```java
-// Set up governance infrastructure
-var submitterReg = new InMemorySubmitterRegistry();
-submitterReg.register("alice", alicePublicKey, "my-app");
-
-var circuitAllowlist = new InMemoryCircuitAllowlist();
-circuitAllowlist.allow("multiplier", "v1");
-
-var stateRootStore = new InMemoryStateRootStore();
-stateRootStore.initialize("my-app", genesisRoot);
-
-var sequenceTracker = new InMemorySequenceTracker();
-var nullifierStore = new InMemoryNullifierStore();
-
-// Create pipeline
-var pipeline = new SubmissionIngestionPipeline(
- orchestrator, vkRegistry, submitterReg, circuitAllowlist,
- stateRootStore, sequenceTracker, nullifierStore);
-
-// Process a submission
-SubmissionResult result = pipeline.process(submission);
-
-if (result.accepted()) {
- System.out.println("State transition accepted!");
-} else {
- System.out.println("Rejected at " + result.stage() + ": " + result.reason().orElse(null));
-}
-```
-
-## Production Notes
-
-The in-memory stores are suitable for development and testing. For production deployments, implement the store interfaces with database-backed persistence.
-
-## Gradle
-
-```gradle
-dependencies {
- implementation 'com.bloxbean.cardano:zeroj-ingestion'
-}
-```
diff --git a/zeroj-ingestion/build.gradle b/zeroj-ingestion/build.gradle
deleted file mode 100644
index f434a55..0000000
--- a/zeroj-ingestion/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-plugins {
- id 'java-library'
-}
-
-description = 'ZeroJ ingestion — submission ingestion pipeline, policy validation'
-
-dependencies {
- api project(':zeroj-submission')
- api project(':zeroj-verifier-core')
- implementation project(':zeroj-codec')
-
- testImplementation project(':zeroj-test-vectors')
- testImplementation project(':zeroj-verifier-groth16')
-}
-
-publishing {
- publications {
- mavenJava(MavenPublication) {
- pom {
- name = 'ZeroJ Ingestion'
- description = 'Proof submission ingestion pipeline and policy validation'
- }
- }
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/AuditExporter.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/AuditExporter.java
deleted file mode 100644
index aaa8820..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/AuditExporter.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Exports audit log entries as newline-delimited JSON (NDJSON).
- *
- * Uses manual string building (no reflection or Jackson) for GraalVM native-image compatibility.
- */
-public final class AuditExporter {
-
- private AuditExporter() {}
-
- /**
- * Export entries as NDJSON to a writer.
- */
- public static void export(List entries, Writer writer) throws IOException {
- for (var entry : entries) {
- writer.write(toJson(entry));
- writer.write('\n');
- }
- writer.flush();
- }
-
- /**
- * Export entries as a single NDJSON string.
- */
- public static String exportToString(List entries) {
- var sb = new StringBuilder();
- for (var entry : entries) {
- sb.append(toJson(entry)).append('\n');
- }
- return sb.toString();
- }
-
- /**
- * Convert a single audit entry to a JSON string.
- */
- public static String toJson(AuditLog.AuditEntry entry) {
- var sb = new StringBuilder("{");
- appendString(sb, "timestamp", entry.timestamp().toString());
- sb.append(',');
- appendString(sb, "appId", entry.appId());
- sb.append(',');
- appendString(sb, "submitterId", entry.submitterId());
- sb.append(',');
- appendStringNullable(sb, "circuitId", entry.circuitId());
- sb.append(',');
- appendStringNullable(sb, "circuitVersion", entry.circuitVersion());
- sb.append(',');
- sb.append("\"sequence\":").append(entry.sequence());
- sb.append(',');
- sb.append("\"accepted\":").append(entry.accepted());
- sb.append(',');
- appendString(sb, "stage", entry.stage().name());
- sb.append(',');
- appendStringNullable(sb, "rejectionReason",
- entry.rejectionReason() != null ? entry.rejectionReason().name() : null);
- sb.append(',');
- appendStringNullable(sb, "message", entry.message());
- sb.append(',');
- appendStringNullable(sb, "eventType", entry.eventType());
- sb.append(',');
- appendContext(sb, entry.context());
- sb.append('}');
- return sb.toString();
- }
-
- private static void appendString(StringBuilder sb, String key, String value) {
- sb.append('"').append(key).append("\":\"").append(escapeJson(value)).append('"');
- }
-
- private static void appendStringNullable(StringBuilder sb, String key, String value) {
- sb.append('"').append(key).append("\":");
- if (value == null) {
- sb.append("null");
- } else {
- sb.append('"').append(escapeJson(value)).append('"');
- }
- }
-
- private static void appendContext(StringBuilder sb, Map context) {
- sb.append("\"context\":{");
- if (context != null && !context.isEmpty()) {
- boolean first = true;
- for (var e : context.entrySet()) {
- if (!first) sb.append(',');
- appendString(sb, e.getKey(), e.getValue());
- first = false;
- }
- }
- sb.append('}');
- }
-
- private static String escapeJson(String s) {
- if (s == null) return "";
- var sb = new StringBuilder(s.length());
- for (int i = 0; i < s.length(); i++) {
- char c = s.charAt(i);
- switch (c) {
- case '"' -> sb.append("\\\"");
- case '\\' -> sb.append("\\\\");
- case '\n' -> sb.append("\\n");
- case '\r' -> sb.append("\\r");
- case '\t' -> sb.append("\\t");
- default -> sb.append(c);
- }
- }
- return sb.toString();
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/AuditLog.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/AuditLog.java
deleted file mode 100644
index f532623..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/AuditLog.java
+++ /dev/null
@@ -1,163 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult;
-
-import java.time.Instant;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-/**
- * Immutable audit trail for verification decisions.
- *
- * Records every submission processed through the ingestion pipeline,
- * including accepted and rejected submissions with full reason tracking.
- */
-public interface AuditLog {
-
- /**
- * Record a verification decision.
- */
- void record(AuditEntry entry);
-
- /**
- * Query entries by time range.
- */
- List queryByTimeRange(Instant from, Instant to);
-
- /**
- * Query entries by submitter.
- */
- List queryBySubmitter(String submitterId);
-
- /**
- * Query entries by circuit.
- */
- List queryByCircuit(String circuitId);
-
- /**
- * Get total count of entries.
- */
- long count();
-
- /**
- * Query entries by app ID.
- */
- default List queryByAppId(String appId) {
- return queryByTimeRange(Instant.MIN, Instant.MAX).stream()
- .filter(e -> appId.equals(e.appId()))
- .toList();
- }
-
- /**
- * Query entries by event type.
- */
- default List queryByEventType(String eventType) {
- return queryByTimeRange(Instant.MIN, Instant.MAX).stream()
- .filter(e -> eventType.equals(e.eventType()))
- .toList();
- }
-
- /**
- * Query rejected entries only.
- */
- default List queryRejections() {
- return queryByTimeRange(Instant.MIN, Instant.MAX).stream()
- .filter(e -> !e.accepted())
- .toList();
- }
-
- /**
- * Get the most recent entries, up to the given limit.
- */
- default List recent(int limit) {
- var all = queryByTimeRange(Instant.MIN, Instant.MAX);
- int size = all.size();
- return all.subList(Math.max(0, size - limit), size);
- }
-
- /**
- * An immutable audit log entry.
- */
- record AuditEntry(
- Instant timestamp,
- String appId,
- String submitterId,
- String circuitId,
- String circuitVersion,
- long sequence,
- boolean accepted,
- SubmissionResult.ValidationStage stage,
- SubmissionResult.RejectionReason rejectionReason,
- String message,
- String eventType,
- Map context
- ) {
- public AuditEntry {
- Objects.requireNonNull(timestamp);
- Objects.requireNonNull(appId);
- Objects.requireNonNull(submitterId);
- Objects.requireNonNull(stage);
- if (context == null) context = Map.of();
- }
-
- /**
- * Backward-compatible constructor (no eventType/context).
- */
- public AuditEntry(
- Instant timestamp, String appId, String submitterId,
- String circuitId, String circuitVersion, long sequence,
- boolean accepted, SubmissionResult.ValidationStage stage,
- SubmissionResult.RejectionReason rejectionReason, String message
- ) {
- this(timestamp, appId, submitterId, circuitId, circuitVersion, sequence,
- accepted, stage, rejectionReason, message, null, Map.of());
- }
-
- /**
- * Create an audit entry from a submission and its result.
- */
- public static AuditEntry from(com.bloxbean.cardano.zeroj.submission.AppProofSubmission submission,
- SubmissionResult result) {
- return new AuditEntry(
- Instant.now(),
- submission.appId(),
- submission.submitterId(),
- submission.circuitId(),
- submission.circuitVersion(),
- submission.sequence(),
- result.accepted(),
- result.stage(),
- result.reason().orElse(null),
- result.message().orElse(null),
- result.accepted() ? "ACCEPTED" : "REJECTED",
- Map.of()
- );
- }
-
- /**
- * Create an audit entry with a custom event type and context.
- */
- public static AuditEntry withContext(
- com.bloxbean.cardano.zeroj.submission.AppProofSubmission submission,
- SubmissionResult result,
- String eventType,
- Map context
- ) {
- return new AuditEntry(
- Instant.now(),
- submission.appId(),
- submission.submitterId(),
- submission.circuitId(),
- submission.circuitVersion(),
- submission.sequence(),
- result.accepted(),
- result.stage(),
- result.reason().orElse(null),
- result.message().orElse(null),
- eventType,
- context != null ? Map.copyOf(context) : Map.of()
- );
- }
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/CircuitAllowlist.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/CircuitAllowlist.java
deleted file mode 100644
index cf07ad4..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/CircuitAllowlist.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-/**
- * Controls which circuit id + version combinations are allowed for submissions.
- */
-public interface CircuitAllowlist {
-
- /**
- * Check if a circuit version is allowed (not retired, not unknown).
- */
- boolean isAllowed(String circuitId, String version);
-
- /**
- * Register an allowed circuit version.
- */
- void allow(String circuitId, String version);
-
- /**
- * Retire a circuit version (existing proofs are still valid, new submissions rejected).
- */
- void retire(String circuitId, String version);
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/CircuitRegistry.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/CircuitRegistry.java
deleted file mode 100644
index 566570a..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/CircuitRegistry.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import java.time.Instant;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Enhanced circuit lifecycle registry with versioning, deprecation, and retirement.
- *
- * Lifecycle: ACTIVE → DEPRECATED → RETIRED
- *
- * - ACTIVE — new submissions accepted
- * - DEPRECATED — new submissions accepted until deprecation deadline, migration hint available
- * - RETIRED — new submissions rejected
- *
- *
- * Implements {@link CircuitAllowlist} for backward compatibility with the pipeline.
- */
-public interface CircuitRegistry extends CircuitAllowlist {
-
- /**
- * Register a new circuit version as ACTIVE.
- */
- void register(CircuitVersionInfo info);
-
- /**
- * Deprecate a circuit version with a grace period and migration hint.
- *
- * @param circuitId the circuit to deprecate
- * @param version the version to deprecate
- * @param deadline submissions accepted until this time
- * @param successorId recommended successor circuit (nullable)
- * @param successorVersion recommended successor version (nullable)
- */
- void deprecate(String circuitId, String version, Instant deadline,
- String successorId, String successorVersion);
-
- /**
- * Get the lifecycle info for a circuit version.
- */
- Optional getInfo(String circuitId, String version);
-
- /**
- * List all versions of a circuit.
- */
- List listVersions(String circuitId);
-
- /**
- * Validate that a migration from one circuit version to its declared successor is valid.
- * Returns true if the successor circuit is registered and ACTIVE.
- *
- * @param circuitId the circuit being deprecated
- * @param version the version being deprecated
- * @return true if the declared successor is valid and active
- */
- default boolean validateMigration(String circuitId, String version) {
- var info = getInfo(circuitId, version);
- if (info.isEmpty()) return false;
- var cvi = info.get();
- if (cvi.successorCircuitId() == null || cvi.successorVersion() == null) return false;
- var successor = getInfo(cvi.successorCircuitId(), cvi.successorVersion());
- return successor.isPresent() && successor.get().lifecycle() == Lifecycle.ACTIVE;
- }
-
- /**
- * Circuit version lifecycle state.
- */
- enum Lifecycle {
- ACTIVE,
- DEPRECATED,
- RETIRED
- }
-
- /**
- * Full information about a circuit version.
- */
- record CircuitVersionInfo(
- String circuitId,
- String version,
- Lifecycle lifecycle,
- Instant registeredAt,
- Instant deprecatedAt,
- Instant deprecationDeadline,
- Instant retiredAt,
- String successorCircuitId,
- String successorVersion
- ) {
- public static CircuitVersionInfo active(String circuitId, String version) {
- return new CircuitVersionInfo(circuitId, version, Lifecycle.ACTIVE,
- Instant.now(), null, null, null, null, null);
- }
-
- /**
- * Check if this version accepts new submissions at the given time.
- */
- public boolean acceptsSubmissionsAt(Instant now) {
- return switch (lifecycle) {
- case ACTIVE -> true;
- case DEPRECATED -> deprecationDeadline == null || now.isBefore(deprecationDeadline);
- case RETIRED -> false;
- };
- }
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryAuditLog.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryAuditLog.java
deleted file mode 100644
index 0b61e09..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryAuditLog.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import java.time.Instant;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * Thread-safe, append-only, in-memory implementation of {@link AuditLog}.
- */
-public class InMemoryAuditLog implements AuditLog {
-
- private final List entries = new CopyOnWriteArrayList<>();
-
- @Override
- public void record(AuditEntry entry) {
- entries.add(entry);
- }
-
- @Override
- public List queryByTimeRange(Instant from, Instant to) {
- return entries.stream()
- .filter(e -> !e.timestamp().isBefore(from) && !e.timestamp().isAfter(to))
- .toList();
- }
-
- @Override
- public List queryBySubmitter(String submitterId) {
- return entries.stream()
- .filter(e -> submitterId.equals(e.submitterId()))
- .toList();
- }
-
- @Override
- public List queryByCircuit(String circuitId) {
- return entries.stream()
- .filter(e -> circuitId.equals(e.circuitId()))
- .toList();
- }
-
- @Override
- public long count() {
- return entries.size();
- }
-
- @Override
- public List queryByAppId(String appId) {
- return entries.stream()
- .filter(e -> appId.equals(e.appId()))
- .toList();
- }
-
- @Override
- public List queryByEventType(String eventType) {
- return entries.stream()
- .filter(e -> eventType.equals(e.eventType()))
- .toList();
- }
-
- @Override
- public List queryRejections() {
- return entries.stream()
- .filter(e -> !e.accepted())
- .toList();
- }
-
- @Override
- public List recent(int limit) {
- int size = entries.size();
- return entries.subList(Math.max(0, size - limit), size).stream().toList();
- }
-
- /**
- * Get all entries (for testing/inspection).
- */
- public List all() {
- return Collections.unmodifiableList(entries);
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitAllowlist.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitAllowlist.java
deleted file mode 100644
index 70efa50..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitAllowlist.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Thread-safe in-memory implementation of {@link CircuitAllowlist}.
- */
-public class InMemoryCircuitAllowlist implements CircuitAllowlist {
-
- // key = "circuitId:version", value = true (allowed) or absent (retired/unknown)
- private final Set allowed = ConcurrentHashMap.newKeySet();
- private final Set retired = ConcurrentHashMap.newKeySet();
-
- @Override
- public boolean isAllowed(String circuitId, String version) {
- var key = circuitId + ":" + version;
- return allowed.contains(key) && !retired.contains(key);
- }
-
- @Override
- public void allow(String circuitId, String version) {
- allowed.add(circuitId + ":" + version);
- }
-
- @Override
- public void retire(String circuitId, String version) {
- retired.add(circuitId + ":" + version);
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitRegistry.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitRegistry.java
deleted file mode 100644
index 153f8a2..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitRegistry.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import java.time.Instant;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Thread-safe in-memory implementation of {@link CircuitRegistry}.
- */
-public class InMemoryCircuitRegistry implements CircuitRegistry {
-
- private final Map registry = new ConcurrentHashMap<>();
-
- private static String key(String circuitId, String version) {
- return circuitId + ":" + version;
- }
-
- @Override
- public void register(CircuitVersionInfo info) {
- registry.put(key(info.circuitId(), info.version()), info);
- }
-
- @Override
- public void deprecate(String circuitId, String version, Instant deadline,
- String successorId, String successorVersion) {
- var key = key(circuitId, version);
- var existing = registry.get(key);
- if (existing == null) return;
-
- registry.put(key, new CircuitVersionInfo(
- circuitId, version, CircuitRegistry.Lifecycle.DEPRECATED,
- existing.registeredAt(), Instant.now(), deadline, null,
- successorId, successorVersion));
- }
-
- @Override
- public void retire(String circuitId, String version) {
- var key = key(circuitId, version);
- var existing = registry.get(key);
- if (existing == null) return;
-
- registry.put(key, new CircuitVersionInfo(
- circuitId, version, CircuitRegistry.Lifecycle.RETIRED,
- existing.registeredAt(), existing.deprecatedAt(),
- existing.deprecationDeadline(), Instant.now(),
- existing.successorCircuitId(), existing.successorVersion()));
- }
-
- @Override
- public Optional getInfo(String circuitId, String version) {
- return Optional.ofNullable(registry.get(key(circuitId, version)));
- }
-
- @Override
- public List listVersions(String circuitId) {
- return registry.values().stream()
- .filter(info -> info.circuitId().equals(circuitId))
- .toList();
- }
-
- /**
- * Retire all circuits whose deprecation deadline has passed.
- * Intended for operator-triggered cleanup.
- *
- * @return the number of circuits retired
- */
- public int retireExpiredCircuits() {
- int count = 0;
- var now = Instant.now();
- for (var entry : registry.entrySet()) {
- var info = entry.getValue();
- if (info.lifecycle() == CircuitRegistry.Lifecycle.DEPRECATED
- && info.deprecationDeadline() != null
- && now.isAfter(info.deprecationDeadline())) {
- registry.put(entry.getKey(), new CircuitVersionInfo(
- info.circuitId(), info.version(), CircuitRegistry.Lifecycle.RETIRED,
- info.registeredAt(), info.deprecatedAt(),
- info.deprecationDeadline(), now,
- info.successorCircuitId(), info.successorVersion()));
- count++;
- }
- }
- return count;
- }
-
- // --- CircuitAllowlist compatibility ---
-
- @Override
- public boolean isAllowed(String circuitId, String version) {
- var info = registry.get(key(circuitId, version));
- if (info == null) return false;
- return info.acceptsSubmissionsAt(Instant.now());
- }
-
- @Override
- public void allow(String circuitId, String version) {
- register(CircuitVersionInfo.active(circuitId, version));
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryNullifierStore.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryNullifierStore.java
deleted file mode 100644
index 452973d..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryNullifierStore.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import java.nio.ByteBuffer;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Thread-safe in-memory implementation of {@link NullifierStore} with app-scoped support.
- */
-public class InMemoryNullifierStore implements NullifierStore {
-
- private final Set used = ConcurrentHashMap.newKeySet();
- private final Set scopedUsed = ConcurrentHashMap.newKeySet();
-
- @Override
- public boolean isUsed(byte[] nullifier) {
- return used.contains(ByteBuffer.wrap(nullifier));
- }
-
- @Override
- public boolean markUsed(byte[] nullifier) {
- return used.add(ByteBuffer.wrap(nullifier.clone()));
- }
-
- @Override
- public boolean isUsed(String appId, byte[] nullifier) {
- return scopedUsed.contains(scopedKey(appId, nullifier));
- }
-
- @Override
- public boolean markUsed(String appId, byte[] nullifier) {
- return scopedUsed.add(scopedKey(appId, nullifier));
- }
-
- private static String scopedKey(String appId, byte[] nullifier) {
- var sb = new StringBuilder(appId).append(':');
- for (byte b : nullifier) sb.append(String.format("%02x", b));
- return sb.toString();
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySequenceTracker.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySequenceTracker.java
deleted file mode 100644
index 81847b9..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySequenceTracker.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Thread-safe in-memory implementation of {@link SequenceTracker}.
- */
-public class InMemorySequenceTracker implements SequenceTracker {
-
- // key = "appId:submitterId"
- private final Map sequences = new ConcurrentHashMap<>();
-
- @Override
- public long getLastSequence(String appId, String submitterId) {
- return sequences.getOrDefault(appId + ":" + submitterId, -1L);
- }
-
- @Override
- public boolean recordSequence(String appId, String submitterId, long sequence) {
- var key = appId + ":" + submitterId;
- return sequences.compute(key, (k, current) -> {
- if (current == null) current = -1L;
- if (sequence <= current) return current; // reject — not monotonically increasing
- return sequence;
- }) == sequence;
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryStateRootStore.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryStateRootStore.java
deleted file mode 100644
index 11c6820..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryStateRootStore.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Thread-safe in-memory implementation of {@link StateRootStore}.
- */
-public class InMemoryStateRootStore implements StateRootStore {
-
- private final Map roots = new ConcurrentHashMap<>();
-
- @Override
- public byte[] getCurrentRoot(String appId) {
- var root = roots.get(appId);
- return root != null ? root.clone() : null;
- }
-
- @Override
- public void updateRoot(String appId, byte[] newRoot) {
- roots.put(appId, newRoot.clone());
- }
-
- /**
- * Initialize the state root for an app (for bootstrapping).
- */
- public void initialize(String appId, byte[] genesisRoot) {
- roots.putIfAbsent(appId, genesisRoot.clone());
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySubmitterRegistry.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySubmitterRegistry.java
deleted file mode 100644
index 98c2cd8..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySubmitterRegistry.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import java.security.PublicKey;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Thread-safe in-memory implementation of {@link SubmitterRegistry} with status tracking.
- */
-public class InMemorySubmitterRegistry implements SubmitterRegistry {
-
- private final Map keys = new ConcurrentHashMap<>();
- private final Map> authorizations = new ConcurrentHashMap<>();
- private final Map statuses = new ConcurrentHashMap<>();
-
- @Override
- public Optional getPublicKey(String submitterId) {
- return Optional.ofNullable(keys.get(submitterId));
- }
-
- @Override
- public boolean isAuthorized(String submitterId, String appId) {
- var apps = authorizations.get(submitterId);
- return apps != null && apps.contains(appId);
- }
-
- @Override
- public void register(String submitterId, PublicKey publicKey, String... authorizedApps) {
- keys.put(submitterId, publicKey);
- statuses.put(submitterId, SubmitterStatus.ACTIVE);
- var apps = authorizations.computeIfAbsent(submitterId, k -> ConcurrentHashMap.newKeySet());
- apps.addAll(Arrays.asList(authorizedApps));
- }
-
- @Override
- public boolean isActive(String submitterId) {
- return statuses.get(submitterId) == SubmitterStatus.ACTIVE;
- }
-
- @Override
- public void revoke(String submitterId) {
- if (statuses.containsKey(submitterId)) {
- statuses.put(submitterId, SubmitterStatus.REVOKED);
- }
- }
-
- @Override
- public void suspend(String submitterId) {
- if (statuses.get(submitterId) == SubmitterStatus.ACTIVE) {
- statuses.put(submitterId, SubmitterStatus.SUSPENDED);
- }
- }
-
- @Override
- public void reinstate(String submitterId) {
- if (statuses.get(submitterId) == SubmitterStatus.SUSPENDED) {
- statuses.put(submitterId, SubmitterStatus.ACTIVE);
- }
- }
-
- @Override
- public boolean rotateKey(String submitterId, PublicKey newPublicKey) {
- if (statuses.get(submitterId) != SubmitterStatus.ACTIVE) return false;
- keys.put(submitterId, newPublicKey);
- return true;
- }
-
- /**
- * Get the current status of a submitter (for testing/inspection).
- */
- public SubmitterStatus getStatus(String submitterId) {
- return statuses.get(submitterId);
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/NullifierStore.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/NullifierStore.java
deleted file mode 100644
index 82b3e71..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/NullifierStore.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-/**
- * Tracks used nullifiers to prevent double-spend.
- */
-public interface NullifierStore {
-
- /**
- * Check if a nullifier has been used (global scope).
- */
- boolean isUsed(byte[] nullifier);
-
- /**
- * Mark a nullifier as used (global scope). Returns false if it was already used (atomic check-and-set).
- */
- boolean markUsed(byte[] nullifier);
-
- /**
- * Check if a nullifier has been used within a specific app scope.
- * App-scoped nullifiers are independent: the same nullifier bytes can be used
- * in different apps without conflict.
- */
- default boolean isUsed(String appId, byte[] nullifier) {
- return isUsed(nullifier);
- }
-
- /**
- * Mark a nullifier as used within a specific app scope. Returns false if already used.
- */
- default boolean markUsed(String appId, byte[] nullifier) {
- return markUsed(nullifier);
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/SequenceTracker.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/SequenceTracker.java
deleted file mode 100644
index a77af80..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/SequenceTracker.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-/**
- * Tracks the last accepted sequence number per submitter per app to prevent replay.
- */
-public interface SequenceTracker {
-
- /**
- * Get the last accepted sequence number for a submitter in an app.
- *
- * @return the last sequence, or -1 if no submission has been accepted
- */
- long getLastSequence(String appId, String submitterId);
-
- /**
- * Record a new accepted sequence. Returns false if the sequence was already used or is not monotonically increasing.
- */
- boolean recordSequence(String appId, String submitterId, long sequence);
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/StateRootStore.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/StateRootStore.java
deleted file mode 100644
index 84725f4..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/StateRootStore.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-/**
- * Tracks the current accepted state root for each app.
- */
-public interface StateRootStore {
-
- /**
- * Get the current accepted state root for an app.
- *
- * @return the current root, or null if no state has been accepted yet
- */
- byte[] getCurrentRoot(String appId);
-
- /**
- * Update the state root after a successful submission.
- */
- void updateRoot(String appId, byte[] newRoot);
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/SubmissionIngestionPipeline.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/SubmissionIngestionPipeline.java
deleted file mode 100644
index 292c7d8..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/SubmissionIngestionPipeline.java
+++ /dev/null
@@ -1,300 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import com.bloxbean.cardano.zeroj.api.*;
-import com.bloxbean.cardano.zeroj.backend.spi.VerificationKeyRegistry;
-import com.bloxbean.cardano.zeroj.codec.SnarkjsJsonCodec;
-import com.bloxbean.cardano.zeroj.verifier.core.VerifierOrchestrator;
-import com.bloxbean.cardano.zeroj.submission.AppProofSubmission;
-import com.bloxbean.cardano.zeroj.submission.Ed25519Signer;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult.RejectionReason;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult.ValidationStage;
-
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Orchestrates the 6-stage validation of a proof-backed state transition submission.
- *
- * Stages (executed in order, fail-fast):
- *
- * - Syntactic — structural field validation
- * - Signature — Ed25519 signature verification + submitter authorization
- * - Circuit resolution — verify circuit is known, allowed, and VK is available
- * - Cryptographic verification — delegate to {@link VerifierOrchestrator}
- * - Policy — state root chain, sequence, nullifier uniqueness
- * - Accept — update state root, record sequence, mark nullifier
- *
- *
- * Each stage produces an explicit rejection reason on failure.
- * The pipeline never recomputes the computation — it only verifies.
- */
-public class SubmissionIngestionPipeline {
-
- private final VerifierOrchestrator verifier;
- private final VerificationKeyRegistry vkRegistry;
- private final SubmitterRegistry submitterRegistry;
- private final CircuitAllowlist circuitAllowlist;
- private final StateRootStore stateRootStore;
- private final SequenceTracker sequenceTracker;
- private final NullifierStore nullifierStore;
- private final AuditLog auditLog; // nullable
-
- public SubmissionIngestionPipeline(
- VerifierOrchestrator verifier,
- VerificationKeyRegistry vkRegistry,
- SubmitterRegistry submitterRegistry,
- CircuitAllowlist circuitAllowlist,
- StateRootStore stateRootStore,
- SequenceTracker sequenceTracker,
- NullifierStore nullifierStore) {
- this(verifier, vkRegistry, submitterRegistry, circuitAllowlist,
- stateRootStore, sequenceTracker, nullifierStore, null);
- }
-
- public SubmissionIngestionPipeline(
- VerifierOrchestrator verifier,
- VerificationKeyRegistry vkRegistry,
- SubmitterRegistry submitterRegistry,
- CircuitAllowlist circuitAllowlist,
- StateRootStore stateRootStore,
- SequenceTracker sequenceTracker,
- NullifierStore nullifierStore,
- AuditLog auditLog) {
- this.verifier = Objects.requireNonNull(verifier);
- this.vkRegistry = Objects.requireNonNull(vkRegistry);
- this.submitterRegistry = Objects.requireNonNull(submitterRegistry);
- this.circuitAllowlist = Objects.requireNonNull(circuitAllowlist);
- this.stateRootStore = Objects.requireNonNull(stateRootStore);
- this.sequenceTracker = Objects.requireNonNull(sequenceTracker);
- this.nullifierStore = Objects.requireNonNull(nullifierStore);
- this.auditLog = auditLog;
- }
-
- /**
- * Process a submission through the full 6-stage pipeline.
- */
- public SubmissionResult process(AppProofSubmission submission) {
- // Stage 1: Syntactic validation
- var syntactic = validateSyntactic(submission);
- if (syntactic != null) return audit(submission, syntactic);
-
- // Stage 2: Signature and authorization
- var signature = validateSignature(submission);
- if (signature != null) return audit(submission, signature);
-
- // Stage 3: Circuit resolution
- var circuit = validateCircuit(submission);
- if (circuit != null) return audit(submission, circuit);
-
- // Stage 4: Cryptographic verification
- var crypto = verifyCryptographic(submission);
- if (crypto != null) return audit(submission, crypto);
-
- // Stage 5: Policy validation
- var policy = validatePolicy(submission);
- if (policy != null) return audit(submission, policy);
-
- // Stage 6: Accept — update all state
- accept(submission);
- return audit(submission, SubmissionResult.ok());
- }
-
- private SubmissionResult audit(AppProofSubmission submission, SubmissionResult result) {
- if (auditLog != null) {
- auditLog.record(AuditLog.AuditEntry.from(submission, result));
- }
- return result;
- }
-
- // --- Stage 1: Syntactic ---
-
- private SubmissionResult validateSyntactic(AppProofSubmission submission) {
- if (submission.proofBytes().length == 0) {
- return SubmissionResult.rejected(ValidationStage.SYNTACTIC,
- RejectionReason.EMPTY_PROOF, "Proof bytes are empty");
- }
- if (submission.vkHash().length != 32) {
- return SubmissionResult.rejected(ValidationStage.SYNTACTIC,
- RejectionReason.INVALID_VK_HASH_LENGTH,
- "VK hash must be 32 bytes, got " + submission.vkHash().length);
- }
- if (submission.publicInputs().isEmpty()) {
- return SubmissionResult.rejected(ValidationStage.SYNTACTIC,
- RejectionReason.MALFORMED_SUBMISSION, "Public inputs must not be empty");
- }
- return null; // pass
- }
-
- // --- Stage 2: Signature ---
-
- private SubmissionResult validateSignature(AppProofSubmission submission) {
- // Look up submitter
- var pubKeyOpt = submitterRegistry.getPublicKey(submission.submitterId());
- if (pubKeyOpt.isEmpty()) {
- return SubmissionResult.rejected(ValidationStage.SIGNATURE,
- RejectionReason.UNKNOWN_SUBMITTER,
- "Unknown submitter: " + submission.submitterId());
- }
-
- // Check submitter status (active check)
- if (!submitterRegistry.isActive(submission.submitterId())) {
- return SubmissionResult.rejected(ValidationStage.SIGNATURE,
- RejectionReason.SUBMITTER_SUSPENDED,
- "Submitter " + submission.submitterId() + " is suspended or revoked");
- }
-
- // Check authorization for this app
- if (!submitterRegistry.isAuthorized(submission.submitterId(), submission.appId())) {
- return SubmissionResult.rejected(ValidationStage.SIGNATURE,
- RejectionReason.UNAUTHORIZED_SUBMITTER,
- "Submitter " + submission.submitterId() + " not authorized for app " + submission.appId());
- }
-
- // Verify Ed25519 signature
- if (!Ed25519Signer.verifySubmission(submission, pubKeyOpt.get())) {
- return SubmissionResult.rejected(ValidationStage.SIGNATURE,
- RejectionReason.INVALID_SIGNATURE, "Ed25519 signature verification failed");
- }
-
- return null; // pass
- }
-
- // --- Stage 3: Circuit resolution ---
-
- private SubmissionResult validateCircuit(AppProofSubmission submission) {
- // Enhanced circuit lifecycle when using CircuitRegistry
- if (circuitAllowlist instanceof CircuitRegistry registry) {
- var infoOpt = registry.getInfo(submission.circuitId(), submission.circuitVersion());
- if (infoOpt.isEmpty()) {
- return SubmissionResult.rejected(ValidationStage.CIRCUIT_RESOLUTION,
- RejectionReason.UNKNOWN_CIRCUIT,
- "Circuit " + submission.circuitId() + " v" + submission.circuitVersion() + " is not registered");
- }
- var info = infoOpt.get();
- if (info.lifecycle() == CircuitRegistry.Lifecycle.RETIRED) {
- return SubmissionResult.rejected(ValidationStage.CIRCUIT_RESOLUTION,
- RejectionReason.RETIRED_CIRCUIT,
- "Circuit " + submission.circuitId() + " v" + submission.circuitVersion() + " is retired");
- }
- if (info.lifecycle() == CircuitRegistry.Lifecycle.DEPRECATED
- && !info.acceptsSubmissionsAt(Instant.now())) {
- String hint = info.successorCircuitId() != null
- ? "; migrate to " + info.successorCircuitId() + " v" + info.successorVersion()
- : "";
- return SubmissionResult.rejected(ValidationStage.CIRCUIT_RESOLUTION,
- RejectionReason.DEPRECATED_CIRCUIT,
- "Circuit " + submission.circuitId() + " v" + submission.circuitVersion()
- + " is deprecated past deadline" + hint);
- }
- } else {
- // Fallback: simple allowlist
- if (!circuitAllowlist.isAllowed(submission.circuitId(), submission.circuitVersion())) {
- return SubmissionResult.rejected(ValidationStage.CIRCUIT_RESOLUTION,
- RejectionReason.RETIRED_CIRCUIT,
- "Circuit " + submission.circuitId() + " v" + submission.circuitVersion() + " is not allowed");
- }
- }
-
- // Check VK exists in registry
- var vkRef = new VerificationKeyRef.ByHash(submission.vkHash());
- var vkOpt = vkRegistry.lookup(vkRef);
- if (vkOpt.isEmpty()) {
- return SubmissionResult.rejected(ValidationStage.CIRCUIT_RESOLUTION,
- RejectionReason.VK_NOT_FOUND,
- "Verification key not found for hash");
- }
-
- // Check VK expiry when using VersionedVkRegistry
- if (vkRegistry instanceof VersionedVkRegistry versioned) {
- if (!versioned.isValid(submission.vkHash())) {
- return SubmissionResult.rejected(ValidationStage.CIRCUIT_RESOLUTION,
- RejectionReason.VK_EXPIRED,
- "Verification key has expired");
- }
- }
-
- return null; // pass
- }
-
- // --- Stage 4: Cryptographic verification ---
-
- private SubmissionResult verifyCryptographic(AppProofSubmission submission) {
- try {
- // Build the proof envelope from the submission
- String proofJson = new String(submission.proofBytes());
- var publicInputs = new PublicInputs(submission.publicInputs());
-
- var envelope = ZkProofEnvelope.builder()
- .proofSystem(submission.proofSystem())
- .curve(submission.curve())
- .circuitId(new CircuitId(submission.circuitId()))
- .proofBytes(submission.proofBytes())
- .publicInputs(publicInputs)
- .vkRef(new VerificationKeyRef.ByHash(submission.vkHash()))
- .proofFormat("snarkjs-json")
- .build();
-
- // Resolve VK
- var vkRef = new VerificationKeyRef.ByHash(submission.vkHash());
- var material = vkRegistry.lookup(vkRef).orElseThrow();
-
- // Verify
- var result = verifier.verify(envelope, material);
-
- if (!result.proofValid()) {
- return SubmissionResult.rejected(ValidationStage.CRYPTOGRAPHIC_VERIFICATION,
- RejectionReason.PROOF_INVALID,
- "Proof verification failed: " + result.message().orElse("unknown"));
- }
-
- return null; // pass
- } catch (Exception e) {
- return SubmissionResult.rejected(ValidationStage.CRYPTOGRAPHIC_VERIFICATION,
- RejectionReason.PROOF_VERIFICATION_ERROR,
- "Proof verification error: " + e.getMessage());
- }
- }
-
- // --- Stage 5: Policy ---
-
- private SubmissionResult validatePolicy(AppProofSubmission submission) {
- // Check state root chain
- byte[] currentRoot = stateRootStore.getCurrentRoot(submission.appId());
- if (currentRoot != null && !Arrays.equals(currentRoot, submission.prevStateRoot())) {
- return SubmissionResult.rejected(ValidationStage.POLICY,
- RejectionReason.STALE_STATE_ROOT,
- "Previous state root does not match current accepted root");
- }
-
- // Check sequence (must be monotonically increasing)
- long lastSeq = sequenceTracker.getLastSequence(submission.appId(), submission.submitterId());
- if (submission.sequence() <= lastSeq) {
- return SubmissionResult.rejected(ValidationStage.POLICY,
- RejectionReason.DUPLICATE_SEQUENCE,
- "Sequence " + submission.sequence() + " not greater than last accepted " + lastSeq);
- }
-
- // Check nullifier uniqueness (if present) — use app-scoped version
- byte[] nullifier = submission.nullifier();
- if (nullifier != null && nullifierStore.isUsed(submission.appId(), nullifier)) {
- return SubmissionResult.rejected(ValidationStage.POLICY,
- RejectionReason.USED_NULLIFIER, "Nullifier already used");
- }
-
- return null; // pass
- }
-
- // --- Stage 6: Accept ---
-
- private void accept(AppProofSubmission submission) {
- stateRootStore.updateRoot(submission.appId(), submission.newStateRoot());
- sequenceTracker.recordSequence(submission.appId(), submission.submitterId(), submission.sequence());
-
- byte[] nullifier = submission.nullifier();
- if (nullifier != null) {
- nullifierStore.markUsed(submission.appId(), nullifier);
- }
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/SubmitterRegistry.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/SubmitterRegistry.java
deleted file mode 100644
index 5eec514..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/SubmitterRegistry.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import java.security.PublicKey;
-import java.util.Optional;
-
-/**
- * Registry of authorized submitters and their Ed25519 public keys.
- */
-public interface SubmitterRegistry {
-
- /**
- * Look up a submitter's public key.
- *
- * @return the public key, or empty if the submitter is not registered
- */
- Optional getPublicKey(String submitterId);
-
- /**
- * Check if a submitter is authorized to submit for a given app.
- */
- boolean isAuthorized(String submitterId, String appId);
-
- /**
- * Register a submitter with their public key and authorized app(s).
- */
- void register(String submitterId, PublicKey publicKey, String... authorizedApps);
-
- /**
- * Check if a submitter is active (not suspended or revoked).
- * Default returns true for backward compatibility.
- */
- default boolean isActive(String submitterId) {
- return getPublicKey(submitterId).isPresent();
- }
-
- /**
- * Revoke a submitter permanently. Revoked submitters cannot be reinstated.
- */
- default void revoke(String submitterId) {
- // no-op default for backward compatibility
- }
-
- /**
- * Suspend a submitter temporarily. Can be reinstated later.
- */
- default void suspend(String submitterId) {
- // no-op default for backward compatibility
- }
-
- /**
- * Reinstate a previously suspended submitter back to ACTIVE.
- * Has no effect on REVOKED submitters.
- */
- default void reinstate(String submitterId) {
- // no-op default for backward compatibility
- }
-
- /**
- * Rotate the submitter's public key.
- *
- * @return true if the key was rotated, false if the submitter does not exist or is not active
- */
- default boolean rotateKey(String submitterId, PublicKey newPublicKey) {
- // no-op default for backward compatibility
- return false;
- }
-
- /**
- * Submitter lifecycle status.
- */
- enum SubmitterStatus {
- ACTIVE,
- SUSPENDED,
- REVOKED
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/VersionedVkRegistry.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/VersionedVkRegistry.java
deleted file mode 100644
index fdba1fb..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/VersionedVkRegistry.java
+++ /dev/null
@@ -1,200 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import com.bloxbean.cardano.zeroj.api.VerificationKeyRef;
-import com.bloxbean.cardano.zeroj.api.VerificationMaterial;
-import com.bloxbean.cardano.zeroj.backend.spi.VerificationKeyRegistry;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.time.Instant;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Verification key registry with rotation support.
- *
- * Allows multiple VK versions per circuit. During rotation, both old and new VK are
- * accepted within a configurable transition window. After the window closes, only the
- * new VK is accepted.
- *
- * This prevents breaking in-flight proofs: a proof generated against VK v1 can still
- * be verified even after VK v2 is registered, as long as v1 hasn't expired.
- */
-public class VersionedVkRegistry implements VerificationKeyRegistry {
-
- private final Map byHash = new ConcurrentHashMap<>();
- private final Map> byCircuit = new ConcurrentHashMap<>();
- private final VkRotationPolicy policy; // nullable — no policy enforcement if null
-
- public VersionedVkRegistry() {
- this(null);
- }
-
- public VersionedVkRegistry(VkRotationPolicy policy) {
- this.policy = policy;
- }
-
- /**
- * Register a new VK version for a circuit. Previous versions remain valid until expired.
- *
- * @param material the verification material
- * @param expiresAt when this VK version expires (null = never expires)
- */
- public void registerVersion(VerificationMaterial material, Instant expiresAt) {
- byte[] hash = material.vkHash().orElseGet(() -> sha256(material.vkBytes()));
- String hashKey = hex(hash);
- byHash.put(hashKey, material);
-
- var entry = new VkEntry(material, Instant.now(), expiresAt);
- byCircuit.computeIfAbsent(material.circuitId().value(), k -> Collections.synchronizedList(new ArrayList<>()))
- .add(entry);
- }
-
- /**
- * Rotate: register a new VK and set an expiry on the previous version.
- * If a {@link VkRotationPolicy} is set, the rotation is validated against it.
- *
- * @param newMaterial the new VK
- * @param transitionWindow how long the old VK remains valid after rotation
- * @return the rotation result
- */
- public RotationResult rotate(VerificationMaterial newMaterial, java.time.Duration transitionWindow) {
- var circuitId = newMaterial.circuitId().value();
-
- // Policy enforcement
- if (policy != null) {
- // Check minimum transition window
- if (transitionWindow.compareTo(policy.minTransitionWindow()) < 0) {
- return new RotationResult.Rejected("Transition window " + transitionWindow
- + " is shorter than policy minimum " + policy.minTransitionWindow());
- }
-
- var entries = byCircuit.get(circuitId);
- if (entries != null) {
- // Check max active VKs (count non-expired + the new one)
- var now = Instant.now();
- long activeCount = entries.stream()
- .filter(e -> e.expiresAt() == null || now.isBefore(e.expiresAt()))
- .count();
- if (activeCount + 1 > policy.maxActiveVksPerCircuit()) {
- return new RotationResult.Rejected("Would exceed max active VKs per circuit ("
- + policy.maxActiveVksPerCircuit() + ")");
- }
-
- // Check min time between rotations
- if (!policy.minTimeBetweenRotations().isZero() && !entries.isEmpty()) {
- var lastRegistered = entries.getLast().registeredAt();
- if (now.isBefore(lastRegistered.plus(policy.minTimeBetweenRotations()))) {
- return new RotationResult.Rejected("Too soon since last rotation; minimum interval is "
- + policy.minTimeBetweenRotations());
- }
- }
- }
- }
-
- // Expire all current active VKs for this circuit
- var entries = byCircuit.get(circuitId);
- if (entries != null) {
- var expiryTime = Instant.now().plus(transitionWindow);
- for (int i = 0; i < entries.size(); i++) {
- var entry = entries.get(i);
- if (entry.expiresAt() == null) {
- entries.set(i, new VkEntry(entry.material(), entry.registeredAt(), expiryTime));
- }
- }
- }
-
- // Register the new VK (never expires until next rotation)
- registerVersion(newMaterial, null);
- return RotationResult.Success.INSTANCE;
- }
-
- /**
- * Result of a VK rotation attempt.
- */
- public sealed interface RotationResult {
- record Success() implements RotationResult {
- static final Success INSTANCE = new Success();
- }
- record Rejected(String reason) implements RotationResult {}
- }
-
- /**
- * Check if a specific VK hash is currently valid (registered and not expired).
- */
- public boolean isValid(byte[] vkHash) {
- var material = byHash.get(hex(vkHash));
- if (material == null) return false;
-
- var entries = byCircuit.get(material.circuitId().value());
- if (entries == null) return false;
-
- var now = Instant.now();
- return entries.stream()
- .anyMatch(e -> Arrays.equals(sha256(e.material().vkBytes()), vkHash)
- && (e.expiresAt() == null || now.isBefore(e.expiresAt())));
- }
-
- /**
- * Get the current (latest non-expired) VK for a circuit.
- */
- public Optional getCurrentVk(String circuitId) {
- var entries = byCircuit.get(circuitId);
- if (entries == null) return Optional.empty();
-
- var now = Instant.now();
- // Return the most recently registered non-expired entry
- for (int i = entries.size() - 1; i >= 0; i--) {
- var entry = entries.get(i);
- if (entry.expiresAt() == null || now.isBefore(entry.expiresAt())) {
- return Optional.of(entry.material());
- }
- }
- return Optional.empty();
- }
-
- /**
- * List all VK entries (active and expired) for a circuit.
- */
- public List listEntries(String circuitId) {
- return byCircuit.getOrDefault(circuitId, List.of());
- }
-
- // --- VerificationKeyRegistry implementation ---
-
- @Override
- public Optional lookup(VerificationKeyRef ref) {
- return switch (ref) {
- case VerificationKeyRef.ByHash h -> {
- var mat = byHash.get(hex(h.hash()));
- yield Optional.ofNullable(mat);
- }
- case VerificationKeyRef.ById id -> getCurrentVk(id.id());
- };
- }
-
- @Override
- public void register(VerificationMaterial material) {
- registerVersion(material, null);
- }
-
- /**
- * A VK entry with registration and expiry timestamps.
- */
- public record VkEntry(VerificationMaterial material, Instant registeredAt, Instant expiresAt) {
- public boolean isExpiredAt(Instant now) {
- return expiresAt != null && now.isAfter(expiresAt);
- }
- }
-
- private static byte[] sha256(byte[] data) {
- try { return MessageDigest.getInstance("SHA-256").digest(data); }
- catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); }
- }
-
- private static String hex(byte[] bytes) {
- var sb = new StringBuilder();
- for (byte b : bytes) sb.append(String.format("%02x", b));
- return sb.toString();
- }
-}
diff --git a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/VkRotationPolicy.java b/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/VkRotationPolicy.java
deleted file mode 100644
index 10cfbe8..0000000
--- a/zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/VkRotationPolicy.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import java.time.Duration;
-
-/**
- * Policy governing VK rotation constraints.
- *
- * @param minTransitionWindow minimum time old VKs must remain valid after rotation
- * @param maxActiveVksPerCircuit maximum number of non-expired VKs per circuit
- * @param minTimeBetweenRotations minimum time between successive rotations for a circuit
- */
-public record VkRotationPolicy(
- Duration minTransitionWindow,
- int maxActiveVksPerCircuit,
- Duration minTimeBetweenRotations
-) {
- public VkRotationPolicy {
- if (minTransitionWindow == null) throw new NullPointerException("minTransitionWindow");
- if (maxActiveVksPerCircuit < 1) throw new IllegalArgumentException("maxActiveVksPerCircuit must be >= 1");
- if (minTimeBetweenRotations == null) throw new NullPointerException("minTimeBetweenRotations");
- }
-
- /**
- * A permissive default policy: 1-hour transition, up to 5 active VKs, no minimum between rotations.
- */
- public static VkRotationPolicy defaultPolicy() {
- return new VkRotationPolicy(Duration.ofHours(1), 5, Duration.ZERO);
- }
-}
diff --git a/zeroj-ingestion/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-ingestion/reflect-config.json b/zeroj-ingestion/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-ingestion/reflect-config.json
deleted file mode 100644
index fe51488..0000000
--- a/zeroj-ingestion/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-ingestion/reflect-config.json
+++ /dev/null
@@ -1 +0,0 @@
-[]
diff --git a/zeroj-ingestion/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-ingestion/resource-config.json b/zeroj-ingestion/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-ingestion/resource-config.json
deleted file mode 100644
index a7360e9..0000000
--- a/zeroj-ingestion/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-ingestion/resource-config.json
+++ /dev/null
@@ -1 +0,0 @@
-{"resources":{"includes":[]}}
diff --git a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/AuditExporterTest.java b/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/AuditExporterTest.java
deleted file mode 100644
index 8b401fe..0000000
--- a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/AuditExporterTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult.RejectionReason;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult.ValidationStage;
-import org.junit.jupiter.api.Test;
-
-import java.io.IOException;
-import java.io.StringWriter;
-import java.time.Instant;
-import java.util.List;
-import java.util.Map;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class AuditExporterTest {
-
- @Test
- void exportSingleEntry() {
- var entry = new AuditLog.AuditEntry(
- Instant.parse("2026-01-15T10:30:00Z"), "app1", "alice", "mul", "v1", 42,
- true, ValidationStage.ACCEPTED, null, null,
- "ACCEPTED", Map.of()
- );
- var json = AuditExporter.toJson(entry);
-
- assertTrue(json.contains("\"timestamp\":\"2026-01-15T10:30:00Z\""));
- assertTrue(json.contains("\"appId\":\"app1\""));
- assertTrue(json.contains("\"submitterId\":\"alice\""));
- assertTrue(json.contains("\"circuitId\":\"mul\""));
- assertTrue(json.contains("\"sequence\":42"));
- assertTrue(json.contains("\"accepted\":true"));
- assertTrue(json.contains("\"stage\":\"ACCEPTED\""));
- assertTrue(json.contains("\"rejectionReason\":null"));
- assertTrue(json.contains("\"eventType\":\"ACCEPTED\""));
- }
-
- @Test
- void exportRejectedEntry() {
- var entry = new AuditLog.AuditEntry(
- Instant.parse("2026-01-15T10:30:00Z"), "app1", "bob", "mul", "v1", 1,
- false, ValidationStage.CRYPTOGRAPHIC_VERIFICATION,
- RejectionReason.PROOF_INVALID, "bad proof",
- "REJECTED", Map.of("detail", "pairing check failed")
- );
- var json = AuditExporter.toJson(entry);
-
- assertTrue(json.contains("\"accepted\":false"));
- assertTrue(json.contains("\"rejectionReason\":\"PROOF_INVALID\""));
- assertTrue(json.contains("\"message\":\"bad proof\""));
- assertTrue(json.contains("\"detail\":\"pairing check failed\""));
- }
-
- @Test
- void exportToString() {
- var entries = List.of(
- new AuditLog.AuditEntry(
- Instant.parse("2026-01-15T10:00:00Z"), "app1", "alice", "mul", "v1", 1,
- true, ValidationStage.ACCEPTED, null, null
- ),
- new AuditLog.AuditEntry(
- Instant.parse("2026-01-15T10:01:00Z"), "app1", "bob", "add", "v1", 2,
- false, ValidationStage.POLICY, RejectionReason.USED_NULLIFIER, "dup"
- )
- );
-
- var ndjson = AuditExporter.exportToString(entries);
- var lines = ndjson.strip().split("\n");
- assertEquals(2, lines.length);
- assertTrue(lines[0].contains("alice"));
- assertTrue(lines[1].contains("bob"));
- }
-
- @Test
- void exportToWriter() throws IOException {
- var entry = new AuditLog.AuditEntry(
- Instant.parse("2026-01-15T10:00:00Z"), "app1", "alice", "mul", "v1", 1,
- true, ValidationStage.ACCEPTED, null, null
- );
- var writer = new StringWriter();
- AuditExporter.export(List.of(entry), writer);
- assertTrue(writer.toString().endsWith("\n"));
- assertTrue(writer.toString().contains("\"appId\":\"app1\""));
- }
-
- @Test
- void jsonEscaping() {
- var entry = new AuditLog.AuditEntry(
- Instant.parse("2026-01-15T10:00:00Z"), "app1", "alice", "mul", "v1", 1,
- false, ValidationStage.SYNTACTIC, RejectionReason.MALFORMED_SUBMISSION,
- "bad \"field\"\nnewline",
- "ERROR", Map.of("path", "a\\b")
- );
- var json = AuditExporter.toJson(entry);
-
- assertTrue(json.contains("bad \\\"field\\\"\\nnewline"));
- assertTrue(json.contains("a\\\\b"));
- }
-
- @Test
- void emptyExport() {
- assertEquals("", AuditExporter.exportToString(List.of()));
- }
-
- @Test
- void roundTrip() {
- // Verify structure is valid JSON-ish (basic sanity)
- var entry = new AuditLog.AuditEntry(
- Instant.parse("2026-01-15T10:30:00Z"), "app1", "alice", "mul", "v1", 1,
- true, ValidationStage.ACCEPTED, null, null,
- "ACCEPTED", Map.of("key", "value")
- );
- var json = AuditExporter.toJson(entry);
- assertTrue(json.startsWith("{"));
- assertTrue(json.endsWith("}"));
- // Basic field presence checks
- assertTrue(json.contains("\"timestamp\""));
- assertTrue(json.contains("\"appId\""));
- assertTrue(json.contains("\"context\""));
- assertTrue(json.contains("\"eventType\""));
- }
-}
diff --git a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/GovernanceAndSecurityTest.java b/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/GovernanceAndSecurityTest.java
deleted file mode 100644
index 28ee52a..0000000
--- a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/GovernanceAndSecurityTest.java
+++ /dev/null
@@ -1,400 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import com.bloxbean.cardano.zeroj.api.*;
-import com.bloxbean.cardano.zeroj.backend.spi.InMemoryVerificationKeyRegistry;
-import com.bloxbean.cardano.zeroj.verifier.core.VerifierOrchestrator;
-import com.bloxbean.cardano.zeroj.verifier.core.VerifierRegistry;
-import com.bloxbean.cardano.zeroj.verifier.groth16.bn254.Groth16BN254Verifier;
-import com.bloxbean.cardano.zeroj.submission.AppProofSubmission;
-import com.bloxbean.cardano.zeroj.submission.Ed25519Signer;
-import com.bloxbean.cardano.zeroj.submission.SubmissionHash;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult.RejectionReason;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult.ValidationStage;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Adversarial security and governance test suite.
- *
- * Tests circuit lifecycle (deprecation/retirement with grace periods),
- * VK rotation with transition windows, audit trail completeness,
- * and edge-case attack scenarios.
- */
-class GovernanceAndSecurityTest {
-
- private static final String APP_ID = "test-app";
- private static final String CIRCUIT_ID = "multiplier";
-
- private KeyPair aliceKeys;
- private byte[] vkHash;
- private String proofJson;
- private byte[] genesisRoot;
-
- @BeforeEach
- void setUp() {
- aliceKeys = Ed25519Signer.generateKeyPair();
- proofJson = loadString("/test-vectors/groth16-bn254/proof.json");
- String vkJson = loadString("/test-vectors/groth16-bn254/verification_key.json");
- vkHash = sha256(vkJson.getBytes(StandardCharsets.UTF_8));
- genesisRoot = sha256("genesis".getBytes());
- }
-
- // ==================== Circuit Lifecycle ====================
-
- @Nested
- class CircuitLifecycleTests {
-
- @Test
- void activeCircuitAcceptsSubmissions() {
- var registry = new InMemoryCircuitRegistry();
- registry.register(CircuitRegistry.CircuitVersionInfo.active(CIRCUIT_ID, "v1"));
-
- assertTrue(registry.isAllowed(CIRCUIT_ID, "v1"));
- var info = registry.getInfo(CIRCUIT_ID, "v1").orElseThrow();
- assertEquals(CircuitRegistry.Lifecycle.ACTIVE, info.lifecycle());
- }
-
- @Test
- void deprecatedCircuitAcceptsDuringGracePeriod() {
- var registry = new InMemoryCircuitRegistry();
- registry.register(CircuitRegistry.CircuitVersionInfo.active(CIRCUIT_ID, "v1"));
-
- // Deprecate with a future deadline
- registry.deprecate(CIRCUIT_ID, "v1",
- Instant.now().plus(Duration.ofHours(1)),
- CIRCUIT_ID, "v2");
-
- // Still allowed during grace period
- assertTrue(registry.isAllowed(CIRCUIT_ID, "v1"));
-
- var info = registry.getInfo(CIRCUIT_ID, "v1").orElseThrow();
- assertEquals(CircuitRegistry.Lifecycle.DEPRECATED, info.lifecycle());
- assertEquals("v2", info.successorVersion());
- }
-
- @Test
- void deprecatedCircuitRejectsAfterDeadline() {
- var registry = new InMemoryCircuitRegistry();
- registry.register(CircuitRegistry.CircuitVersionInfo.active(CIRCUIT_ID, "v1"));
-
- // Deprecate with a past deadline
- registry.deprecate(CIRCUIT_ID, "v1",
- Instant.now().minus(Duration.ofHours(1)),
- CIRCUIT_ID, "v2");
-
- assertFalse(registry.isAllowed(CIRCUIT_ID, "v1"));
- }
-
- @Test
- void retiredCircuitAlwaysRejects() {
- var registry = new InMemoryCircuitRegistry();
- registry.register(CircuitRegistry.CircuitVersionInfo.active(CIRCUIT_ID, "v1"));
- registry.retire(CIRCUIT_ID, "v1");
-
- assertFalse(registry.isAllowed(CIRCUIT_ID, "v1"));
- var info = registry.getInfo(CIRCUIT_ID, "v1").orElseThrow();
- assertEquals(CircuitRegistry.Lifecycle.RETIRED, info.lifecycle());
- assertNotNull(info.retiredAt());
- }
-
- @Test
- void listVersionsShowsAll() {
- var registry = new InMemoryCircuitRegistry();
- registry.register(CircuitRegistry.CircuitVersionInfo.active(CIRCUIT_ID, "v1"));
- registry.register(CircuitRegistry.CircuitVersionInfo.active(CIRCUIT_ID, "v2"));
- registry.retire(CIRCUIT_ID, "v1");
-
- var versions = registry.listVersions(CIRCUIT_ID);
- assertEquals(2, versions.size());
- }
-
- @Test
- void unknownCircuitNotAllowed() {
- var registry = new InMemoryCircuitRegistry();
- assertFalse(registry.isAllowed("nonexistent", "v1"));
- assertTrue(registry.getInfo("nonexistent", "v1").isEmpty());
- }
- }
-
- // ==================== VK Rotation ====================
-
- @Nested
- class VkRotationTests {
-
- @Test
- void freshVkIsValid() {
- var registry = new VersionedVkRegistry();
- var mat = makeMaterial("vk-data-v1");
- registry.registerVersion(mat, null);
-
- byte[] hash = sha256("vk-data-v1".getBytes());
- assertTrue(registry.isValid(hash));
- }
-
- @Test
- void rotatedVk_bothValidDuringTransition() {
- var registry = new VersionedVkRegistry();
- var matV1 = makeMaterial("vk-v1");
- registry.registerVersion(matV1, null);
-
- var matV2 = makeMaterial("vk-v2");
- registry.rotate(matV2, Duration.ofHours(1));
-
- // Both should be valid during transition
- assertTrue(registry.isValid(sha256("vk-v1".getBytes())));
- assertTrue(registry.isValid(sha256("vk-v2".getBytes())));
- }
-
- @Test
- void expiredVk_notValid() {
- var registry = new VersionedVkRegistry();
- var mat = makeMaterial("vk-expired");
- // Register with an already-passed expiry
- registry.registerVersion(mat, Instant.now().minus(Duration.ofHours(1)));
-
- assertFalse(registry.isValid(sha256("vk-expired".getBytes())));
- }
-
- @Test
- void getCurrentVk_returnsLatest() {
- var registry = new VersionedVkRegistry();
- registry.registerVersion(makeMaterial("vk-v1"), null);
- registry.registerVersion(makeMaterial("vk-v2"), null);
-
- var current = registry.getCurrentVk(CIRCUIT_ID).orElseThrow();
- // Should return v2 (most recently registered)
- assertArrayEquals(sha256("vk-v2".getBytes()), sha256(current.vkBytes()));
- }
-
- @Test
- void lookupByHash_works() {
- var registry = new VersionedVkRegistry();
- var mat = makeMaterial("vk-lookup");
- registry.register(mat);
-
- var found = registry.lookup(new VerificationKeyRef.ByHash(sha256("vk-lookup".getBytes())));
- assertTrue(found.isPresent());
- }
-
- @Test
- void lookupById_returnsCurrentVersion() {
- var registry = new VersionedVkRegistry();
- registry.register(makeMaterial("vk-id-test"));
-
- var found = registry.lookup(new VerificationKeyRef.ById(CIRCUIT_ID));
- assertTrue(found.isPresent());
- }
-
- private VerificationMaterial makeMaterial(String vkData) {
- byte[] vkBytes = vkData.getBytes(StandardCharsets.UTF_8);
- return VerificationMaterial.of(vkBytes, ProofSystemId.GROTH16, CurveId.BN254,
- new CircuitId(CIRCUIT_ID), sha256(vkBytes));
- }
- }
-
- // ==================== Audit Trail ====================
-
- @Nested
- class AuditTrailTests {
-
- @Test
- void auditLogRecordsAcceptedSubmission() {
- var auditLog = new InMemoryAuditLog();
- var pipeline = buildPipelineWithAudit(auditLog);
-
- var sub = validSubmission(1, genesisRoot, sha256("s1".getBytes()));
- pipeline.process(sub);
-
- assertEquals(1, auditLog.count());
- var entry = auditLog.all().getFirst();
- assertTrue(entry.accepted());
- assertEquals(APP_ID, entry.appId());
- assertEquals("alice", entry.submitterId());
- assertEquals(CIRCUIT_ID, entry.circuitId());
- assertEquals(1, entry.sequence());
- assertEquals(ValidationStage.ACCEPTED, entry.stage());
- }
-
- @Test
- void auditLogRecordsRejection() {
- var auditLog = new InMemoryAuditLog();
- var pipeline = buildPipelineWithAudit(auditLog);
-
- // Submit with wrong state root
- var sub = validSubmission(1, sha256("wrong".getBytes()), sha256("s1".getBytes()));
- pipeline.process(sub);
-
- assertEquals(1, auditLog.count());
- var entry = auditLog.all().getFirst();
- assertFalse(entry.accepted());
- assertEquals(ValidationStage.POLICY, entry.stage());
- assertEquals(RejectionReason.STALE_STATE_ROOT, entry.rejectionReason());
- }
-
- @Test
- void auditLogRecordsAllDecisions() {
- var auditLog = new InMemoryAuditLog();
- var pipeline = buildPipelineWithAudit(auditLog);
-
- var root1 = sha256("s1".getBytes());
- pipeline.process(validSubmission(1, genesisRoot, root1)); // accepted
- pipeline.process(validSubmission(1, root1, sha256("s2".getBytes()))); // rejected (duplicate seq)
- pipeline.process(validSubmission(2, root1, sha256("s2".getBytes()))); // accepted
-
- assertEquals(3, auditLog.count());
- assertEquals(2, auditLog.queryBySubmitter("alice").stream().filter(AuditLog.AuditEntry::accepted).count());
- assertEquals(1, auditLog.queryBySubmitter("alice").stream().filter(e -> !e.accepted()).count());
- }
-
- @Test
- void auditLogQueryByCircuit() {
- var auditLog = new InMemoryAuditLog();
- var pipeline = buildPipelineWithAudit(auditLog);
-
- pipeline.process(validSubmission(1, genesisRoot, sha256("s1".getBytes())));
-
- var entries = auditLog.queryByCircuit(CIRCUIT_ID);
- assertEquals(1, entries.size());
- assertEquals(CIRCUIT_ID, entries.getFirst().circuitId());
- }
-
- @Test
- void auditLogQueryByTimeRange() {
- var auditLog = new InMemoryAuditLog();
- var pipeline = buildPipelineWithAudit(auditLog);
-
- var before = Instant.now();
- pipeline.process(validSubmission(1, genesisRoot, sha256("s1".getBytes())));
- var after = Instant.now();
-
- var entries = auditLog.queryByTimeRange(before, after);
- assertEquals(1, entries.size());
-
- // Query outside range should return empty
- var oldEntries = auditLog.queryByTimeRange(
- Instant.now().minus(Duration.ofDays(1)),
- Instant.now().minus(Duration.ofHours(1)));
- assertTrue(oldEntries.isEmpty());
- }
- }
-
- // ==================== Edge-case Attacks ====================
-
- @Nested
- class EdgeCaseAttacks {
-
- @Test
- void zeroSequenceAccepted() {
- var pipeline = buildPipeline();
- var sub = validSubmission(0, genesisRoot, sha256("s1".getBytes()));
- assertTrue(pipeline.process(sub).accepted());
- }
-
- @Test
- void maxSequenceAccepted() {
- var pipeline = buildPipeline();
- var sub = validSubmission(Long.MAX_VALUE - 1, genesisRoot, sha256("s1".getBytes()));
- assertTrue(pipeline.process(sub).accepted());
- }
-
- @Test
- void emptyNullifierNotStoredAsUsed() {
- // Submission with no nullifier should not consume any nullifier slot
- var pipeline = buildPipeline();
- var sub = validSubmission(1, genesisRoot, sha256("s1".getBytes()));
- assertTrue(pipeline.process(sub).accepted());
- // A subsequent submission with a real nullifier should work
- var root1 = sha256("s1".getBytes());
- var sub2 = buildSubmissionWithNullifier(2, root1, sha256("s2".getBytes()), sha256("n1".getBytes()));
- assertTrue(pipeline.process(sub2).accepted());
- }
- }
-
- // ==================== Helpers ====================
-
- private SubmissionIngestionPipeline buildPipeline() {
- return buildPipelineWithAudit(null);
- }
-
- private SubmissionIngestionPipeline buildPipelineWithAudit(AuditLog auditLog) {
- String vkJson = loadString("/test-vectors/groth16-bn254/verification_key.json");
- byte[] vkBytes = vkJson.getBytes(StandardCharsets.UTF_8);
-
- var verifierRegistry = VerifierRegistry.empty();
- verifierRegistry.register(new Groth16BN254Verifier());
-
- var vkRegistry = new InMemoryVerificationKeyRegistry();
- vkRegistry.register(VerificationMaterial.of(
- vkBytes, ProofSystemId.GROTH16, CurveId.BN254,
- new CircuitId(CIRCUIT_ID), vkHash));
-
- var orchestrator = new VerifierOrchestrator(verifierRegistry, vkRegistry);
-
- var submitterReg = new InMemorySubmitterRegistry();
- submitterReg.register("alice", aliceKeys.getPublic(), APP_ID);
-
- var circuitAllowlist = new InMemoryCircuitAllowlist();
- circuitAllowlist.allow(CIRCUIT_ID, "v1");
-
- var stateRootStore = new InMemoryStateRootStore();
- stateRootStore.initialize(APP_ID, genesisRoot);
-
- return new SubmissionIngestionPipeline(
- orchestrator, vkRegistry, submitterReg, circuitAllowlist,
- stateRootStore, new InMemorySequenceTracker(), new InMemoryNullifierStore(),
- auditLog);
- }
-
- private AppProofSubmission validSubmission(long sequence, byte[] prevRoot, byte[] newRoot) {
- return buildSubmissionWithNullifier(sequence, prevRoot, newRoot, null);
- }
-
- private AppProofSubmission buildSubmissionWithNullifier(long sequence, byte[] prevRoot, byte[] newRoot, byte[] nullifier) {
- var unsigned = AppProofSubmission.builder()
- .appId(APP_ID).proofSystem(ProofSystemId.GROTH16).curve(CurveId.BN254)
- .circuitId(CIRCUIT_ID).circuitVersion("v1")
- .prevStateRoot(prevRoot).newStateRoot(newRoot)
- .publicInputs(List.of(BigInteger.valueOf(33), BigInteger.valueOf(3)))
- .proofBytes(proofJson.getBytes(StandardCharsets.UTF_8))
- .vkHash(vkHash).submitterId("alice")
- .submitterSignature(new byte[64]).sequence(sequence)
- .nullifier(nullifier)
- .build();
-
- byte[] hash = SubmissionHash.compute(unsigned);
- byte[] sig = Ed25519Signer.sign(hash, aliceKeys.getPrivate());
-
- return AppProofSubmission.builder()
- .appId(unsigned.appId()).proofSystem(unsigned.proofSystem()).curve(unsigned.curve())
- .circuitId(unsigned.circuitId()).circuitVersion(unsigned.circuitVersion())
- .prevStateRoot(unsigned.prevStateRoot()).newStateRoot(unsigned.newStateRoot())
- .publicInputs(unsigned.publicInputs()).proofBytes(unsigned.proofBytes())
- .vkHash(unsigned.vkHash()).submitterId(unsigned.submitterId())
- .submitterSignature(sig).sequence(unsigned.sequence())
- .nullifier(unsigned.nullifier())
- .build();
- }
-
- private String loadString(String path) {
- try (var in = getClass().getResourceAsStream(path)) {
- if (in == null) fail("Resource not found: " + path);
- return new String(in.readAllBytes(), StandardCharsets.UTF_8);
- } catch (Exception e) { return fail(e.getMessage()); }
- }
-
- private static byte[] sha256(byte[] data) {
- try { return MessageDigest.getInstance("SHA-256").digest(data); }
- catch (Exception e) { throw new RuntimeException(e); }
- }
-}
diff --git a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryAuditLogTest.java b/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryAuditLogTest.java
deleted file mode 100644
index f46696c..0000000
--- a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryAuditLogTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult.RejectionReason;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult.ValidationStage;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Map;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class InMemoryAuditLogTest {
-
- private InMemoryAuditLog log;
-
- @BeforeEach
- void setUp() {
- log = new InMemoryAuditLog();
- }
-
- @Test
- void recordAndCount() {
- log.record(entry("app1", "alice", "mul", true));
- assertEquals(1, log.count());
- }
-
- @Test
- void emptyLogCount() {
- assertEquals(0, log.count());
- }
-
- @Test
- void queryByTimeRange() {
- var now = Instant.now();
- log.record(entryAt(now.minus(2, ChronoUnit.HOURS), "app1", "alice", true));
- log.record(entryAt(now.minus(1, ChronoUnit.HOURS), "app1", "bob", true));
- log.record(entryAt(now, "app1", "alice", false));
-
- var results = log.queryByTimeRange(now.minus(90, ChronoUnit.MINUTES), now);
- assertEquals(2, results.size());
- }
-
- @Test
- void queryBySubmitter() {
- log.record(entry("app1", "alice", "mul", true));
- log.record(entry("app1", "bob", "mul", false));
- log.record(entry("app1", "alice", "add", true));
-
- assertEquals(2, log.queryBySubmitter("alice").size());
- assertEquals(1, log.queryBySubmitter("bob").size());
- assertEquals(0, log.queryBySubmitter("carol").size());
- }
-
- @Test
- void queryByCircuit() {
- log.record(entry("app1", "alice", "mul", true));
- log.record(entry("app1", "alice", "add", true));
- log.record(entry("app1", "bob", "mul", false));
-
- assertEquals(2, log.queryByCircuit("mul").size());
- assertEquals(1, log.queryByCircuit("add").size());
- }
-
- @Test
- void queryByAppId() {
- log.record(entry("app1", "alice", "mul", true));
- log.record(entry("app2", "alice", "mul", true));
-
- assertEquals(1, log.queryByAppId("app1").size());
- assertEquals(1, log.queryByAppId("app2").size());
- }
-
- @Test
- void queryByEventType() {
- log.record(entryWithType("app1", "alice", true, "ACCEPTED"));
- log.record(entryWithType("app1", "bob", false, "REJECTED"));
- log.record(entryWithType("app1", "carol", true, "ACCEPTED"));
-
- assertEquals(2, log.queryByEventType("ACCEPTED").size());
- assertEquals(1, log.queryByEventType("REJECTED").size());
- }
-
- @Test
- void queryRejections() {
- log.record(entry("app1", "alice", "mul", true));
- log.record(entry("app1", "bob", "mul", false));
- log.record(entry("app1", "carol", "mul", false));
-
- assertEquals(2, log.queryRejections().size());
- }
-
- @Test
- void recent() {
- for (int i = 0; i < 10; i++) {
- log.record(entry("app1", "s" + i, "mul", true));
- }
-
- var recent = log.recent(3);
- assertEquals(3, recent.size());
- assertEquals("s7", recent.get(0).submitterId());
- assertEquals("s9", recent.get(2).submitterId());
- }
-
- @Test
- void recentMoreThanAvailable() {
- log.record(entry("app1", "alice", "mul", true));
- var recent = log.recent(100);
- assertEquals(1, recent.size());
- }
-
- @Test
- void all() {
- log.record(entry("app1", "alice", "mul", true));
- log.record(entry("app1", "bob", "mul", false));
-
- var all = log.all();
- assertEquals(2, all.size());
- }
-
- @Test
- void auditEntryBackwardCompatibleConstructor() {
- var entry = new AuditLog.AuditEntry(
- Instant.now(), "app1", "alice", "mul", "v1", 1,
- true, ValidationStage.ACCEPTED, null, null
- );
- assertNull(entry.eventType());
- assertEquals(Map.of(), entry.context());
- }
-
- @Test
- void auditEntryWithContext() {
- var entry = new AuditLog.AuditEntry(
- Instant.now(), "app1", "alice", "mul", "v1", 1,
- true, ValidationStage.ACCEPTED, null, null,
- "CUSTOM_EVENT", Map.of("key", "value")
- );
- assertEquals("CUSTOM_EVENT", entry.eventType());
- assertEquals("value", entry.context().get("key"));
- }
-
- private AuditLog.AuditEntry entry(String appId, String submitterId, String circuitId, boolean accepted) {
- return new AuditLog.AuditEntry(
- Instant.now(), appId, submitterId, circuitId, "v1", 1,
- accepted,
- accepted ? ValidationStage.ACCEPTED : ValidationStage.CRYPTOGRAPHIC_VERIFICATION,
- accepted ? null : RejectionReason.PROOF_INVALID,
- accepted ? null : "proof failed"
- );
- }
-
- private AuditLog.AuditEntry entryAt(Instant ts, String appId, String submitterId, boolean accepted) {
- return new AuditLog.AuditEntry(
- ts, appId, submitterId, "mul", "v1", 1,
- accepted,
- accepted ? ValidationStage.ACCEPTED : ValidationStage.CRYPTOGRAPHIC_VERIFICATION,
- accepted ? null : RejectionReason.PROOF_INVALID,
- null
- );
- }
-
- private AuditLog.AuditEntry entryWithType(String appId, String submitterId, boolean accepted, String eventType) {
- return new AuditLog.AuditEntry(
- Instant.now(), appId, submitterId, "mul", "v1", 1,
- accepted,
- accepted ? ValidationStage.ACCEPTED : ValidationStage.CRYPTOGRAPHIC_VERIFICATION,
- accepted ? null : RejectionReason.PROOF_INVALID,
- null,
- eventType, Map.of()
- );
- }
-}
diff --git a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitAllowlistTest.java b/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitAllowlistTest.java
deleted file mode 100644
index 345d257..0000000
--- a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitAllowlistTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class InMemoryCircuitAllowlistTest {
-
- private InMemoryCircuitAllowlist allowlist;
-
- @BeforeEach
- void setUp() {
- allowlist = new InMemoryCircuitAllowlist();
- }
-
- @Test
- void allowAndCheck() {
- allowlist.allow("mul", "v1");
- assertTrue(allowlist.isAllowed("mul", "v1"));
- }
-
- @Test
- void unknownNotAllowed() {
- assertFalse(allowlist.isAllowed("mul", "v1"));
- }
-
- @Test
- void retireCircuit() {
- allowlist.allow("mul", "v1");
- allowlist.retire("mul", "v1");
- assertFalse(allowlist.isAllowed("mul", "v1"));
- }
-
- @Test
- void retireIdempotent() {
- allowlist.allow("mul", "v1");
- allowlist.retire("mul", "v1");
- allowlist.retire("mul", "v1"); // second retire is idempotent
- assertFalse(allowlist.isAllowed("mul", "v1"));
- }
-
- @Test
- void allowIdempotent() {
- allowlist.allow("mul", "v1");
- allowlist.allow("mul", "v1"); // double allow is fine
- assertTrue(allowlist.isAllowed("mul", "v1"));
- }
-
- @Test
- void independentCircuits() {
- allowlist.allow("mul", "v1");
- allowlist.allow("add", "v1");
- allowlist.retire("mul", "v1");
-
- assertFalse(allowlist.isAllowed("mul", "v1"));
- assertTrue(allowlist.isAllowed("add", "v1"));
- }
-
- @Test
- void versionIndependence() {
- allowlist.allow("mul", "v1");
- allowlist.allow("mul", "v2");
- allowlist.retire("mul", "v1");
-
- assertFalse(allowlist.isAllowed("mul", "v1"));
- assertTrue(allowlist.isAllowed("mul", "v2"));
- }
-}
diff --git a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitRegistryTest.java b/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitRegistryTest.java
deleted file mode 100644
index 72ce549..0000000
--- a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitRegistryTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class InMemoryCircuitRegistryTest {
-
- private InMemoryCircuitRegistry registry;
-
- @BeforeEach
- void setUp() {
- registry = new InMemoryCircuitRegistry();
- }
-
- @Test
- void registerActiveCircuit() {
- var info = CircuitRegistry.CircuitVersionInfo.active("mul", "v1");
- registry.register(info);
-
- assertTrue(registry.isAllowed("mul", "v1"));
- var fetched = registry.getInfo("mul", "v1");
- assertTrue(fetched.isPresent());
- assertEquals(CircuitRegistry.Lifecycle.ACTIVE, fetched.get().lifecycle());
- }
-
- @Test
- void unknownCircuitNotAllowed() {
- assertFalse(registry.isAllowed("unknown", "v1"));
- assertTrue(registry.getInfo("unknown", "v1").isEmpty());
- }
-
- @Test
- void deprecateCircuit() {
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v1"));
- var deadline = Instant.now().plus(7, ChronoUnit.DAYS);
-
- registry.deprecate("mul", "v1", deadline, "mul", "v2");
-
- var info = registry.getInfo("mul", "v1").orElseThrow();
- assertEquals(CircuitRegistry.Lifecycle.DEPRECATED, info.lifecycle());
- assertNotNull(info.deprecatedAt());
- assertEquals(deadline, info.deprecationDeadline());
- assertEquals("mul", info.successorCircuitId());
- assertEquals("v2", info.successorVersion());
- // Still allowed before deadline
- assertTrue(registry.isAllowed("mul", "v1"));
- }
-
- @Test
- void deprecatedCircuitRejectedPastDeadline() {
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v1"));
- // Deadline in the past
- var deadline = Instant.now().minus(1, ChronoUnit.HOURS);
- registry.deprecate("mul", "v1", deadline, "mul", "v2");
-
- assertFalse(registry.isAllowed("mul", "v1"));
- }
-
- @Test
- void retireCircuit() {
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v1"));
- registry.retire("mul", "v1");
-
- var info = registry.getInfo("mul", "v1").orElseThrow();
- assertEquals(CircuitRegistry.Lifecycle.RETIRED, info.lifecycle());
- assertNotNull(info.retiredAt());
- assertFalse(registry.isAllowed("mul", "v1"));
- }
-
- @Test
- void fullLifecycle_activeToDeprecatedToRetired() {
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v1"));
- assertEquals(CircuitRegistry.Lifecycle.ACTIVE, registry.getInfo("mul", "v1").orElseThrow().lifecycle());
-
- registry.deprecate("mul", "v1", Instant.now().plus(1, ChronoUnit.DAYS), null, null);
- assertEquals(CircuitRegistry.Lifecycle.DEPRECATED, registry.getInfo("mul", "v1").orElseThrow().lifecycle());
-
- registry.retire("mul", "v1");
- assertEquals(CircuitRegistry.Lifecycle.RETIRED, registry.getInfo("mul", "v1").orElseThrow().lifecycle());
- }
-
- @Test
- void retireExpiredCircuits() {
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v1"));
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v2"));
-
- // Deprecate v1 with past deadline, v2 with future deadline
- registry.deprecate("mul", "v1", Instant.now().minus(1, ChronoUnit.HOURS), "mul", "v2");
- registry.deprecate("mul", "v2", Instant.now().plus(7, ChronoUnit.DAYS), null, null);
-
- int retired = registry.retireExpiredCircuits();
- assertEquals(1, retired);
-
- assertEquals(CircuitRegistry.Lifecycle.RETIRED, registry.getInfo("mul", "v1").orElseThrow().lifecycle());
- assertEquals(CircuitRegistry.Lifecycle.DEPRECATED, registry.getInfo("mul", "v2").orElseThrow().lifecycle());
- }
-
- @Test
- void retireExpiredCircuits_noExpired() {
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v1"));
- assertEquals(0, registry.retireExpiredCircuits());
- }
-
- @Test
- void validateMigration_validSuccessor() {
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v1"));
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v2"));
- registry.deprecate("mul", "v1", Instant.now().plus(7, ChronoUnit.DAYS), "mul", "v2");
-
- assertTrue(registry.validateMigration("mul", "v1"));
- }
-
- @Test
- void validateMigration_noSuccessor() {
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v1"));
- registry.deprecate("mul", "v1", Instant.now().plus(7, ChronoUnit.DAYS), null, null);
-
- assertFalse(registry.validateMigration("mul", "v1"));
- }
-
- @Test
- void validateMigration_successorRetired() {
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v1"));
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v2"));
- registry.deprecate("mul", "v1", Instant.now().plus(7, ChronoUnit.DAYS), "mul", "v2");
- registry.retire("mul", "v2");
-
- assertFalse(registry.validateMigration("mul", "v1"));
- }
-
- @Test
- void listVersions() {
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v1"));
- registry.register(CircuitRegistry.CircuitVersionInfo.active("mul", "v2"));
- registry.register(CircuitRegistry.CircuitVersionInfo.active("add", "v1"));
-
- assertEquals(2, registry.listVersions("mul").size());
- assertEquals(1, registry.listVersions("add").size());
- assertEquals(0, registry.listVersions("unknown").size());
- }
-
- @Test
- void allowDelegatesToRegister() {
- registry.allow("mul", "v1");
- assertTrue(registry.isAllowed("mul", "v1"));
- var info = registry.getInfo("mul", "v1");
- assertTrue(info.isPresent());
- assertEquals(CircuitRegistry.Lifecycle.ACTIVE, info.get().lifecycle());
- }
-
- @Test
- void deprecateNonExistentIsNoOp() {
- registry.deprecate("nonexistent", "v1", Instant.now(), null, null);
- assertTrue(registry.getInfo("nonexistent", "v1").isEmpty());
- }
-
- @Test
- void retireNonExistentIsNoOp() {
- registry.retire("nonexistent", "v1");
- assertTrue(registry.getInfo("nonexistent", "v1").isEmpty());
- }
-}
diff --git a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryNullifierStoreTest.java b/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryNullifierStoreTest.java
deleted file mode 100644
index edcffee..0000000
--- a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryNullifierStoreTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class InMemoryNullifierStoreTest {
-
- private InMemoryNullifierStore store;
-
- @BeforeEach
- void setUp() {
- store = new InMemoryNullifierStore();
- }
-
- @Test
- void markUsedAndCheck() {
- byte[] nullifier = {1, 2, 3, 4};
- assertFalse(store.isUsed(nullifier));
- assertTrue(store.markUsed(nullifier));
- assertTrue(store.isUsed(nullifier));
- }
-
- @Test
- void markUsedReturnsFalseOnDuplicate() {
- byte[] nullifier = {1, 2, 3, 4};
- assertTrue(store.markUsed(nullifier));
- assertFalse(store.markUsed(nullifier));
- }
-
- @Test
- void differentNullifersIndependent() {
- byte[] n1 = {1, 2, 3};
- byte[] n2 = {4, 5, 6};
- store.markUsed(n1);
-
- assertTrue(store.isUsed(n1));
- assertFalse(store.isUsed(n2));
- }
-
- @Test
- void appScopedMarkAndCheck() {
- byte[] nullifier = {1, 2, 3, 4};
- assertFalse(store.isUsed("app1", nullifier));
- assertTrue(store.markUsed("app1", nullifier));
- assertTrue(store.isUsed("app1", nullifier));
- }
-
- @Test
- void appScopedReturnsFalseOnDuplicate() {
- byte[] nullifier = {1, 2, 3, 4};
- assertTrue(store.markUsed("app1", nullifier));
- assertFalse(store.markUsed("app1", nullifier));
- }
-
- @Test
- void appScopedIndependence() {
- byte[] nullifier = {1, 2, 3, 4};
- store.markUsed("app1", nullifier);
-
- assertTrue(store.isUsed("app1", nullifier));
- assertFalse(store.isUsed("app2", nullifier));
- }
-
- @Test
- void appScopedSameNullifierDifferentApps() {
- byte[] nullifier = {10, 20, 30};
- assertTrue(store.markUsed("app1", nullifier));
- assertTrue(store.markUsed("app2", nullifier)); // different app scope
-
- assertTrue(store.isUsed("app1", nullifier));
- assertTrue(store.isUsed("app2", nullifier));
- }
-
- @Test
- void globalAndScopedAreIndependent() {
- byte[] nullifier = {1, 2, 3};
- store.markUsed(nullifier); // global
- assertFalse(store.isUsed("app1", nullifier)); // scoped is separate
-
- store.markUsed("app1", nullifier);
- assertTrue(store.isUsed("app1", nullifier));
- }
-}
diff --git a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySequenceTrackerTest.java b/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySequenceTrackerTest.java
deleted file mode 100644
index f0c307d..0000000
--- a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySequenceTrackerTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class InMemorySequenceTrackerTest {
-
- private InMemorySequenceTracker tracker;
-
- @BeforeEach
- void setUp() {
- tracker = new InMemorySequenceTracker();
- }
-
- @Test
- void initialSequenceIsNegativeOne() {
- assertEquals(-1L, tracker.getLastSequence("app1", "alice"));
- }
-
- @Test
- void recordFirstSequence() {
- assertTrue(tracker.recordSequence("app1", "alice", 0));
- assertEquals(0L, tracker.getLastSequence("app1", "alice"));
- }
-
- @Test
- void monotonicIncrease() {
- assertTrue(tracker.recordSequence("app1", "alice", 1));
- assertTrue(tracker.recordSequence("app1", "alice", 2));
- assertTrue(tracker.recordSequence("app1", "alice", 5)); // gap is ok, just must increase
- assertEquals(5L, tracker.getLastSequence("app1", "alice"));
- }
-
- @Test
- void rejectDuplicate() {
- tracker.recordSequence("app1", "alice", 3);
- // Duplicate sequence: compute returns current (3) which equals sequence (3),
- // so recordSequence returns true but does not advance. The pipeline handles
- // duplicate rejection via its own <= check in validatePolicy.
- // The tracker's atomicity guarantee is: it never goes backwards.
- assertTrue(tracker.recordSequence("app1", "alice", 3));
- assertEquals(3L, tracker.getLastSequence("app1", "alice"));
- }
-
- @Test
- void rejectDecreasing() {
- tracker.recordSequence("app1", "alice", 5);
- assertFalse(tracker.recordSequence("app1", "alice", 3));
- assertEquals(5L, tracker.getLastSequence("app1", "alice"));
- }
-
- @Test
- void perSubmitterIndependence() {
- tracker.recordSequence("app1", "alice", 10);
- tracker.recordSequence("app1", "bob", 5);
-
- assertEquals(10L, tracker.getLastSequence("app1", "alice"));
- assertEquals(5L, tracker.getLastSequence("app1", "bob"));
- }
-
- @Test
- void perAppIndependence() {
- tracker.recordSequence("app1", "alice", 10);
- tracker.recordSequence("app2", "alice", 1);
-
- assertEquals(10L, tracker.getLastSequence("app1", "alice"));
- assertEquals(1L, tracker.getLastSequence("app2", "alice"));
- }
-}
diff --git a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryStateRootStoreTest.java b/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryStateRootStoreTest.java
deleted file mode 100644
index 2929483..0000000
--- a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryStateRootStoreTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class InMemoryStateRootStoreTest {
-
- private InMemoryStateRootStore store;
-
- @BeforeEach
- void setUp() {
- store = new InMemoryStateRootStore();
- }
-
- @Test
- void initialRootIsNull() {
- assertNull(store.getCurrentRoot("app1"));
- }
-
- @Test
- void initializeGenesisRoot() {
- byte[] genesis = {0, 0, 0, 1};
- store.initialize("app1", genesis);
- assertArrayEquals(genesis, store.getCurrentRoot("app1"));
- }
-
- @Test
- void initializeOnlyOnce() {
- store.initialize("app1", new byte[]{1});
- store.initialize("app1", new byte[]{2}); // should not overwrite
- assertArrayEquals(new byte[]{1}, store.getCurrentRoot("app1"));
- }
-
- @Test
- void updateRoot() {
- store.initialize("app1", new byte[]{1});
- store.updateRoot("app1", new byte[]{2});
- assertArrayEquals(new byte[]{2}, store.getCurrentRoot("app1"));
- }
-
- @Test
- void updateWithoutInitialize() {
- store.updateRoot("app1", new byte[]{5});
- assertArrayEquals(new byte[]{5}, store.getCurrentRoot("app1"));
- }
-
- @Test
- void perAppIndependence() {
- store.initialize("app1", new byte[]{1});
- store.initialize("app2", new byte[]{2});
-
- assertArrayEquals(new byte[]{1}, store.getCurrentRoot("app1"));
- assertArrayEquals(new byte[]{2}, store.getCurrentRoot("app2"));
- }
-
- @Test
- void defensiveCopy() {
- byte[] root = {1, 2, 3};
- store.updateRoot("app1", root);
- root[0] = 99; // mutate original
- assertArrayEquals(new byte[]{1, 2, 3}, store.getCurrentRoot("app1")); // should be unchanged
- }
-}
diff --git a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySubmitterRegistryTest.java b/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySubmitterRegistryTest.java
deleted file mode 100644
index 9534531..0000000
--- a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySubmitterRegistryTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import com.bloxbean.cardano.zeroj.submission.Ed25519Signer;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.security.KeyPair;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class InMemorySubmitterRegistryTest {
-
- private InMemorySubmitterRegistry registry;
- private KeyPair aliceKeys;
- private KeyPair bobKeys;
-
- @BeforeEach
- void setUp() {
- registry = new InMemorySubmitterRegistry();
- aliceKeys = Ed25519Signer.generateKeyPair();
- bobKeys = Ed25519Signer.generateKeyPair();
- }
-
- @Test
- void registerAndLookup() {
- registry.register("alice", aliceKeys.getPublic(), "app1", "app2");
-
- assertTrue(registry.getPublicKey("alice").isPresent());
- assertEquals(aliceKeys.getPublic(), registry.getPublicKey("alice").get());
- }
-
- @Test
- void unknownSubmitter() {
- assertTrue(registry.getPublicKey("unknown").isEmpty());
- }
-
- @Test
- void authorization() {
- registry.register("alice", aliceKeys.getPublic(), "app1");
-
- assertTrue(registry.isAuthorized("alice", "app1"));
- assertFalse(registry.isAuthorized("alice", "app2"));
- assertFalse(registry.isAuthorized("unknown", "app1"));
- }
-
- @Test
- void isActiveAfterRegistration() {
- registry.register("alice", aliceKeys.getPublic(), "app1");
- assertTrue(registry.isActive("alice"));
- }
-
- @Test
- void isActiveUnknownSubmitter() {
- assertFalse(registry.isActive("unknown"));
- }
-
- @Test
- void suspendSubmitter() {
- registry.register("alice", aliceKeys.getPublic(), "app1");
- registry.suspend("alice");
-
- assertFalse(registry.isActive("alice"));
- assertEquals(SubmitterRegistry.SubmitterStatus.SUSPENDED, registry.getStatus("alice"));
- // Key is still present
- assertTrue(registry.getPublicKey("alice").isPresent());
- }
-
- @Test
- void reinstateSubmitter() {
- registry.register("alice", aliceKeys.getPublic(), "app1");
- registry.suspend("alice");
- registry.reinstate("alice");
-
- assertTrue(registry.isActive("alice"));
- assertEquals(SubmitterRegistry.SubmitterStatus.ACTIVE, registry.getStatus("alice"));
- }
-
- @Test
- void revokeSubmitter() {
- registry.register("alice", aliceKeys.getPublic(), "app1");
- registry.revoke("alice");
-
- assertFalse(registry.isActive("alice"));
- assertEquals(SubmitterRegistry.SubmitterStatus.REVOKED, registry.getStatus("alice"));
- }
-
- @Test
- void reinstateDoesNotAffectRevoked() {
- registry.register("alice", aliceKeys.getPublic(), "app1");
- registry.revoke("alice");
- registry.reinstate("alice"); // should have no effect
-
- assertFalse(registry.isActive("alice"));
- assertEquals(SubmitterRegistry.SubmitterStatus.REVOKED, registry.getStatus("alice"));
- }
-
- @Test
- void suspendDoesNotAffectRevoked() {
- registry.register("alice", aliceKeys.getPublic(), "app1");
- registry.revoke("alice");
- registry.suspend("alice"); // should not downgrade from REVOKED
-
- assertEquals(SubmitterRegistry.SubmitterStatus.REVOKED, registry.getStatus("alice"));
- }
-
- @Test
- void rotateKey() {
- registry.register("alice", aliceKeys.getPublic(), "app1");
- assertTrue(registry.rotateKey("alice", bobKeys.getPublic()));
-
- assertEquals(bobKeys.getPublic(), registry.getPublicKey("alice").orElseThrow());
- }
-
- @Test
- void rotateKeyFailsWhenSuspended() {
- registry.register("alice", aliceKeys.getPublic(), "app1");
- registry.suspend("alice");
- assertFalse(registry.rotateKey("alice", bobKeys.getPublic()));
- }
-
- @Test
- void rotateKeyFailsWhenRevoked() {
- registry.register("alice", aliceKeys.getPublic(), "app1");
- registry.revoke("alice");
- assertFalse(registry.rotateKey("alice", bobKeys.getPublic()));
- }
-
- @Test
- void rotateKeyFailsForUnknown() {
- assertFalse(registry.rotateKey("unknown", aliceKeys.getPublic()));
- }
-
- @Test
- void multipleSubmittersIndependent() {
- registry.register("alice", aliceKeys.getPublic(), "app1");
- registry.register("bob", bobKeys.getPublic(), "app1");
- registry.suspend("alice");
-
- assertFalse(registry.isActive("alice"));
- assertTrue(registry.isActive("bob"));
- }
-}
diff --git a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/SubmissionIngestionPipelineTest.java b/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/SubmissionIngestionPipelineTest.java
deleted file mode 100644
index 5dfae58..0000000
--- a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/SubmissionIngestionPipelineTest.java
+++ /dev/null
@@ -1,412 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import com.bloxbean.cardano.zeroj.api.*;
-import com.bloxbean.cardano.zeroj.backend.spi.InMemoryVerificationKeyRegistry;
-import com.bloxbean.cardano.zeroj.codec.SnarkjsJsonCodec;
-import com.bloxbean.cardano.zeroj.verifier.core.VerifierOrchestrator;
-import com.bloxbean.cardano.zeroj.verifier.core.VerifierRegistry;
-import com.bloxbean.cardano.zeroj.verifier.groth16.bn254.Groth16BN254Verifier;
-import com.bloxbean.cardano.zeroj.submission.AppProofSubmission;
-import com.bloxbean.cardano.zeroj.submission.Ed25519Signer;
-import com.bloxbean.cardano.zeroj.submission.SubmissionHash;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult.RejectionReason;
-import com.bloxbean.cardano.zeroj.submission.SubmissionResult.ValidationStage;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Security-focused tests for the submission ingestion pipeline.
- *
- * Uses real snarkjs Groth16/BN254 proofs and Ed25519 signatures.
- * Tests every rejection path explicitly.
- */
-class SubmissionIngestionPipelineTest {
-
- private static final String APP_ID = "test-app";
- private static final String CIRCUIT_ID = "multiplier";
- private static final String CIRCUIT_VERSION = "v1";
- private static final String SUBMITTER_ID = "alice";
-
- private SubmissionIngestionPipeline pipeline;
- private InMemoryVerificationKeyRegistry vkRegistry;
- private InMemorySubmitterRegistry submitterRegistry;
- private InMemoryCircuitAllowlist circuitAllowlist;
- private InMemoryStateRootStore stateRootStore;
- private InMemorySequenceTracker sequenceTracker;
- private InMemoryNullifierStore nullifierStore;
-
- private KeyPair aliceKeyPair;
- private KeyPair bobKeyPair;
- private byte[] vkHash;
- private String proofJson;
- private byte[] genesisRoot;
-
- @BeforeEach
- void setUp() {
- // Generate Ed25519 key pairs
- aliceKeyPair = Ed25519Signer.generateKeyPair();
- bobKeyPair = Ed25519Signer.generateKeyPair();
-
- // Load real snarkjs test vectors
- proofJson = loadString("/test-vectors/groth16-bn254/proof.json");
- String vkJson = loadString("/test-vectors/groth16-bn254/verification_key.json");
- byte[] vkBytes = vkJson.getBytes(StandardCharsets.UTF_8);
- vkHash = sha256(vkBytes);
-
- // Set up verifier with real BN254 Groth16 backend
- var verifierRegistry = VerifierRegistry.empty();
- verifierRegistry.register(new Groth16BN254Verifier());
-
- // VK registry
- vkRegistry = new InMemoryVerificationKeyRegistry();
- vkRegistry.register(VerificationMaterial.of(
- vkBytes, ProofSystemId.GROTH16, CurveId.BN254,
- new CircuitId(CIRCUIT_ID), vkHash));
-
- var orchestrator = new VerifierOrchestrator(verifierRegistry, vkRegistry);
-
- // Submitter registry — Alice authorized, Bob not registered
- submitterRegistry = new InMemorySubmitterRegistry();
- submitterRegistry.register(SUBMITTER_ID, aliceKeyPair.getPublic(), APP_ID);
-
- // Circuit allowlist
- circuitAllowlist = new InMemoryCircuitAllowlist();
- circuitAllowlist.allow(CIRCUIT_ID, CIRCUIT_VERSION);
-
- // State root store — initialize with genesis root
- stateRootStore = new InMemoryStateRootStore();
- genesisRoot = sha256("genesis".getBytes(StandardCharsets.UTF_8));
- stateRootStore.initialize(APP_ID, genesisRoot);
-
- // Sequence tracker + nullifier store
- sequenceTracker = new InMemorySequenceTracker();
- nullifierStore = new InMemoryNullifierStore();
-
- pipeline = new SubmissionIngestionPipeline(
- orchestrator, vkRegistry, submitterRegistry, circuitAllowlist,
- stateRootStore, sequenceTracker, nullifierStore);
- }
-
- // ==================== HAPPY PATH ====================
-
- @Test
- void happyPath_validSubmissionAccepted() {
- var submission = validSubmission(1, genesisRoot, sha256("state-1".getBytes()));
- var result = pipeline.process(submission);
-
- assertTrue(result.accepted(), "Valid submission should be accepted. Got: " + result);
- assertEquals(ValidationStage.ACCEPTED, result.stage());
- }
-
- @Test
- void happyPath_sequentialSubmissionsAccepted() {
- var root1 = sha256("state-1".getBytes());
- var root2 = sha256("state-2".getBytes());
-
- var sub1 = validSubmission(1, genesisRoot, root1);
- var sub2 = validSubmission(2, root1, root2);
-
- assertTrue(pipeline.process(sub1).accepted());
- assertTrue(pipeline.process(sub2).accepted());
- }
-
- // ==================== STAGE 2: SIGNATURE ATTACKS ====================
-
- @Test
- void security_unknownSubmitterRejected() {
- var submission = buildSubmission("unknown-attacker", bobKeyPair, 1, genesisRoot, sha256("hack".getBytes()), null);
- var result = pipeline.process(submission);
-
- assertFalse(result.accepted());
- assertEquals(ValidationStage.SIGNATURE, result.stage());
- assertEquals(RejectionReason.UNKNOWN_SUBMITTER, result.reason().orElse(null));
- }
-
- @Test
- void security_unauthorizedSubmitterRejected() {
- // Register bob but don't authorize for this app
- submitterRegistry.register("bob", bobKeyPair.getPublic(), "other-app");
-
- var submission = buildSubmission("bob", bobKeyPair, 1, genesisRoot, sha256("hack".getBytes()), null);
- var result = pipeline.process(submission);
-
- assertFalse(result.accepted());
- assertEquals(ValidationStage.SIGNATURE, result.stage());
- assertEquals(RejectionReason.UNAUTHORIZED_SUBMITTER, result.reason().orElse(null));
- }
-
- @Test
- void security_tamperedSignatureRejected() {
- var submission = validSubmissionUnsigned(1, genesisRoot, sha256("state-1".getBytes()));
- // Sign with wrong key (bob's key, but submitterId is alice)
- byte[] hash = SubmissionHash.compute(submission);
- byte[] wrongSig = Ed25519Signer.sign(hash, bobKeyPair.getPrivate());
-
- var tampered = AppProofSubmission.builder()
- .appId(submission.appId())
- .proofSystem(submission.proofSystem())
- .curve(submission.curve())
- .circuitId(submission.circuitId())
- .circuitVersion(submission.circuitVersion())
- .prevStateRoot(submission.prevStateRoot())
- .newStateRoot(submission.newStateRoot())
- .publicInputs(submission.publicInputs())
- .proofBytes(submission.proofBytes())
- .vkHash(submission.vkHash())
- .submitterId(submission.submitterId())
- .submitterSignature(wrongSig) // signed by bob, not alice
- .sequence(submission.sequence())
- .build();
-
- var result = pipeline.process(tampered);
- assertFalse(result.accepted());
- assertEquals(ValidationStage.SIGNATURE, result.stage());
- assertEquals(RejectionReason.INVALID_SIGNATURE, result.reason().orElse(null));
- }
-
- @Test
- void security_signatureOverDifferentDataRejected() {
- // Build a valid submission but replace the signature with one over different data
- var submission = validSubmissionUnsigned(1, genesisRoot, sha256("state-1".getBytes()));
- byte[] forgedMessage = sha256("forged-data".getBytes());
- byte[] forgedSig = Ed25519Signer.sign(forgedMessage, aliceKeyPair.getPrivate());
-
- var forged = rebuildWithSignature(submission, forgedSig);
- var result = pipeline.process(forged);
-
- assertFalse(result.accepted());
- assertEquals(ValidationStage.SIGNATURE, result.stage());
- assertEquals(RejectionReason.INVALID_SIGNATURE, result.reason().orElse(null));
- }
-
- // ==================== STAGE 3: CIRCUIT ATTACKS ====================
-
- @Test
- void security_retiredCircuitRejected() {
- circuitAllowlist.retire(CIRCUIT_ID, CIRCUIT_VERSION);
-
- var submission = validSubmission(1, genesisRoot, sha256("state-1".getBytes()));
- var result = pipeline.process(submission);
-
- assertFalse(result.accepted());
- assertEquals(ValidationStage.CIRCUIT_RESOLUTION, result.stage());
- assertEquals(RejectionReason.RETIRED_CIRCUIT, result.reason().orElse(null));
- }
-
- @Test
- void security_unknownCircuitVersionRejected() {
- var submission = buildSubmission(SUBMITTER_ID, aliceKeyPair, 1, genesisRoot,
- sha256("state-1".getBytes()), null, "v99");
- var result = pipeline.process(submission);
-
- assertFalse(result.accepted());
- assertEquals(ValidationStage.CIRCUIT_RESOLUTION, result.stage());
- }
-
- // ==================== STAGE 4: CRYPTOGRAPHIC ATTACKS ====================
-
- @Test
- void security_invalidProofRejected() {
- // Use tampered proof (pi_a.x + 1)
- String tamperedProofJson = loadString("/test-vectors/groth16-bn254-invalid/proof_tampered.json");
-
- var submission = buildSubmissionWithProof(tamperedProofJson, 1, genesisRoot, sha256("state-1".getBytes()));
- var result = pipeline.process(submission);
-
- assertFalse(result.accepted());
- assertEquals(ValidationStage.CRYPTOGRAPHIC_VERIFICATION, result.stage());
- assertEquals(RejectionReason.PROOF_INVALID, result.reason().orElse(null));
- }
-
- // ==================== STAGE 5: POLICY ATTACKS ====================
-
- @Test
- void security_staleStateRootRejected() {
- // Submit with a root that doesn't match the current state
- byte[] wrongRoot = sha256("wrong-root".getBytes());
- var submission = validSubmission(1, wrongRoot, sha256("state-1".getBytes()));
-
- var result = pipeline.process(submission);
- assertFalse(result.accepted());
- assertEquals(ValidationStage.POLICY, result.stage());
- assertEquals(RejectionReason.STALE_STATE_ROOT, result.reason().orElse(null));
- }
-
- @Test
- void security_replayedSequenceRejected() {
- var root1 = sha256("state-1".getBytes());
- var sub1 = validSubmission(1, genesisRoot, root1);
- assertTrue(pipeline.process(sub1).accepted());
-
- // Replay with same sequence number
- var replay = validSubmission(1, root1, sha256("state-2".getBytes()));
- var result = pipeline.process(replay);
-
- assertFalse(result.accepted());
- assertEquals(ValidationStage.POLICY, result.stage());
- assertEquals(RejectionReason.DUPLICATE_SEQUENCE, result.reason().orElse(null));
- }
-
- @Test
- void security_usedNullifierRejected() {
- byte[] nullifier = sha256("unique-nullifier".getBytes());
-
- var root1 = sha256("state-1".getBytes());
- var sub1 = buildSubmission(SUBMITTER_ID, aliceKeyPair, 1, genesisRoot, root1, nullifier);
- assertTrue(pipeline.process(sub1).accepted());
-
- // Try to reuse the same nullifier (double-spend attempt)
- var root2 = sha256("state-2".getBytes());
- var sub2 = buildSubmission(SUBMITTER_ID, aliceKeyPair, 2, root1, root2, nullifier);
- var result = pipeline.process(sub2);
-
- assertFalse(result.accepted());
- assertEquals(ValidationStage.POLICY, result.stage());
- assertEquals(RejectionReason.USED_NULLIFIER, result.reason().orElse(null));
- }
-
- @Test
- void security_outOfOrderSequenceRejected() {
- // Accept sequence 5
- var root1 = sha256("state-1".getBytes());
- var sub1 = validSubmission(5, genesisRoot, root1);
- assertTrue(pipeline.process(sub1).accepted());
-
- // Try sequence 3 (going backwards)
- var root2 = sha256("state-2".getBytes());
- var sub2 = validSubmission(3, root1, root2);
- var result = pipeline.process(sub2);
-
- assertFalse(result.accepted());
- assertEquals(ValidationStage.POLICY, result.stage());
- assertEquals(RejectionReason.DUPLICATE_SEQUENCE, result.reason().orElse(null));
- }
-
- // ==================== HELPERS ====================
-
- private AppProofSubmission validSubmission(long sequence, byte[] prevRoot, byte[] newRoot) {
- return buildSubmission(SUBMITTER_ID, aliceKeyPair, sequence, prevRoot, newRoot, null);
- }
-
- private AppProofSubmission validSubmissionUnsigned(long sequence, byte[] prevRoot, byte[] newRoot) {
- return AppProofSubmission.builder()
- .appId(APP_ID)
- .proofSystem(ProofSystemId.GROTH16)
- .curve(CurveId.BN254)
- .circuitId(CIRCUIT_ID)
- .circuitVersion(CIRCUIT_VERSION)
- .prevStateRoot(prevRoot)
- .newStateRoot(newRoot)
- .publicInputs(List.of(BigInteger.valueOf(33), BigInteger.valueOf(3)))
- .proofBytes(proofJson.getBytes(StandardCharsets.UTF_8))
- .vkHash(vkHash)
- .submitterId(SUBMITTER_ID)
- .submitterSignature(new byte[64]) // placeholder
- .sequence(sequence)
- .build();
- }
-
- private AppProofSubmission buildSubmission(String submitterId, KeyPair keyPair,
- long sequence, byte[] prevRoot, byte[] newRoot,
- byte[] nullifier) {
- return buildSubmission(submitterId, keyPair, sequence, prevRoot, newRoot, nullifier, CIRCUIT_VERSION);
- }
-
- private AppProofSubmission buildSubmission(String submitterId, KeyPair keyPair,
- long sequence, byte[] prevRoot, byte[] newRoot,
- byte[] nullifier, String circuitVersion) {
- // Build unsigned first to compute hash
- var unsigned = AppProofSubmission.builder()
- .appId(APP_ID)
- .proofSystem(ProofSystemId.GROTH16)
- .curve(CurveId.BN254)
- .circuitId(CIRCUIT_ID)
- .circuitVersion(circuitVersion)
- .prevStateRoot(prevRoot)
- .newStateRoot(newRoot)
- .publicInputs(List.of(BigInteger.valueOf(33), BigInteger.valueOf(3)))
- .proofBytes(proofJson.getBytes(StandardCharsets.UTF_8))
- .vkHash(vkHash)
- .submitterId(submitterId)
- .submitterSignature(new byte[64]) // placeholder for hash computation
- .sequence(sequence)
- .nullifier(nullifier)
- .build();
-
- // Sign the submission hash
- byte[] hash = SubmissionHash.compute(unsigned);
- byte[] signature = Ed25519Signer.sign(hash, keyPair.getPrivate());
-
- // Rebuild with real signature
- return rebuildWithSignature(unsigned, signature);
- }
-
- private AppProofSubmission buildSubmissionWithProof(String customProofJson,
- long sequence, byte[] prevRoot, byte[] newRoot) {
- var unsigned = AppProofSubmission.builder()
- .appId(APP_ID)
- .proofSystem(ProofSystemId.GROTH16)
- .curve(CurveId.BN254)
- .circuitId(CIRCUIT_ID)
- .circuitVersion(CIRCUIT_VERSION)
- .prevStateRoot(prevRoot)
- .newStateRoot(newRoot)
- .publicInputs(List.of(BigInteger.valueOf(33), BigInteger.valueOf(3)))
- .proofBytes(customProofJson.getBytes(StandardCharsets.UTF_8))
- .vkHash(vkHash)
- .submitterId(SUBMITTER_ID)
- .submitterSignature(new byte[64])
- .sequence(sequence)
- .build();
-
- byte[] hash = SubmissionHash.compute(unsigned);
- byte[] signature = Ed25519Signer.sign(hash, aliceKeyPair.getPrivate());
- return rebuildWithSignature(unsigned, signature);
- }
-
- private AppProofSubmission rebuildWithSignature(AppProofSubmission original, byte[] signature) {
- return AppProofSubmission.builder()
- .appId(original.appId())
- .proofSystem(original.proofSystem())
- .curve(original.curve())
- .circuitId(original.circuitId())
- .circuitVersion(original.circuitVersion())
- .prevStateRoot(original.prevStateRoot())
- .newStateRoot(original.newStateRoot())
- .publicInputs(original.publicInputs())
- .proofBytes(original.proofBytes())
- .vkHash(original.vkHash())
- .submitterId(original.submitterId())
- .submitterSignature(signature)
- .sequence(original.sequence())
- .nullifier(original.nullifier())
- .metadata(original.metadata())
- .build();
- }
-
- private String loadString(String path) {
- try (var in = getClass().getResourceAsStream(path)) {
- if (in == null) fail("Resource not found: " + path);
- return new String(in.readAllBytes(), StandardCharsets.UTF_8);
- } catch (Exception e) {
- return fail("Failed to read: " + path);
- }
- }
-
- private static byte[] sha256(byte[] data) {
- try {
- return MessageDigest.getInstance("SHA-256").digest(data);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/VersionedVkRegistryTest.java b/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/VersionedVkRegistryTest.java
deleted file mode 100644
index 136055f..0000000
--- a/zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/VersionedVkRegistryTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-package com.bloxbean.cardano.zeroj.ingestion;
-
-import com.bloxbean.cardano.zeroj.api.CircuitId;
-import com.bloxbean.cardano.zeroj.api.VerificationKeyRef;
-import com.bloxbean.cardano.zeroj.api.VerificationMaterial;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Optional;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class VersionedVkRegistryTest {
-
- private VersionedVkRegistry registry;
- private VerificationMaterial material1;
- private VerificationMaterial material2;
-
- @BeforeEach
- void setUp() {
- registry = new VersionedVkRegistry();
- material1 = createMaterial("circuit1", new byte[]{1, 2, 3});
- material2 = createMaterial("circuit1", new byte[]{4, 5, 6});
- }
-
- @Test
- void registerAndLookupByHash() {
- registry.registerVersion(material1, null);
-
- var hash = sha256(material1.vkBytes());
- var result = registry.lookup(new VerificationKeyRef.ByHash(hash));
- assertTrue(result.isPresent());
- assertArrayEquals(material1.vkBytes(), result.get().vkBytes());
- }
-
- @Test
- void registerAndLookupById() {
- registry.registerVersion(material1, null);
-
- var result = registry.lookup(new VerificationKeyRef.ById("circuit1"));
- assertTrue(result.isPresent());
- }
-
- @Test
- void lookupMissing() {
- assertTrue(registry.lookup(new VerificationKeyRef.ByHash(new byte[32])).isEmpty());
- assertTrue(registry.lookup(new VerificationKeyRef.ById("missing")).isEmpty());
- }
-
- @Test
- void isValidNonExpired() {
- registry.registerVersion(material1, null);
- assertTrue(registry.isValid(sha256(material1.vkBytes())));
- }
-
- @Test
- void isValidExpired() {
- registry.registerVersion(material1, Instant.now().minus(1, ChronoUnit.HOURS));
- assertFalse(registry.isValid(sha256(material1.vkBytes())));
- }
-
- @Test
- void rotateExpiresOldVk() {
- registry.registerVersion(material1, null);
- registry.rotate(material2, Duration.ofHours(1));
-
- // Both should be in registry
- assertEquals(2, registry.listEntries("circuit1").size());
-
- // Current should be material2
- var current = registry.getCurrentVk("circuit1").orElseThrow();
- assertArrayEquals(material2.vkBytes(), current.vkBytes());
-
- // Old one should have expiry set
- var entries = registry.listEntries("circuit1");
- assertNotNull(entries.getFirst().expiresAt());
- assertNull(entries.getLast().expiresAt());
- }
-
- @Test
- void rotateReturnSuccess() {
- registry.registerVersion(material1, null);
- var result = registry.rotate(material2, Duration.ofHours(1));
- assertInstanceOf(VersionedVkRegistry.RotationResult.Success.class, result);
- }
-
- @Test
- void policyEnforcement_minTransitionWindow() {
- var policy = new VkRotationPolicy(Duration.ofHours(2), 5, Duration.ZERO);
- var reg = new VersionedVkRegistry(policy);
- reg.registerVersion(material1, null);
-
- // 1-hour window is less than 2-hour minimum
- var result = reg.rotate(material2, Duration.ofHours(1));
- assertInstanceOf(VersionedVkRegistry.RotationResult.Rejected.class, result);
- assertTrue(((VersionedVkRegistry.RotationResult.Rejected) result).reason()
- .contains("shorter than policy minimum"));
- }
-
- @Test
- void policyEnforcement_maxActiveVks() {
- var policy = new VkRotationPolicy(Duration.ZERO, 2, Duration.ZERO);
- var reg = new VersionedVkRegistry(policy);
- reg.registerVersion(material1, null);
- reg.registerVersion(material2, null);
-
- // Third VK would exceed max of 2
- var material3 = createMaterial("circuit1", new byte[]{7, 8, 9});
- var result = reg.rotate(material3, Duration.ofHours(1));
- assertInstanceOf(VersionedVkRegistry.RotationResult.Rejected.class, result);
- assertTrue(((VersionedVkRegistry.RotationResult.Rejected) result).reason()
- .contains("max active VKs"));
- }
-
- @Test
- void policyEnforcement_minTimeBetweenRotations() {
- var policy = new VkRotationPolicy(Duration.ZERO, 10, Duration.ofHours(1));
- var reg = new VersionedVkRegistry(policy);
- reg.registerVersion(material1, null);
-
- // Immediate rotation should be rejected
- var result = reg.rotate(material2, Duration.ofHours(1));
- assertInstanceOf(VersionedVkRegistry.RotationResult.Rejected.class, result);
- assertTrue(((VersionedVkRegistry.RotationResult.Rejected) result).reason()
- .contains("Too soon"));
- }
-
- @Test
- void getCurrentVkReturnsLatestNonExpired() {
- registry.registerVersion(material1, Instant.now().minus(1, ChronoUnit.HOURS)); // expired
- registry.registerVersion(material2, null); // active
-
- var current = registry.getCurrentVk("circuit1").orElseThrow();
- assertArrayEquals(material2.vkBytes(), current.vkBytes());
- }
-
- @Test
- void getCurrentVkEmptyWhenAllExpired() {
- registry.registerVersion(material1, Instant.now().minus(1, ChronoUnit.HOURS));
- assertTrue(registry.getCurrentVk("circuit1").isEmpty());
- }
-
- @Test
- void registerViaInterface() {
- registry.register(material1);
- assertTrue(registry.lookup(new VerificationKeyRef.ById("circuit1")).isPresent());
- }
-
- @Test
- void vkEntryIsExpiredAt() {
- var now = Instant.now();
- var entry = new VersionedVkRegistry.VkEntry(material1, now, now.minus(1, ChronoUnit.HOURS));
- assertTrue(entry.isExpiredAt(now));
-
- var entry2 = new VersionedVkRegistry.VkEntry(material1, now, null);
- assertFalse(entry2.isExpiredAt(now));
- }
-
- private static VerificationMaterial createMaterial(String circuitId, byte[] vkBytes) {
- return new VerificationMaterial(
- vkBytes,
- com.bloxbean.cardano.zeroj.api.ProofSystemId.GROTH16,
- com.bloxbean.cardano.zeroj.api.CurveId.BN254,
- new CircuitId(circuitId),
- Optional.of(sha256(vkBytes))
- );
- }
-
- private static byte[] sha256(byte[] data) {
- try { return MessageDigest.getInstance("SHA-256").digest(data); }
- catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); }
- }
-}
diff --git a/zeroj-onchain-julc/README.md b/zeroj-onchain-julc/README.md
new file mode 100644
index 0000000..390e643
--- /dev/null
+++ b/zeroj-onchain-julc/README.md
@@ -0,0 +1,48 @@
+# zeroj-onchain-julc
+
+Reusable Julc validators and on-chain helpers for Cardano Plutus V3 proof
+verification.
+
+This module is the Cardano on-chain verification layer for ZeroJ. It contains
+Julc-compiled spending validators, proof/VK conversion helpers, and feasibility
+tools for estimating whether a proof system and curve are practical on Plutus
+V3.
+
+## Current Status
+
+| Validator / Helper | Status | Notes |
+|--------------------|--------|-------|
+| `Groth16BLS12381Verifier` | Working | Production-oriented BLS12-381 Groth16 verifier using Plutus V3 BLS builtins |
+| `PlonkBLS12381FullVerifier` | Experimental prototype | Re-derives transcript and checks inverse constraints; KZG batch opening pairing check is deferred |
+| `SnarkjsToCardano` | Working helper | Converts snarkjs Groth16 JSON points to Cardano-compatible compressed bytes |
+| `ProverToCardano` | Working helper | Converts ZeroJ prover artifacts to on-chain data shapes |
+| `ScriptBudgetEstimator` | Planning helper | Estimates Plutus CPU/memory budgets for supported combinations |
+| `OnChainFeasibility` | Planning helper | Matrix for proof system / curve support on Cardano |
+| `ReferenceScriptDeployer` | Config helper | Describes CIP-0033 reference-script deployment patterns; does not submit transactions |
+
+## Why It Is Useful
+
+- Bridges ZeroJ off-chain proof generation with Cardano on-chain verification.
+- Keeps Groth16 BLS12-381 as the reliable Plutus V3 path.
+- Makes PlonK status explicit and measurable without overstating production
+ readiness.
+- Provides budget and deployment metadata for applications using CCL or other
+ Cardano transaction builders.
+
+## Testing
+
+```bash
+./gradlew :zeroj-onchain-julc:test
+```
+
+The tests run validators in the Julc VM and include Groth16 positive/negative
+checks, pure Java Groth16 prover to on-chain verification, budget estimation, and
+the PlonK prototype transcript/inverse-check path.
+
+## Gradle
+
+```gradle
+dependencies {
+ implementation 'com.bloxbean.cardano:zeroj-onchain-julc'
+}
+```
diff --git a/zeroj-onchain-julc/build.gradle b/zeroj-onchain-julc/build.gradle
index bd37920..5e6fcb7 100644
--- a/zeroj-onchain-julc/build.gradle
+++ b/zeroj-onchain-julc/build.gradle
@@ -2,7 +2,7 @@ plugins {
id 'java-library'
}
-description = 'ZeroJ on-chain Julc verifiers — reusable Groth16 and PlonK BLS12-381 Plutus V3 validators'
+description = 'ZeroJ on-chain Julc verifiers — reusable Groth16 validator and experimental PlonK BLS12-381 prototype'
ext {
julcVersion = '0.1.0-pre10'
diff --git a/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/OnChainFeasibility.java b/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/OnChainFeasibility.java
similarity index 57%
rename from incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/OnChainFeasibility.java
rename to zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/OnChainFeasibility.java
index 781f807..fad0a08 100644
--- a/incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/OnChainFeasibility.java
+++ b/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/OnChainFeasibility.java
@@ -1,4 +1,4 @@
-package com.bloxbean.cardano.zeroj.onchain;
+package com.bloxbean.cardano.zeroj.onchain.julc;
import com.bloxbean.cardano.zeroj.api.CurveId;
import com.bloxbean.cardano.zeroj.api.ProofSystemId;
@@ -6,35 +6,19 @@
import java.util.List;
/**
- * Structured feasibility matrix for on-chain ZK proof verification on Cardano.
- *
- * Provides a data-driven view of which proof system / curve combinations
- * are feasible for on-chain verification given Plutus V3 constraints.
- *
- * Cardano protocol parameters limit script execution to ~10B CPU units and ~14MB memory
- * per transaction (as of Chang hard fork). These limits determine feasibility.
+ * Feasibility matrix for ZeroJ proof verification on Cardano Plutus V3.
*/
public final class OnChainFeasibility {
private OnChainFeasibility() {}
- /**
- * Feasibility status for an on-chain verification configuration.
- */
public enum Status {
- /** Fully working and tested on testnet/mainnet. */
WORKING,
- /** Experimental — prototype exists, not production-ready. */
EXPERIMENTAL,
- /** Assessment only — theoretical analysis, no implementation. */
ASSESSMENT_ONLY,
- /** Not feasible with current Plutus capabilities. */
NOT_FEASIBLE
}
- /**
- * A single entry in the feasibility matrix.
- */
public record Entry(
ProofSystemId proofSystem,
CurveId curve,
@@ -45,53 +29,47 @@ public record Entry(
) {}
/**
- * Get the current feasibility matrix based on known implementations and estimates.
+ * Current implementation and feasibility view for proof-system / curve
+ * combinations relevant to Cardano.
*/
public static List matrix() {
return List.of(
new Entry(ProofSystemId.GROTH16, CurveId.BLS12_381, Status.WORKING,
ScriptBudgetEstimator.estimateCpu(ProofSystemId.GROTH16, CurveId.BLS12_381, 1),
ScriptBudgetEstimator.estimateMemory(ProofSystemId.GROTH16, CurveId.BLS12_381, 1),
- "Verified on Cardano preprod via Julc. ~2.4B CPU for 1 public input."),
+ "Shipping on-chain path via Julc and Plutus V3 BLS12-381 builtins."),
new Entry(ProofSystemId.PLONK, CurveId.BLS12_381, Status.EXPERIMENTAL,
ScriptBudgetEstimator.estimateCpu(ProofSystemId.PLONK, CurveId.BLS12_381, 1),
ScriptBudgetEstimator.estimateMemory(ProofSystemId.PLONK, CurveId.BLS12_381, 1),
- "Feasible: Keccak_256 (CIP-0101, Chang HF) and Sha2_256 (Alonzo) both available in Plutus V3. "
- + "snarkjs PlonK uses Keccak_256, gnark PlonK uses Sha2_256 for Fiat-Shamir."),
+ "Prototype only: transcript and inverse checks exist, but the KZG batch opening pairing check is deferred."),
new Entry(ProofSystemId.HALO2, CurveId.BLS12_381, Status.ASSESSMENT_ONLY,
-1, -1,
- "KZG variant on BLS12-381. Very high cost due to many MSM operations."),
+ "Research only; expected high cost due to MSM-heavy verification."),
new Entry(ProofSystemId.GROTH16, CurveId.BN254, Status.NOT_FEASIBLE,
-1, -1,
- "No BN254 pairing builtins in Plutus V3. Cannot verify on-chain."),
+ "No BN254 pairing builtins in Plutus V3."),
new Entry(ProofSystemId.PLONK, CurveId.BN254, Status.NOT_FEASIBLE,
-1, -1,
- "No BN254 pairing builtins in Plutus V3. Cannot verify on-chain."),
+ "No BN254 pairing builtins in Plutus V3."),
new Entry(ProofSystemId.HALO2, CurveId.PALLAS, Status.NOT_FEASIBLE,
-1, -1,
- "No Pallas curve builtins in Plutus. IPA verification not implementable on-chain.")
+ "No Pallas curve builtins in Plutus V3.")
);
}
- /**
- * Look up feasibility for a specific combination.
- */
public static Entry lookup(ProofSystemId proofSystem, CurveId curve) {
return matrix().stream()
.filter(e -> e.proofSystem() == proofSystem && e.curve() == curve)
.findFirst()
.orElse(new Entry(proofSystem, curve, Status.NOT_FEASIBLE, -1, -1,
- "Unknown combination — no assessment available."));
+ "Unknown combination; no assessment available."));
}
- /**
- * Check if on-chain verification is feasible (WORKING or EXPERIMENTAL).
- */
public static boolean isFeasible(ProofSystemId proofSystem, CurveId curve) {
var entry = lookup(proofSystem, curve);
return entry.status() == Status.WORKING || entry.status() == Status.EXPERIMENTAL;
diff --git a/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/PlonkBLS12381FullVerifier.java b/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/PlonkBLS12381FullVerifier.java
index a98c9dd..e0deae2 100644
--- a/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/PlonkBLS12381FullVerifier.java
+++ b/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/PlonkBLS12381FullVerifier.java
@@ -10,9 +10,9 @@
import java.math.BigInteger;
/**
- * Full trustless on-chain PlonK BLS12-381 verifier (Plutus V3).
+ * Experimental on-chain PlonK BLS12-381 verifier prototype (Plutus V3).
*
- * Performs complete verification including Fiat-Shamir challenge re-derivation
+ *
Performs Fiat-Shamir challenge re-derivation
* matching gnark's exact transcript format:
*
* - SHA-256 hash with challenge name prefix and hash chain
@@ -25,7 +25,10 @@
* that uncompressed bytes correspond to valid G1 points by uncompressing and
* comparing with the compressed versions.
*
- * Specialized for 1 public input (multiplier circuit: Z=33).
+ * This version is not a full trustless PlonK verifier yet: the KZG batch
+ * opening pairing check is deferred. It is specialized for 1 public input
+ * (multiplier circuit: Z=33) and currently validates the transcript and
+ * precomputed inverse constraints.
*/
@SpendingValidator
public class PlonkBLS12381FullVerifier {
@@ -198,7 +201,9 @@ static boolean validate(PlutusData datum, PlonkProof p, PlutusData ctx) {
// additional transcript rounds (v, u) from the KZG fold step.
// This requires more work to implement — saving for next iteration.
- // For this version: verify Fiat-Shamir + inverse checks + return true if all pass
+ // Prototype scope: verify Fiat-Shamir + inverse checks + return true if all pass.
+ // This is not a production PlonK acceptance condition until the KZG
+ // batch opening pairing check above is implemented.
return inv1Ok && inv2Ok;
}
diff --git a/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/ReferenceScriptDeployer.java b/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/ReferenceScriptDeployer.java
new file mode 100644
index 0000000..ee4499d
--- /dev/null
+++ b/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/ReferenceScriptDeployer.java
@@ -0,0 +1,41 @@
+package com.bloxbean.cardano.zeroj.onchain.julc;
+
+/**
+ * Structured deployment configuration for Cardano CIP-0033 reference scripts.
+ *
+ * The class does not submit transactions. It records the deployment pattern
+ * and data needed by transaction-building integrations such as zeroj-ccl.
+ */
+public final class ReferenceScriptDeployer {
+
+ private ReferenceScriptDeployer() {}
+
+ public enum DeploymentPattern {
+ VK_IN_SCRIPT,
+ REFERENCE_SCRIPT_DATUM_VK,
+ VK_HASH_COMMITMENT
+ }
+
+ public record DeploymentConfig(
+ DeploymentPattern pattern,
+ byte[] scriptBytes,
+ byte[] vkBytes,
+ byte[] vkHash,
+ int estimatedScriptSize
+ ) {
+ public static DeploymentConfig vkInScript(byte[] scriptBytes, byte[] vkBytes) {
+ return new DeploymentConfig(DeploymentPattern.VK_IN_SCRIPT,
+ scriptBytes, vkBytes, null, scriptBytes.length);
+ }
+
+ public static DeploymentConfig referenceWithDatumVk(byte[] scriptBytes, byte[] vkBytes) {
+ return new DeploymentConfig(DeploymentPattern.REFERENCE_SCRIPT_DATUM_VK,
+ scriptBytes, vkBytes, null, scriptBytes.length);
+ }
+
+ public static DeploymentConfig vkHashCommitment(byte[] scriptBytes, byte[] vkHash) {
+ return new DeploymentConfig(DeploymentPattern.VK_HASH_COMMITMENT,
+ scriptBytes, null, vkHash, scriptBytes.length);
+ }
+ }
+}
diff --git a/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/ScriptBudgetEstimator.java b/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/ScriptBudgetEstimator.java
new file mode 100644
index 0000000..e4ed253
--- /dev/null
+++ b/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/ScriptBudgetEstimator.java
@@ -0,0 +1,85 @@
+package com.bloxbean.cardano.zeroj.onchain.julc;
+
+import com.bloxbean.cardano.zeroj.api.CurveId;
+import com.bloxbean.cardano.zeroj.api.ProofSystemId;
+
+/**
+ * Estimates Cardano Plutus V3 execution budgets for ZeroJ on-chain verifiers.
+ *
+ * The constants are conservative estimates based on BLS12-381 builtin costs.
+ * Actual budgets should still be measured with the Julc VM and a representative
+ * transaction before mainnet deployment.
+ */
+public final class ScriptBudgetEstimator {
+
+ private ScriptBudgetEstimator() {}
+
+ public static final long MILLER_LOOP_CPU = 402_099_373L;
+ public static final long FINAL_VERIFY_CPU = 388_656_972L;
+ public static final long G1_SCALAR_MUL_CPU = 94_607_019L;
+ public static final long G1_ADD_CPU = 1_046_420L;
+ public static final long G1_MSM_PER_ELEMENT_CPU = 80_000_000L;
+ public static final long BLAKE2B_256_CPU = 2_477_736L;
+
+ /**
+ * Estimate CPU units for on-chain verification, or {@code -1} when the
+ * proof system / curve combination is not feasible on Plutus V3.
+ */
+ public static long estimateCpu(ProofSystemId proofSystem, CurveId curve, int numPublicInputs) {
+ if (numPublicInputs < 0) {
+ throw new IllegalArgumentException("numPublicInputs must be non-negative");
+ }
+ if (curve != CurveId.BLS12_381) {
+ return -1;
+ }
+
+ return switch (proofSystem) {
+ case GROTH16 -> estimateGroth16Cpu(numPublicInputs);
+ case PLONK -> estimatePlonkCpu(numPublicInputs);
+ default -> -1;
+ };
+ }
+
+ /**
+ * Estimate memory units for on-chain verification, or {@code -1} when the
+ * proof system / curve combination is not feasible on Plutus V3.
+ */
+ public static long estimateMemory(ProofSystemId proofSystem, CurveId curve, int numPublicInputs) {
+ if (numPublicInputs < 0) {
+ throw new IllegalArgumentException("numPublicInputs must be non-negative");
+ }
+ if (curve != CurveId.BLS12_381) {
+ return -1;
+ }
+
+ return switch (proofSystem) {
+ case GROTH16 -> estimateGroth16Memory(numPublicInputs);
+ case PLONK -> estimatePlonkMemory(numPublicInputs);
+ default -> -1;
+ };
+ }
+
+ private static long estimateGroth16Cpu(int numPublicInputs) {
+ return 4 * MILLER_LOOP_CPU
+ + FINAL_VERIFY_CPU
+ + numPublicInputs * G1_SCALAR_MUL_CPU
+ + numPublicInputs * G1_ADD_CPU;
+ }
+
+ private static long estimatePlonkCpu(int numPublicInputs) {
+ int numMsmElements = 8 + numPublicInputs;
+ return 2 * MILLER_LOOP_CPU
+ + FINAL_VERIFY_CPU
+ + numMsmElements * G1_MSM_PER_ELEMENT_CPU
+ + 10 * G1_ADD_CPU
+ + 6 * BLAKE2B_256_CPU;
+ }
+
+ private static long estimateGroth16Memory(int numPublicInputs) {
+ return 2_000_000L + numPublicInputs * 100_000L;
+ }
+
+ private static long estimatePlonkMemory(int numPublicInputs) {
+ return 4_000_000L + numPublicInputs * 150_000L;
+ }
+}
diff --git a/incubator/zeroj-onchain-experimental/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-onchain-experimental/reflect-config.json b/zeroj-onchain-julc/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-onchain-julc/reflect-config.json
similarity index 100%
rename from incubator/zeroj-onchain-experimental/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-onchain-experimental/reflect-config.json
rename to zeroj-onchain-julc/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-onchain-julc/reflect-config.json
diff --git a/incubator/zeroj-onchain-experimental/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-onchain-experimental/resource-config.json b/zeroj-onchain-julc/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-onchain-julc/resource-config.json
similarity index 100%
rename from incubator/zeroj-onchain-experimental/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-onchain-experimental/resource-config.json
rename to zeroj-onchain-julc/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-onchain-julc/resource-config.json
diff --git a/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/OnChainFeasibilityTest.java b/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/OnChainFeasibilityTest.java
new file mode 100644
index 0000000..d76adfb
--- /dev/null
+++ b/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/OnChainFeasibilityTest.java
@@ -0,0 +1,38 @@
+package com.bloxbean.cardano.zeroj.onchain.julc;
+
+import com.bloxbean.cardano.zeroj.api.CurveId;
+import com.bloxbean.cardano.zeroj.api.ProofSystemId;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class OnChainFeasibilityTest {
+
+ @Test
+ void marksBls12381Groth16WorkingAndPlonkExperimental() {
+ var groth16 = OnChainFeasibility.lookup(ProofSystemId.GROTH16, CurveId.BLS12_381);
+ var plonk = OnChainFeasibility.lookup(ProofSystemId.PLONK, CurveId.BLS12_381);
+
+ assertEquals(OnChainFeasibility.Status.WORKING, groth16.status());
+ assertEquals(OnChainFeasibility.Status.EXPERIMENTAL, plonk.status());
+ assertTrue(OnChainFeasibility.isFeasible(ProofSystemId.GROTH16, CurveId.BLS12_381));
+ assertTrue(OnChainFeasibility.isFeasible(ProofSystemId.PLONK, CurveId.BLS12_381));
+ }
+
+ @Test
+ void marksBn254AsNotFeasibleOnCardano() {
+ var entry = OnChainFeasibility.lookup(ProofSystemId.GROTH16, CurveId.BN254);
+
+ assertEquals(OnChainFeasibility.Status.NOT_FEASIBLE, entry.status());
+ assertFalse(OnChainFeasibility.isFeasible(ProofSystemId.GROTH16, CurveId.BN254));
+ }
+
+ @Test
+ void unknownCombinationDefaultsToNotFeasible() {
+ var entry = OnChainFeasibility.lookup(ProofSystemId.BBS, CurveId.BLS12_381);
+
+ assertEquals(OnChainFeasibility.Status.NOT_FEASIBLE, entry.status());
+ }
+}
diff --git a/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/PlonkBLS12381FullVerifierTest.java b/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/PlonkBLS12381FullVerifierTest.java
index a315a35..e41cfc1 100644
--- a/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/PlonkBLS12381FullVerifierTest.java
+++ b/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/PlonkBLS12381FullVerifierTest.java
@@ -13,7 +13,7 @@
import java.util.HexFormat;
/**
- * Unit test: validates the full PlonK verifier in the Julc VM with gnark test vectors.
+ * Unit test: validates the PlonK Julc prototype with gnark test vectors.
* Verifies Fiat-Shamir challenge re-derivation matches gnark's exported values.
*/
class PlonkBLS12381FullVerifierTest extends ContractTest {
@@ -146,7 +146,7 @@ void fiatShamir_matchesGnark() {
System.out.println("[test] Budget: " + result.budgetConsumed());
assertSuccess(result);
- System.out.println("[test] PlonK proof verified ON-CHAIN with trustless Fiat-Shamir!");
+ System.out.println("[test] PlonK Julc prototype accepted transcript and inverse checks.");
}
private static byte[] hex(String h) {
diff --git a/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/ReferenceScriptDeployerTest.java b/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/ReferenceScriptDeployerTest.java
new file mode 100644
index 0000000..fcabdcc
--- /dev/null
+++ b/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/ReferenceScriptDeployerTest.java
@@ -0,0 +1,52 @@
+package com.bloxbean.cardano.zeroj.onchain.julc;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class ReferenceScriptDeployerTest {
+
+ @Test
+ void createsReferenceScriptWithDatumVkConfig() {
+ byte[] script = {1, 2, 3};
+ byte[] vk = {4, 5};
+
+ var config = ReferenceScriptDeployer.DeploymentConfig.referenceWithDatumVk(script, vk);
+
+ assertEquals(ReferenceScriptDeployer.DeploymentPattern.REFERENCE_SCRIPT_DATUM_VK, config.pattern());
+ assertArrayEquals(script, config.scriptBytes());
+ assertArrayEquals(vk, config.vkBytes());
+ assertNull(config.vkHash());
+ assertEquals(3, config.estimatedScriptSize());
+ }
+
+ @Test
+ void createsVkInScriptConfig() {
+ byte[] script = {1, 2};
+ byte[] vk = {3, 4};
+
+ var config = ReferenceScriptDeployer.DeploymentConfig.vkInScript(script, vk);
+
+ assertEquals(ReferenceScriptDeployer.DeploymentPattern.VK_IN_SCRIPT, config.pattern());
+ assertArrayEquals(script, config.scriptBytes());
+ assertArrayEquals(vk, config.vkBytes());
+ assertNull(config.vkHash());
+ assertEquals(2, config.estimatedScriptSize());
+ }
+
+ @Test
+ void createsVkHashCommitmentConfig() {
+ byte[] script = {1, 2, 3, 4};
+ byte[] vkHash = {5, 6};
+
+ var config = ReferenceScriptDeployer.DeploymentConfig.vkHashCommitment(script, vkHash);
+
+ assertEquals(ReferenceScriptDeployer.DeploymentPattern.VK_HASH_COMMITMENT, config.pattern());
+ assertArrayEquals(script, config.scriptBytes());
+ assertNull(config.vkBytes());
+ assertArrayEquals(vkHash, config.vkHash());
+ assertEquals(4, config.estimatedScriptSize());
+ }
+}
diff --git a/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/ScriptBudgetEstimatorTest.java b/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/ScriptBudgetEstimatorTest.java
new file mode 100644
index 0000000..2d48197
--- /dev/null
+++ b/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/ScriptBudgetEstimatorTest.java
@@ -0,0 +1,43 @@
+package com.bloxbean.cardano.zeroj.onchain.julc;
+
+import com.bloxbean.cardano.zeroj.api.CurveId;
+import com.bloxbean.cardano.zeroj.api.ProofSystemId;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ScriptBudgetEstimatorTest {
+
+ @Test
+ void estimatesGroth16Bls12381Budget() {
+ long cpu = ScriptBudgetEstimator.estimateCpu(ProofSystemId.GROTH16, CurveId.BLS12_381, 1);
+ long memory = ScriptBudgetEstimator.estimateMemory(ProofSystemId.GROTH16, CurveId.BLS12_381, 1);
+
+ assertTrue(cpu > 0);
+ assertTrue(memory > 0);
+ }
+
+ @Test
+ void estimatesPlonkAsMoreMemoryIntensiveThanGroth16() {
+ long grothMemory = ScriptBudgetEstimator.estimateMemory(ProofSystemId.GROTH16, CurveId.BLS12_381, 1);
+ long plonkMemory = ScriptBudgetEstimator.estimateMemory(ProofSystemId.PLONK, CurveId.BLS12_381, 1);
+
+ assertTrue(plonkMemory > grothMemory);
+ }
+
+ @Test
+ void returnsMinusOneForNonCardanoCurves() {
+ assertEquals(-1, ScriptBudgetEstimator.estimateCpu(ProofSystemId.GROTH16, CurveId.BN254, 1));
+ assertEquals(-1, ScriptBudgetEstimator.estimateMemory(ProofSystemId.GROTH16, CurveId.BN254, 1));
+ }
+
+ @Test
+ void rejectsNegativePublicInputCounts() {
+ assertThrows(IllegalArgumentException.class,
+ () -> ScriptBudgetEstimator.estimateCpu(ProofSystemId.GROTH16, CurveId.BLS12_381, -1));
+ assertThrows(IllegalArgumentException.class,
+ () -> ScriptBudgetEstimator.estimateMemory(ProofSystemId.GROTH16, CurveId.BLS12_381, -1));
+ }
+}
diff --git a/zeroj-prover-gnark/README.md b/zeroj-prover-gnark/README.md
index 988fc83..b8cbdab 100644
--- a/zeroj-prover-gnark/README.md
+++ b/zeroj-prover-gnark/README.md
@@ -8,15 +8,14 @@ This module provides high-performance in-process proof generation using gnark (G
- Groth16 proving for BLS12-381 and BN254
- PlonK proving (beta) for BLS12-381 and BN254
-- Implements `ProverService` interface (drop-in replacement for sidecar client)
+- Uses shared `zeroj-prover-spi` response and error types
- `AutoCloseable` for proper native resource management
## Key Types
| Type | Description |
|------|-------------|
-| `GnarkProver` | Native prover — `proveRaw(curveId, r1cs, pkPath, witnessPath)` |
-| `PlonkGnarkVerifier` | PlonK proof verification via gnark (separate from Groth16 verifiers) |
+| `GnarkProver` | Native prover — `proveRaw(curve, r1csPath, pkPath, witnessPath)` |
| `GnarkLibrary` | FFM bindings to `libzeroj_gnark` |
| `GnarkNativeLoader` | Library loading and initialization |
@@ -38,13 +37,21 @@ This produces `libzeroj_gnark.dylib` (macOS) or `libzeroj_gnark.so` (Linux) in `
The compiled native library must be on the classpath or system library path. The library is large (~30-50MB) because it includes the Go runtime.
+The checked-in native resource is built for the current development host. Before
+publishing or testing on another platform, run `make build` on that target so
+`src/main/resources/native/-/` contains the matching shared
+library.
+
## Usage
```java
try (var prover = new GnarkProver()) {
// Groth16 proving
ProveResponse response = prover.proveRaw(
- CurveId.BLS12_381, r1csBytes, pkPath, witnessPath);
+ "bls12381",
+ Path.of("circuit.r1cs"),
+ Path.of("proving_key.bin"),
+ Path.of("witness.bin"));
// Check version
String version = prover.gnarkVersion();
diff --git a/zeroj-prover-gnark/build.gradle b/zeroj-prover-gnark/build.gradle
index 86f6c1f..b9d7209 100644
--- a/zeroj-prover-gnark/build.gradle
+++ b/zeroj-prover-gnark/build.gradle
@@ -2,7 +2,7 @@ plugins {
id 'java-library'
}
-description = 'ZeroJ gnark prover — native in-process BLS12-381/BN254 Groth16 proving via FFM'
+description = 'ZeroJ gnark prover — native in-process BLS12-381/BN254 Groth16 and PlonK proving via FFM'
test {
// Set GOMAXPROCS before Go runtime bootstraps inside the shared library.
@@ -16,13 +16,11 @@ test {
dependencies {
api project(':zeroj-api')
api project(':zeroj-codec')
- api project(':zeroj-backend-spi')
- api project(':zeroj-prover-sidecar')
+ api project(':zeroj-prover-spi')
api project(':zeroj-circuit-dsl')
implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.2'
testImplementation project(':zeroj-test-vectors')
- testImplementation project(':zeroj-verifier-core')
testImplementation project(':zeroj-verifier-groth16')
}
@@ -31,7 +29,7 @@ publishing {
mavenJava(MavenPublication) {
pom {
name = 'ZeroJ Prover Gnark'
- description = 'Native in-process BLS12-381/BN254 Groth16 proving via gnark FFM bindings'
+ description = 'Native in-process BLS12-381/BN254 Groth16 and PlonK proving via gnark FFM bindings'
}
}
}
diff --git a/zeroj-prover-gnark/gnark-wrapper/main.go b/zeroj-prover-gnark/gnark-wrapper/main.go
index 06898ca..9bda436 100644
--- a/zeroj-prover-gnark/gnark-wrapper/main.go
+++ b/zeroj-prover-gnark/gnark-wrapper/main.go
@@ -8,6 +8,7 @@ import "C"
import (
"bytes"
"encoding/base64"
+ "encoding/binary"
"encoding/json"
"fmt"
"io"
@@ -62,6 +63,17 @@ func parseCurve(curveName string) (ecc.ID, error) {
}
}
+func curveWireName(curve ecc.ID) string {
+ switch curve {
+ case ecc.BLS12_381:
+ return "bls12381"
+ case ecc.BN254:
+ return "bn128"
+ default:
+ return curve.String()
+ }
+}
+
// ============================================================
// Groth16
// ============================================================
@@ -128,7 +140,7 @@ func zeroj_groth16_setup(
return PROVER_ERROR
}
- return writeSetupResult(pk, vk, "groth16", "zeroj-groth16-pk-*.bin", pkPathOut, vkOut, errorOut)
+ return writeSetupResult(pk, vk, curve, "groth16", "zeroj-groth16-pk-*.bin", pkPathOut, vkOut, errorOut)
}
// ============================================================
@@ -165,7 +177,7 @@ func zeroj_plonk_setup(
return PROVER_ERROR
}
- return writeSetupResult(pk, vk, "plonk", "zeroj-plonk-pk-*.bin", pkPathOut, vkOut, errorOut)
+ return writeSetupResult(pk, vk, curve, "plonk", "zeroj-plonk-pk-*.bin", pkPathOut, vkOut, errorOut)
}
//export zeroj_plonk_prove
@@ -312,7 +324,7 @@ func readWitness(path string, curve ecc.ID) (witness.Witness, error) {
return wit, nil
}
-func serializeTo(obj writerTo, protocol string) (string, error) {
+func serializeTo(obj writerTo, protocol string, curve ecc.ID) (string, error) {
var buf bytes.Buffer
if _, err := obj.WriteTo(&buf); err != nil {
return "", fmt.Errorf("failed to serialize: %v", err)
@@ -320,6 +332,7 @@ func serializeTo(obj writerTo, protocol string) (string, error) {
result := map[string]interface{}{
"binary": base64.StdEncoding.EncodeToString(buf.Bytes()),
"protocol": protocol,
+ "curve": curveWireName(curve),
}
jsonBytes, err := json.Marshal(result)
if err != nil {
@@ -331,7 +344,7 @@ func serializeTo(obj writerTo, protocol string) (string, error) {
func writeProveResult(proof writerTo, wit witness.Witness, curve ecc.ID, provingMs int64, protocol string,
proofOut **C.char, publicOut **C.char, errorOut **C.char) C.int {
- proofJSON, err := serializeTo(proof, protocol)
+ proofJSON, err := serializeTo(proof, protocol, curve)
if err != nil {
*errorOut = C.CString(fmt.Sprintf("failed to serialize proof: %v", err))
return PROVER_ERROR
@@ -350,7 +363,7 @@ func writeProveResult(proof writerTo, wit witness.Witness, curve ecc.ID, proving
return PROVER_OK
}
-func writeSetupResult(pk writerTo, vk writerTo, protocol string, pkPattern string,
+func writeSetupResult(pk writerTo, vk writerTo, curve ecc.ID, protocol string, pkPattern string,
pkPathOut **C.char, vkOut **C.char, errorOut **C.char) C.int {
tmpFile, err := os.CreateTemp("", pkPattern)
@@ -365,7 +378,7 @@ func writeSetupResult(pk writerTo, vk writerTo, protocol string, pkPattern strin
}
tmpFile.Close()
- vkJSON, err := serializeTo(vk, protocol)
+ vkJSON, err := serializeTo(vk, protocol, curve)
if err != nil {
*errorOut = C.CString(fmt.Sprintf("failed to serialize vk: %v", err))
return PROVER_ERROR
@@ -386,13 +399,15 @@ func serializePublicWitness(wit witness.Witness, curve ecc.ID) (string, error) {
return "", fmt.Errorf("failed to marshal public witness: %v", err)
}
fieldSize := getFieldSize(curve)
- if len(data) < 8 {
+ if len(data) < 12 {
return "[]", nil
}
- elemData := data[8:]
+ nbPublic := int(binary.BigEndian.Uint32(data[8:12]))
+ elemData := data[12:]
var values []string
- for i := 0; i+fieldSize <= len(elemData); i += fieldSize {
- val := new(big.Int).SetBytes(elemData[i : i+fieldSize])
+ for i := 0; i < nbPublic && (i+1)*fieldSize <= len(elemData); i++ {
+ offset := i * fieldSize
+ val := new(big.Int).SetBytes(elemData[offset : offset+fieldSize])
values = append(values, val.String())
}
jsonBytes, err := json.Marshal(values)
@@ -691,7 +706,7 @@ func writeFullProveResult(proof writerTo, vk writerTo, wit witness.Witness, curv
provingMs int64, protocol string,
proofOut **C.char, publicOut **C.char, vkOut **C.char, errorOut **C.char) C.int {
- proofJSON, err := serializeTo(proof, protocol)
+ proofJSON, err := serializeTo(proof, protocol, curve)
if err != nil {
*errorOut = C.CString(fmt.Sprintf("failed to serialize proof: %v", err))
return PROVER_ERROR
@@ -703,7 +718,7 @@ func writeFullProveResult(proof writerTo, vk writerTo, wit witness.Witness, curv
return PROVER_ERROR
}
- vkJSON, err := serializeTo(vk, protocol)
+ vkJSON, err := serializeTo(vk, protocol, curve)
if err != nil {
*errorOut = C.CString(fmt.Sprintf("failed to serialize vk: %v", err))
return PROVER_ERROR
diff --git a/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkLibrary.java b/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkLibrary.java
index c0d9f92..8738d61 100644
--- a/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkLibrary.java
+++ b/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkLibrary.java
@@ -1,6 +1,6 @@
package com.bloxbean.cardano.zeroj.prover.gnark;
-import com.bloxbean.cardano.zeroj.prover.sidecar.ProverException;
+import com.bloxbean.cardano.zeroj.prover.spi.ProverException;
import java.io.IOException;
import java.lang.foreign.*;
diff --git a/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkNativeLoader.java b/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkNativeLoader.java
index ec4ba95..2042cfd 100644
--- a/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkNativeLoader.java
+++ b/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkNativeLoader.java
@@ -13,7 +13,7 @@
* ({@code /native/{platform}/libzeroj_gnark.{so,dylib}}) to a temp
* directory and returns the path for FFM loading.
*
- * Unlike rapidsnark, gnark does not ship pre-built binaries. The shared
+ *
The gnark wrapper does not ship pre-built binaries. The shared
* library must be compiled from the Go wrapper using {@code make -C gnark-wrapper build}
* before packaging.
*/
diff --git a/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkProver.java b/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkProver.java
index 201b351..d2d1de7 100644
--- a/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkProver.java
+++ b/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkProver.java
@@ -2,13 +2,9 @@
import com.bloxbean.cardano.zeroj.api.CircuitId;
import com.bloxbean.cardano.zeroj.api.CurveId;
-import com.bloxbean.cardano.zeroj.api.ZkProofEnvelope;
import com.bloxbean.cardano.zeroj.circuit.r1cs.R1CSConstraintSystem;
-import com.bloxbean.cardano.zeroj.codec.SnarkjsJsonCodec;
-import com.bloxbean.cardano.zeroj.prover.sidecar.ProveRequest;
-import com.bloxbean.cardano.zeroj.prover.sidecar.ProveResponse;
-import com.bloxbean.cardano.zeroj.prover.sidecar.ProverException;
-import com.bloxbean.cardano.zeroj.prover.sidecar.ProverService;
+import com.bloxbean.cardano.zeroj.prover.spi.ProveResponse;
+import com.bloxbean.cardano.zeroj.prover.spi.ProverException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -23,7 +19,7 @@
* Native in-process Groth16 prover using gnark via FFM.
*
* Supports both BLS12-381 (Cardano-native) and BN254 curves.
- * Unlike rapidsnark, gnark requires Go compilation before use.
+ * gnark requires Go compilation before use.
* Build the native library with:
* {@code
* make -C zeroj-prover-gnark/gnark-wrapper build
@@ -44,7 +40,7 @@
* Note: The gnark shared library includes the Go runtime (~30-50MB).
* It is shipped as a separate Maven artifact, not bundled in the main JAR.
*/
-public class GnarkProver implements ProverService, AutoCloseable {
+public class GnarkProver implements AutoCloseable {
private static final ObjectMapper MAPPER = new ObjectMapper();
@@ -242,41 +238,6 @@ public record FullProveResponse(
String vkJson
) {}
- // --- ProverService interface ---
-
- /**
- * Not supported — use {@link #proveRaw(String, Path, Path, String)} instead.
- *
- * @throws UnsupportedOperationException always
- */
- @Override
- public ProveResponse prove(ProveRequest request) {
- throw new UnsupportedOperationException(
- "Native gnark prover requires R1CS + proving key + witness. "
- + "Use proveRaw(curve, r1csPath, pkPath, witnessJson) instead.");
- }
-
- /**
- * Not supported — see {@link #prove(ProveRequest)}.
- *
- * @throws UnsupportedOperationException always
- */
- @Override
- public ZkProofEnvelope proveAndWrap(ProveRequest request, String circuitId) {
- throw new UnsupportedOperationException(
- "Native gnark prover requires R1CS + proving key + witness.");
- }
-
- @Override
- public boolean isHealthy() {
- return true;
- }
-
- @Override
- public List listCircuits() {
- return List.of();
- }
-
@Override
public void close() {
library.close();
@@ -357,7 +318,7 @@ private FullProveResponse toFullProveResponse(GnarkLibrary.FullProveResult resul
default -> curve;
};
- var proveResponse = new ProveResponse(result.resultJson(), publicSignals,
+ var proveResponse = new ProveResponse(extractProofJson(result.resultJson(), normalizedCurve, protocol), publicSignals,
protocol, normalizedCurve, provingTimeMs);
return new FullProveResponse(proveResponse, result.vkJson());
} catch (Exception e) {
@@ -384,10 +345,37 @@ private ProveResponse toProveResponse(GnarkLibrary.ProveResult result, String cu
default -> curve;
};
- return new ProveResponse(result.resultJson(), publicSignals, protocol, normalizedCurve, provingTimeMs);
+ return new ProveResponse(extractProofJson(result.resultJson(), normalizedCurve, protocol), publicSignals,
+ protocol, normalizedCurve, provingTimeMs);
} catch (Exception e) {
throw new ProverException(ProverException.ErrorCode.INVALID_RESPONSE,
"Failed to parse gnark output: " + e.getMessage(), e);
}
}
+
+ private static String extractProofJson(String resultJson, String curve, String protocol) throws Exception {
+ JsonNode root = MAPPER.readTree(resultJson);
+ String proofJson;
+ if (root.has("proof")) {
+ JsonNode proof = root.get("proof");
+ proofJson = proof.isTextual() ? proof.asText() : proof.toString();
+ } else if (root.has("binary")) {
+ proofJson = resultJson;
+ } else {
+ throw new IllegalArgumentException("gnark result JSON missing proof field");
+ }
+
+ JsonNode proofRoot = MAPPER.readTree(proofJson);
+ if (proofRoot.isObject()) {
+ var proofObject = (com.fasterxml.jackson.databind.node.ObjectNode) proofRoot;
+ if (!proofObject.has("curve")) {
+ proofObject.put("curve", curve);
+ }
+ if (!proofObject.has("protocol")) {
+ proofObject.put("protocol", protocol);
+ }
+ return MAPPER.writeValueAsString(proofObject);
+ }
+ return proofJson;
+ }
}
diff --git a/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/PlonkGnarkVerifier.java b/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/PlonkGnarkVerifier.java
deleted file mode 100644
index bf53283..0000000
--- a/zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/PlonkGnarkVerifier.java
+++ /dev/null
@@ -1,143 +0,0 @@
-package com.bloxbean.cardano.zeroj.prover.gnark;
-
-import com.bloxbean.cardano.zeroj.api.*;
-import com.bloxbean.cardano.zeroj.backend.spi.BackendDescriptor;
-import com.bloxbean.cardano.zeroj.backend.spi.ZkVerifier;
-import com.bloxbean.cardano.zeroj.codec.GnarkPlonkCodec;
-
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-/**
- * PlonK BLS12-381 verifier that delegates to gnark's native {@code plonk.Verify()} via FFM.
- *
- * Implements the {@link ZkVerifier} SPI so PlonK proofs can route through
- * {@link com.bloxbean.cardano.zeroj.verifier.core.VerifierOrchestrator}.
- *
- * Requirements:
- *
- * - gnark native library must be available at runtime
- * - Proof bytes in the {@link ZkProofEnvelope} must be gnark PlonK JSON format
- * (containing a "binary" field with base64-encoded proof)
- * - {@link VerificationMaterial#vkBytes()} must be gnark binary VK format
- *
- *
- * Convention: The public witness binary must be stored as a temp file from
- * the envelope's public inputs. This verifier reconstructs the gnark binary witness
- * from the envelope's public inputs using a helper file exported by the test vector generator.
- * For production use, the public witness binary should be stored alongside the VK.
- */
-public class PlonkGnarkVerifier implements ZkVerifier {
-
- private static final BackendDescriptor DESCRIPTOR =
- new BackendDescriptor(ProofSystemId.PLONK, CurveId.BLS12_381, "plonk-bls12381-gnark");
-
- private GnarkLibrary library;
-
- public PlonkGnarkVerifier() {
- // Lazy init — library loaded on first verify call
- }
-
- public PlonkGnarkVerifier(GnarkLibrary library) {
- this.library = library;
- }
-
- @Override
- public VerificationResult verify(ZkProofEnvelope envelope, VerificationMaterial material) {
- try {
- if (library == null) {
- library = new GnarkLibrary();
- }
-
- // Extract proof base64 from envelope's proof bytes (gnark JSON format)
- String proofJson = new String(envelope.proofBytes(), StandardCharsets.UTF_8);
- String proofBase64 = GnarkPlonkCodec.extractProofBase64(proofJson);
-
- // Write VK binary to temp file (gnark expects file paths)
- Path vkTempFile = Files.createTempFile("zeroj-plonk-vk-", ".bin");
- Files.write(vkTempFile, material.vkBytes());
-
- // Write public witness to temp file.
- // Priority: envelope metadata (base64-encoded gnark binary witness) > reconstruct from public inputs.
- Path pubWitTempFile = Files.createTempFile("zeroj-plonk-pubwit-", ".bin");
- byte[] pubWitBytes;
- String metaWitness = envelope.metadata().get("gnark.publicWitness");
- if (metaWitness != null) {
- pubWitBytes = java.util.Base64.getDecoder().decode(metaWitness);
- } else if (material.vkHash().isPresent() && material.vkHash().get().length > 32) {
- // Legacy fallback: vkHash overloaded with public witness binary (deprecated)
- pubWitBytes = material.vkHash().get();
- } else {
- // Reconstruct from public inputs (best effort — may not match gnark format exactly)
- pubWitBytes = buildGnarkPublicWitness(envelope.publicInputs(), envelope.curve());
- }
- Files.write(pubWitTempFile, pubWitBytes);
-
- try {
- String curveStr = envelope.curve() == CurveId.BLS12_381 ? "bls12381" : "bn254";
- boolean valid = library.plonkVerify(curveStr,
- vkTempFile.toAbsolutePath().toString(),
- proofBase64,
- pubWitTempFile.toAbsolutePath().toString());
-
- return valid ? VerificationResult.cryptoValid()
- : VerificationResult.proofInvalid("gnark PlonK verification failed");
- } finally {
- Files.deleteIfExists(vkTempFile);
- Files.deleteIfExists(pubWitTempFile);
- }
- } catch (Exception e) {
- return VerificationResult.error(VerificationResult.ReasonCode.INTERNAL_ERROR,
- "PlonK verification error: " + e.getMessage());
- }
- }
-
- @Override
- public BackendDescriptor descriptor() {
- return DESCRIPTOR;
- }
-
- /**
- * Build gnark binary public witness from public inputs.
- * gnark witness binary format (version 1):
- * - 4 bytes: gnark curve ID (little-endian uint32)
- * - 4 bytes: nbPublic (LE uint32)
- * - 4 bytes: nbSecret (LE uint32) = 0
- * - field elements: each 32 bytes (BN254) or 48 bytes (BLS12-381), big-endian
- */
- private byte[] buildGnarkPublicWitness(PublicInputs inputs, CurveId curve) {
- int fieldSize = curve == CurveId.BLS12_381 ? 48 : 32;
- int curveId = curve == CurveId.BLS12_381 ? 4 : 0; // gnark curve IDs
- int nbPublic = inputs.size();
-
- byte[] result = new byte[12 + nbPublic * fieldSize];
-
- // Header (little-endian uint32s)
- writeUint32LE(result, 0, curveId);
- writeUint32LE(result, 4, nbPublic);
- writeUint32LE(result, 8, 0); // nbSecret = 0
-
- // Field elements
- for (int i = 0; i < nbPublic; i++) {
- byte[] feBytes = inputs.values().get(i).toByteArray();
- // Pad/trim to fieldSize, big-endian
- int offset = 12 + i * fieldSize;
- if (feBytes.length <= fieldSize) {
- System.arraycopy(feBytes, 0, result, offset + fieldSize - feBytes.length, feBytes.length);
- } else {
- // Strip leading zero byte if present
- System.arraycopy(feBytes, feBytes.length - fieldSize, result, offset, fieldSize);
- }
- }
-
- return result;
- }
-
- private void writeUint32LE(byte[] buf, int offset, int value) {
- buf[offset] = (byte) (value & 0xFF);
- buf[offset + 1] = (byte) ((value >> 8) & 0xFF);
- buf[offset + 2] = (byte) ((value >> 16) & 0xFF);
- buf[offset + 3] = (byte) ((value >> 24) & 0xFF);
- }
-}
diff --git a/zeroj-prover-gnark/src/main/resources/META-INF/services/com.bloxbean.cardano.zeroj.backend.spi.ZkVerifier b/zeroj-prover-gnark/src/main/resources/META-INF/services/com.bloxbean.cardano.zeroj.backend.spi.ZkVerifier
deleted file mode 100644
index 2d3c6f1..0000000
--- a/zeroj-prover-gnark/src/main/resources/META-INF/services/com.bloxbean.cardano.zeroj.backend.spi.ZkVerifier
+++ /dev/null
@@ -1 +0,0 @@
-com.bloxbean.cardano.zeroj.prover.gnark.PlonkGnarkVerifier
diff --git a/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkFullProveTest.java b/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkFullProveTest.java
index 79fc6d9..be76dee 100644
--- a/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkFullProveTest.java
+++ b/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkFullProveTest.java
@@ -68,8 +68,10 @@ void groth16FullProve_multiplierCircuit() {
assertNotNull(result.vkJson());
assertEquals("groth16", result.proveResponse().protocol());
assertEquals("bls12381", result.proveResponse().curve());
- assertFalse(result.proveResponse().publicSignals().isEmpty(),
- "Should have public signals");
+ assertEquals(List.of(BigInteger.valueOf(33)), result.proveResponse().publicSignals());
+ assertTrue(result.proveResponse().proofJson().contains("\"binary\""));
+ assertTrue(result.proveResponse().proofJson().contains("\"curve\":\"bls12381\""));
+ assertFalse(result.proveResponse().proofJson().contains("\"proof\""));
System.out.println("Groth16 full prove SUCCESS (3 * 11 = 33)");
System.out.println(" Public signals: " + result.proveResponse().publicSignals());
@@ -97,6 +99,10 @@ void plonkFullProve_multiplierCircuit() {
assertNotNull(result.vkJson());
assertEquals("plonk", result.proveResponse().protocol());
assertEquals("bls12381", result.proveResponse().curve());
+ assertEquals(List.of(BigInteger.valueOf(33)), result.proveResponse().publicSignals());
+ assertTrue(result.proveResponse().proofJson().contains("\"binary\""));
+ assertTrue(result.proveResponse().proofJson().contains("\"curve\":\"bls12381\""));
+ assertFalse(result.proveResponse().proofJson().contains("\"proof\""));
System.out.println("PlonK full prove SUCCESS (3 * 11 = 33)");
System.out.println(" Public signals: " + result.proveResponse().publicSignals());
diff --git a/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkPlonkTest.java b/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkPlonkTest.java
index d3f5803..60697b0 100644
--- a/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkPlonkTest.java
+++ b/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkPlonkTest.java
@@ -51,9 +51,10 @@ void plonkProve_generatesValidProof() {
assertEquals("plonk", response.protocol(), "Protocol should be plonk");
assertEquals("bls12381", response.curve());
assertTrue(response.provingTimeMs() >= 0);
- // gnark's PlonK witness binary format may not include public signals in the
- // same way as Groth16. The proof is valid (verified in E2E test below).
- // Public signals can also be read from the separately exported public.json.
+ assertEquals(java.util.List.of(java.math.BigInteger.valueOf(33)), response.publicSignals());
+ assertTrue(response.proofJson().contains("\"binary\""));
+ assertTrue(response.proofJson().contains("\"curve\":\"bls12381\""));
+ assertFalse(response.proofJson().contains("\"proof\""));
System.out.println("PlonK proof generated. Public: " + response.publicSignals()
+ " | Time: " + response.provingTimeMs() + "ms");
}
@@ -97,7 +98,7 @@ void plonkVerify_endToEnd_setupProveVerify() {
System.out.println(" Public inputs: " + response.publicSignals());
// 3. Extract proof base64 from response
- // The response.proofJson() contains {"binary":"","protocol":"plonk"}
+ // The response.proofJson() contains {"binary":"","protocol":"plonk","curve":"bls12381"}
// For verify, we need the raw base64 from the proof binary
// Use the pre-generated proof for verification (same proof, verified in Go)
boolean valid = prover.plonkVerify(
diff --git a/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkProverTest.java b/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkProverTest.java
index 3d52d8f..f9a3f08 100644
--- a/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkProverTest.java
+++ b/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/GnarkProverTest.java
@@ -26,33 +26,6 @@ void gnarkVersion_returnsVersion() {
}
}
- @Test
- @EnabledIf("isNativeLibraryAvailable")
- void isHealthy_returnsTrue() {
- try (var prover = new GnarkProver()) {
- assertTrue(prover.isHealthy());
- }
- }
-
- @Test
- @EnabledIf("isNativeLibraryAvailable")
- void listCircuits_returnsEmptyList() {
- try (var prover = new GnarkProver()) {
- assertTrue(prover.listCircuits().isEmpty());
- }
- }
-
- @Test
- void prove_throwsUnsupportedOperation() {
- // prove(ProveRequest) should throw for native prover
- if (isNativeLibraryAvailable()) {
- try (var prover = new GnarkProver()) {
- assertThrows(UnsupportedOperationException.class,
- () -> prover.prove(null));
- }
- }
- }
-
static boolean isNativeLibraryAvailable() {
return GnarkNativeLoader.isAvailable();
}
diff --git a/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/PlonkSpiIntegrationTest.java b/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/PlonkSpiIntegrationTest.java
deleted file mode 100644
index 354e107..0000000
--- a/zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/PlonkSpiIntegrationTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package com.bloxbean.cardano.zeroj.prover.gnark;
-
-import com.bloxbean.cardano.zeroj.api.*;
-import com.bloxbean.cardano.zeroj.backend.spi.InMemoryVerificationKeyRegistry;
-import com.bloxbean.cardano.zeroj.codec.GnarkPlonkCodec;
-import com.bloxbean.cardano.zeroj.verifier.core.VerifierOrchestrator;
-import com.bloxbean.cardano.zeroj.verifier.core.VerifierRegistry;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.EnabledIf;
-
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Integration test: PlonK proofs routing through VerifierOrchestrator via SPI.
- *
- * Demonstrates that PlonK proofs can be verified using the same orchestrator
- * and SPI infrastructure as Groth16 — no application code changes needed.
- */
-@EnabledIf("isNativeLibraryAvailable")
-class PlonkSpiIntegrationTest {
-
- static final String TEST_VECTORS = System.getProperty("user.dir")
- + "/../zeroj-test-vectors/src/main/resources/test-vectors/plonk-bls12381/";
-
- @Test
- void plonkProof_routesThroughOrchestrator() throws Exception {
- // 1. Register PlonK verifier backend
- var registry = VerifierRegistry.empty();
- var gnarkLib = new GnarkLibrary();
- registry.register(new PlonkGnarkVerifier(gnarkLib));
-
- var vkRegistry = new InMemoryVerificationKeyRegistry();
- var orchestrator = new VerifierOrchestrator(registry, vkRegistry);
-
- // 2. Build ZkProofEnvelope from gnark PlonK JSON
- String proofJson = Files.readString(Path.of(TEST_VECTORS, "proof.json"), StandardCharsets.UTF_8);
- String vkJson = Files.readString(Path.of(TEST_VECTORS, "verification_key.json"), StandardCharsets.UTF_8);
- String publicJson = Files.readString(Path.of(TEST_VECTORS, "public.json"), StandardCharsets.UTF_8);
-
- ZkProofEnvelope envelope = GnarkPlonkCodec.toEnvelopeFromJson(
- proofJson, vkJson, publicJson, new CircuitId("plonk-multiplier"));
-
- assertEquals(ProofSystemId.PLONK, envelope.proofSystem());
- assertEquals(CurveId.BLS12_381, envelope.curve());
-
- // 3. Create VerificationMaterial with gnark binary VK
- // Convention: vkHash carries the public witness binary (for gnark FFM verifier)
- byte[] vkBinaryBytes = Files.readAllBytes(Path.of(TEST_VECTORS, "verification_key.bin"));
- byte[] pubWitBytes = Files.readAllBytes(Path.of(TEST_VECTORS, "public_witness.bin"));
- var material = VerificationMaterial.of(vkBinaryBytes,
- ProofSystemId.PLONK, CurveId.BLS12_381, new CircuitId("plonk-multiplier"),
- pubWitBytes);
-
- // 4. Verify through orchestrator — routes to PlonkGnarkVerifier via SPI
- VerificationResult result = orchestrator.verify(envelope, material);
-
- assertTrue(result.proofValid(),
- "PlonK proof should verify through orchestrator: " + result.message().orElse(""));
-
- System.out.println("PlonK proof verified via VerifierOrchestrator (SPI routing)");
- System.out.println(" ProofSystem: " + envelope.proofSystem());
- System.out.println(" Curve: " + envelope.curve());
- System.out.println(" Public inputs: " + envelope.publicInputs().values());
- System.out.println(" Result: " + (result.proofValid() ? "VALID" : "INVALID"));
-
- gnarkLib.close();
- }
-
- @Test
- void plonkProof_wrongVk_fails() throws Exception {
- var registry = VerifierRegistry.empty();
- var gnarkLib = new GnarkLibrary();
- registry.register(new PlonkGnarkVerifier(gnarkLib));
- var vkRegistry = new InMemoryVerificationKeyRegistry();
- var orchestrator = new VerifierOrchestrator(registry, vkRegistry);
-
- String proofJson = Files.readString(Path.of(TEST_VECTORS, "proof.json"), StandardCharsets.UTF_8);
- String publicJson = Files.readString(Path.of(TEST_VECTORS, "public.json"), StandardCharsets.UTF_8);
- // Use proof.json as "wrong VK" — will fail binary VK parsing
- ZkProofEnvelope envelope = GnarkPlonkCodec.toEnvelopeFromJson(
- proofJson, proofJson, publicJson, new CircuitId("plonk-multiplier"));
-
- // Wrong VK bytes → verification should fail
- var material = VerificationMaterial.of(new byte[]{1, 2, 3},
- ProofSystemId.PLONK, CurveId.BLS12_381, new CircuitId("plonk-multiplier"));
-
- VerificationResult result = orchestrator.verify(envelope, material);
- assertFalse(result.proofValid(), "Wrong VK should fail");
-
- gnarkLib.close();
- }
-
- static boolean isNativeLibraryAvailable() {
- return GnarkNativeLoader.isAvailable();
- }
-}
diff --git a/zeroj-prover-spi/README.md b/zeroj-prover-spi/README.md
new file mode 100644
index 0000000..78f33a4
--- /dev/null
+++ b/zeroj-prover-spi/README.md
@@ -0,0 +1,48 @@
+# zeroj-prover-spi
+
+Service Provider Interface for proof generation backends.
+
+This module defines the small prover-side contract shared by native, pure Java,
+or remote proving implementations. It is intentionally separate from
+`zeroj-backend-spi`, which is verifier-side only.
+
+## Key Types
+
+| Type | Purpose |
+|------|---------|
+| `ProverService` | Minimal interface for generating a proof from a request |
+| `ProveRequest` | Circuit name, input map, and optional proving-key identifier |
+| `ProveResponse` | Proof JSON, public signals, protocol, curve, and proving time |
+| `ProverException` | Typed proving failure with backend-friendly error codes |
+
+## Why It Is Useful
+
+- Keeps prover contracts in a stable core module instead of tying them to one
+ transport or implementation.
+- Lets `zeroj-prover-gnark` and future prover backends share response/error
+ types without depending on a specific transport implementation.
+- Makes local-first proving the default while leaving room for optional remote
+ deployments later.
+
+## Example
+
+```java
+ProverService prover = request -> {
+ // Backend-specific proving logic.
+ return new ProveResponse(proofJson, publicSignals, "groth16", "bls12381", 42);
+};
+
+var response = prover.prove(ProveRequest.of("multiplier", Map.of(
+ "x", "3",
+ "y", "11",
+ "z", "33"
+)));
+```
+
+## Gradle
+
+```gradle
+dependencies {
+ implementation 'com.bloxbean.cardano:zeroj-prover-spi'
+}
+```
diff --git a/zeroj-prover-spi/build.gradle b/zeroj-prover-spi/build.gradle
new file mode 100644
index 0000000..7cd5fb6
--- /dev/null
+++ b/zeroj-prover-spi/build.gradle
@@ -0,0 +1,20 @@
+plugins {
+ id 'java-library'
+}
+
+description = 'ZeroJ prover SPI — proof generation service contracts'
+
+dependencies {
+ api project(':zeroj-api')
+}
+
+publishing {
+ publications {
+ mavenJava(MavenPublication) {
+ pom {
+ name = 'ZeroJ Prover SPI'
+ description = 'Service Provider Interface for ZK proof generation backends'
+ }
+ }
+ }
+}
diff --git a/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProveRequest.java b/zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProveRequest.java
similarity index 68%
rename from incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProveRequest.java
rename to zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProveRequest.java
index 37e66d6..9e43198 100644
--- a/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProveRequest.java
+++ b/zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProveRequest.java
@@ -1,14 +1,14 @@
-package com.bloxbean.cardano.zeroj.prover.sidecar;
+package com.bloxbean.cardano.zeroj.prover.spi;
import java.util.Map;
import java.util.Objects;
/**
- * Request to generate a ZK proof via the prover sidecar.
+ * Request to generate a ZK proof.
*
- * @param circuitName the circuit to prove (must be pre-loaded in the sidecar)
+ * @param circuitName the circuit to prove
* @param input the circuit input as a key-value map (public + private inputs)
- * @param provingKeyId optional proving key ID (if the sidecar manages multiple keys per circuit)
+ * @param provingKeyId optional proving key ID when a backend manages multiple keys
*/
public record ProveRequest(
String circuitName,
@@ -24,7 +24,7 @@ public record ProveRequest(
}
/**
- * Create a request with default proving key.
+ * Create a request with the backend's default proving key.
*/
public static ProveRequest of(String circuitName, Map input) {
return new ProveRequest(circuitName, input, null);
diff --git a/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProveResponse.java b/zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProveResponse.java
similarity index 64%
rename from incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProveResponse.java
rename to zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProveResponse.java
index 8261fed..0bfa733 100644
--- a/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProveResponse.java
+++ b/zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProveResponse.java
@@ -1,16 +1,16 @@
-package com.bloxbean.cardano.zeroj.prover.sidecar;
+package com.bloxbean.cardano.zeroj.prover.spi;
import java.math.BigInteger;
import java.util.List;
import java.util.Objects;
/**
- * Response from the prover sidecar after successful proof generation.
+ * Response from a proof generation backend.
*
- * @param proofJson the snarkjs proof.json content
- * @param publicSignals the public signals (snarkjs public.json content)
- * @param protocol proof system (e.g., "groth16")
- * @param curve curve (e.g., "bn128", "bls12381")
+ * @param proofJson proof JSON content
+ * @param publicSignals public signals
+ * @param protocol proof system (for example, {@code groth16})
+ * @param curve curve (for example, {@code bls12381})
* @param provingTimeMs time spent proving in milliseconds
*/
public record ProveResponse(
diff --git a/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProverException.java b/zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProverException.java
similarity index 71%
rename from incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProverException.java
rename to zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProverException.java
index 885565b..9e48f17 100644
--- a/incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProverException.java
+++ b/zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProverException.java
@@ -1,7 +1,7 @@
-package com.bloxbean.cardano.zeroj.prover.sidecar;
+package com.bloxbean.cardano.zeroj.prover.spi;
/**
- * Exception thrown when proof generation via the sidecar fails.
+ * Exception thrown when proof generation fails.
*/
public class ProverException extends RuntimeException {
@@ -22,17 +22,17 @@ public ErrorCode errorCode() {
}
public enum ErrorCode {
- /** Sidecar is unreachable. */
+ /** Proving backend is unreachable or failed to initialize. */
CONNECTION_FAILED,
- /** Sidecar returned an error response. */
+ /** Proving backend returned an error response. */
PROVING_FAILED,
/** Request timed out. */
TIMEOUT,
/** All retry attempts exhausted. */
RETRIES_EXHAUSTED,
- /** Circuit not found in sidecar. */
+ /** Circuit not found by the backend. */
CIRCUIT_NOT_FOUND,
- /** Invalid witness / input. */
+ /** Invalid witness or input. */
INVALID_INPUT,
/** Unexpected response format. */
INVALID_RESPONSE
diff --git a/zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProverService.java b/zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProverService.java
new file mode 100644
index 0000000..df13382
--- /dev/null
+++ b/zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProverService.java
@@ -0,0 +1,19 @@
+package com.bloxbean.cardano.zeroj.prover.spi;
+
+/**
+ * Contract for a ZK proof generation backend.
+ *
+ * Implementations may be backed by a local native prover, a pure Java prover,
+ * or a remote proving service.
+ */
+public interface ProverService {
+
+ /**
+ * Generate a proof for the given request.
+ *
+ * @param request the proving request
+ * @return the prove response with proof bytes/JSON and public signals
+ * @throws ProverException if proving fails
+ */
+ ProveResponse prove(ProveRequest request);
+}
diff --git a/incubator/zeroj-prover-sidecar/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-sidecar/reflect-config.json b/zeroj-prover-spi/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-spi/reflect-config.json
similarity index 60%
rename from incubator/zeroj-prover-sidecar/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-sidecar/reflect-config.json
rename to zeroj-prover-spi/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-spi/reflect-config.json
index 3882036..3990ec7 100644
--- a/incubator/zeroj-prover-sidecar/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-sidecar/reflect-config.json
+++ b/zeroj-prover-spi/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-spi/reflect-config.json
@@ -1,18 +1,18 @@
[
{
- "name": "com.bloxbean.cardano.zeroj.prover.sidecar.ProveRequest",
+ "name": "com.bloxbean.cardano.zeroj.prover.spi.ProveRequest",
"allDeclaredConstructors": true,
"allDeclaredMethods": true,
"allDeclaredFields": true
},
{
- "name": "com.bloxbean.cardano.zeroj.prover.sidecar.ProveResponse",
+ "name": "com.bloxbean.cardano.zeroj.prover.spi.ProveResponse",
"allDeclaredConstructors": true,
"allDeclaredMethods": true,
"allDeclaredFields": true
},
{
- "name": "com.bloxbean.cardano.zeroj.prover.sidecar.ProverConfig",
+ "name": "com.bloxbean.cardano.zeroj.prover.spi.ProverException$ErrorCode",
"allDeclaredConstructors": true,
"allDeclaredMethods": true,
"allDeclaredFields": true
diff --git a/incubator/zeroj-prover-sidecar/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-sidecar/resource-config.json b/zeroj-prover-spi/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-spi/resource-config.json
similarity index 100%
rename from incubator/zeroj-prover-sidecar/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-sidecar/resource-config.json
rename to zeroj-prover-spi/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-spi/resource-config.json
diff --git a/zeroj-submission/README.md b/zeroj-submission/README.md
deleted file mode 100644
index 88b2a06..0000000
--- a/zeroj-submission/README.md
+++ /dev/null
@@ -1,65 +0,0 @@
-# zeroj-submission
-
-Proof submission wire format, Ed25519 signatures, and result types.
-
-This module defines the data model for **proof-backed state transition submissions**. A submission represents a signed request to transition application state, backed by a ZK proof. It includes the proof, public inputs, state roots, submitter identity, and an Ed25519 signature.
-
-## Key Types
-
-| Type | Description |
-|------|-------------|
-| `AppProofSubmission` | Immutable submission message — app ID, proof, state roots, submitter signature, sequence number |
-| `SubmissionResult` | Result of pipeline processing — accepted/rejected, stage, reason, message |
-| `SubmissionHash` | Deterministic SHA-256 hash of submission fields for Ed25519 signing |
-| `Ed25519Signer` | Generate key pairs, sign submission hashes, verify signatures |
-
-### Validation Stages
-
-| Stage | Description |
-|-------|-------------|
-| `SYNTACTIC` | Proof non-empty, VK hash valid, public inputs present |
-| `SIGNATURE` | Ed25519 signature valid, submitter known and authorized |
-| `CIRCUIT_RESOLUTION` | Circuit allowed, VK found in registry |
-| `CRYPTOGRAPHIC_VERIFICATION` | ZK proof is valid |
-| `POLICY` | State root chain, sequence ordering, nullifier uniqueness |
-| `ACCEPTED` | All checks passed, state updated |
-
-### Rejection Reasons
-
-`EMPTY_PROOF`, `INVALID_VK_HASH_LENGTH`, `MALFORMED_SUBMISSION`, `INVALID_SIGNATURE`, `UNKNOWN_SUBMITTER`, `UNAUTHORIZED_SUBMITTER`, `UNKNOWN_CIRCUIT`, `RETIRED_CIRCUIT`, `VK_NOT_FOUND`, `PROOF_INVALID`, `PROOF_VERIFICATION_ERROR`, `STALE_STATE_ROOT`, `DUPLICATE_SEQUENCE`, `USED_NULLIFIER`, `SEQUENCE_GAP`
-
-## Usage
-
-```java
-// Generate submitter keys
-KeyPair keys = Ed25519Signer.generateKeyPair();
-
-// Build a submission
-var submission = AppProofSubmission.builder()
- .appId("my-app")
- .proofSystem(ProofSystemId.GROTH16)
- .curve(CurveId.BN254)
- .circuitId("multiplier")
- .circuitVersion("v1")
- .prevStateRoot(currentRoot)
- .newStateRoot(newRoot)
- .publicInputs(List.of(BigInteger.valueOf(33)))
- .proofBytes(proofBytes)
- .vkHash(vkHash)
- .submitterId("alice")
- .submitterSignature(new byte[64]) // placeholder
- .sequence(1)
- .build();
-
-// Sign with Ed25519
-byte[] hash = SubmissionHash.compute(submission);
-byte[] signature = Ed25519Signer.sign(hash, keys.getPrivate());
-```
-
-## Gradle
-
-```gradle
-dependencies {
- implementation 'com.bloxbean.cardano:zeroj-submission'
-}
-```
diff --git a/zeroj-submission/build.gradle b/zeroj-submission/build.gradle
deleted file mode 100644
index 6f1d137..0000000
--- a/zeroj-submission/build.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-plugins {
- id 'java-library'
-}
-
-description = 'ZeroJ submission — proof submission model, CBOR wire format, Ed25519 signatures'
-
-dependencies {
- api project(':zeroj-api')
- api 'co.nstant.in:cbor:0.9'
-}
-
-publishing {
- publications {
- mavenJava(MavenPublication) {
- pom {
- name = 'ZeroJ Submission'
- description = 'Proof submission protocol model and wire format'
- }
- }
- }
-}
diff --git a/zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/AppProofSubmission.java b/zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/AppProofSubmission.java
deleted file mode 100644
index 4055a7f..0000000
--- a/zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/AppProofSubmission.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package com.bloxbean.cardano.zeroj.submission;
-
-import com.bloxbean.cardano.zeroj.api.CurveId;
-import com.bloxbean.cardano.zeroj.api.ProofSystemId;
-
-import java.math.BigInteger;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-/**
- * A proof-backed state transition submission.
- *
- * This is the wire-level message that a submitter sends to verifier nodes.
- * Nodes validate it through the 6-stage ingestion pipeline.
- *
- * All fields are immutable. Byte arrays are defensively copied.
- */
-public final class AppProofSubmission {
-
- private final String appId;
- private final ProofSystemId proofSystem;
- private final CurveId curve;
- private final String circuitId;
- private final String circuitVersion;
- private final byte[] prevStateRoot;
- private final byte[] newStateRoot;
- private final List publicInputs;
- private final byte[] proofBytes;
- private final byte[] vkHash;
- private final String submitterId;
- private final byte[] submitterSignature;
- private final long sequence;
- private final byte[] nullifier; // nullable
- private final Map metadata;
-
- private AppProofSubmission(Builder b) {
- this.appId = Objects.requireNonNull(b.appId, "appId required");
- this.proofSystem = Objects.requireNonNull(b.proofSystem, "proofSystem required");
- this.curve = Objects.requireNonNull(b.curve, "curve required");
- this.circuitId = Objects.requireNonNull(b.circuitId, "circuitId required");
- this.circuitVersion = Objects.requireNonNull(b.circuitVersion, "circuitVersion required");
- this.prevStateRoot = Objects.requireNonNull(b.prevStateRoot, "prevStateRoot required").clone();
- this.newStateRoot = Objects.requireNonNull(b.newStateRoot, "newStateRoot required").clone();
- this.publicInputs = List.copyOf(Objects.requireNonNull(b.publicInputs, "publicInputs required"));
- this.proofBytes = Objects.requireNonNull(b.proofBytes, "proofBytes required").clone();
- if (this.proofBytes.length == 0) throw new IllegalArgumentException("proofBytes must not be empty");
- this.vkHash = Objects.requireNonNull(b.vkHash, "vkHash required").clone();
- if (this.vkHash.length != 32) throw new IllegalArgumentException("vkHash must be 32 bytes");
- this.submitterId = Objects.requireNonNull(b.submitterId, "submitterId required");
- this.submitterSignature = Objects.requireNonNull(b.submitterSignature, "submitterSignature required").clone();
- this.sequence = b.sequence;
- if (this.sequence < 0) throw new IllegalArgumentException("sequence must be >= 0");
- this.nullifier = b.nullifier != null ? b.nullifier.clone() : null;
- this.metadata = b.metadata != null ? Map.copyOf(b.metadata) : Map.of();
- }
-
- // --- Getters (defensive copies for byte arrays) ---
-
- public String appId() { return appId; }
- public ProofSystemId proofSystem() { return proofSystem; }
- public CurveId curve() { return curve; }
- public String circuitId() { return circuitId; }
- public String circuitVersion() { return circuitVersion; }
- public byte[] prevStateRoot() { return prevStateRoot.clone(); }
- public byte[] newStateRoot() { return newStateRoot.clone(); }
- public List publicInputs() { return publicInputs; }
- public byte[] proofBytes() { return proofBytes.clone(); }
- public byte[] vkHash() { return vkHash.clone(); }
- public String submitterId() { return submitterId; }
- public byte[] submitterSignature() { return submitterSignature.clone(); }
- public long sequence() { return sequence; }
- public byte[] nullifier() { return nullifier != null ? nullifier.clone() : null; }
- public Map metadata() { return metadata; }
-
- public static Builder builder() { return new Builder(); }
-
- public static final class Builder {
- private String appId;
- private ProofSystemId proofSystem;
- private CurveId curve;
- private String circuitId;
- private String circuitVersion;
- private byte[] prevStateRoot;
- private byte[] newStateRoot;
- private List publicInputs;
- private byte[] proofBytes;
- private byte[] vkHash;
- private String submitterId;
- private byte[] submitterSignature;
- private long sequence;
- private byte[] nullifier;
- private Map metadata;
-
- public Builder appId(String v) { this.appId = v; return this; }
- public Builder proofSystem(ProofSystemId v) { this.proofSystem = v; return this; }
- public Builder curve(CurveId v) { this.curve = v; return this; }
- public Builder circuitId(String v) { this.circuitId = v; return this; }
- public Builder circuitVersion(String v) { this.circuitVersion = v; return this; }
- public Builder prevStateRoot(byte[] v) { this.prevStateRoot = v; return this; }
- public Builder newStateRoot(byte[] v) { this.newStateRoot = v; return this; }
- public Builder publicInputs(List v) { this.publicInputs = v; return this; }
- public Builder proofBytes(byte[] v) { this.proofBytes = v; return this; }
- public Builder vkHash(byte[] v) { this.vkHash = v; return this; }
- public Builder submitterId(String v) { this.submitterId = v; return this; }
- public Builder submitterSignature(byte[] v) { this.submitterSignature = v; return this; }
- public Builder sequence(long v) { this.sequence = v; return this; }
- public Builder nullifier(byte[] v) { this.nullifier = v; return this; }
- public Builder metadata(Map v) { this.metadata = v; return this; }
-
- public AppProofSubmission build() { return new AppProofSubmission(this); }
- }
-
- @Override
- public String toString() {
- return "AppProofSubmission[app=" + appId + ", circuit=" + circuitId + "/" + circuitVersion
- + ", submitter=" + submitterId + ", seq=" + sequence + "]";
- }
-}
diff --git a/zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/Ed25519Signer.java b/zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/Ed25519Signer.java
deleted file mode 100644
index d8b8f4a..0000000
--- a/zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/Ed25519Signer.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.bloxbean.cardano.zeroj.submission;
-
-import java.security.*;
-import java.security.spec.EdECPoint;
-import java.security.spec.EdECPublicKeySpec;
-import java.security.spec.NamedParameterSpec;
-import java.util.Objects;
-
-/**
- * Ed25519 signature utilities for submission signing and verification.
- *
- * Uses Java's built-in EdDSA support (available since Java 15).
- */
-public final class Ed25519Signer {
-
- private Ed25519Signer() {}
-
- /**
- * Generate a new Ed25519 key pair (for testing and development).
- */
- public static KeyPair generateKeyPair() {
- try {
- var kpg = KeyPairGenerator.getInstance("Ed25519");
- return kpg.generateKeyPair();
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("Ed25519 not available", e);
- }
- }
-
- /**
- * Sign a message with an Ed25519 private key.
- *
- * @param message the message to sign
- * @param privateKey the Ed25519 private key
- * @return the 64-byte Ed25519 signature
- */
- public static byte[] sign(byte[] message, PrivateKey privateKey) {
- Objects.requireNonNull(message, "message must not be null");
- Objects.requireNonNull(privateKey, "privateKey must not be null");
- try {
- var sig = Signature.getInstance("Ed25519");
- sig.initSign(privateKey);
- sig.update(message);
- return sig.sign();
- } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
- throw new RuntimeException("Ed25519 signing failed", e);
- }
- }
-
- /**
- * Verify an Ed25519 signature.
- *
- * @param message the original message
- * @param signature the 64-byte signature to verify
- * @param publicKey the Ed25519 public key
- * @return true if the signature is valid
- */
- public static boolean verify(byte[] message, byte[] signature, PublicKey publicKey) {
- Objects.requireNonNull(message, "message must not be null");
- Objects.requireNonNull(signature, "signature must not be null");
- Objects.requireNonNull(publicKey, "publicKey must not be null");
- try {
- var sig = Signature.getInstance("Ed25519");
- sig.initVerify(publicKey);
- sig.update(message);
- return sig.verify(signature);
- } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
- return false;
- }
- }
-
- /**
- * Extract the raw 32-byte public key from a Java PublicKey.
- */
- public static byte[] publicKeyBytes(PublicKey publicKey) {
- // Ed25519 public keys are encoded as X.509 SubjectPublicKeyInfo
- // The raw 32-byte key is at the end of the encoded form
- byte[] encoded = publicKey.getEncoded();
- byte[] raw = new byte[32];
- System.arraycopy(encoded, encoded.length - 32, raw, 0, 32);
- return raw;
- }
-
- /**
- * Sign a submission (signs the deterministic submission hash).
- */
- public static byte[] signSubmission(AppProofSubmission submission, PrivateKey privateKey) {
- byte[] hash = SubmissionHash.compute(submission);
- return sign(hash, privateKey);
- }
-
- /**
- * Verify a submission signature.
- */
- public static boolean verifySubmission(AppProofSubmission submission, PublicKey publicKey) {
- byte[] hash = SubmissionHash.compute(submission);
- return verify(hash, submission.submitterSignature(), publicKey);
- }
-}
diff --git a/zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/SubmissionHash.java b/zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/SubmissionHash.java
deleted file mode 100644
index 4593e37..0000000
--- a/zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/SubmissionHash.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package com.bloxbean.cardano.zeroj.submission;
-
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-/**
- * Computes the deterministic hash of an {@link AppProofSubmission} for Ed25519 signing.
- *
- * The hash covers all semantically significant fields (everything except the signature itself).
- * This is the message that must be signed by the submitter.
- *
- * Hash is SHA-256 over a canonical byte encoding with length-prefixed fields.
- */
-public final class SubmissionHash {
-
- private SubmissionHash() {}
-
- /**
- * Compute the signable hash of a submission.
- *
- * @return 32-byte SHA-256 hash
- */
- public static byte[] compute(AppProofSubmission submission) {
- try {
- var digest = MessageDigest.getInstance("SHA-256");
-
- // Domain separator to prevent cross-protocol signature reuse
- digest.update("zeroj-submission-v1".getBytes(StandardCharsets.UTF_8));
-
- digest.update(lengthPrefixed(submission.appId()));
- digest.update(lengthPrefixed(submission.proofSystem().value()));
- digest.update(lengthPrefixed(submission.curve().value()));
- digest.update(lengthPrefixed(submission.circuitId()));
- digest.update(lengthPrefixed(submission.circuitVersion()));
- digest.update(lengthPrefixedBytes(submission.prevStateRoot()));
- digest.update(lengthPrefixedBytes(submission.newStateRoot()));
-
- // Public inputs: count + each as length-prefixed BigInteger bytes
- var inputs = submission.publicInputs();
- digest.update(ByteBuffer.allocate(4).putInt(inputs.size()).array());
- for (var input : inputs) {
- byte[] inputBytes = input.toByteArray();
- digest.update(lengthPrefixedBytes(inputBytes));
- }
-
- digest.update(lengthPrefixedBytes(submission.proofBytes()));
- digest.update(submission.vkHash()); // always 32 bytes, no length prefix needed
- digest.update(lengthPrefixed(submission.submitterId()));
- digest.update(ByteBuffer.allocate(8).putLong(submission.sequence()).array());
-
- // Nullifier (optional — tag byte 0x00 for absent, 0x01 + data for present)
- var nullifier = submission.nullifier();
- if (nullifier == null) {
- digest.update((byte) 0x00);
- } else {
- digest.update((byte) 0x01);
- digest.update(lengthPrefixedBytes(nullifier));
- }
-
- return digest.digest();
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("SHA-256 not available", e);
- }
- }
-
- private static byte[] lengthPrefixed(String s) {
- byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
- var buf = ByteBuffer.allocate(4 + bytes.length);
- buf.putInt(bytes.length);
- buf.put(bytes);
- return buf.array();
- }
-
- private static byte[] lengthPrefixedBytes(byte[] data) {
- var buf = ByteBuffer.allocate(4 + data.length);
- buf.putInt(data.length);
- buf.put(data);
- return buf.array();
- }
-}
diff --git a/zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/SubmissionResult.java b/zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/SubmissionResult.java
deleted file mode 100644
index 69abbbb..0000000
--- a/zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/SubmissionResult.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.bloxbean.cardano.zeroj.submission;
-
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- * Result of the submission ingestion pipeline.
- *
- * Records which validation stage failed (if any) and provides a reason.
- *
- * @param accepted whether the submission was accepted
- * @param stage the validation stage that produced this result
- * @param reason machine-readable rejection reason (empty if accepted)
- * @param message human-readable description
- */
-public record SubmissionResult(
- boolean accepted,
- ValidationStage stage,
- Optional reason,
- Optional message
-) {
-
- public SubmissionResult {
- Objects.requireNonNull(stage);
- Objects.requireNonNull(reason);
- Objects.requireNonNull(message);
- }
-
- public static SubmissionResult ok() {
- return new SubmissionResult(true, ValidationStage.ACCEPTED, Optional.empty(), Optional.empty());
- }
-
- public static SubmissionResult rejected(ValidationStage stage, RejectionReason reason, String message) {
- return new SubmissionResult(false, stage, Optional.of(reason), Optional.ofNullable(message));
- }
-
- /**
- * The validation stages in the ingestion pipeline, in execution order.
- */
- public enum ValidationStage {
- SYNTACTIC,
- SIGNATURE,
- CIRCUIT_RESOLUTION,
- CRYPTOGRAPHIC_VERIFICATION,
- POLICY,
- ACCEPTED
- }
-
- /**
- * Machine-readable rejection reasons.
- */
- public enum RejectionReason {
- // Syntactic
- MALFORMED_SUBMISSION,
- EMPTY_PROOF,
- INVALID_VK_HASH_LENGTH,
-
- // Signature
- INVALID_SIGNATURE,
- UNKNOWN_SUBMITTER,
- UNAUTHORIZED_SUBMITTER,
- SUBMITTER_SUSPENDED,
-
- // Circuit resolution
- UNKNOWN_CIRCUIT,
- DEPRECATED_CIRCUIT,
- RETIRED_CIRCUIT,
- VK_NOT_FOUND,
- VK_EXPIRED,
-
- // Cryptographic
- PROOF_INVALID,
- PROOF_VERIFICATION_ERROR,
-
- // Policy
- STALE_STATE_ROOT,
- DUPLICATE_SEQUENCE,
- USED_NULLIFIER,
- SEQUENCE_GAP
- }
-}
diff --git a/zeroj-submission/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-submission/reflect-config.json b/zeroj-submission/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-submission/reflect-config.json
deleted file mode 100644
index fe51488..0000000
--- a/zeroj-submission/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-submission/reflect-config.json
+++ /dev/null
@@ -1 +0,0 @@
-[]
diff --git a/zeroj-submission/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-submission/resource-config.json b/zeroj-submission/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-submission/resource-config.json
deleted file mode 100644
index a7360e9..0000000
--- a/zeroj-submission/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-submission/resource-config.json
+++ /dev/null
@@ -1 +0,0 @@
-{"resources":{"includes":[]}}
diff --git a/zeroj-submission/src/test/java/com/bloxbean/cardano/zeroj/submission/ProtocolTest.java b/zeroj-submission/src/test/java/com/bloxbean/cardano/zeroj/submission/ProtocolTest.java
deleted file mode 100644
index 88a288f..0000000
--- a/zeroj-submission/src/test/java/com/bloxbean/cardano/zeroj/submission/ProtocolTest.java
+++ /dev/null
@@ -1,268 +0,0 @@
-package com.bloxbean.cardano.zeroj.submission;
-
-import com.bloxbean.cardano.zeroj.api.CurveId;
-import com.bloxbean.cardano.zeroj.api.ProofSystemId;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class ProtocolTest {
-
- // ==================== AppProofSubmission ====================
-
- @Nested
- class AppProofSubmissionTests {
-
- @Test
- void builder_validSubmission() {
- var sub = validBuilder().build();
- assertEquals("app1", sub.appId());
- assertEquals(ProofSystemId.GROTH16, sub.proofSystem());
- assertEquals(CurveId.BN254, sub.curve());
- assertEquals("circuit1", sub.circuitId());
- assertEquals("v1", sub.circuitVersion());
- assertEquals(1, sub.sequence());
- assertEquals("submitter1", sub.submitterId());
- assertEquals(2, sub.publicInputs().size());
- }
-
- @Test
- void builder_missingRequiredFieldThrows() {
- assertThrows(NullPointerException.class, () ->
- AppProofSubmission.builder().build());
- assertThrows(NullPointerException.class, () ->
- AppProofSubmission.builder().appId("a").build());
- }
-
- @Test
- void builder_emptyProofBytesThrows() {
- assertThrows(IllegalArgumentException.class, () ->
- validBuilder().proofBytes(new byte[0]).build());
- }
-
- @Test
- void builder_wrongVkHashLengthThrows() {
- assertThrows(IllegalArgumentException.class, () ->
- validBuilder().vkHash(new byte[16]).build());
- }
-
- @Test
- void builder_negativeSequenceThrows() {
- assertThrows(IllegalArgumentException.class, () ->
- validBuilder().sequence(-1).build());
- }
-
- @Test
- void defensiveCopy_proofBytes() {
- byte[] original = {1, 2, 3};
- var sub = validBuilder().proofBytes(original).build();
- original[0] = 99;
- assertEquals(1, sub.proofBytes()[0]);
- }
-
- @Test
- void defensiveCopy_vkHash() {
- byte[] hash = new byte[32];
- hash[0] = 42;
- var sub = validBuilder().vkHash(hash).build();
- hash[0] = 99;
- assertEquals(42, sub.vkHash()[0]);
- }
-
- @Test
- void nullifierIsOptional() {
- var sub = validBuilder().build();
- assertNull(sub.nullifier());
-
- var subWithNullifier = validBuilder().nullifier(new byte[]{1, 2}).build();
- assertArrayEquals(new byte[]{1, 2}, subWithNullifier.nullifier());
- }
-
- @Test
- void metadataIsImmutable() {
- var sub = validBuilder().build();
- assertThrows(UnsupportedOperationException.class, () ->
- sub.metadata().put("x", "y"));
- }
-
- private AppProofSubmission.Builder validBuilder() {
- return AppProofSubmission.builder()
- .appId("app1")
- .proofSystem(ProofSystemId.GROTH16)
- .curve(CurveId.BN254)
- .circuitId("circuit1")
- .circuitVersion("v1")
- .prevStateRoot(new byte[32])
- .newStateRoot(new byte[32])
- .publicInputs(List.of(BigInteger.valueOf(33), BigInteger.valueOf(3)))
- .proofBytes(new byte[]{1, 2, 3})
- .vkHash(new byte[32])
- .submitterId("submitter1")
- .submitterSignature(new byte[64])
- .sequence(1);
- }
- }
-
- // ==================== Ed25519Signer ====================
-
- @Nested
- class Ed25519SignerTests {
-
- @Test
- void generateKeyPair() {
- KeyPair kp = Ed25519Signer.generateKeyPair();
- assertNotNull(kp.getPublic());
- assertNotNull(kp.getPrivate());
- }
-
- @Test
- void signAndVerify_roundTrip() {
- KeyPair kp = Ed25519Signer.generateKeyPair();
- byte[] message = "test message".getBytes();
-
- byte[] signature = Ed25519Signer.sign(message, kp.getPrivate());
- assertNotNull(signature);
- assertTrue(signature.length > 0);
-
- assertTrue(Ed25519Signer.verify(message, signature, kp.getPublic()));
- }
-
- @Test
- void verify_wrongKeyRejected() {
- KeyPair alice = Ed25519Signer.generateKeyPair();
- KeyPair bob = Ed25519Signer.generateKeyPair();
-
- byte[] message = "secret".getBytes();
- byte[] sig = Ed25519Signer.sign(message, alice.getPrivate());
-
- // Verify with wrong key should fail
- assertFalse(Ed25519Signer.verify(message, sig, bob.getPublic()));
- }
-
- @Test
- void verify_tamperedMessageRejected() {
- KeyPair kp = Ed25519Signer.generateKeyPair();
- byte[] sig = Ed25519Signer.sign("original".getBytes(), kp.getPrivate());
-
- assertFalse(Ed25519Signer.verify("tampered".getBytes(), sig, kp.getPublic()));
- }
-
- @Test
- void verify_tamperedSignatureRejected() {
- KeyPair kp = Ed25519Signer.generateKeyPair();
- byte[] message = "test".getBytes();
- byte[] sig = Ed25519Signer.sign(message, kp.getPrivate());
-
- // Flip a bit
- sig[0] ^= 0x01;
- assertFalse(Ed25519Signer.verify(message, sig, kp.getPublic()));
- }
-
- @Test
- void publicKeyBytes_is32Bytes() {
- KeyPair kp = Ed25519Signer.generateKeyPair();
- byte[] raw = Ed25519Signer.publicKeyBytes(kp.getPublic());
- assertEquals(32, raw.length);
- }
- }
-
- // ==================== SubmissionHash ====================
-
- @Nested
- class SubmissionHashTests {
-
- @Test
- void hash_isDeterministic() {
- var sub = makeSubmission(1);
- byte[] h1 = SubmissionHash.compute(sub);
- byte[] h2 = SubmissionHash.compute(sub);
- assertEquals(32, h1.length);
- assertArrayEquals(h1, h2);
- }
-
- @Test
- void hash_differentSequenceProducesDifferentHash() {
- byte[] h1 = SubmissionHash.compute(makeSubmission(1));
- byte[] h2 = SubmissionHash.compute(makeSubmission(2));
- assertFalse(java.util.Arrays.equals(h1, h2));
- }
-
- @Test
- void hash_differentAppProducesDifferentHash() {
- var sub1 = AppProofSubmission.builder()
- .appId("app-A").proofSystem(ProofSystemId.GROTH16).curve(CurveId.BN254)
- .circuitId("c").circuitVersion("v1")
- .prevStateRoot(new byte[32]).newStateRoot(new byte[32])
- .publicInputs(List.of(BigInteger.ONE))
- .proofBytes(new byte[]{1}).vkHash(new byte[32])
- .submitterId("s").submitterSignature(new byte[64]).sequence(1).build();
- var sub2 = AppProofSubmission.builder()
- .appId("app-B").proofSystem(ProofSystemId.GROTH16).curve(CurveId.BN254)
- .circuitId("c").circuitVersion("v1")
- .prevStateRoot(new byte[32]).newStateRoot(new byte[32])
- .publicInputs(List.of(BigInteger.ONE))
- .proofBytes(new byte[]{1}).vkHash(new byte[32])
- .submitterId("s").submitterSignature(new byte[64]).sequence(1).build();
-
- assertFalse(java.util.Arrays.equals(
- SubmissionHash.compute(sub1), SubmissionHash.compute(sub2)));
- }
-
- @Test
- void hash_nullifierPresenceChangesHash() {
- var without = makeSubmission(1);
- var with = AppProofSubmission.builder()
- .appId("app").proofSystem(ProofSystemId.GROTH16).curve(CurveId.BN254)
- .circuitId("c").circuitVersion("v1")
- .prevStateRoot(new byte[32]).newStateRoot(new byte[32])
- .publicInputs(List.of(BigInteger.ONE))
- .proofBytes(new byte[]{1}).vkHash(new byte[32])
- .submitterId("s").submitterSignature(new byte[64]).sequence(1)
- .nullifier(new byte[]{9, 9, 9}).build();
-
- assertFalse(java.util.Arrays.equals(
- SubmissionHash.compute(without), SubmissionHash.compute(with)));
- }
-
- private AppProofSubmission makeSubmission(long seq) {
- return AppProofSubmission.builder()
- .appId("app").proofSystem(ProofSystemId.GROTH16).curve(CurveId.BN254)
- .circuitId("c").circuitVersion("v1")
- .prevStateRoot(new byte[32]).newStateRoot(new byte[32])
- .publicInputs(List.of(BigInteger.ONE))
- .proofBytes(new byte[]{1}).vkHash(new byte[32])
- .submitterId("s").submitterSignature(new byte[64]).sequence(seq).build();
- }
- }
-
- // ==================== SubmissionResult ====================
-
- @Nested
- class SubmissionResultTests {
-
- @Test
- void ok_isAccepted() {
- var r = SubmissionResult.ok();
- assertTrue(r.accepted());
- assertEquals(SubmissionResult.ValidationStage.ACCEPTED, r.stage());
- assertTrue(r.reason().isEmpty());
- }
-
- @Test
- void rejected_hasDetails() {
- var r = SubmissionResult.rejected(
- SubmissionResult.ValidationStage.SIGNATURE,
- SubmissionResult.RejectionReason.INVALID_SIGNATURE,
- "bad sig");
- assertFalse(r.accepted());
- assertEquals(SubmissionResult.ValidationStage.SIGNATURE, r.stage());
- assertEquals(SubmissionResult.RejectionReason.INVALID_SIGNATURE, r.reason().orElse(null));
- assertEquals("bad sig", r.message().orElse(null));
- }
- }
-}
diff --git a/zeroj-test-vectors/README.md b/zeroj-test-vectors/README.md
index a796bd5..83a1ffb 100644
--- a/zeroj-test-vectors/README.md
+++ b/zeroj-test-vectors/README.md
@@ -10,8 +10,13 @@ This module contains test resources used across multiple ZeroJ modules. It is **
|-----------|-------------|
| `test-vectors/groth16-bn254/` | Groth16/BN254 proof, VK, public inputs (snarkjs format) |
| `test-vectors/groth16-bls12381/` | Groth16/BLS12-381 proof, VK, public inputs |
+| `test-vectors/groth16-bn254-cubic/` | Additional Groth16/BN254 cubic circuit vector |
+| `test-vectors/groth16-bn254-invalid/` | Tampered proof/input/VK cases for negative tests |
+| `test-vectors/plonk-bn254/` | PlonK/BN254 snarkjs proof, VK, public inputs |
| `test-vectors/eip197-bn254-pairing/` | Ethereum EIP-197 pairing test vectors for BN254 validation |
| `test-vectors/plonk-bls12381/` | PlonK/BLS12-381 test vectors (gnark format) |
+| `test-vectors/circom-wasm-bn254/` | circom WASM/R1CS fixture for witness-calculation tests |
+| `test-vectors/malformed/` | Invalid JSON/proof fixtures for codec validation |
## Test Circuit
diff --git a/zeroj-verifier-groth16/README.md b/zeroj-verifier-groth16/README.md
index 49dde4e..b64d9a4 100644
--- a/zeroj-verifier-groth16/README.md
+++ b/zeroj-verifier-groth16/README.md
@@ -6,8 +6,9 @@ This module provides two verification backends:
| Backend | Curve | Implementation | Performance |
|---------|-------|----------------|-------------|
-| `Groth16BN254Verifier` | BN254 | Pure Java | ~100-300ms |
-| `Groth16BLS12381Verifier` | BLS12-381 | Native via blst | ~1ms |
+| `Groth16BN254Verifier` | BN254 | Pure Java | snarkjs/circom-compatible |
+| `Groth16BLS12381PureJavaVerifier` | BLS12-381 | Pure Java | zero native dependencies |
+| `Groth16BLS12381Verifier` | BLS12-381 | Native via blst | faster opt-in path |
## BN254 (Pure Java)
@@ -19,9 +20,12 @@ The BN254 backend is implemented entirely in Java with no native dependencies. I
The pairing check verifies: `e(A,B) * e(-alpha,beta) * e(-vk_x,gamma) * e(-C,delta) == 1`
-## BLS12-381 (Native blst)
+## BLS12-381
-The BLS12-381 backend delegates pairing operations to the `blst` native library via `zeroj-blst`. This is the same curve used by Cardano's Plutus V3 BLS primitives.
+The BLS12-381 pure Java backend uses `zeroj-bls12381` and requires no native
+library. The blst-backed backend delegates pairing operations to `zeroj-blst`
+for the faster native path. BLS12-381 is the same curve used by Cardano's
+Plutus V3 BLS primitives.
## Usage
@@ -29,7 +33,8 @@ The BLS12-381 backend delegates pairing operations to the `blst` native library
// Register backends
var registry = VerifierRegistry.empty();
registry.register(new Groth16BN254Verifier()); // Pure Java
-registry.register(new Groth16BLS12381Verifier()); // Native blst
+registry.register(new Groth16BLS12381PureJavaVerifier()); // Pure Java
+registry.register(new Groth16BLS12381Verifier()); // Native blst
// Parse snarkjs artifacts and verify
var envelope = SnarkjsJsonCodec.toEnvelopeFromJson(proofJson, vkJson, publicJson,
diff --git a/zeroj-verifier-plonk/README.md b/zeroj-verifier-plonk/README.md
index 4fe7edc..216844e 100644
--- a/zeroj-verifier-plonk/README.md
+++ b/zeroj-verifier-plonk/README.md
@@ -2,18 +2,21 @@
PlonK proof verification for BLS12-381 and BN254 curves.
-This module provides pure Java PlonK verification, removing the gnark native dependency for the verification path. Proving still uses gnark (via `zeroj-prover-gnark`), but verification is done entirely in Java + blst.
+This module provides pure Java PlonK verification for structured snarkjs/ZeroJ
+proof JSON. gnark's opaque binary PlonK proof JSON is not accepted by these
+verifiers yet; verify that format with gnark native verification until a
+dedicated adapter is added.
| Backend | Curve | Implementation | Status |
|---------|-------|----------------|--------|
-| `PlonkBLS12381Verifier` | BLS12-381 | Java + blst (pairing) | Full implementation |
+| `PlonkBLS12381Verifier` | BLS12-381 | Pure Java | Full implementation |
| `PlonkBN254Verifier` | BN254 | Pure Java | Challenge derivation done, pairing TODO |
## Architecture
PlonK verification involves 6 steps:
-1. **Fiat-Shamir challenge derivation** — re-derive beta, gamma, alpha, zeta, v, u from the proof transcript using SHA-256
+1. **Fiat-Shamir challenge derivation** — re-derive beta, gamma, alpha, zeta, v, u from the proof transcript using Keccak-256
2. **Vanishing polynomial** — evaluate Z_H(zeta) = zeta^n - 1
3. **Lagrange polynomial** — evaluate L_1(zeta) for the gate identity check
4. **Public input polynomial** — compute PI(zeta) from the public inputs and Lagrange basis
@@ -24,9 +27,9 @@ PlonK verification involves 6 steps:
| Class | Purpose |
|-------|---------|
-| `PlonkBLS12381Verifier` | Implements `ZkVerifier` SPI — full PlonK verification via blst pairings |
+| `PlonkBLS12381Verifier` | Implements `ZkVerifier` SPI — full pure Java PlonK verification |
| `PlonkBN254Verifier` | Implements `ZkVerifier` SPI — BN254 PlonK (scaffold, challenge derivation complete) |
-| `FiatShamirTranscript` | SHA-256 transcript for deterministic challenge generation |
+| `FiatShamirTranscript` | Shared Keccak-256 transcript from `zeroj-crypto` for deterministic snarkjs-compatible challenge generation |
| `KzgVerifier` | KZG polynomial commitment opening proof verification |
| `PlonkProof` | Parsed proof record (commitments + evaluations) |
| `PlonkVerificationKey` | Parsed VK record (selectors, permutations, SRS, domain) |
@@ -35,7 +38,7 @@ PlonK verification involves 6 steps:
```java
// Via SPI (auto-discovered by VerifierOrchestrator)
-var registry = VerifierRegistry.discover(); // finds PlonkBLS12381Verifier
+var registry = VerifierRegistry.withServiceLoader(); // finds ServiceLoader-registered verifiers
// Or direct
var verifier = new PlonkBLS12381Verifier();
@@ -62,7 +65,7 @@ The transcript must match the prover's byte layout exactly. The current implemen
## Test Vectors
- `zeroj-test-vectors/.../plonk-bn254/` — snarkjs PlonK BN254 (multiplier: 3 × 11 = 33)
-- `zeroj-test-vectors/.../plonk-bls12381/` — gnark PlonK BLS12-381 (same circuit)
+- `zeroj-test-vectors/.../plonk-bls12381/` — gnark PlonK BLS12-381 artifacts used for transcript and compatibility tests
## Gradle
diff --git a/zeroj-verifier-plonk/build.gradle b/zeroj-verifier-plonk/build.gradle
index c9cdef2..f1e5f60 100644
--- a/zeroj-verifier-plonk/build.gradle
+++ b/zeroj-verifier-plonk/build.gradle
@@ -8,8 +8,10 @@ dependencies {
api project(':zeroj-backend-spi')
implementation project(':zeroj-codec')
implementation project(':zeroj-bls12381')
+ implementation project(':zeroj-crypto')
implementation project(':zeroj-verifier-groth16') // BN254 field arithmetic reuse
+ testImplementation project(':zeroj-circuit-dsl')
testImplementation project(':zeroj-test-vectors')
}
diff --git a/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBLS12381Verifier.java b/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBLS12381Verifier.java
index 8d1359a..6ca1b45 100644
--- a/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBLS12381Verifier.java
+++ b/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBLS12381Verifier.java
@@ -6,6 +6,7 @@
import com.bloxbean.cardano.zeroj.bls12381.ec.*;
import com.bloxbean.cardano.zeroj.bls12381.field.*;
import com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing;
+import com.bloxbean.cardano.zeroj.crypto.transcript.FiatShamirTranscript;
import java.math.BigInteger;
import java.util.List;
@@ -14,7 +15,7 @@
* Pure Java PlonK verifier for BLS12-381 — no native dependencies.
*
* Uses the pure Java BLS12-381 field arithmetic and pairing implementation
- * in {@link com.bloxbean.cardano.zeroj.verifier.plonk.bls12381}.
+ * from {@code zeroj-bls12381}.
*
* Implements the snarkjs PlonK verification algorithm:
*
@@ -41,6 +42,12 @@ public BackendDescriptor descriptor() {
@Override
public VerificationResult verify(ZkProofEnvelope envelope, VerificationMaterial material) {
try {
+ if (envelope.proofFormat().filter("gnark-plonk-json"::equals).isPresent()) {
+ return VerificationResult.error(
+ VerificationResult.ReasonCode.UNSUPPORTED_PROOF_SYSTEM,
+ "gnark binary PlonK JSON is not accepted by the snarkjs/ZeroJ structured PlonK verifier");
+ }
+
var proofJson = new String(envelope.proofBytes());
var vkJson = new String(material.vkBytes());
@@ -70,124 +77,141 @@ public VerificationResult verify(ZkProofEnvelope envelope, VerificationMaterial
int n = vk.domainSize();
BigInteger omega = vk.omega();
- // Step 1: Fiat-Shamir challenges
- var transcript = new FiatShamirTranscript(Fr);
- appendG1(transcript, proof.cmA());
- appendG1(transcript, proof.cmB());
- appendG1(transcript, proof.cmC());
- BigInteger beta = transcript.squeezeNonZeroChallenge();
- BigInteger gamma = transcript.squeezeNonZeroChallenge();
-
- appendG1(transcript, proof.cmZ());
- BigInteger alpha = transcript.squeezeNonZeroChallenge();
-
- appendG1(transcript, proof.cmT1());
- appendG1(transcript, proof.cmT2());
- appendG1(transcript, proof.cmT3());
- BigInteger zeta = transcript.squeezeNonZeroChallenge();
-
- transcript.appendScalar(proof.evalA());
- transcript.appendScalar(proof.evalB());
- transcript.appendScalar(proof.evalC());
- transcript.appendScalar(proof.evalS1());
- transcript.appendScalar(proof.evalS2());
- transcript.appendScalar(proof.evalZOmega());
- BigInteger v = transcript.squeezeNonZeroChallenge();
-
- appendG1(transcript, proof.wZeta());
- appendG1(transcript, proof.wZetaOmega());
- BigInteger u = transcript.squeezeNonZeroChallenge();
-
- // Step 2: Z_H(zeta) = zeta^n - 1
- BigInteger zetaPowN = zeta.modPow(BigInteger.valueOf(n), Fr);
- BigInteger zh = zetaPowN.subtract(BigInteger.ONE).mod(Fr);
-
- // Step 3: L1(zeta)
- BigInteger nInv = BigInteger.valueOf(n).modInverse(Fr);
- BigInteger l1 = zh.multiply(nInv).mod(Fr)
- .multiply(zeta.subtract(BigInteger.ONE).mod(Fr).modInverse(Fr)).mod(Fr);
-
- // Step 4: PI(zeta)
- BigInteger pi = BigInteger.ZERO;
+ // Step 1: Fiat-Shamir challenges. This mirrors the BLS12-381 prover
+ // and BN254 verifier transcript round boundaries.
+ G1Point A = toG1(proof.cmA()), B = toG1(proof.cmB()), C = toG1(proof.cmC());
+ G1Point Z = toG1(proof.cmZ());
+ G1Point T1 = toG1(proof.cmT1()), T2 = toG1(proof.cmT2()), T3 = toG1(proof.cmT3());
+ G1Point Wxi = toG1(proof.wZeta()), Wxiw = toG1(proof.wZetaOmega());
+
+ G1Point Qm = toG1(vk.qM()), Ql = toG1(vk.qL()), Qr = toG1(vk.qR());
+ G1Point Qo = toG1(vk.qO()), Qc = toG1(vk.qC());
+ G1Point S1 = toG1(vk.s1()), S2 = toG1(vk.s2()), S3 = toG1(vk.s3());
+ G2Point X_2 = toG2(vk.x2());
+
+ var transcript = new FiatShamirTranscript(Fr, 32, 48);
+ addG1(transcript, Qm); addG1(transcript, Ql); addG1(transcript, Qr);
+ addG1(transcript, Qo); addG1(transcript, Qc);
+ addG1(transcript, S1); addG1(transcript, S2); addG1(transcript, S3);
for (int i = 0; i < publicInputs.size(); i++) {
- BigInteger omegaI = omega.modPow(BigInteger.valueOf(i), Fr);
- BigInteger li = zh.multiply(nInv).mod(Fr)
- .multiply(omegaI).mod(Fr)
- .multiply(zeta.subtract(omegaI).mod(Fr).modInverse(Fr)).mod(Fr);
- pi = pi.add(publicInputs.get(i).multiply(li).mod(Fr)).mod(Fr);
+ transcript.addScalar(publicInputs.get(i));
}
+ addG1(transcript, A); addG1(transcript, B); addG1(transcript, C);
+ BigInteger beta = transcript.getChallenge();
+
+ transcript.reset();
+ transcript.addScalar(beta);
+ BigInteger gamma = transcript.getChallenge();
+
+ transcript.reset();
+ transcript.addScalar(beta);
+ transcript.addScalar(gamma);
+ addG1(transcript, Z);
+ BigInteger alpha = transcript.getChallenge();
+
+ transcript.reset();
+ transcript.addScalar(alpha);
+ addG1(transcript, T1); addG1(transcript, T2); addG1(transcript, T3);
+ BigInteger xi = transcript.getChallenge();
+
+ BigInteger eval_a = proof.evalA(), eval_b = proof.evalB(), eval_c = proof.evalC();
+ BigInteger eval_s1 = proof.evalS1(), eval_s2 = proof.evalS2(), eval_zw = proof.evalZOmega();
+
+ transcript.reset();
+ transcript.addScalar(xi);
+ transcript.addScalar(eval_a); transcript.addScalar(eval_b); transcript.addScalar(eval_c);
+ transcript.addScalar(eval_s1); transcript.addScalar(eval_s2); transcript.addScalar(eval_zw);
+ BigInteger v1 = transcript.getChallenge();
+ BigInteger v2 = v1.multiply(v1).mod(Fr);
+ BigInteger v3 = v2.multiply(v1).mod(Fr);
+ BigInteger v4 = v3.multiply(v1).mod(Fr);
+ BigInteger v5 = v4.multiply(v1).mod(Fr);
+
+ transcript.reset();
+ addG1(transcript, Wxi); addG1(transcript, Wxiw);
+ BigInteger u = transcript.getChallenge();
+
+ // Step 2: Z_H(xi) = xi^n - 1
+ int power = Integer.numberOfTrailingZeros(n);
+ BigInteger xin = xi;
+ for (int i = 0; i < power; i++) {
+ xin = xin.multiply(xin).mod(Fr);
+ }
+ BigInteger zh = xin.subtract(BigInteger.ONE).mod(Fr);
- // Step 5: r0 (linearized polynomial evaluation)
- BigInteger a = proof.evalA(), b = proof.evalB(), c = proof.evalC();
- BigInteger s1Eval = proof.evalS1(), s2Eval = proof.evalS2();
- BigInteger zOmega = proof.evalZOmega();
-
- BigInteger r0 = pi.subtract(l1.multiply(alpha).mod(Fr).multiply(alpha).mod(Fr)).mod(Fr);
-
- BigInteger perm = a.add(beta.multiply(s1Eval).mod(Fr)).add(gamma).mod(Fr);
- perm = perm.multiply(b.add(beta.multiply(s2Eval).mod(Fr)).add(gamma).mod(Fr)).mod(Fr);
- perm = perm.multiply(c.add(gamma).mod(Fr)).mod(Fr);
- perm = perm.multiply(zOmega).mod(Fr);
- perm = perm.multiply(alpha).mod(Fr);
- r0 = r0.subtract(perm).mod(Fr);
-
- // Step 6: Linearized commitment and pairing — pure Java G1/G2 arithmetic
- G1Point wZetaPt = toG1(proof.wZeta());
- G1Point wZetaOmegaPt = toG1(proof.wZetaOmega());
-
- // LHS = [W_zeta] + u * [W_zeta_omega]
- G1Point lhsG1 = wZetaPt.add(wZetaOmegaPt.scalarMul(u));
+ // Step 3: Lagrange evaluations for public input positions.
+ BigInteger nBI = BigInteger.valueOf(n);
+ BigInteger l1 = zh.multiply(nBI.multiply(xi.subtract(BigInteger.ONE).mod(Fr)).mod(Fr).modInverse(Fr)).mod(Fr);
- // RHS base = zeta * [W_zeta] + u*zeta*omega * [W_zeta_omega]
- BigInteger zetaOmega = zeta.multiply(omega).mod(Fr);
- G1Point rhsBase = wZetaPt.scalarMul(zeta).add(wZetaOmegaPt.scalarMul(u.multiply(zetaOmega).mod(Fr)));
+ // Step 4: PI(xi). Public inputs are subtracted, matching the prover's
+ // PI polynomial where PI(omega^i) = -public_i.
+ BigInteger pi = BigInteger.ZERO;
+ BigInteger wPow = BigInteger.ONE;
+ for (int i = 0; i < publicInputs.size(); i++) {
+ BigInteger li = wPow.multiply(zh).mod(Fr)
+ .multiply(nBI.multiply(xi.subtract(wPow).mod(Fr)).mod(Fr).modInverse(Fr)).mod(Fr);
+ pi = pi.subtract(publicInputs.get(i).multiply(li).mod(Fr)).mod(Fr);
+ wPow = wPow.multiply(omega).mod(Fr);
+ }
- // Linearized commitment [F]
+ // Step 5: r0
+ BigInteger alpha2 = alpha.multiply(alpha).mod(Fr);
+ BigInteger e1 = pi;
+ BigInteger e2 = l1.multiply(alpha2).mod(Fr);
+ BigInteger e3a = eval_a.add(beta.multiply(eval_s1).mod(Fr)).add(gamma).mod(Fr);
+ BigInteger e3b = eval_b.add(beta.multiply(eval_s2).mod(Fr)).add(gamma).mod(Fr);
+ BigInteger e3c = eval_c.add(gamma).mod(Fr);
+ BigInteger e3 = e3a.multiply(e3b).mod(Fr).multiply(e3c).mod(Fr)
+ .multiply(eval_zw).mod(Fr).multiply(alpha).mod(Fr);
+ BigInteger r0 = e1.subtract(e2).mod(Fr).subtract(e3).mod(Fr);
+
+ // Step 6: Linearized commitment and pairing.
BigInteger k1 = vk.k1(), k2 = vk.k2();
- // Gate: a*[qL] + b*[qR] + c*[qO] + a*b*[qM] + [qC]
- G1Point fGate = toG1(vk.qL()).scalarMul(a)
- .add(toG1(vk.qR()).scalarMul(b))
- .add(toG1(vk.qO()).scalarMul(c))
- .add(toG1(vk.qM()).scalarMul(a.multiply(b).mod(Fr)))
- .add(toG1(vk.qC()));
-
- // Permutation Z coefficient
- BigInteger permZ = a.add(beta.multiply(zeta).mod(Fr)).add(gamma).mod(Fr);
- permZ = permZ.multiply(b.add(beta.multiply(k1).mod(Fr).multiply(zeta).mod(Fr)).add(gamma).mod(Fr)).mod(Fr);
- permZ = permZ.multiply(c.add(beta.multiply(k2).mod(Fr).multiply(zeta).mod(Fr)).add(gamma).mod(Fr)).mod(Fr);
- permZ = permZ.multiply(alpha).mod(Fr);
- permZ = permZ.add(alpha.multiply(alpha).mod(Fr).multiply(l1).mod(Fr)).mod(Fr);
- G1Point fPerm = toG1(proof.cmZ()).scalarMul(permZ);
-
- // Permutation sigma3 coefficient
- BigInteger permS3 = a.add(beta.multiply(s1Eval).mod(Fr)).add(gamma).mod(Fr);
- permS3 = permS3.multiply(b.add(beta.multiply(s2Eval).mod(Fr)).add(gamma).mod(Fr)).mod(Fr);
- permS3 = permS3.multiply(alpha).mod(Fr).multiply(beta).mod(Fr).multiply(zOmega).mod(Fr);
- G1Point fS3 = toG1(vk.s3()).scalarMul(permS3);
-
- // Quotient: zh * ([t1] + zeta^n*[t2] + zeta^(2n)*[t3])
- G1Point fQuotient = toG1(proof.cmT1())
- .add(toG1(proof.cmT2()).scalarMul(zetaPowN))
- .add(toG1(proof.cmT3()).scalarMul(zetaPowN.multiply(zetaPowN).mod(Fr)));
- fQuotient = fQuotient.scalarMul(zh);
-
- // [F] = fGate + fPerm - fS3 - fQuotient
- G1Point fCommit = fGate.add(fPerm).add(fS3.negate()).add(fQuotient.negate());
-
- // RHS = rhsBase + [F] - r0 * G1_generator
- // We need the G1 generator; for BLS12-381 it's a well-known point
- G1Point g1Gen = BLS12381_G1_GENERATOR;
- G1Point rhsG1 = rhsBase.add(fCommit).add(g1Gen.scalarMul(r0).negate());
-
- // G2 points: [x]_2 from VK, G2 generator
- G2Point x2Pt = toG2(vk.x2());
- G2Point g2Gen = BLS12381_G2_GENERATOR;
-
- // Pairing check: e(lhsG1, x2) * e(-rhsG1, g2Gen) == 1
+ G1Point d1 = Qm.scalarMul(eval_a.multiply(eval_b).mod(Fr))
+ .add(Ql.scalarMul(eval_a))
+ .add(Qr.scalarMul(eval_b))
+ .add(Qo.scalarMul(eval_c))
+ .add(Qc);
+
+ BigInteger betaxi = beta.multiply(xi).mod(Fr);
+ BigInteger d2a = eval_a.add(betaxi).add(gamma).mod(Fr)
+ .multiply(eval_b.add(betaxi.multiply(k1).mod(Fr)).add(gamma).mod(Fr)).mod(Fr)
+ .multiply(eval_c.add(betaxi.multiply(k2).mod(Fr)).add(gamma).mod(Fr)).mod(Fr)
+ .multiply(alpha).mod(Fr);
+ BigInteger d2b = l1.multiply(alpha2).mod(Fr);
+ G1Point d2 = Z.scalarMul(d2a.add(d2b).add(u).mod(Fr));
+
+ BigInteger d3s = eval_a.add(beta.multiply(eval_s1).mod(Fr)).add(gamma).mod(Fr)
+ .multiply(eval_b.add(beta.multiply(eval_s2).mod(Fr)).add(gamma).mod(Fr)).mod(Fr)
+ .multiply(alpha.multiply(beta).mod(Fr).multiply(eval_zw).mod(Fr)).mod(Fr);
+ G1Point d3 = S3.scalarMul(d3s);
+
+ G1Point d4 = T1.add(T2.scalarMul(xin)).add(T3.scalarMul(xin.multiply(xin).mod(Fr))).scalarMul(zh);
+ G1Point D = d1.add(d2).add(d3.negate()).add(d4.negate());
+
+ G1Point F = D.add(A.scalarMul(v1)).add(B.scalarMul(v2)).add(C.scalarMul(v3))
+ .add(S1.scalarMul(v4)).add(S2.scalarMul(v5));
+
+ BigInteger eScalar = r0.negate().mod(Fr)
+ .add(v1.multiply(eval_a).mod(Fr))
+ .add(v2.multiply(eval_b).mod(Fr))
+ .add(v3.multiply(eval_c).mod(Fr))
+ .add(v4.multiply(eval_s1).mod(Fr))
+ .add(v5.multiply(eval_s2).mod(Fr))
+ .add(u.multiply(eval_zw).mod(Fr))
+ .mod(Fr);
+ G1Point E = BLS12381_G1_GENERATOR.scalarMul(eScalar);
+
+ G1Point B1 = F.add(E.negate())
+ .add(Wxi.scalarMul(xi))
+ .add(Wxiw.scalarMul(u.multiply(xi).mod(Fr).multiply(omega).mod(Fr)));
+ G1Point A1 = Wxi.add(Wxiw.scalarMul(u));
+
+ // Pairing check: e(B1, G2) * e(-A1, X_2) == 1
boolean valid = BLS12381Pairing.pairingCheck(
- new G1Point[]{lhsG1, rhsG1.negate()},
- new G2Point[]{x2Pt, g2Gen});
+ new G1Point[]{B1, A1.negate()},
+ new G2Point[]{BLS12381_G2_GENERATOR, X_2});
return valid ? VerificationResult.cryptoValid()
: VerificationResult.proofInvalid("PlonK BLS12-381 pairing check failed");
@@ -221,8 +245,12 @@ public VerificationResult verify(ZkProofEnvelope envelope, VerificationMaterial
// --- Helpers ---
- private void appendG1(FiatShamirTranscript transcript, List point) {
- if (point.size() >= 2) transcript.appendG1Point(point.get(0), point.get(1));
+ private void addG1(FiatShamirTranscript transcript, G1Point point) {
+ if (point.isInfinity()) {
+ transcript.addPolCommitment(BigInteger.ZERO, BigInteger.ZERO);
+ } else {
+ transcript.addPolCommitment(point.x().value(), point.y().value());
+ }
}
private G1Point toG1(List coords) {
diff --git a/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBN254Verifier.java b/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBN254Verifier.java
index 4a035a4..80f21d5 100644
--- a/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBN254Verifier.java
+++ b/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBN254Verifier.java
@@ -3,6 +3,7 @@
import com.bloxbean.cardano.zeroj.api.*;
import com.bloxbean.cardano.zeroj.backend.spi.BackendDescriptor;
import com.bloxbean.cardano.zeroj.backend.spi.ZkVerifier;
+import com.bloxbean.cardano.zeroj.crypto.transcript.FiatShamirTranscript;
import com.bloxbean.cardano.zeroj.verifier.groth16.bn254.*;
import java.math.BigInteger;
@@ -27,6 +28,12 @@ public class PlonkBN254Verifier implements ZkVerifier {
@Override
public VerificationResult verify(ZkProofEnvelope envelope, VerificationMaterial material) {
try {
+ if (envelope.proofFormat().filter("gnark-plonk-json"::equals).isPresent()) {
+ return VerificationResult.error(
+ VerificationResult.ReasonCode.UNSUPPORTED_PROOF_SYSTEM,
+ "gnark binary PlonK JSON is not accepted by the snarkjs/ZeroJ structured PlonK verifier");
+ }
+
var sp = com.bloxbean.cardano.zeroj.codec.SnarkjsPlonkCodec.parseProof(new String(envelope.proofBytes()));
var sv = com.bloxbean.cardano.zeroj.codec.SnarkjsPlonkCodec.parseVerificationKey(new String(material.vkBytes()));
diff --git a/zeroj-verifier-plonk/src/test/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBLS12381VerifierTest.java b/zeroj-verifier-plonk/src/test/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBLS12381VerifierTest.java
new file mode 100644
index 0000000..112ded5
--- /dev/null
+++ b/zeroj-verifier-plonk/src/test/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBLS12381VerifierTest.java
@@ -0,0 +1,144 @@
+package com.bloxbean.cardano.zeroj.verifier.plonk;
+
+import com.bloxbean.cardano.zeroj.api.CircuitId;
+import com.bloxbean.cardano.zeroj.api.CurveId;
+import com.bloxbean.cardano.zeroj.api.ProofSystemId;
+import com.bloxbean.cardano.zeroj.api.VerificationMaterial;
+import com.bloxbean.cardano.zeroj.circuit.CircuitBuilder;
+import com.bloxbean.cardano.zeroj.codec.SnarkjsPlonkCodec;
+import com.bloxbean.cardano.zeroj.crypto.plonk.PlonKProofBLS381;
+import com.bloxbean.cardano.zeroj.crypto.plonk.PlonKProverBLS381;
+import com.bloxbean.cardano.zeroj.crypto.plonk.PlonKProvingKeyBLS381;
+import com.bloxbean.cardano.zeroj.crypto.plonk.PlonKSetupBLS381;
+import com.bloxbean.cardano.zeroj.crypto.setup.PowersOfTauBLS381;
+import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381;
+import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381;
+import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class PlonkBLS12381VerifierTest {
+
+ @Test
+ void verify_javaGeneratedProof_accepts() {
+ var circuit = CircuitBuilder.create("multiplier")
+ .publicVar("c").secretVar("a").secretVar("b")
+ .define(api -> api.assertEqual(api.mul(api.var("a"), api.var("b")), api.var("c")));
+
+ var plonk = circuit.compilePlonK(CurveId.BLS12_381);
+ var witness = circuit.calculateWitness(Map.of(
+ "c", List.of(BigInteger.valueOf(33)),
+ "a", List.of(BigInteger.valueOf(3)),
+ "b", List.of(BigInteger.valueOf(11))), CurveId.BLS12_381);
+
+ var srs = PowersOfTauBLS381.generate(8);
+ int numGates = plonk.numGates();
+ BigInteger[][] gates = new BigInteger[numGates][5];
+ for (int i = 0; i < numGates; i++) {
+ var row = plonk.gateRows().get(i);
+ gates[i] = new BigInteger[]{row.qL(), row.qR(), row.qO(), row.qM(), row.qC()};
+ }
+
+ var pk = PlonKSetupBLS381.setup(numGates, plonk.numPublicInputs(), gates,
+ plonk.sigmaA(), plonk.sigmaB(), plonk.sigmaC(), plonk.numWires(), srs);
+
+ var extWitness = plonk.extendWitness(witness);
+ int n = pk.domainSize();
+ MontFr381[] wireA = new MontFr381[n];
+ MontFr381[] wireB = new MontFr381[n];
+ MontFr381[] wireC = new MontFr381[n];
+ for (int i = 0; i < n; i++) {
+ if (i < numGates) {
+ var row = plonk.gateRows().get(i);
+ wireA[i] = MontFr381.fromBigInteger(extWitness[row.wireA()]);
+ wireB[i] = MontFr381.fromBigInteger(extWitness[row.wireB()]);
+ wireC[i] = MontFr381.fromBigInteger(extWitness[row.wireC()]);
+ } else {
+ wireA[i] = wireB[i] = wireC[i] = MontFr381.ZERO;
+ }
+ }
+
+ BigInteger[] publicInputs = new BigInteger[plonk.numPublicInputs()];
+ for (int i = 0; i < publicInputs.length; i++) {
+ publicInputs[i] = witness[i + 1];
+ }
+
+ var proof = PlonKProverBLS381.prove(pk, wireA, wireB, wireC, publicInputs);
+ String proofJson = proofJson(proof);
+ String vkJson = vkJson(pk);
+ String publicJson = "[\"33\"]";
+
+ var envelope = SnarkjsPlonkCodec.toEnvelopeFromJson(
+ proofJson, vkJson, publicJson, new CircuitId("bls381-plonk-multiplier"));
+ var material = VerificationMaterial.of(vkJson.getBytes(StandardCharsets.UTF_8),
+ ProofSystemId.PLONK, CurveId.BLS12_381, new CircuitId("bls381-plonk-multiplier"));
+
+ var result = new PlonkBLS12381Verifier().verify(envelope, material);
+ assertTrue(result.proofValid(), () -> result.message().orElse("verification failed"));
+ }
+
+ private static String proofJson(PlonKProofBLS381 proof) {
+ return "{"
+ + "\"A\":" + g1(proof.commitA()) + ","
+ + "\"B\":" + g1(proof.commitB()) + ","
+ + "\"C\":" + g1(proof.commitC()) + ","
+ + "\"Z\":" + g1(proof.commitZ()) + ","
+ + "\"T1\":" + g1(proof.commitT1()) + ","
+ + "\"T2\":" + g1(proof.commitT2()) + ","
+ + "\"T3\":" + g1(proof.commitT3()) + ","
+ + "\"eval_a\":\"" + proof.evalA() + "\","
+ + "\"eval_b\":\"" + proof.evalB() + "\","
+ + "\"eval_c\":\"" + proof.evalC() + "\","
+ + "\"eval_s1\":\"" + proof.evalS1() + "\","
+ + "\"eval_s2\":\"" + proof.evalS2() + "\","
+ + "\"eval_zw\":\"" + proof.evalZw() + "\","
+ + "\"Wxi\":" + g1(proof.commitWxi()) + ","
+ + "\"Wxiw\":" + g1(proof.commitWxiw()) + ","
+ + "\"protocol\":\"plonk\","
+ + "\"curve\":\"bls12381\""
+ + "}";
+ }
+
+ private static String vkJson(PlonKProvingKeyBLS381 pk) {
+ return "{"
+ + "\"protocol\":\"plonk\","
+ + "\"curve\":\"bls12381\","
+ + "\"nPublic\":" + pk.nPublic() + ","
+ + "\"power\":" + Integer.numberOfTrailingZeros(pk.domainSize()) + ","
+ + "\"k1\":\"" + pk.k1() + "\","
+ + "\"k2\":\"" + pk.k2() + "\","
+ + "\"Qm\":" + g1(pk.qmCommit()) + ","
+ + "\"Ql\":" + g1(pk.qlCommit()) + ","
+ + "\"Qr\":" + g1(pk.qrCommit()) + ","
+ + "\"Qo\":" + g1(pk.qoCommit()) + ","
+ + "\"Qc\":" + g1(pk.qcCommit()) + ","
+ + "\"S1\":" + g1(pk.s1Commit()) + ","
+ + "\"S2\":" + g1(pk.s2Commit()) + ","
+ + "\"S3\":" + g1(pk.s3Commit()) + ","
+ + "\"X_2\":" + g2(pk.x2()) + ","
+ + "\"w\":\"" + pk.omega().toBigInteger() + "\""
+ + "}";
+ }
+
+ private static String g1(JacobianG1BLS381.AffineG1 p) {
+ if (p.isInfinity()) {
+ return "[\"0\",\"1\",\"0\"]";
+ }
+ return "[\"" + p.xBigInt() + "\",\"" + p.yBigInt() + "\",\"1\"]";
+ }
+
+ private static String g2(JacobianG2BLS381.AffineG2 p) {
+ if (p.isInfinity()) {
+ return "[[\"0\",\"0\"],[\"1\",\"0\"],[\"0\",\"0\"]]";
+ }
+ return "[[\"" + p.x().reBigInt() + "\",\"" + p.x().imBigInt() + "\"],"
+ + "[\"" + p.y().reBigInt() + "\",\"" + p.y().imBigInt() + "\"],"
+ + "[\"1\",\"0\"]]";
+ }
+}
diff --git a/zeroj-verifier-plonk/src/test/java/com/bloxbean/cardano/zeroj/verifier/plonk/SnarkjsTranscriptCompatTest.java b/zeroj-verifier-plonk/src/test/java/com/bloxbean/cardano/zeroj/verifier/plonk/SnarkjsTranscriptCompatTest.java
index ffe6383..666d8f2 100644
--- a/zeroj-verifier-plonk/src/test/java/com/bloxbean/cardano/zeroj/verifier/plonk/SnarkjsTranscriptCompatTest.java
+++ b/zeroj-verifier-plonk/src/test/java/com/bloxbean/cardano/zeroj/verifier/plonk/SnarkjsTranscriptCompatTest.java
@@ -1,6 +1,8 @@
package com.bloxbean.cardano.zeroj.verifier.plonk;
import com.bloxbean.cardano.zeroj.codec.SnarkjsPlonkCodec;
+import com.bloxbean.cardano.zeroj.crypto.transcript.FiatShamirTranscript;
+import com.bloxbean.cardano.zeroj.crypto.transcript.Keccak256;
import com.bloxbean.cardano.zeroj.verifier.groth16.bn254.Fp;
import com.bloxbean.cardano.zeroj.verifier.groth16.bn254.G1Point;
import org.junit.jupiter.api.BeforeEach;