Verifier Interface
Low-level cryptographic verification interface for integrating custom proof systems with ZKVAULT.
Interface Architecture
ZKVAULT provides a modular verifier interface that abstracts proof system specifics while maintaining cryptographic security guarantees.
Verifier Trait
pub trait ProofVerifier {
/// Verify a proof with public inputs
fn verify(
&self,
proof: &[u8],
public_inputs: &[u64],
verification_key: &VerificationKey,
) -> Result<bool, VerifierError>;
/// Get compute unit estimate for verification
fn compute_cost(&self, proof_size: usize, input_count: usize) -> u32;
/// Validate proof format without full verification
fn validate_format(&self, proof: &[u8]) -> Result<(), VerifierError>;
/// Get expected proof size for this system
fn expected_proof_size(&self) -> usize;
}Built-in Verifiers
Groth16Verifier
Optimized Groth16 verifier with BN254 pairing operations.
pub struct Groth16Verifier {
pairing_engine: BN254Engine,
curve_params: BN254Params,
}
impl Groth16Verifier {
pub fn new() -> Self {
Self {
pairing_engine: BN254Engine::new(),
curve_params: BN254Params::default(),
}
}
}
impl ProofVerifier for Groth16Verifier {
fn verify(
&self,
proof: &[u8],
public_inputs: &[u64],
vk: &VerificationKey,
) -> Result<bool, VerifierError> {
// Deserialize proof: (π_A, π_B, π_C)
let pi_a = G1Affine::deserialize(&proof[0..64])?;
let pi_b = G2Affine::deserialize(&proof[64..192])?;
let pi_c = G1Affine::deserialize(&proof[192..256])?;
// Prepare public input encoding
let vk_x = self.compute_input_contribution(public_inputs, &vk.ic)?;
// Pairing check: e(A, B) = e(α, β) · e(vk_x, γ) · e(C, δ)
let mut ml_inputs = Vec::new();
ml_inputs.push((pi_a, pi_b));
ml_inputs.push((vk.alpha_g1.neg(), vk.beta_g2));
ml_inputs.push((vk_x.neg(), vk.gamma_g2));
ml_inputs.push((pi_c.neg(), vk.delta_g2));
let result = self.pairing_engine.multi_miller_loop(&ml_inputs);
let result = self.pairing_engine.final_exponentiation(result);
Ok(result == Fq12::one())
}
fn compute_cost(&self, _proof_size: usize, input_count: usize) -> u32 {
// Base cost: 2 G1 deserializations + 1 G2 + pairing
let base = 45_000;
let per_input = 1_200; // G1 scalar multiplication per input
base + (per_input * input_count as u32)
}
}PLONKVerifier
PLONK verifier with Kate commitment verification and permutation checks.
pub struct PLONKVerifier {
srs: StructuredReferenceString,
domain: EvaluationDomain,
}
impl ProofVerifier for PLONKVerifier {
fn verify(
&self,
proof: &[u8],
public_inputs: &[u64],
vk: &VerificationKey,
) -> Result<bool, VerifierError> {
// Deserialize PLONK proof components
let proof = PLONKProof::deserialize(proof)?;
// Compute challenges using Fiat-Shamir
let mut transcript = Transcript::new(b"ZKVAULT-PLONK");
transcript.append_message(b"vk", &vk.to_bytes());
transcript.append_message(b"inputs", &encode_inputs(public_inputs));
let beta = transcript.challenge_scalar(b"beta");
let gamma = transcript.challenge_scalar(b"gamma");
let alpha = transcript.challenge_scalar(b"alpha");
let zeta = transcript.challenge_scalar(b"zeta");
// Verify opening proofs at zeta
self.verify_kate_opening(&proof.a_opening, &proof.a_commit, zeta)?;
self.verify_kate_opening(&proof.b_opening, &proof.b_commit, zeta)?;
self.verify_kate_opening(&proof.c_opening, &proof.c_commit, zeta)?;
// Verify permutation argument
self.verify_permutation(&proof, &vk, beta, gamma, alpha)?;
// Verify quotient polynomial
self.verify_quotient(&proof, &vk, zeta)?;
Ok(true)
}
}Custom Verifier Integration
Register custom proof systems by implementing the ProofVerifier trait.
Registration Process
// In your Solana program
use zkvault::verifier::{ProofVerifier, VerifierRegistry};
#[program]
pub mod my_custom_zk {
pub fn register_custom_verifier(ctx: Context<RegisterVerifier>) -> Result<()> {
let verifier = MyCustomVerifier::new();
let verifier_info = VerifierInfo {
name: "MyCustomZK".to_string(),
version: "1.0.0".to_string(),
proof_size: verifier.expected_proof_size(),
supported_curves: vec![Curve::BLS12_381],
};
VerifierRegistry::register(
ctx.accounts.registry,
verifier_info,
Box::new(verifier),
)?;
Ok(())
}
}
pub struct MyCustomVerifier {
// Custom verifier state
}
impl ProofVerifier for MyCustomVerifier {
fn verify(&self, proof: &[u8], inputs: &[u64], vk: &VerificationKey)
-> Result<bool, VerifierError> {
// Implement custom verification logic
todo!()
}
// Implement other required methods...
}Verification Key Management
VK Structure
#[account]
pub struct VerificationKey {
pub protocol: ProofProtocol, // Groth16, PLONK, Halo2, etc.
pub curve: Curve, // BN254, BLS12_381, etc.
pub data: Vec<u8>, // Serialized VK data
pub hash: [u8; 32], // VK commitment
pub upload_timestamp: i64,
pub authority: Pubkey,
}
pub enum ProofProtocol {
Groth16,
PLONK,
Halo2,
Marlin,
Custom(u8),
}
pub enum Curve {
BN254,
BLS12_381,
BLS12_377,
}VK Upload and Storage
pub fn upload_verification_key(
ctx: Context<UploadVK>,
vk_data: Vec<u8>,
protocol: ProofProtocol,
) -> Result<()> {
let vk = &mut ctx.accounts.verification_key;
// Compute hash for integrity
let hash = solana_program::keccak::hash(&vk_data);
// Validate VK format
match protocol {
ProofProtocol::Groth16 => {
require!(vk_data.len() == GROTH16_VK_SIZE, InvalidVKSize);
}
ProofProtocol::PLONK => {
require!(vk_data.len() >= PLONK_VK_MIN_SIZE, InvalidVKSize);
}
_ => {}
}
vk.protocol = protocol;
vk.data = vk_data;
vk.hash = hash.to_bytes();
vk.upload_timestamp = Clock::get()?.unix_timestamp;
vk.authority = ctx.accounts.authority.key();
Ok(())
}Pairing Engine Optimization
ZKVAULT includes optimized pairing implementations for Solana's BPF constraints.
BN254 Optimal Ate Pairing
pub struct BN254Engine {
precomputed_lines: Vec<LineFunction>,
}
impl BN254Engine {
/// Optimized Miller loop with precomputation
pub fn multi_miller_loop(&self, pairs: &[(G1Affine, G2Affine)]) -> Fq12 {
let mut f = Fq12::one();
// 6x + 2 loop for BN254
let loop_count = 6 * BN254_X + 2;
for i in (0..loop_count.bits()).rev() {
// Double step
f = f.square();
for (p, q) in pairs {
let line = self.double_line(q);
f *= self.evaluate_line(&line, p);
}
// Add step if bit is set
if loop_count.bit(i) {
for (p, q) in pairs {
let line = self.add_line(q);
f *= self.evaluate_line(&line, p);
}
}
}
f
}
/// Final exponentiation: f^((p^12 - 1) / r)
pub fn final_exponentiation(&self, f: Fq12) -> Fq12 {
// Easy part: f^(p^6 - 1)
let f_inv = f.inverse();
let f_p6 = f.frobenius_map(6);
let f_easy = f_p6 * f_inv;
// Hard part: f^((p^6 + 1) / r)
self.hard_exponentiation(f_easy)
}
}Compute Unit Budgeting
Verification operations must stay within Solana's compute budget.
CU Allocation Strategy
pub fn verify_with_cu_budget(
proof: &[u8],
inputs: &[u64],
vk: &VerificationKey,
max_cu: u32,
) -> Result<bool> {
// Estimate required CUs
let estimated_cu = estimate_verification_cost(proof, inputs, vk)?;
require!(estimated_cu <= max_cu, ExceedsComputeBudget);
// Request additional CUs if needed
if estimated_cu > 200_000 {
solana_program::compute_budget::request_units(estimated_cu)?;
}
// Execute verification
let verifier = get_verifier(vk.protocol)?;
verifier.verify(proof, inputs, vk)
}
fn estimate_verification_cost(
proof: &[u8],
inputs: &[u64],
vk: &VerificationKey,
) -> Result<u32> {
let base_cost = match vk.protocol {
ProofProtocol::Groth16 => 45_000,
ProofProtocol::PLONK => 68_000,
ProofProtocol::Halo2 => 92_000,
_ => 100_000,
};
let input_cost = inputs.len() as u32 * 1_200;
let pairing_cost = 15_000 * proof_pairing_count(vk.protocol);
Ok(base_cost + input_cost + pairing_cost)
}Error Handling
Verifier Errors
#[error_code]
pub enum VerifierError {
#[msg("Proof deserialization failed")]
InvalidProofFormat,
#[msg("Public input count mismatch")]
InputCountMismatch,
#[msg("Pairing check failed")]
PairingCheckFailed,
#[msg("Point not on curve")]
InvalidCurvePoint,
#[msg("Subgroup check failed")]
SubgroupCheckFailed,
#[msg("Unsupported proof protocol")]
UnsupportedProtocol,
#[msg("Verification key mismatch")]
VKMismatch,
#[msg("Exceeds compute budget")]
ExceedsComputeBudget,
}