Zero-Knowledge Proofs
Orbinum uses Groth16 proofs over the BN254 curve for efficient on-chain verification of private transactions.
Table of Contents
Proving System
Groth16
Orbinum uses Groth16, a zkSNARK proving system chosen for:
| Property | Benefit |
|---|---|
| Constant proof size | 192 bytes regardless of circuit complexity |
| Fast verification | ~3 pairings, O(1) verification time |
| EVM compatibility | Native precompiles on Ethereum |
| Mature tooling | SnarkJS, Circom, arkworks support |
BN254 Curve
The BN254 (alt_bn128) curve provides:
- 254-bit prime field
- ~100 bits of security
- Ethereum precompile support (EIP-196, EIP-197)
- Efficient pairing operations
Cryptographic Primitives
Poseidon Hash
Orbinum uses Poseidon as the primary hash function:
// From fp-zk-primitives
pub fn poseidon_hash_2(left: [u8; 32], right: [u8; 32]) -> [u8; 32]
pub fn poseidon_hash_4(inputs: [[u8; 32]; 4]) -> [u8; 32]
Why Poseidon?
| Property | Poseidon | SHA256 | Pedersen |
|---|---|---|---|
| R1CS constraints | ~300 | ~25,000 | ~750 |
| ZK-friendly | Yes | No | Yes |
| Standardized | Yes | Yes | No |
Commitment Scheme
Notes are committed using Poseidon:
pub fn create_commitment(
value: u128,
pk_d: [u8; 32],
blinding: [u8; 32],
) -> [u8; 32] {
poseidon_hash_4([
value.to_le_bytes().try_into()?,
pk_d,
blinding,
[0u8; 32], // padding
])
}
Nullifier Derivation
Nullifiers prevent double-spending:
pub fn compute_nullifier(
commitment: [u8; 32],
spending_key: [u8; 32],
) -> [u8; 32] {
poseidon_hash_2(commitment, spending_key)
}
Merkle Tree
Binary Merkle tree with Poseidon hashing:
pub fn compute_merkle_root(
leaves: &[[u8; 32]],
) -> [u8; 32]
pub fn verify_merkle_proof(
leaf: [u8; 32],
proof: &MerkleProof,
root: [u8; 32],
) -> bool
Circuit Architecture
Directory Structure
circuits/
├── circuits/
│ ├── main.circom # Entry point
│ ├── merkle.circom # Merkle proof verification
│ ├── poseidon.circom # Poseidon hash
│ └── transfer.circom # Transfer constraints
├── scripts/
│ ├── compile.sh # Compile circuits
│ └── setup.sh # Trusted setup
└── test/
└── transfer.test.js # Circuit tests
Transfer Circuit
The main circuit verifies private transfers:
Private Inputs:
value_a,value_b: Input note valuesblinding_a,blinding_b: Input blinding factorspath_a,path_b: Merkle proofssk: Spending key
Public Inputs:
nullifier_a,nullifier_b: Computed nullifierscommitment_c,commitment_d: Output commitmentsmerkle_root: Tree root
Constraints:
// 1. Verify input notes exist
merkle_verify(commitment_a, path_a, root) == 1
merkle_verify(commitment_b, path_b, root) == 1
// 2. Verify nullifiers
nullifier_a == poseidon(commitment_a, sk)
nullifier_b == poseidon(commitment_b, sk)
// 3. Verify value conservation
value_a + value_b == value_c + value_d
// 4. Verify output commitments
commitment_c == poseidon(value_c, pk_c, blinding_c)
commitment_d == poseidon(value_d, pk_d, blinding_d)
Circuit Metrics
| Circuit | Constraints | Proving Time | Proof Size |
|---|---|---|---|
| Shield | ~5,000 | ~1s | 192 bytes |
| Transfer (2-in-2-out) | ~50,000 | ~10s | 192 bytes |
| Unshield | ~25,000 | ~5s | 192 bytes |
Verification
On-Chain Verifier
The pallet-zk-verifier handles proof verification:
// From frame/zk-verifier/src/lib.rs
#[pallet::call]
impl<T: Config> Pallet<T> {
pub fn verify_proof(
origin: OriginFor<T>,
circuit_id: CircuitId,
proof: BoundedVec<u8, T::MaxProofSize>,
public_inputs: BoundedVec<[u8; 32], T::MaxPublicInputs>,
) -> DispatchResult
}
Verification Process
Verification Key Management
#[pallet::storage]
pub type VerificationKeys<T> = StorageMap<
_,
Blake2_128Concat,
CircuitId,
VerificationKey,
OptionQuery,
>;
Keys are registered by governance:
pub fn register_verification_key(
origin: OriginFor<T>,
circuit_id: CircuitId,
vk: VerificationKey,
) -> DispatchResult
Trusted Setup
Ceremony
Groth16 requires a trusted setup per circuit:
Setup Scripts
# Compile circuit
./scripts/compile.sh transfer
# Generate proving/verification keys
./scripts/setup.sh transfer
Security Requirements
| Requirement | Description |
|---|---|
| 1-of-N honest | At least one participant must be honest |
| Toxic waste | Random values must be destroyed |
| Verifiability | Setup can be audited |
WIP - Ceremony Pending
Production trusted setup ceremony has not been performed. Current keys are for testing only.
Performance
Benchmarks
| Operation | Time (Client) | Time (On-chain) |
|---|---|---|
| Proof generation | ~10s | N/A |
| Proof verification | ~15ms | ~50ms |
| Commitment creation | <1ms | <1ms |
| Merkle proof | <10ms | <10ms |
Optimization Targets
| Area | Current | Target |
|---|---|---|
| Proof generation | 10s | <5s |
| Circuit constraints | 50k | 30k |
| Verification gas | ~200k | ~150k |
Related Documentation
- Shielded Pool - How proofs are used
- EVM Compatibility - Precompile integration