ZK Voting with Native PQC-Quorum Integration - Optimized for Groth16 precompile at 0x16
npm install @cheny56/zk-voting-nativeZero-Knowledge Voting with Native PQC-Quorum Integration. Optimized for the Groth16 precompile at address 0x16.


- š Native Precompile Integration - Uses Groth16 verifier at 0x16
- ā” Gas-Efficient - Precompile verification costs ~200k gas vs ~5M for Solidity
- š§ Go Prover - gnark-based proof generation
- š MiMC Hash - Compatible with gnark circuits
- š¦ Modular Design - Separate lib for circuits, tallying, and client
``bash`
npm install @pqc-quorum/zk-voting-native
`bash`Build the Go prover
cd go && go build -o ../bin/zk-prover .
`javascript
const { VotingClient, ProofGenerator } = require('@pqc-quorum/zk-voting-native');
// Connect to PQC-Quorum node
const client = new VotingClient('http://localhost:8545', privateKey);
// Cast a vote with native ZK proof
await client.castVote(voterSecret, leafIndex, voteChoice);
`
- Overview
- Architecture
- Prerequisites
- Smart Contracts
- Go Prover
- JavaScript Client
- API Reference
- Examples
- Benchmarks
- Comparison with @pqc/zk-voting
This package provides a ZK voting system optimized for PQC-Quorum nodes with native Groth16 verification:
``
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā NATIVE ZK VOTING FLOW ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā āāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā JavaScript ā ā Go Prover ā ā PQC-Quorum Node ā ā
ā ā Client ā ā (gnark) ā ā ā ā
ā āāāāāāāāā¬āāāāāāāāā āāāāāāāāā¬āāāāāāāāā āāāāāāāāāāāāā¬āāāāāāāāāāāāā ā
ā ā ā ā ā
ā ā 1. Prepare inputs ā ā ā
ā āāāāāāāāāāāāāāāāāāāāāā>ā ā ā
ā ā ā 2. Generate proof ā ā
ā ā ā (gnark Groth16) ā ā
ā ā<āāāāāāāāāāāāāāāāāāāāā⤠ā ā
ā ā 3. Proof bytes ā ā ā
ā ā ā ā ā
ā ā 4. Submit tx āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā>ā ā
ā ā ā ā ā
ā ā ā 5. Verify via 0x16 āā>ā ā
ā ā ā ā (Precompile) ā
ā ā<āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā 6. Vote recorded ā ā ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
| Feature | snarkjs (Solidity) | Native (Precompile) |
|---------|-------------------|---------------------|
| Verification Gas | ~5,000,000 | ~200,000 |
| Proof Gen Speed | ~2-5 seconds | ~200ms |
| Trusted Setup | Required | Pre-deployed |
| Hash Function | Poseidon | MiMC |
| Best For | Any EVM chain | PQC-Quorum |
- Node.js >= 18.0.0
- Go >= 1.21 (for proof generation)
- PQC-Quorum node with ZK precompiles enabled
| Contract | Address | Description |
|----------|---------|-------------|
| VoterRegistry | Deployed | Merkle tree of eligible voters |
| ZKBallot | Deployed | Main voting with precompile verification |
| TallyManager | Deployed | Tallying orchestration |
| IZKPrecompile | 0x16 | Native Groth16 verifier |
`solidity
// Register voters (admin only)
function registerVoter(bytes32 commitment) external;
function registerVotersBatch(bytes32[] calldata commitments) external;
function closeRegistration() external;
// Query state
function merkleRoot() external view returns (bytes32);
function getVoterCount() external view returns (uint256);
`
`solidity
// Cast vote with native ZK verification
function castVote(
bytes calldata proof, // Groth16 proof bytes
bytes calldata publicInputs, // [merkleRoot, nullifier, commitment, count]
bytes[] calldata encryptedVote // For homomorphic tally (optional)
) external;
// End voting
function endVoting() external;
// Query
function getTotalVotes() external view returns (uint256);
function nullifierUsed(bytes32) external view returns (bool);
`
The precompile at 0x16 provides efficient Groth16 verification:
`solidity`
interface IZKPrecompile {
function verifyGroth16(
bytes32 vkHash, // Hash of verification key
bytes calldata proof, // 8 G1/G2 elements (256 bytes)
bytes calldata inputs // Public inputs (32 bytes each)
) external view returns (bool);
}
The Go prover uses gnark for Groth16 proof generation.
`go
// go/circuit/vote_circuit.go
type VoteCircuit struct {
// Private inputs
VoterSecret frontend.Variable
VoterLeafIndex frontend.Variable
MerklePath [20]frontend.Variable
PathIndices [20]frontend.Variable
VoteChoice frontend.Variable
VoteSalt frontend.Variable
// Public inputs
MerkleRoot frontend.Variable gnark:",public"gnark:",public"
Nullifier frontend.Variable gnark:",public"
VoteCommitment frontend.Variable gnark:",public"
CandidateCount frontend.Variable `
}
`bash`
cd go
go mod tidy
go build -o ../bin/zk-prover .
`bashGenerate proof
./bin/zk-prover prove \
--secret 0x1234... \
--leaf-index 5 \
--merkle-path 0xabc...,0xdef...,... \
--path-indices 0,1,0,... \
--vote-choice 2 \
--vote-salt 0x5678... \
--merkle-root 0x9abc... \
--candidate-count 4
JavaScript Client
$3
High-level client for the complete voting flow:
`javascript
const { VotingClient } = require('@pqc-quorum/zk-voting-native');const client = new VotingClient('http://localhost:8545', privateKey);
// Deploy contracts
await client.deployVoterRegistry();
await client.deployZKBallot(candidates, duration, vkHash);
await client.deployTallyManager();
// Admin: Register voters
await client.registerVoters([commitment1, commitment2, ...]);
await client.closeRegistration();
// Voter: Cast vote
await client.castVote({
voterSecret: BigInt('0x...'),
leafIndex: 5,
voteChoice: 2,
candidateCount: 4,
});
// Query state
const totalVotes = await client.getTotalVotes();
const candidates = await client.getCandidates();
`$3
JavaScript wrapper for the Go prover:
`javascript
const { ProofGenerator } = require('@pqc-quorum/zk-voting-native');const prover = new ProofGenerator({
proverPath: './bin/zk-prover',
circuitPath: './circuits/vote.r1cs',
pkPath: './keys/proving.key',
});
// Generate proof
const { proof, publicInputs } = await prover.generateProof({
voterSecret,
leafIndex,
merklePath,
pathIndices,
voteChoice,
voteSalt,
merkleRoot,
candidateCount,
});
`API Reference
$3
`javascript
class VotingClient {
constructor(rpcUrl: string, privateKey: string);
// Deployment
deployVoterRegistry(): Promise;
deployZKBallot(
candidates: string[],
durationSeconds: number,
vkHash: string
): Promise;
deployTallyManager(): Promise;
// Connect to existing
connectVoterRegistry(address: string): void;
connectZKBallot(address: string): void;
connectTallyManager(address: string): void;
// Admin functions
registerVoter(commitment: string): Promise;
registerVoters(commitments: string[]): Promise;
closeRegistration(): Promise;
// Voting
castVote(params: CastVoteParams): Promise<{
nullifier: string;
voteCommitment: string;
txHash: string;
}>;
// Queries
getMerkleRoot(): Promise;
getTotalVotes(): Promise;
getCandidates(): Promise;
isNullifierUsed(nullifier: string): Promise;
getVoteCommitments(): Promise;
// Tallying
endVoting(): Promise;
submitHomomorphicTally(tally: number[]): Promise;
}interface CastVoteParams {
voterSecret: bigint;
leafIndex: number;
voteChoice: number;
candidateCount: number;
}
`$3
`javascript
const {
// Crypto
mimcHash,
bigIntToHex32,
hex32ToBigInt,
// Merkle Tree
MerkleTree,
// Vote Proof
generateVoteProofInputs,
TREE_DEPTH,
// Tally
RevealTally,
HomomorphicTally,
PaillierCrypto,
} = require('@pqc-quorum/zk-voting-native/lib');
`$3
`javascript
const { MerkleTree, mimcHash } = require('@pqc-quorum/zk-voting-native/lib');// Create tree
const tree = new MerkleTree(20); // depth 20
// Add leaves
await tree.addLeaf(commitment1);
await tree.addLeaf(commitment2);
// Get root
const root = tree.getRoot();
// Generate proof
const { pathElements, pathIndices } = await tree.generateProof(commitment, index);
`Examples
$3
`javascript
const {
VotingClient,
ProofGenerator,
MerkleTree,
mimcHash,
bigIntToHex32,
} = require('@pqc-quorum/zk-voting-native');async function completeVotingExample() {
// Setup
const rpcUrl = 'http://localhost:8545';
const adminKey = process.env.ADMIN_PRIVATE_KEY;
const candidates = ['Alice', 'Bob', 'Charlie'];
// Initialize client
const client = new VotingClient(rpcUrl, adminKey);
// Deploy contracts
console.log('Deploying contracts...');
await client.deployVoterRegistry();
await client.deployZKBallot(candidates, 3600, VK_HASH);
await client.deployTallyManager();
// Register voters
const voters = [];
const tree = new MerkleTree(20);
for (let i = 0; i < 10; i++) {
const secret = BigInt(crypto.randomBytes(32).toString('hex'), 16);
const commitment = await mimcHash(secret);
voters.push({ secret, commitment, index: i });
await tree.addLeaf(commitment);
}
const commitments = voters.map(v => bigIntToHex32(v.commitment));
await client.registerVoters(commitments);
await client.closeRegistration();
console.log(
Registered ${voters.length} voters);
console.log(Merkle root: ${bigIntToHex32(tree.getRoot())});
// Cast votes
for (const voter of voters) {
const voteChoice = Math.floor(Math.random() * candidates.length);
const result = await client.castVote({
voterSecret: voter.secret,
leafIndex: voter.index,
voteChoice,
candidateCount: candidates.length,
});
console.log(Vote cast: ${result.txHash});
}
// End voting and get results
await client.endVoting();
const totalVotes = await client.getTotalVotes();
console.log(Total votes: ${totalVotes});
}
`$3
`javascript
const { VotingClient, MockProofGenerator } = require('@pqc-quorum/zk-voting-native');// Use mock prover for testing (no Go required)
const client = new VotingClient(rpcUrl, privateKey, {
proofGenerator: new MockProofGenerator(),
});
`Benchmarks
Run benchmarks with:
`bash
npm run benchmark
`$3
| Operation | Time | Gas |
|-----------|------|-----|
| Proof Generation | ~200ms | - |
| On-chain Verification | - | ~200,000 |
| Vote Registration | ~50ms | ~45,000 |
| Full Vote Cast | ~300ms | ~250,000 |
$3
| Metric | Native (gnark) | snarkjs |
|--------|---------------|---------|
| Proof Gen | 200ms | 2-5s |
| Verify Gas | 200k | 5M+ |
| Setup | Pre-deployed | Custom |
Comparison
$3
| Use Case | Recommended Package |
|----------|---------------------|
| PQC-Quorum node |
@pqc-quorum/zk-voting-native |
| Any EVM chain | @pqc/zk-voting |
| Maximum portability | @pqc/zk-voting |
| Maximum performance | @pqc-quorum/zk-voting-native |
| No Go environment | @pqc/zk-voting |$3
| Feature | @pqc/zk-voting | @pqc-quorum/zk-voting-native |
|---------|---------------|-------------------------------|
| ZK System | Circom/snarkjs | gnark/Groth16 |
| Hash Function | Poseidon | MiMC |
| Verification | Solidity | Precompile (0x16) |
| Proof Generation | JavaScript | Go |
| EVM Compatibility | Any | PQC-Quorum |
| Gas Cost | ~5M | ~200k |
| Trusted Setup | Custom | Pre-deployed |
| Homomorphic Tally | ā | ā |
| Reveal-Based Tally | ā | ā |
Development
`bash
Install dependencies
npm installBuild Go prover
npm run compile-goRun example
npm run exampleRun benchmarks
npm run benchmark
``MIT
- @pqc/zk-voting - Portable ZK voting with snarkjs
- @pqc-quorum/zk-client - ZK RPC client for PQC-Quorum nodes