Zero-Knowledge Proofs in Practice: ZK-SNARK Verification in Java

ZK-SNARKs (Zero-Knowledge Succinct Non-Interactive Arguments of Knowledge) represent one of the most powerful cryptographic breakthroughs of the past decade. While generating proofs is computationally intensive, verification is efficient and practical for Java applications. This article explores ZK-SNARK verification implementation in Java, from basic concepts to production-ready systems.


Understanding ZK-SNARKs

What are ZK-SNARKs?

  • Zero-Knowledge: Prove knowledge without revealing the knowledge itself
  • Succinct: Proofs are small and fast to verify
  • Non-Interactive: No back-and-forth between prover and verifier
  • Arguments of Knowledge: Computational soundness guarantees

Key Components:

  1. Setup: Generate proving and verification keys
  2. Prove: Create proof using witness and proving key
  3. Verify: Check proof validity using verification key

Technology Stack and Dependencies

Maven Dependencies:

<properties>
<web3j.version>4.9.7</web3j.version>
<bouncycastle.version>1.76</bouncycastle.version>
<circom.version>2.1.5</circom.version>
<snarkjs.version>0.7.0</snarkjs.version>
</properties>
<dependencies>
<!-- Ethereum and Web3 for blockchain integration -->
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>${web3j.version}</version>
</dependency>
<!-- Cryptography libraries -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<!-- JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- BigInteger utilities -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
</dependency>
<!-- HTTP client for external verification -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.11.0</version>
</dependency>
</dependencies>

Gradle:

dependencies {
implementation("org.web3j:core:4.9.7")
implementation("org.bouncycastle:bcprov-jdk18on:1.76")
implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2")
implementation("org.apache.commons:commons-math3:3.6.1")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
}

Core ZK-SNARK Verification Implementation

1. Proof Data Structures:

package com.example.zksnark.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigInteger;
import java.util.List;
import java.util.Objects;
public class ZKProof {
private final List<BigInteger> a;
private final List<List<BigInteger>> b;
private final List<BigInteger> c;
private final List<BigInteger> input;
public ZKProof(
@JsonProperty("a") List<BigInteger> a,
@JsonProperty("b") List<List<BigInteger>> b,
@JsonProperty("c") List<BigInteger> c,
@JsonProperty("input") List<BigInteger> input) {
this.a = a;
this.b = b;
this.c = c;
this.input = input;
}
// Getters
public List<BigInteger> getA() { return a; }
public List<List<BigInteger>> getB() { return b; }
public List<BigInteger> getC() { return c; }
public List<BigInteger> getInput() { return input; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ZKProof zkProof = (ZKProof) o;
return Objects.equals(a, zkProof.a) &&
Objects.equals(b, zkProof.b) &&
Objects.equals(c, zkProof.c) &&
Objects.equals(input, zkProof.input);
}
@Override
public int hashCode() {
return Objects.hash(a, b, c, input);
}
@Override
public String toString() {
return String.format(
"ZKProof{a=%s, b=%s, c=%s, input=%s}",
a != null ? "[" + a.size() + " elements]" : "null",
b != null ? "[" + b.size() + "x" + (b.isEmpty() ? "?" : b.get(0).size()) + "]" : "null",
c != null ? "[" + c.size() + " elements]" : "null",
input != null ? "[" + input.size() + " elements]" : "null"
);
}
}

2. Verification Key Structure:

package com.example.zksnark.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigInteger;
import java.util.List;
public class VerificationKey {
private final List<List<BigInteger>> alpha;
private final List<List<BigInteger>> beta;
private final List<List<BigInteger>> gamma;
private final List<List<BigInteger>> delta;
private final List<List<List<BigInteger>>> ic;
public VerificationKey(
@JsonProperty("alpha") List<List<BigInteger>> alpha,
@JsonProperty("beta") List<List<BigInteger>> beta,
@JsonProperty("gamma") List<List<BigInteger>> gamma,
@JsonProperty("delta") List<List<BigInteger>> delta,
@JsonProperty("ic") List<List<List<BigInteger>>> ic) {
this.alpha = alpha;
this.beta = beta;
this.gamma = gamma;
this.delta = delta;
this.ic = ic;
}
// Getters
public List<List<BigInteger>> getAlpha() { return alpha; }
public List<List<BigInteger>> getBeta() { return beta; }
public List<List<BigInteger>> getGamma() { return gamma; }
public List<List<BigInteger>> getDelta() { return delta; }
public List<List<List<BigInteger>>> getIc() { return ic; }
public String getSummary() {
return String.format(
"VerificationKey{alpha=%dx%d, beta=%dx%d, gamma=%dx%d, delta=%dx%d, ic=%d}",
alpha.size(), alpha.get(0).size(),
beta.size(), beta.get(0).size(),
gamma.size(), gamma.get(0).size(),
delta.size(), delta.get(0).size(),
ic.size()
);
}
}

3. Core Verification Engine:

package com.example.zksnark.core;
import com.example.zksnark.model.ZKProof;
import com.example.zksnark.model.VerificationKey;
import java.math.BigInteger;
import java.util.List;
import java.util.ArrayList;
public class Groth16Verifier {
private final BigInteger prime;
private final EllipticCurveOperations curveOps;
// Prime field modulus for BN254 curve
private static final BigInteger BN254_PRIME = new BigInteger(
"21888242871839275222246405745257275088696311157297823662689037894645226208583");
public Groth16Verifier() {
this(BN254_PRIME);
}
public Groth16Verifier(BigInteger prime) {
this.prime = prime;
this.curveOps = new EllipticCurveOperations(prime);
}
/**
* Verify a Groth16 ZK-SNARK proof
*/
public boolean verifyProof(ZKProof proof, VerificationKey vk) {
try {
// Validate inputs
validateProof(proof);
validateVerificationKey(vk);
// Prepare public inputs
List<BigInteger> publicInputs = proof.getInput();
// Compute the commitment to public inputs
EllipticCurvePoint inputCommitment = computeInputCommitment(publicInputs, vk.getIc());
// Perform pairing checks
boolean pairing1 = checkPairing1(proof, vk, inputCommitment);
boolean pairing2 = checkPairing2(proof, vk);
return pairing1 && pairing2;
} catch (Exception e) {
System.err.println("Proof verification failed: " + e.getMessage());
return false;
}
}
/**
* First pairing equation: e(A, B) = e(alpha, beta) * e(input_commitment, gamma) * e(C, delta)
*/
private boolean checkPairing1(ZKProof proof, VerificationKey vk, EllipticCurvePoint inputCommitment) {
try {
// Convert proof elements to curve points
EllipticCurvePoint A = convertToG1(proof.getA());
EllipticCurvePoint B = convertToG2(proof.getB());
EllipticCurvePoint C = convertToG1(proof.getC());
// Convert verification key elements
EllipticCurvePoint alpha = convertToG1(vk.getAlpha().get(0));
EllipticCurvePoint beta = convertToG2(vk.getBeta().get(0));
EllipticCurvePoint gamma = convertToG2(vk.getGamma().get(0));
EllipticCurvePoint delta = convertToG2(vk.getDelta().get(0));
// Compute pairings
PairingResult left = curveOps.pairing(A, B);
PairingResult right1 = curveOps.pairing(alpha, beta);
PairingResult right2 = curveOps.pairing(inputCommitment, gamma);
PairingResult right3 = curveOps.pairing(C, delta);
// Combine right side: right1 * right2 * right3
PairingResult right = curveOps.multiplyPairings(
curveOps.multiplyPairings(right1, right2), right3);
return left.equals(right);
} catch (Exception e) {
System.err.println("Pairing check 1 failed: " + e.getMessage());
return false;
}
}
/**
* Second pairing equation: e(A, delta) = e(alpha, X) for some X
* This is a simplified version - actual implementation depends on the circuit
*/
private boolean checkPairing2(ZKProof proof, VerificationKey vk) {
// Implementation depends on specific circuit structure
// For many applications, only the first pairing check is needed
return true;
}
private EllipticCurvePoint computeInputCommitment(List<BigInteger> inputs, 
List<List<List<BigInteger>>> ic) {
if (inputs.isEmpty()) {
return convertToG1(ic.get(0).get(0)); // Base commitment
}
// Start with the base (ic[0])
EllipticCurvePoint commitment = convertToG1(ic.get(0).get(0));
// Add each input multiplied by its corresponding IC point
for (int i = 0; i < inputs.size(); i++) {
EllipticCurvePoint icPoint = convertToG1(ic.get(i + 1).get(0));
EllipticCurvePoint scaledPoint = curveOps.scalarMultiply(icPoint, inputs.get(i));
commitment = curveOps.add(commitment, scaledPoint);
}
return commitment;
}
private EllipticCurvePoint convertToG1(List<BigInteger> coordinates) {
if (coordinates.size() < 2) {
throw new IllegalArgumentException("G1 point requires at least 2 coordinates");
}
return new EllipticCurvePoint(coordinates.get(0), coordinates.get(1));
}
private EllipticCurvePoint convertToG2(List<List<BigInteger>> coordinates) {
// G2 points have 2 coordinates, each being a pair of BigIntegers
if (coordinates.size() < 2 || coordinates.get(0).size() < 2) {
throw new IllegalArgumentException("G2 point requires 2x2 coordinates");
}
// For simplicity, we're treating G2 points similarly in this example
// Real implementation would handle the extension field properly
return new EllipticCurvePoint(
coordinates.get(0).get(0), // Real part of x
coordinates.get(1).get(0)  // Real part of y (simplified)
);
}
private void validateProof(ZKProof proof) {
if (proof == null) throw new IllegalArgumentException("Proof cannot be null");
if (proof.getA() == null || proof.getA().size() < 2) 
throw new IllegalArgumentException("Invalid proof A");
if (proof.getB() == null || proof.getB().size() < 2) 
throw new IllegalArgumentException("Invalid proof B");
if (proof.getC() == null || proof.getC().size() < 2) 
throw new IllegalArgumentException("Invalid proof C");
if (proof.getInput() == null) 
throw new IllegalArgumentException("Proof inputs cannot be null");
}
private void validateVerificationKey(VerificationKey vk) {
if (vk == null) throw new IllegalArgumentException("Verification key cannot be null");
if (vk.getAlpha() == null || vk.getAlpha().isEmpty()) 
throw new IllegalArgumentException("Invalid verification key alpha");
if (vk.getBeta() == null || vk.getBeta().isEmpty()) 
throw new IllegalArgumentException("Invalid verification key beta");
if (vk.getGamma() == null || vk.getGamma().isEmpty()) 
throw new IllegalArgumentException("Invalid verification key gamma");
if (vk.getDelta() == null || vk.getDelta().isEmpty()) 
throw new IllegalArgumentException("Invalid verification key delta");
if (vk.getIc() == null || vk.getIc().isEmpty()) 
throw new IllegalArgumentException("Invalid verification key IC");
}
}

4. Elliptic Curve Operations:

package com.example.zksnark.core;
import java.math.BigInteger;
public class EllipticCurveOperations {
private final BigInteger prime;
private final BigInteger curveOrder;
public EllipticCurveOperations(BigInteger prime) {
this.prime = prime;
this.curveOrder = new BigInteger(
"21888242871839275222246405745257275088548364400416034343698204186575808495617");
}
public EllipticCurvePoint add(EllipticCurvePoint p1, EllipticCurvePoint p2) {
if (p1.isInfinity()) return p2;
if (p2.isInfinity()) return p1;
// Point addition formula for elliptic curves
if (p1.getX().equals(p2.getX())) {
if (p1.getY().equals(p2.getY())) {
return doublePoint(p1); // Point doubling
} else {
return EllipticCurvePoint.INFINITY; // Points are inverses
}
}
BigInteger lambda = p2.getY().subtract(p1.getY())
.multiply(p2.getX().subtract(p1.getX()).modInverse(prime))
.mod(prime);
BigInteger x3 = lambda.multiply(lambda)
.subtract(p1.getX())
.subtract(p2.getX())
.mod(prime);
BigInteger y3 = lambda.multiply(p1.getX().subtract(x3))
.subtract(p1.getY())
.mod(prime);
return new EllipticCurvePoint(x3, y3);
}
public EllipticCurvePoint scalarMultiply(EllipticCurvePoint point, BigInteger scalar) {
if (scalar.equals(BigInteger.ZERO)) return EllipticCurvePoint.INFINITY;
if (scalar.equals(BigInteger.ONE)) return point;
// Double-and-add algorithm
EllipticCurvePoint result = EllipticCurvePoint.INFINITY;
EllipticCurvePoint addend = point;
while (scalar.compareTo(BigInteger.ZERO) > 0) {
if (scalar.testBit(0)) {
result = add(result, addend);
}
addend = doublePoint(addend);
scalar = scalar.shiftRight(1);
}
return result;
}
private EllipticCurvePoint doublePoint(EllipticCurvePoint point) {
if (point.isInfinity()) return point;
// Point doubling formula: λ = (3x² + a) / (2y)
BigInteger lambda = BigInteger.valueOf(3)
.multiply(point.getX())
.multiply(point.getX())
.multiply(point.getY().multiply(BigInteger.valueOf(2)).modInverse(prime))
.mod(prime);
BigInteger x3 = lambda.multiply(lambda)
.subtract(point.getX().multiply(BigInteger.valueOf(2)))
.mod(prime);
BigInteger y3 = lambda.multiply(point.getX().subtract(x3))
.subtract(point.getY())
.mod(prime);
return new EllipticCurvePoint(x3, y3);
}
public PairingResult pairing(EllipticCurvePoint g1Point, EllipticCurvePoint g2Point) {
// Simplified pairing implementation
// Real implementation would use proper pairing functions (ate pairing, etc.)
BigInteger result = g1Point.getX()
.multiply(g2Point.getX())
.add(g1Point.getY().multiply(g2Point.getY()))
.mod(prime);
return new PairingResult(result);
}
public PairingResult multiplyPairings(PairingResult p1, PairingResult p2) {
return new PairingResult(p1.getValue().multiply(p2.getValue()).mod(prime));
}
}

5. Supporting Data Structures:

package com.example.zksnark.core;
import java.math.BigInteger;
import java.util.Objects;
public class EllipticCurvePoint {
public static final EllipticCurvePoint INFINITY = new EllipticCurvePoint(true);
private final BigInteger x;
private final BigInteger y;
private final boolean isInfinity;
public EllipticCurvePoint(BigInteger x, BigInteger y) {
this.x = x;
this.y = y;
this.isInfinity = false;
}
private EllipticCurvePoint(boolean isInfinity) {
this.x = BigInteger.ZERO;
this.y = BigInteger.ZERO;
this.isInfinity = isInfinity;
}
public BigInteger getX() { 
if (isInfinity) throw new IllegalStateException("Point at infinity has no coordinates");
return x; 
}
public BigInteger getY() { 
if (isInfinity) throw new IllegalStateException("Point at infinity has no coordinates");
return y; 
}
public boolean isInfinity() { return isInfinity; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EllipticCurvePoint that = (EllipticCurvePoint) o;
if (isInfinity && that.isInfinity) return true;
if (isInfinity || that.isInfinity) return false;
return Objects.equals(x, that.x) && Objects.equals(y, that.y);
}
@Override
public int hashCode() {
return Objects.hash(x, y, isInfinity);
}
@Override
public String toString() {
return isInfinity ? "ECPoint(INFINITY)" : 
String.format("ECPoint(%s, %s)", 
x.toString(16).substring(0, 16) + "...", 
y.toString(16).substring(0, 16) + "...");
}
}
package com.example.zksnark.core;
import java.math.BigInteger;
import java.util.Objects;
public class PairingResult {
private final BigInteger value;
public PairingResult(BigInteger value) {
this.value = value;
}
public BigInteger getValue() { return value; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PairingResult that = (PairingResult) o;
return Objects.equals(value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}

Practical Implementation Examples

1. Age Verification System:

package com.example.zksnark.application;
import com.example.zksnark.core.Groth16Verifier;
import com.example.zksnark.model.ZKProof;
import com.example.zksnark.model.VerificationKey;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
public class AgeVerificationSystem {
private final Groth16Verifier verifier;
private final VerificationKey ageVerificationKey;
private final ObjectMapper objectMapper;
public AgeVerificationSystem(String verificationKeyPath) throws Exception {
this.verifier = new Groth16Verifier();
this.objectMapper = new ObjectMapper();
this.ageVerificationKey = loadVerificationKey(verificationKeyPath);
}
/**
* Verify that a user is over 18 without revealing their exact age
*/
public boolean verifyAgeProof(ZKProof proof) {
boolean isValid = verifier.verifyProof(proof, ageVerificationKey);
if (isValid) {
System.out.println("Age verification proof is VALID");
System.out.println("User has proven they are over 18 without revealing exact age");
} else {
System.out.println("Age verification proof is INVALID");
}
return isValid;
}
/**
* Verify specific age threshold
*/
public boolean verifyAgeThreshold(ZKProof proof, int minAge) {
// The proof should have public inputs encoding the minimum age
List<BigInteger> inputs = proof.getInput();
if (inputs.size() >= 1) {
BigInteger provenMinAge = inputs.get(0);
System.out.println("Proven minimum age: " + provenMinAge);
}
return verifyAgeProof(proof);
}
private VerificationKey loadVerificationKey(String path) throws Exception {
return objectMapper.readValue(new File(path), VerificationKey.class);
}
public static void main(String[] args) {
try {
AgeVerificationSystem system = new AgeVerificationSystem("keys/age_verification_key.json");
// In practice, this would come from a user or external source
ZKProof sampleProof = createSampleAgeProof();
boolean isValid = system.verifyAgeThreshold(sampleProof, 18);
System.out.println("Age verification result: " + isValid);
} catch (Exception e) {
System.err.println("Age verification failed: " + e.getMessage());
}
}
private static ZKProof createSampleAgeProof() {
// This would be a real proof generated by a prover
// For demonstration, we create a mock proof structure
List<BigInteger> a = Arrays.asList(
new BigInteger("123456789"),
new BigInteger("987654321")
);
List<List<BigInteger>> b = Arrays.asList(
Arrays.asList(new BigInteger("111111111"), new BigInteger("222222222")),
Arrays.asList(new BigInteger("333333333"), new BigInteger("444444444"))
);
List<BigInteger> c = Arrays.asList(
new BigInteger("555555555"),
new BigInteger("666666666")
);
List<BigInteger> input = Arrays.asList(
BigInteger.valueOf(18) // Minimum age threshold
);
return new ZKProof(a, b, c, input);
}
}

2. Financial Transaction Verifier:

package com.example.zksnark.application;
import com.example.zksnark.core.Groth16Verifier;
import com.example.zksnark.model.ZKProof;
import com.example.zksnark.model.VerificationKey;
import java.math.BigInteger;
import java.util.List;
public class TransactionVerifier {
private final Groth16Verifier verifier;
private final VerificationKey transactionKey;
public TransactionVerifier(VerificationKey transactionKey) {
this.verifier = new Groth16Verifier();
this.transactionKey = transactionKey;
}
/**
* Verify transaction validity without revealing amounts
*/
public TransactionVerificationResult verifyTransaction(ZKProof proof) {
long startTime = System.currentTimeMillis();
boolean isValid = verifier.verifyProof(proof, transactionKey);
long endTime = System.currentTimeMillis();
TransactionVerificationResult result = new TransactionVerificationResult(
isValid,
endTime - startTime,
extractTransactionInfo(proof)
);
logVerificationResult(result);
return result;
}
/**
* Verify batch of transactions
*/
public BatchVerificationResult verifyTransactions(List<ZKProof> proofs) {
int validCount = 0;
int invalidCount = 0;
long totalTime = 0;
for (ZKProof proof : proofs) {
TransactionVerificationResult result = verifyTransaction(proof);
if (result.isValid()) {
validCount++;
} else {
invalidCount++;
}
totalTime += result.getVerificationTimeMs();
}
return new BatchVerificationResult(validCount, invalidCount, totalTime);
}
private TransactionInfo extractTransactionInfo(ZKProof proof) {
List<BigInteger> inputs = proof.getInput();
// Extract public information from proof inputs
// This depends on the specific circuit structure
BigInteger amount = inputs.size() > 0 ? inputs.get(0) : BigInteger.ZERO;
BigInteger timestamp = inputs.size() > 1 ? inputs.get(1) : BigInteger.ZERO;
return new TransactionInfo(amount, timestamp);
}
private void logVerificationResult(TransactionVerificationResult result) {
String status = result.isValid() ? "VALID" : "INVALID";
System.out.printf("Transaction verification: %s (took %d ms)%n",
status, result.getVerificationTimeMs());
}
public static class TransactionVerificationResult {
private final boolean valid;
private final long verificationTimeMs;
private final TransactionInfo transactionInfo;
public TransactionVerificationResult(boolean valid, long verificationTimeMs, 
TransactionInfo transactionInfo) {
this.valid = valid;
this.verificationTimeMs = verificationTimeMs;
this.transactionInfo = transactionInfo;
}
// Getters
public boolean isValid() { return valid; }
public long getVerificationTimeMs() { return verificationTimeMs; }
public TransactionInfo getTransactionInfo() { return transactionInfo; }
}
public static class BatchVerificationResult {
private final int validCount;
private final int invalidCount;
private final long totalTimeMs;
public BatchVerificationResult(int validCount, int invalidCount, long totalTimeMs) {
this.validCount = validCount;
this.invalidCount = invalidCount;
this.totalTimeMs = totalTimeMs;
}
// Getters
public int getValidCount() { return validCount; }
public int getInvalidCount() { return invalidCount; }
public long getTotalTimeMs() { return totalTimeMs; }
public double getAverageTimeMs() { 
return (double) totalTimeMs / (validCount + invalidCount); 
}
}
public static class TransactionInfo {
private final BigInteger amount;
private final BigInteger timestamp;
public TransactionInfo(BigInteger amount, BigInteger timestamp) {
this.amount = amount;
this.timestamp = timestamp;
}
// Getters
public BigInteger getAmount() { return amount; }
public BigInteger getTimestamp() { return timestamp; }
}
}

Web3 Integration

1. Ethereum Smart Contract Integration:

package com.example.zksnark.integration;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.gas.DefaultGasProvider;
import java.math.BigInteger;
public class EthereumVerifier {
private final Web3j web3j;
private final Credentials credentials;
private final String contractAddress;
public EthereumVerifier(String rpcUrl, String privateKey, String contractAddress) {
this.web3j = Web3j.build(new HttpService(rpcUrl));
this.credentials = Credentials.create(privateKey);
this.contractAddress = contractAddress;
}
/**
* Verify proof on Ethereum blockchain
*/
public boolean verifyOnChain(ZKProof proof, VerificationKey vk) throws Exception {
// Load the verifier contract
VerifierContract verifier = VerifierContract.load(
contractAddress, web3j, credentials, new DefaultGasProvider());
// Convert proof to contract call parameters
VerifierContract.Proof contractProof = convertToContractProof(proof);
// Call the verify function on-chain
return verifier.verify(
contractProof.a,
contractProof.b,
contractProof.c,
proof.getInput()
).send();
}
private VerifierContract.Proof convertToContractProof(ZKProof proof) {
// Convert Java proof structure to Web3j contract structure
// This depends on the specific contract ABI
return new VerifierContract.Proof(
proof.getA(),
proof.getB(),
proof.getC()
);
}
/**
* Get verification gas cost estimate
*/
public BigInteger estimateGasCost(ZKProof proof) throws Exception {
VerifierContract verifier = VerifierContract.load(
contractAddress, web3j, credentials, new DefaultGasProvider());
VerifierContract.Proof contractProof = convertToContractProof(proof);
return verifier.verify(
contractProof.a,
contractProof.b,
contractProof.c,
proof.getInput()
).estimateGas();
}
}

2. REST API for Proof Verification:

package com.example.zksnark.api;
import com.example.zksnark.core.Groth16Verifier;
import com.example.zksnark.model.ZKProof;
import com.example.zksnark.model.VerificationKey;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/api/v1/zk")
public class ZKVerificationController {
private final Groth16Verifier verifier;
private final Map<String, VerificationKey> verificationKeys;
private final ObjectMapper objectMapper;
public ZKVerificationController() {
this.verifier = new Groth16Verifier();
this.verificationKeys = new ConcurrentHashMap<>();
this.objectMapper = new ObjectMapper();
loadDefaultKeys();
}
@PostMapping("/verify/{circuitId}")
public VerificationResponse verifyProof(
@PathVariable String circuitId,
@RequestBody VerificationRequest request) {
try {
VerificationKey vk = verificationKeys.get(circuitId);
if (vk == null) {
return VerificationResponse.error("Unknown circuit: " + circuitId);
}
ZKProof proof = request.getProof();
long startTime = System.currentTimeMillis();
boolean isValid = verifier.verifyProof(proof, vk);
long endTime = System.currentTimeMillis();
return VerificationResponse.success(isValid, endTime - startTime);
} catch (Exception e) {
return VerificationResponse.error("Verification failed: " + e.getMessage());
}
}
@PostMapping("/circuit/{circuitId}")
public String registerCircuit(
@PathVariable String circuitId,
@RequestBody VerificationKey verificationKey) {
verificationKeys.put(circuitId, verificationKey);
return "Circuit registered successfully: " + circuitId;
}
@GetMapping("/circuits")
public Map<String, String> getRegisteredCircuits() {
Map<String, String> circuits = new ConcurrentHashMap<>();
verificationKeys.forEach((id, vk) -> {
circuits.put(id, vk.getSummary());
});
return circuits;
}
private void loadDefaultKeys() {
// Load commonly used verification keys
// In production, these would be loaded from secure storage
}
// DTO classes
public static class VerificationRequest {
private ZKProof proof;
public ZKProof getProof() { return proof; }
public void setProof(ZKProof proof) { this.proof = proof; }
}
public static class VerificationResponse {
private boolean success;
private boolean valid;
private long verificationTimeMs;
private String error;
public static VerificationResponse success(boolean valid, long verificationTimeMs) {
VerificationResponse response = new VerificationResponse();
response.success = true;
response.valid = valid;
response.verificationTimeMs = verificationTimeMs;
return response;
}
public static VerificationResponse error(String error) {
VerificationResponse response = new VerificationResponse();
response.success = false;
response.error = error;
return response;
}
// Getters and setters
public boolean isSuccess() { return success; }
public boolean isValid() { return valid; }
public long getVerificationTimeMs() { return verificationTimeMs; }
public String getError() { return error; }
}
}

Security Considerations

1. Secure Key Management:
```java
package com.example.zksnark.security;

import com.example.zksnark.model.VerificationKey;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.nio.file.Files;
import java.security.Key;
import java.util.Base64;

public class SecureKeyManager {
private final byte[] encryptionKey;
private final ObjectMapper objectMapper;

public SecureKeyManager(String encryptionKey) {
this.encryptionKey = encryptionKey.getBytes();
this.objectMapper = new ObjectMapper();
}
/**
* Load and decrypt verification key
*/
public VerificationKey loadEncryptedKey(String filePath) throws Exception {
byte[] encryptedData = Files.readAllBytes(new File(filePath).toPath());
byte[] decryptedData = decrypt(encryptedData);
return objectMapper.readValue(decryptedData, VerificationKey.class);
}
/**
* Encrypt and save verification key
*/
public void saveEncryptedKey(VerificationKey vk, String filePath) throws Exception {
byte[] jsonData = objectMapper.writeValueAsBytes(vk);
byte[] encryptedData = encrypt(jsonData);
Files.write(new File(filePath).toPath(), encryptedData);
}
private byte[] encrypt(byte[] data) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
Key key = new SecretKeySpec(encryptionKey, "AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
private byte[] decrypt(byte[] encryptedData) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
Key key = new SecretKeySpec(encryptionKey, "AES");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(encryptedData);
}
/**
* Validate verification key integrity
*/
public boolean validateKeyIntegrity(VerificationKey vk) {
try {
// Check that all required fields are present and valid
if (vk.getAlpha() == null || vk.getAlpha().isEmpty()) return false;
if (vk.getBeta() == null || vk.getBeta().isEmpty()) return false;
if (vk.getGamma() == null || vk.getGamma().isEmpty()) return false;
if (vk.getDelta() == null || vk.getDelta().isEmpty()) return false;
if (vk.getIc() == null || vk.getIc().isEmpty()) return

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper