inherence-verifier 0.1.0

Reference verifier for Inherence receipts (verification protocol v1).
Documentation
//! SPEC §6.6 — Groth16 proof verification.

use ark_bn254::{Bn254, Fr};
use ark_groth16::{Groth16, Proof, VerifyingKey, prepare_verifying_key};
use ark_serialize::CanonicalDeserialize;
use ark_snark::SNARK;
use base64::Engine;
use serde_json::Value;

use crate::binding;
use crate::error::VerifyError;
use crate::VerifyConfig;

/// If `cryptographic_proof.proof` is non-null, deserialize the
/// arkworks proof bytes, deserialize the vk pinned by
/// `public_inputs.vk_hash`, reconstruct the Fr public inputs per
/// SPEC §6.7, and run `Groth16::verify`.
pub fn verify_if_present(p: &Value, cfg: &VerifyConfig) -> Result<(), VerifyError> {
    let proof_evidence = p["verification"]["evidence"].as_array()
        .and_then(|arr| arr.iter().find(|e| e["type"] == "cryptographic_proof"))
        .ok_or_else(|| VerifyError::SchemaViolation("no cryptographic_proof evidence".into()))?;

    let proof_b64 = match proof_evidence.get("proof").and_then(|v| v.as_str()) {
        Some(s) if !s.is_empty() => s,
        _ => return Ok(()),  // ATTESTED with null proof — accept per §6.6 last para
    };

    let proof_bytes = base64::engine::general_purpose::STANDARD.decode(proof_b64)
        .map_err(|e| VerifyError::ProofInvalid(format!("proof not base64: {e}")))?;
    let proof = Proof::<Bn254>::deserialize_compressed(&proof_bytes[..])
        .or_else(|_| Proof::<Bn254>::deserialize_uncompressed(&proof_bytes[..]))
        .map_err(|e| VerifyError::ProofInvalid(format!("proof deserialize failed: {e}")))?;

    let vk_hash = p["verification"]["public_inputs"]["vk_hash"].as_str().unwrap_or("");
    let vk_bytes = cfg.lookup_vk(vk_hash)
        .ok_or_else(|| VerifyError::UnknownVk(vk_hash.to_string()))?;
    let vk = VerifyingKey::<Bn254>::deserialize_compressed(vk_bytes)
        .or_else(|_| VerifyingKey::<Bn254>::deserialize_uncompressed(vk_bytes))
        .map_err(|e| VerifyError::ProofInvalid(format!("vk deserialize failed: {e}")))?;

    let pi = &p["verification"]["public_inputs"];
    let action_hash = pi["action_hash"].as_str().unwrap_or("");
    let contract_hash = pi["contract_hash"].as_str().unwrap_or("");
    let decision_bit = pi["decision_bit"].as_i64().unwrap_or(-1) as u8;
    let inputs_fr: Vec<Fr> = binding::public_inputs_fr(
        action_hash, contract_hash, decision_bit, vk_hash,
    )?;

    let pvk = prepare_verifying_key(&vk);
    let ok = Groth16::<Bn254>::verify_with_processed_vk(&pvk, &inputs_fr, &proof)
        .map_err(|e| VerifyError::ProofInvalid(format!("verify call failed: {e}")))?;
    if !ok {
        return Err(VerifyError::ProofInvalid("verify returned false".into()));
    }
    Ok(())
}