use std::marker::PhantomData;
use super::state::{Batched, Pending, ProofState, Verified};
use crate::error::Error;
use crate::registry::VkId;
use crate::traits::PairingEngine;
#[derive(Debug)]
pub struct Proof<E: PairingEngine, S: ProofState> {
data: Vec<u8>,
public_inputs: Vec<E::Fr>,
vk_id: VkId,
_state: PhantomData<S>,
}
impl<E: PairingEngine, S: ProofState> Proof<E, S> {
pub fn vk_id(&self) -> VkId {
self.vk_id
}
pub fn public_inputs(&self) -> &[E::Fr] {
&self.public_inputs
}
pub fn data(&self) -> &[u8] {
&self.data
}
}
impl<E: PairingEngine> Proof<E, Pending> {
pub fn new(data: Vec<u8>, public_inputs: Vec<E::Fr>, vk_id: VkId) -> Self {
Self {
data,
public_inputs,
vk_id,
_state: PhantomData,
}
}
pub fn verify(
self,
registry: &crate::registry::VkRegistry<E>,
) -> Result<Proof<E, Verified>, Error> {
use crate::crypto::{PlonkProof, PlonkVerifier};
let registered_vk = registry.require(self.vk_id)?;
if self.public_inputs.len() > registered_vk.vk.num_public_inputs {
return Err(Error::PublicInputMismatch {
expected: registered_vk.vk.num_public_inputs,
got: self.public_inputs.len(),
});
}
if self.data.is_empty() {
return Err(Error::VerificationFailed("empty proof data".into()));
}
let plonk_proof = PlonkProof::<E>::from_bytes(&self.data).map_err(|e| {
Error::VerificationFailed(format!("proof deserialization failed: {}", e))
})?;
let verifier = PlonkVerifier::new(registered_vk.vk.clone());
let is_valid = verifier
.verify(&plonk_proof, &self.public_inputs)
.map_err(|e| Error::VerificationFailed(format!("verification error: {}", e)))?;
if !is_valid {
return Err(Error::VerificationFailed("pairing check failed".into()));
}
Ok(Proof {
data: self.data,
public_inputs: self.public_inputs,
vk_id: self.vk_id,
_state: PhantomData,
})
}
}
impl<E: PairingEngine> Proof<E, Verified> {
#[allow(dead_code)]
pub(crate) fn new_verified(data: Vec<u8>, public_inputs: Vec<E::Fr>, vk_id: VkId) -> Self {
Self {
data,
public_inputs,
vk_id,
_state: PhantomData,
}
}
pub fn submit(self) -> Proof<E, Batched> {
Proof {
data: self.data,
public_inputs: self.public_inputs,
vk_id: self.vk_id,
_state: PhantomData,
}
}
}
impl<E: PairingEngine> Proof<E, super::state::Aggregated> {
pub(crate) fn new_aggregated(data: Vec<u8>, public_inputs: Vec<E::Fr>) -> Self {
Self {
data,
public_inputs,
vk_id: VkId::new(0), _state: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::bn254::Bn254;
use crate::crypto::VerificationKey;
use crate::registry::VkRegistry;
use group::{Curve, Group};
use halo2curves::bn256::{Fr, G1, G2};
use rand::rngs::OsRng;
fn mock_vk(num_public_inputs: usize) -> VerificationKey<Bn254> {
VerificationKey {
num_public_inputs,
domain_size: 1024,
selector_commitments: vec![
G1::random(OsRng).to_affine(),
G1::random(OsRng).to_affine(),
],
permutation_commitments: vec![G1::random(OsRng).to_affine()],
x_g2: G2::random(OsRng).to_affine(),
g2_generator: G2::generator().to_affine(),
}
}
fn mock_proof_data() -> Vec<u8> {
use crate::crypto::{PlonkProof, ProofEvaluations};
use ff::Field;
let proof = PlonkProof::<Bn254> {
wire_commitments: [
G1::random(OsRng).to_affine(),
G1::random(OsRng).to_affine(),
G1::random(OsRng).to_affine(),
],
z_commitment: G1::random(OsRng).to_affine(),
t_commitments: vec![
G1::random(OsRng).to_affine(),
G1::random(OsRng).to_affine(),
G1::random(OsRng).to_affine(),
],
opening_proof: G1::random(OsRng).to_affine(),
shifted_opening_proof: G1::random(OsRng).to_affine(),
evaluations: ProofEvaluations {
a_eval: Fr::random(OsRng),
b_eval: Fr::random(OsRng),
c_eval: Fr::random(OsRng),
s1_eval: Fr::random(OsRng),
s2_eval: Fr::random(OsRng),
z_shifted_eval: Fr::random(OsRng),
},
};
proof.to_bytes()
}
#[test]
fn proof_is_generic_over_state() {
fn _accepts_pending<E: PairingEngine>(_: &Proof<E, Pending>) {}
fn _accepts_verified<E: PairingEngine>(_: &Proof<E, Verified>) {}
fn _accepts_batched<E: PairingEngine>(_: &Proof<E, Batched>) {}
}
#[test]
fn state_transitions_consume_proof() {
fn _verify_consumes<E: PairingEngine>(p: Proof<E, Pending>) {
let _ = p;
}
fn _submit_consumes<E: PairingEngine>(p: Proof<E, Verified>) {
let _ = p;
}
}
#[test]
fn verify_rejects_unknown_vk() {
let registry = VkRegistry::<Bn254>::new();
let proof =
Proof::<Bn254, Pending>::new(mock_proof_data(), vec![Fr::from(1u64)], VkId::new(999));
let result = proof.verify(®istry);
assert!(matches!(result, Err(Error::UnknownVk(_))));
}
#[test]
fn verify_rejects_too_many_public_inputs() {
let registry = VkRegistry::<Bn254>::new();
let vk_id = registry.register("test", mock_vk(2));
let proof = Proof::<Bn254, Pending>::new(
mock_proof_data(),
vec![Fr::from(1u64), Fr::from(2u64), Fr::from(3u64)], vk_id,
);
let result = proof.verify(®istry);
assert!(matches!(
result,
Err(Error::PublicInputMismatch {
expected: 2,
got: 3
})
));
}
#[test]
fn verify_rejects_empty_proof() {
let registry = VkRegistry::<Bn254>::new();
let vk_id = registry.register("test", mock_vk(5));
let proof = Proof::<Bn254, Pending>::new(
vec![], vec![Fr::from(1u64)],
vk_id,
);
let result = proof.verify(®istry);
assert!(matches!(result, Err(Error::VerificationFailed(_))));
}
#[test]
fn verify_accepts_valid_proof() {
let registry = VkRegistry::<Bn254>::new();
let vk_id = registry.register("test", mock_vk(2));
let proof = Proof::<Bn254, Pending>::new(
mock_proof_data(), vec![Fr::from(1u64), Fr::from(2u64)], vk_id,
);
let verified = proof.verify(®istry).expect("should verify");
assert_eq!(verified.vk_id(), vk_id);
assert_eq!(verified.public_inputs().len(), 2);
}
#[test]
fn verify_allows_exact_max_public_inputs() {
let registry = VkRegistry::<Bn254>::new();
let vk_id = registry.register("test", mock_vk(3));
let proof = Proof::<Bn254, Pending>::new(
mock_proof_data(),
vec![Fr::from(1u64), Fr::from(2u64), Fr::from(3u64)], vk_id,
);
let result = proof.verify(®istry);
assert!(result.is_ok()); }
}