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,
}