@cheny56/zk-confidential-onchain
v1.0.0
Published
Confidential smart contracts using native ZK precompile (0x16). Optimized for PQC-Quorum nodes with ~10x gas savings.
Maintainers
Readme
@cheny56/zk-confidential-onchain
Confidential Smart Contracts using Native ZK Precompile
A privacy-preserving token system optimized for PQC-Quorum nodes with native ZK support:
- ✅ Inputs are private - Transfer amounts are hidden
- ✅ State is hidden - Balances stored as commitments
- ✅ Only proofs are public - ZK proofs verify correctness without revealing data
- ⚡ Native verification - Uses precompile at
0x16for ~10x gas savings
Features
- ⚡ Native Precompile - ~500K gas vs ~5M gas for Solidity verification
- 🔐 Go Prover - Uses
gnarkfor fast, reliable proof generation - 🔒 Full Privacy - Balances, transfers, identities all hidden
- 🏢 Enterprise Ready - Designed for PQC-Quorum private networks
Prerequisites
This package requires:
- PQC-Quorum node with ZK precompile enabled at
0x16 - Go 1.21+ for proof generation
- Node.js 18+ for the client SDK
Installation
npm install @cheny56/zk-confidential-onchainQuick Start
1. Build the Go Prover
cd go
go build -o ../bin/prover ./prover2. Use the Client
const { ConfidentialTokenClient } = require('@cheny56/zk-confidential-onchain/client');
// Initialize client
const client = new ConfidentialTokenClient({
provider: provider,
signer: wallet,
contractAddress: '0x...',
proverPath: './bin/prover', // Path to Go prover binary
});
await client.init();
// Deposit 1 ETH privately
const { note } = await client.deposit(ethers.parseEther('1'));
console.log('Private balance:', client.getBalance());
// Transfer 0.5 ETH to someone
const recipientSecret = client.generateRecipientSecret();
await client.transfer(ethers.parseEther('0.5'), recipientSecret);
// Withdraw to public address
await client.withdraw(ethers.parseEther('0.3'), recipientAddress);Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ ON-CHAIN ZK CONFIDENTIAL TOKEN │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ JavaScript │ │ Go Prover │ │ Quorum Node │ │
│ │ Client SDK │ │ (gnark) │ │ (Precompile) │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. Client prepares transaction inputs │ │
│ │ 2. Client calls Go prover binary with inputs │ │
│ │ 3. Prover generates Groth16 proof using gnark │ │
│ │ 4. Client sends proof to ConfidentialToken contract │ │
│ │ 5. Contract calls precompile at 0x16 to verify │ │
│ │ 6. Precompile verifies in ~500K gas (vs ~5M Solidity) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘Precompile Interface
The ZK precompile at address 0x16 provides:
// Verify a Groth16 proof
function verifyGroth16(
bytes32 vkHash, // Hash of verification key
bytes proof, // The ZK proof
bytes publicInputs // Public inputs to the circuit
) external view returns (bool);
// Register a verification key
function registerVK(
bytes vk // Serialized verification key
) external returns (bytes32 vkHash);Table of Contents
- How It Works
- Go Prover Setup
- API Reference
- Step-by-Step Guide
- Contract Deployment
- Performance
- Security Considerations
How It Works
Deposit Flow
User Client Go Prover Contract
│ │ │ │
│──deposit(amount)──────>│ │ │
│ │──generateMintProof────>│ │
│ │<──────proof────────────│ │
│ │ │ │
│ │──────deposit(commitment, proof)───────────>│
│ │ │ ┌────────────┴────┐
│ │ │ │ staticcall 0x16 │
│ │ │ │ verifyGroth16() │
│ │ │ └────────────┬────┘
│<───────────────────────│<──────────────────────tx confirmed─────────│Transfer Flow (Private → Private)
- Select input notes - Find unspent notes totaling transfer amount
- Create output notes - Recipient note + change note
- Compute nullifier - Prevents double-spending
- Generate ZK proof - Proves ownership and value conservation
- Submit transaction - Contract verifies via precompile
Go Prover Setup
Directory Structure
go/
├── circuits/
│ ├── mint_circuit.go # Mint circuit definition
│ └── transfer_circuit.go # Transfer circuit definition
├── prover/
│ └── prover.go # Main prover binary
├── go.mod
└── go.sumBuilding
cd go
go mod tidy
go build -o ../bin/prover ./proverTesting the Prover
# Generate a test proof
./bin/prover mint \
--value=1000 \
--secret=0x1234... \
--nonce=0x5678...API Reference
ConfidentialTokenClient
const client = new ConfidentialTokenClient({
provider, // Ethers provider
signer, // Ethers signer
contractAddress, // Contract address
proverPath, // Path to Go prover binary
demoMode, // Skip proofs for testing (default: false)
});
// Initialize
await client.init(masterSecret?);
// Deposit (public → private)
const { note, tx } = await client.deposit(amount);
// Transfer (private → private)
const { recipientNote, changeNote, tx } = await client.transfer(amount, recipientSecret);
// Withdraw (private → public)
const { tx } = await client.withdraw(amount, recipientAddress);
// Query
client.getBalance(); // Total private balance
client.getSpendableNotes(); // Spendable notes
client.exportWallet(); // Backup wallet
// Utility
const secret = client.generateRecipientSecret(); // Generate recipient secretNote
const { Note } = require('@cheny56/zk-confidential-onchain/lib');
// Create manually
const note = new Note(value, ownerSecret, nonce?);
await note.init();
// Properties
note.value; // Note amount
note.commitment; // Public commitment
note.nullifier; // Nullifier (after tree position set)
note.canSpend(); // Check if spendableMerkleTree
const { MerkleTree } = require('@cheny56/zk-confidential-onchain/lib');
const tree = new MerkleTree(20); // depth 20
await tree.init();
const index = await tree.insert(commitment);
const { path, indices } = await tree.generateProof(index);
const valid = await tree.verifyProof(leaf, index, path, indices, root);Step-by-Step Guide
Step 1: Setup Environment
# Clone repository
git clone https://github.com/cheny56/zk-confidential-onchain
cd zk-confidential-onchain
# Install dependencies
npm install
# Build Go prover
cd go && go build -o ../bin/prover ./prover && cd ..Step 2: Deploy Contract
const { ethers } = require('ethers');
const ConfidentialToken = require('./artifacts/ConfidentialToken.json');
// Connect to PQC-Quorum node
const provider = new ethers.JsonRpcProvider('http://localhost:8545');
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
// Deploy contract
const factory = new ethers.ContractFactory(
ConfidentialToken.abi,
ConfidentialToken.bytecode,
wallet
);
const token = await factory.deploy(
'Private Token',
'PRIV',
mintVKHash, // Hash of mint verification key
transferVKHash, // Hash of transfer verification key
withdrawVKHash // Hash of withdraw verification key
);
console.log('Contract deployed:', await token.getAddress());Step 3: Register Verification Keys
// The verification keys are registered on first use or can be pre-registered
// The Go prover outputs the verification keys during setup
// Register via precompile (one-time)
const precompile = new ethers.Contract('0x0000000000000000000000000000000000000016', [
'function registerVK(bytes vk) returns (bytes32)'
], wallet);
const mintVKHash = await precompile.registerVK(mintVKBytes);
const transferVKHash = await precompile.registerVK(transferVKBytes);Step 4: Use the Client
const { ConfidentialTokenClient } = require('@cheny56/zk-confidential-onchain/client');
const client = new ConfidentialTokenClient({
provider,
signer: wallet,
contractAddress: await token.getAddress(),
proverPath: './bin/prover',
});
await client.init();
client.setContract(token);
// Deposit 1000 wei
await client.deposit(1000n);
console.log('Balance:', client.getBalance());
// Transfer 300 to someone
const recipientSecret = client.generateRecipientSecret();
await client.transfer(300n, recipientSecret);
// Withdraw 200 to public address
await client.withdraw(200n, '0x...');Step 5: Verify Setup
# Run verification example
node examples/verify-setup.jsContract Deployment
Deploy Script
node scripts/deploy.jsManual Deployment
const { ethers } = require('ethers');
async function deploy() {
const provider = new ethers.JsonRpcProvider('http://localhost:8545');
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
// Read contract artifact
const artifact = require('./artifacts/ConfidentialToken.json');
// Deploy
const factory = new ethers.ContractFactory(
artifact.abi,
artifact.bytecode,
wallet
);
// Verification key hashes (from Go prover setup)
const mintVKHash = '0x...';
const transferVKHash = '0x...';
const withdrawVKHash = '0x...';
const contract = await factory.deploy(
'Confidential Token',
'CONF',
mintVKHash,
transferVKHash,
withdrawVKHash
);
await contract.waitForDeployment();
console.log('Deployed to:', await contract.getAddress());
}Performance
Gas Comparison
| Operation | Native Precompile | Solidity Verifier | Savings | |-----------|-------------------|-------------------|---------| | Mint | ~400K gas | ~3M gas | 87% | | Transfer | ~600K gas | ~5M gas | 88% | | Withdraw | ~500K gas | ~4M gas | 87% |
Proof Generation Time
| Circuit | Time (Go/gnark) | Time (snarkjs) | |-----------|-----------------|----------------| | Mint | ~100ms | ~2s | | Transfer | ~500ms | ~10s | | Withdraw | ~300ms | ~5s |
Security Considerations
- Trusted Setup: The circuit requires a one-time trusted setup
- Precompile Trust: The native precompile must be trusted
- Prover Isolation: Run the Go prover in a secure environment
- Key Management: Protect master secrets and backups
- Nullifier Tracking: On-chain nullifier set prevents double-spending
Package Contents
zk-confidential-onchain/
├── lib/
│ ├── index.js # Main exports
│ ├── mimc.js # MiMC hash (gnark compatible)
│ ├── merkle.js # Merkle tree
│ └── note.js # Note management
├── client/
│ └── confidential-client.js # Contract interaction
├── contracts/
│ ├── ConfidentialToken.sol
│ └── CommitmentTree.sol
├── go/
│ ├── circuits/ # gnark circuit definitions
│ └── prover/ # Prover binary
├── examples/
│ ├── basic-balance.js
│ ├── transfer-simulation.js
│ └── verify-setup.js
├── scripts/
│ └── deploy.js
├── package.json
└── README.mdLicense
MIT
Related
- @cheny56/zk-confidential-offchain - Circom/snarkjs version (any EVM)
- @cheny56/zk-client - Low-level ZK RPC client
