From bad5d2b6672e363f9a045bd2797ddafecf07b563 Mon Sep 17 00:00:00 2001 From: Satya Date: Sun, 17 May 2026 14:58:00 +0800 Subject: [PATCH 1/3] chore: cleanup zeroj module structure --- NOTICE | 24 - README.md | 46 +- build.gradle | 6 +- .../zeroj-onchain-experimental/README.md | 59 -- .../zeroj-onchain-experimental/build.gradle | 25 - .../zeroj/onchain/OnChainProofPreparer.java | 92 --- .../zeroj/onchain/OnChainVkPreparer.java | 70 -- .../onchain/ReferenceScriptDeployer.java | 78 -- .../zeroj/onchain/ScriptBudgetEstimator.java | 122 ---- .../LICENSES/GPL-3.0.txt | 674 ------------------ .../LICENSES/LGPL-3.0.txt | 165 ----- incubator/zeroj-prover-rapidsnark/NOTICE | 54 -- incubator/zeroj-prover-rapidsnark/README.md | 92 --- .../zeroj-prover-rapidsnark/build.gradle | 94 --- .../rapidsnark/NativeLibraryLoader.java | 155 ---- .../prover/rapidsnark/RapidsnarkLibrary.java | 268 ------- .../prover/rapidsnark/RapidsnarkProver.java | 251 ------- .../reflect-config.json | 1 - .../resource-config.json | 3 - .../rapidsnark/NativeLibraryLoaderTest.java | 40 -- .../rapidsnark/RapidsnarkProverTest.java | 434 ----------- .../resources/test-circuits/cubic/cubic.zkey | Bin 3066 -> 0 bytes .../test-circuits/cubic/cubic_witness.wtns | Bin 204 -> 0 bytes .../test-circuits/cubic/verification_key.json | 94 --- .../test-circuits/multiplier/multiplier.zkey | Bin 3022 -> 0 bytes .../multiplier/multiplier_witness.wtns | Bin 204 -> 0 bytes .../multiplier/verification_key.json | 99 --- incubator/zeroj-prover-sidecar/README.md | 51 -- incubator/zeroj-prover-sidecar/build.gradle | 27 - .../zeroj-prover-sidecar/docker/Dockerfile | 23 - .../zeroj-prover-sidecar/docker/README.md | 80 --- .../docker/docker-compose.yml | 19 - .../zeroj-prover-sidecar/docker/package.json | 13 - .../zeroj-prover-sidecar/docker/server.js | 99 --- .../zeroj/prover/sidecar/ProverConfig.java | 56 -- .../zeroj/prover/sidecar/ProverService.java | 48 -- .../prover/sidecar/SidecarProverClient.java | 245 ------- .../sidecar/SidecarProverClientTest.java | 197 ----- incubator/zeroj-prover-wasm/README.md | 4 +- .../zeroj/prover/wasm/WitnessExporter.java | 4 +- settings.gradle | 22 +- {zeroj-bom => zeroj-bom-all}/README.md | 31 +- {zeroj-bom => zeroj-bom-all}/build.gradle | 17 +- zeroj-bom-core/README.md | 39 + zeroj-bom-core/build.gradle | 70 ++ .../zeroj/circuit/r1cs/R1CSSerializer.java | 2 +- .../circuit/lib/StdlibUsageExamplesTest.java | 2 +- .../cardano/zeroj/codec/GnarkPlonkCodec.java | 8 +- .../zeroj/codec/GnarkPlonkCodecTest.java | 31 + zeroj-crypto/build.gradle | 4 +- .../zeroj/crypto/groth16/R1CSImporter.java | 2 +- .../zeroj/crypto/plonk/PlonKProver.java | 2 +- .../zeroj/crypto/plonk/PlonKProverBLS381.java | 4 +- .../transcript}/FiatShamirTranscript.java | 3 +- .../zeroj/crypto/transcript}/Keccak256.java | 6 +- .../crypto/plonk/PlonKBLS381EndToEndTest.java | 4 +- .../zeroj/crypto/plonk/PlonKEndToEndTest.java | 2 +- .../zeroj/crypto/setup/PowersOfTauTest.java | 2 +- .../transcript}/FiatShamirTranscriptTest.java | 29 +- zeroj-examples/README.md | 15 +- zeroj-examples/build.gradle | 2 - .../cardano/zeroj/examples/EndToEndDemo.java | 117 +-- .../examples/GnarkPlonkEndToEndDemo.java | 154 ++-- zeroj-ingestion/README.md | 103 --- zeroj-ingestion/build.gradle | 25 - .../zeroj/ingestion/AuditExporter.java | 114 --- .../cardano/zeroj/ingestion/AuditLog.java | 163 ----- .../zeroj/ingestion/CircuitAllowlist.java | 22 - .../zeroj/ingestion/CircuitRegistry.java | 104 --- .../zeroj/ingestion/InMemoryAuditLog.java | 79 -- .../ingestion/InMemoryCircuitAllowlist.java | 31 - .../ingestion/InMemoryCircuitRegistry.java | 101 --- .../ingestion/InMemoryNullifierStore.java | 40 -- .../ingestion/InMemorySequenceTracker.java | 28 - .../ingestion/InMemoryStateRootStore.java | 30 - .../ingestion/InMemorySubmitterRegistry.java | 74 -- .../zeroj/ingestion/NullifierStore.java | 33 - .../zeroj/ingestion/SequenceTracker.java | 19 - .../zeroj/ingestion/StateRootStore.java | 19 - .../SubmissionIngestionPipeline.java | 300 -------- .../zeroj/ingestion/SubmitterRegistry.java | 76 -- .../zeroj/ingestion/VersionedVkRegistry.java | 200 ------ .../zeroj/ingestion/VkRotationPolicy.java | 29 - .../zeroj-ingestion/reflect-config.json | 1 - .../zeroj-ingestion/resource-config.json | 1 - .../zeroj/ingestion/AuditExporterTest.java | 121 ---- .../ingestion/GovernanceAndSecurityTest.java | 400 ----------- .../zeroj/ingestion/InMemoryAuditLogTest.java | 174 ----- .../InMemoryCircuitAllowlistTest.java | 69 -- .../InMemoryCircuitRegistryTest.java | 167 ----- .../ingestion/InMemoryNullifierStoreTest.java | 85 --- .../InMemorySequenceTrackerTest.java | 71 -- .../ingestion/InMemoryStateRootStoreTest.java | 65 -- .../InMemorySubmitterRegistryTest.java | 142 ---- .../SubmissionIngestionPipelineTest.java | 412 ----------- .../ingestion/VersionedVkRegistryTest.java | 178 ----- zeroj-onchain-julc/build.gradle | 2 +- .../onchain/julc}/OnChainFeasibility.java | 44 +- .../julc/PlonkBLS12381FullVerifier.java | 13 +- .../onchain/julc/ReferenceScriptDeployer.java | 41 ++ .../onchain/julc/ScriptBudgetEstimator.java | 85 +++ .../zeroj-onchain-julc}/reflect-config.json | 0 .../zeroj-onchain-julc}/resource-config.json | 0 .../onchain/julc/OnChainFeasibilityTest.java | 38 + .../julc/PlonkBLS12381FullVerifierTest.java | 4 +- .../julc/ReferenceScriptDeployerTest.java | 52 ++ .../julc/ScriptBudgetEstimatorTest.java | 43 ++ zeroj-prover-gnark/README.md | 15 +- zeroj-prover-gnark/build.gradle | 8 +- zeroj-prover-gnark/gnark-wrapper/main.go | 39 +- .../zeroj/prover/gnark/GnarkLibrary.java | 2 +- .../zeroj/prover/gnark/GnarkNativeLoader.java | 2 +- .../zeroj/prover/gnark/GnarkProver.java | 78 +- .../prover/gnark/PlonkGnarkVerifier.java | 143 ---- ...xbean.cardano.zeroj.backend.spi.ZkVerifier | 1 - .../prover/gnark/GnarkFullProveTest.java | 10 +- .../zeroj/prover/gnark/GnarkPlonkTest.java | 9 +- .../zeroj/prover/gnark/GnarkProverTest.java | 27 - .../prover/gnark/PlonkSpiIntegrationTest.java | 100 --- zeroj-prover-spi/build.gradle | 20 + .../zeroj/prover/spi}/ProveRequest.java | 10 +- .../zeroj/prover/spi}/ProveResponse.java | 12 +- .../zeroj/prover/spi}/ProverException.java | 12 +- .../zeroj/prover/spi/ProverService.java | 19 + .../zeroj-prover-spi}/reflect-config.json | 6 +- .../zeroj-prover-spi}/resource-config.json | 0 zeroj-submission/README.md | 65 -- zeroj-submission/build.gradle | 21 - .../zeroj/submission/AppProofSubmission.java | 120 ---- .../zeroj/submission/Ed25519Signer.java | 99 --- .../zeroj/submission/SubmissionHash.java | 82 --- .../zeroj/submission/SubmissionResult.java | 81 --- .../zeroj-submission/reflect-config.json | 1 - .../zeroj-submission/resource-config.json | 1 - .../zeroj/submission/ProtocolTest.java | 268 ------- zeroj-verifier-plonk/README.md | 13 +- zeroj-verifier-plonk/build.gradle | 2 + .../verifier/plonk/PlonkBLS12381Verifier.java | 254 ++++--- .../verifier/plonk/PlonkBN254Verifier.java | 7 + .../plonk/PlonkBLS12381VerifierTest.java | 144 ++++ .../plonk/SnarkjsTranscriptCompatTest.java | 2 + 141 files changed, 1051 insertions(+), 8800 deletions(-) delete mode 100644 incubator/zeroj-onchain-experimental/README.md delete mode 100644 incubator/zeroj-onchain-experimental/build.gradle delete mode 100644 incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/OnChainProofPreparer.java delete mode 100644 incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/OnChainVkPreparer.java delete mode 100644 incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/ReferenceScriptDeployer.java delete mode 100644 incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain/ScriptBudgetEstimator.java delete mode 100644 incubator/zeroj-prover-rapidsnark/LICENSES/GPL-3.0.txt delete mode 100644 incubator/zeroj-prover-rapidsnark/LICENSES/LGPL-3.0.txt delete mode 100644 incubator/zeroj-prover-rapidsnark/NOTICE delete mode 100644 incubator/zeroj-prover-rapidsnark/README.md delete mode 100644 incubator/zeroj-prover-rapidsnark/build.gradle delete mode 100644 incubator/zeroj-prover-rapidsnark/src/main/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/NativeLibraryLoader.java delete mode 100644 incubator/zeroj-prover-rapidsnark/src/main/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/RapidsnarkLibrary.java delete mode 100644 incubator/zeroj-prover-rapidsnark/src/main/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/RapidsnarkProver.java delete mode 100644 incubator/zeroj-prover-rapidsnark/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-rapidsnark/reflect-config.json delete mode 100644 incubator/zeroj-prover-rapidsnark/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-rapidsnark/resource-config.json delete mode 100644 incubator/zeroj-prover-rapidsnark/src/test/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/NativeLibraryLoaderTest.java delete mode 100644 incubator/zeroj-prover-rapidsnark/src/test/java/com/bloxbean/cardano/zeroj/prover/rapidsnark/RapidsnarkProverTest.java delete mode 100644 incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/cubic/cubic.zkey delete mode 100644 incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/cubic/cubic_witness.wtns delete mode 100644 incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/cubic/verification_key.json delete mode 100644 incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/multiplier/multiplier.zkey delete mode 100644 incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/multiplier/multiplier_witness.wtns delete mode 100644 incubator/zeroj-prover-rapidsnark/src/test/resources/test-circuits/multiplier/verification_key.json delete mode 100644 incubator/zeroj-prover-sidecar/README.md delete mode 100644 incubator/zeroj-prover-sidecar/build.gradle delete mode 100644 incubator/zeroj-prover-sidecar/docker/Dockerfile delete mode 100644 incubator/zeroj-prover-sidecar/docker/README.md delete mode 100644 incubator/zeroj-prover-sidecar/docker/docker-compose.yml delete mode 100644 incubator/zeroj-prover-sidecar/docker/package.json delete mode 100644 incubator/zeroj-prover-sidecar/docker/server.js delete mode 100644 incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProverConfig.java delete mode 100644 incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/ProverService.java delete mode 100644 incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar/SidecarProverClient.java delete mode 100644 incubator/zeroj-prover-sidecar/src/test/java/com/bloxbean/cardano/zeroj/prover/sidecar/SidecarProverClientTest.java rename {zeroj-bom => zeroj-bom-all}/README.md (65%) rename {zeroj-bom => zeroj-bom-all}/build.gradle (83%) create mode 100644 zeroj-bom-core/README.md create mode 100644 zeroj-bom-core/build.gradle create mode 100644 zeroj-codec/src/test/java/com/bloxbean/cardano/zeroj/codec/GnarkPlonkCodecTest.java rename {zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk => zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/transcript}/FiatShamirTranscript.java (98%) rename {zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk => zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/transcript}/Keccak256.java (97%) rename {zeroj-verifier-plonk/src/test/java/com/bloxbean/cardano/zeroj/verifier/plonk => zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/transcript}/FiatShamirTranscriptTest.java (86%) delete mode 100644 zeroj-ingestion/README.md delete mode 100644 zeroj-ingestion/build.gradle delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/AuditExporter.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/AuditLog.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/CircuitAllowlist.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/CircuitRegistry.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryAuditLog.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitAllowlist.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitRegistry.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryNullifierStore.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySequenceTracker.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryStateRootStore.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySubmitterRegistry.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/NullifierStore.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/SequenceTracker.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/StateRootStore.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/SubmissionIngestionPipeline.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/SubmitterRegistry.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/VersionedVkRegistry.java delete mode 100644 zeroj-ingestion/src/main/java/com/bloxbean/cardano/zeroj/ingestion/VkRotationPolicy.java delete mode 100644 zeroj-ingestion/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-ingestion/reflect-config.json delete mode 100644 zeroj-ingestion/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-ingestion/resource-config.json delete mode 100644 zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/AuditExporterTest.java delete mode 100644 zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/GovernanceAndSecurityTest.java delete mode 100644 zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryAuditLogTest.java delete mode 100644 zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitAllowlistTest.java delete mode 100644 zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryCircuitRegistryTest.java delete mode 100644 zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryNullifierStoreTest.java delete mode 100644 zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySequenceTrackerTest.java delete mode 100644 zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemoryStateRootStoreTest.java delete mode 100644 zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/InMemorySubmitterRegistryTest.java delete mode 100644 zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/SubmissionIngestionPipelineTest.java delete mode 100644 zeroj-ingestion/src/test/java/com/bloxbean/cardano/zeroj/ingestion/VersionedVkRegistryTest.java rename {incubator/zeroj-onchain-experimental/src/main/java/com/bloxbean/cardano/zeroj/onchain => zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc}/OnChainFeasibility.java (57%) create mode 100644 zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/ReferenceScriptDeployer.java create mode 100644 zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/ScriptBudgetEstimator.java rename {incubator/zeroj-onchain-experimental/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-onchain-experimental => zeroj-onchain-julc/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-onchain-julc}/reflect-config.json (100%) rename {incubator/zeroj-onchain-experimental/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-onchain-experimental => zeroj-onchain-julc/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-onchain-julc}/resource-config.json (100%) create mode 100644 zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/OnChainFeasibilityTest.java create mode 100644 zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/ReferenceScriptDeployerTest.java create mode 100644 zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/ScriptBudgetEstimatorTest.java delete mode 100644 zeroj-prover-gnark/src/main/java/com/bloxbean/cardano/zeroj/prover/gnark/PlonkGnarkVerifier.java delete mode 100644 zeroj-prover-gnark/src/main/resources/META-INF/services/com.bloxbean.cardano.zeroj.backend.spi.ZkVerifier delete mode 100644 zeroj-prover-gnark/src/test/java/com/bloxbean/cardano/zeroj/prover/gnark/PlonkSpiIntegrationTest.java create mode 100644 zeroj-prover-spi/build.gradle rename {incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar => zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi}/ProveRequest.java (68%) rename {incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar => zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi}/ProveResponse.java (64%) rename {incubator/zeroj-prover-sidecar/src/main/java/com/bloxbean/cardano/zeroj/prover/sidecar => zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi}/ProverException.java (71%) create mode 100644 zeroj-prover-spi/src/main/java/com/bloxbean/cardano/zeroj/prover/spi/ProverService.java rename {incubator/zeroj-prover-sidecar/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-sidecar => zeroj-prover-spi/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-spi}/reflect-config.json (60%) rename {incubator/zeroj-prover-sidecar/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-sidecar => zeroj-prover-spi/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-prover-spi}/resource-config.json (100%) delete mode 100644 zeroj-submission/README.md delete mode 100644 zeroj-submission/build.gradle delete mode 100644 zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/AppProofSubmission.java delete mode 100644 zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/Ed25519Signer.java delete mode 100644 zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/SubmissionHash.java delete mode 100644 zeroj-submission/src/main/java/com/bloxbean/cardano/zeroj/submission/SubmissionResult.java delete mode 100644 zeroj-submission/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-submission/reflect-config.json delete mode 100644 zeroj-submission/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-submission/resource-config.json delete mode 100644 zeroj-submission/src/test/java/com/bloxbean/cardano/zeroj/submission/ProtocolTest.java create mode 100644 zeroj-verifier-plonk/src/test/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBLS12381VerifierTest.java 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/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 3cad14d28a870f85750b694922fc098687f01489..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3066 zcmdT`X*kqv7q@5BShDYiF=Im3!B~>DC_}uAW$a6~$G&FY$}*KLdTg2OvVHmxzGLKcYgQzpZol-|9!*DHJqA?ii+vjDUt5j zDMs1T9D6opIX01FzjPRh=fa4n-+tlmp5YmnYik)7*P>_A3M-ad3_!ZbQZUDPn^`-H zIvIX#;g?qwB>o#i;s1F~U)CS%+A)7wr4piPztk^AJY>_PycF9^Ym&a^J;Ug7rMojO=4!|Dm7MI z+Ot^LR)fc5O6Ff7Y`|hUg7-Jk(alCXZodGNoP!J)@0CD4SXG#NSm?C;YA3&0{o_Ry zhcaU*12N=Tvb+HlBGO6ocXPs>0O|+>IPcmlfiEui5~TIJnn_N*mI{jZqT|)ds6bqO zAD+h2dQQjTrcANvfypYsD}tz+uu10lw(6B!xL@cPmS41!J03$ONAZg}@f960XcyJ3 zMITCRSnv#5*Z^EQ)T87@NvPQnR=C1bILSQTDGTmRI8US-lw9nj2rF+H*;rbf$C#OABvCEmqu>zo@WF3e_ zd=li^Yr0-&Vt+cgS-gp0p>2#+#{}yc8A0&aS)bc0o|sIDih8gjv&HbP=pgmN^JT4q zZ$cHpFt8jNR1t8^%6v0>i7Z6drxmtZsv_b$5sKy3s1A2afVt^kxET7XJkL*o=~1Ie zv>~2pwAM0ZO-~D#swM&17oA4smo-$ofGs*D!xq-M#5#J45E@RMC+kut<_IXWHA!aw^svV7n&#teIxG^Z0<+L_FMz zeVqkEFOWA@h}d2@Ka!ur%UAV^!D%mV4w!=&#!2naHi;QU)<2q3z1yA|*U*V--DM`P=`$Ou#gr%HM;r?H| z^v9SJ;BnYWyCXopB_*#wOD%t`Y(`jr0Vt}X`OLdoK__hui~z>2ZE&%Bq~o%8!q%w` z76nOe@x2=?(%70WnZrn5H(_kjB*FX2++fY5a`L+pE|uQ<${v?0*L!3Do{yBZ8=;m; zZilfzYG>J%sQHC2z4>`)LnX%cImug>1{v>(HHqu^HyUkm>YfW@I$yr5!SR~Z$Y|P@ zOl?Jy=v}c|nmDa+w97p>`0hO?H{QnZ%6)~TR@{xJ(sG>Hi2j)u9};<$Q7*KTu0AP_ ze7HYr`99m-+&sE_H=$$nYe_Kj9U`Ck2&Cr`5n!NR%8r4WwuCsM*v;m|oz}ey7=V7` zf=SFLbMf=CDSi0|O?Ye!mO*fb z0ecbdYjtlX@dSkBJXMkhnzg3@SiC*lqHY>|00NrMSIq!BUbBBM3U8eOkk%w6#K5I< zkwfQZ%IEfL8Sb1mRy2Mqs)krZ^UCfg-6bwgO+G2gzs8*2pL%pvMEax|KQ!7+dy1SM z;3MLCh92tMUCPn6myzvq02H2#zaI&8$56dRwku_gSP**=qQYf7ZJc9??dU`qX7wzZ zy!iKpKybZS-{R+`{?9zh^&a4$XyD|->fH4WdD;L0Rp~5Z5)D#}?K^XWwwSyfv=Ar! zlH9#Q^l7}go1Kk3TOn(9nZ+Y>g^z3FR7d`mhIhx*xLktt26q` z{SbHogoHDDZ85wgy%KeP4ir?q{KD3Q#!ZK!No+c+~UmG@Gim!}LpQPeZetoC)_LDBfT-F3c z0*!d@YvEA}&F{W9POoZ8ub*k~%VnHE7_v#|VV~mT40w5xF?ENvRWB+^ zxKM8vo^(fEW&0i!>aUs^!EOt97QR|8nd9INJz|73>`S!*W-9H& zqx#9fyf@ENW?QOmSxaBfdp9sXB_Fre=P+O1lC8~JOt!iJnDt?Y*sDnl7`tuOzDdXW zcp^8A#hrV{KC6qTU(E0p!ZRlMl^|#B-mxMr+LVgqAKq@0OMTpSHpyOc|BjctG+N>E zLP=;Ds!{QbH-{wTsjtD*^vAgj-DDy^$%b|daQ&v^DxlzjK#-2bIz5C5k!JE?-KM3? zjoeJN(GegD{_E34Tc<>&U9NBq>{HD_CSSZ6i^mt*c5gXCixXm?ShvovqheduV d1}s1|Abkx$3<4ni_<%G%NuV4v0eMaW@&N475extT 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 02ad7f135d0403a79f16a45fd167bc081d101690..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3022 zcmd^BS2P@27aqinBzm1eLP*rndx_{1GD(CWNI`~-E=sP^TSV^}Z4CD!dJjerC4^`} zw1lWblo-QR!*Gp1_vL^2?^=1d5C6B;KKnd;`#Wc!efB!X+rb7!0RRBbofz4lIWaQG zbIKD-CJ!gzJuw)`cbq+%S2d>lDKf!6DCNPuprBeE1DtTS)U4Yrgd~}B5=Tthp4Ccl ztw&v7k`elE4q2ZpN%k(jN{b_gN3{*mwfsTO8s&Z@j-rUGbWU-tIeS3$0k1K!p7kq| zz^KxoeHWI)7cniVZ@r@8ZNq;avL+S7!^J%ozIX=MiXOb41M%W+d$e4XHT-a*KTqa_|hk+k{BgI0!RFR3x0}jIwv3S*@bBhN8%~0 zM7zG{GdJmXNp2E|f+wOA#d1}64<;5{;nr}zT;+R+BRuUFToa*^7e5>GXvjm#Q(h2L zCupHCs4mYcz}rYUSQ8TKM&Y9;2U(q*<_t=K3E&P?4AA8oilz{CD|msQ`|I-damst< zU$rd5B(e<;4VKS3_^c?05RX`QmL0+~e`H#DWni~ch60X`{J8k7IkCq;O>Eh!KS_vS z0`4@iIE!dd^^@k?H=TUOC@1q)Zr?ZVyd?VU+{Rq?_tn&E0~Wbgv)2>ymrYB$BA(GBe3g0Zhvn%pWNa#s$wJ zJehfHEy7mjLMBpVQxBiyLwCn?CX)H(WM1{mMHh_{-b6#IWqLG0djU-%NLXnM$x{|q z!O94GscP}N2Gfl{j1|vQo!p_86C-297O0QR?Q<^=)+t=f-tceaEc~>#L=awGrM)=x z$b}9HCcTQk-Q3AY+u54;+nbK-U$o%f`jW*7Bb838h%SJ6&CFHLX7BzxB@v4y-s**~+yS1lKFocpb5(&(aYb~3EM_BRrX)dmHtAXX>vIF?@nK#bQ`7SI8$BPiHmFM&6D>! zh?V$m%3a!csPRfA=`D)UAT_wPqGSSup));n#=CV-G54GULA@1b%oms>Jc@uP>HZQ< z1^zC`FDd=|!D62~q<)W0v+v~lA?V#|C1NWn6o|gS#zmX)a z2x*|gR_|jb9O!ovR&-t8Uq?K+2m+B)IK83&elGv-J1}0uRmOb)Ka{>W!ou97qi(JL zLtrnyFyOJ-u0Y~qB;Y(W`~9fK$EzbNZ8Sz~#WF(k3H=)uk`zIh`eUhYBJ6DN*xDWS z957ak0%wN>klqfX4ca=UsMYF#@`{5C!sqh#v{1x4PHxuyE3b&<6+1)|jrsvvj^>7qnOIs72xfNKqY=*8$FZH5y z*F$dOj#apPOIt2JY^|pi53R0lh=0d?maOx$$E39PE$MBj(&{Ja1axV&+tw$G_qz(+ zRV@oPM*9r`{FyT0Dfqq16=Uve@9Z%1p7cOe{+zTbk)?hno9YfDUDwr5+)3CiWiHh6 z1W#LGda(`<=%)SNarq2YslvGyYKCVt-y*wZibjqncUiIDJgo~Noh~R~)wUr5i*t2j zP-hm?)C3=BR}cDv30JgNQP>Y^3%+571gQ!=t3L^WXV~P1N<+a5+!f)O?|Z+qwZ}E` zbA(cgg)>I=Y_jF`aIwFLY%}sOkE9kXjFPYiNz?Ks-{4YWSu3+lmQY%P(QZokd+`IV z`41l>MTJ~T)J!J>C!gM}?=O3P-P&S6evH<$Y0oLapgH4jL>S!AeSvKw%lzse!a9x( z$_Rw)rZd|0-T&B|m~3k%XEQkFPB38dF_ zEu!oL#hkn!708l@S;OZ^f;gs?J(y z%r*5FIP~r3t?r zAC^_C*Qfc?c@p3fRSh{1YboIK-fw0k{2`9nE7V6ZuZYe^OL0QycU1m#NsrlrwY)qm RL7zUQ6i^mt*c5gXCixXm?ShvovqheduV d1}s1|Abkx$3<4ni_<$llNuV4v0eNl$@&NBq5flIb 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-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/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/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/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/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/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):

- *
    - *
  1. Syntactic — structural field validation
  2. - *
  3. Signature — Ed25519 signature verification + submitter authorization
  4. - *
  5. Circuit resolution — verify circuit is known, allowed, and VK is available
  6. - *
  7. Cryptographic verification — delegate to {@link VerifierOrchestrator}
  8. - *
  9. Policy — state root chain, sequence, nullifier uniqueness
  10. - *
  11. Accept — update state root, record sequence, mark nullifier
  12. - *
- * - *

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/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/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-verifier-plonk/README.md b/zeroj-verifier-plonk/README.md index 4fe7edc..c4ffcfc 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) | 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; From 3753bc7425f70cc6ffd6b35b3c87b9275c5e3801 Mon Sep 17 00:00:00 2001 From: Satya Date: Sun, 17 May 2026 15:15:14 +0800 Subject: [PATCH 2/3] Updated adr docs --- ...ation-of-crypto-and-policy-verification.md | 7 +- .../0007-module-structure-and-boundaries.md | 8 +- docs/adr/0010-java-circuit-dsl.md | 20 +- ...020-module-cleanup-and-core-restructure.md | 431 ++++++++++++++++++ docs/alternate-prover-backends.md | 26 +- docs/architecture-overview.md | 67 +-- docs/circuit-dsl-user-guide.md | 11 +- docs/getting-started.md | 10 +- docs/plan-graalwasm-witness-calculator.md | 23 +- docs/plonk-support.md | 20 +- zeroj-examples/docs/e2e-plonk-sealed-bid.md | 16 +- 11 files changed, 553 insertions(+), 86 deletions(-) create mode 100644 docs/adr/0020-module-cleanup-and-core-restructure.md 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/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 From e14c95f90f611afac3b1f48a6e1ed684be69ade4 Mon Sep 17 00:00:00 2001 From: Satya Date: Sun, 17 May 2026 16:26:45 +0800 Subject: [PATCH 3/3] Add/update missing README.md --- zeroj-api/README.md | 2 +- zeroj-backend-spi/README.md | 2 +- zeroj-bls12381/README.md | 42 +++++++++++++++++++++++ zeroj-circuit-dsl/README.md | 58 ++++++++++++++++++++++++++++++++ zeroj-circuit-lib/README.md | 55 ++++++++++++++++++++++++++++++ zeroj-codec/README.md | 2 +- zeroj-crypto/README.md | 44 ++++++++++++++++++++++++ zeroj-onchain-julc/README.md | 48 ++++++++++++++++++++++++++ zeroj-prover-spi/README.md | 48 ++++++++++++++++++++++++++ zeroj-test-vectors/README.md | 5 +++ zeroj-verifier-groth16/README.md | 15 ++++++--- zeroj-verifier-plonk/README.md | 4 +-- 12 files changed, 315 insertions(+), 10 deletions(-) create mode 100644 zeroj-bls12381/README.md create mode 100644 zeroj-circuit-dsl/README.md create mode 100644 zeroj-circuit-lib/README.md create mode 100644 zeroj-crypto/README.md create mode 100644 zeroj-onchain-julc/README.md create mode 100644 zeroj-prover-spi/README.md 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-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-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-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-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-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-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-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 c4ffcfc..216844e 100644 --- a/zeroj-verifier-plonk/README.md +++ b/zeroj-verifier-plonk/README.md @@ -38,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(); @@ -65,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