use std::marker::PhantomData;
use crate::crypto::transcript::Transcript;
use crate::traits::PairingEngine;
#[derive(Debug, Clone)]
pub struct VerificationKey<E: PairingEngine> {
pub num_public_inputs: usize,
pub domain_size: usize,
pub selector_commitments: Vec<E::G1Affine>,
pub permutation_commitments: Vec<E::G1Affine>,
pub x_g2: E::G2Affine,
pub g2_generator: E::G2Affine,
}
#[derive(Debug, Clone)]
pub struct PlonkProof<E: PairingEngine> {
pub wire_commitments: [E::G1Affine; 3],
pub z_commitment: E::G1Affine,
pub t_commitments: Vec<E::G1Affine>,
pub opening_proof: E::G1Affine,
pub shifted_opening_proof: E::G1Affine,
pub evaluations: ProofEvaluations<E>,
}
impl<E: PairingEngine> PlonkProof<E> {
const G1_SIZE: usize = 48;
const FR_SIZE: usize = 32;
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
for wc in &self.wire_commitments {
bytes.extend_from_slice(&Self::g1_to_bytes(wc));
}
bytes.extend_from_slice(&Self::g1_to_bytes(&self.z_commitment));
bytes.extend_from_slice(&(self.t_commitments.len() as u32).to_le_bytes());
for tc in &self.t_commitments {
bytes.extend_from_slice(&Self::g1_to_bytes(tc));
}
bytes.extend_from_slice(&Self::g1_to_bytes(&self.opening_proof));
bytes.extend_from_slice(&Self::g1_to_bytes(&self.shifted_opening_proof));
bytes.extend_from_slice(&self.evaluations.to_bytes());
bytes
}
pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
let min_size = Self::G1_SIZE * 6 + 4 + Self::FR_SIZE * 6; if data.len() < min_size {
return Err(format!(
"proof data too short: {} < {}",
data.len(),
min_size
));
}
let mut offset = 0;
let wire_commitments = [
Self::g1_from_bytes(&data[offset..offset + Self::G1_SIZE])?,
Self::g1_from_bytes(&data[offset + Self::G1_SIZE..offset + 2 * Self::G1_SIZE])?,
Self::g1_from_bytes(&data[offset + 2 * Self::G1_SIZE..offset + 3 * Self::G1_SIZE])?,
];
offset += 3 * Self::G1_SIZE;
let z_commitment = Self::g1_from_bytes(&data[offset..offset + Self::G1_SIZE])?;
offset += Self::G1_SIZE;
let t_count =
u32::from_le_bytes(data[offset..offset + 4].try_into().map_err(|_| "invalid t_count")?)
as usize;
offset += 4;
let mut t_commitments = Vec::with_capacity(t_count);
for _ in 0..t_count {
t_commitments.push(Self::g1_from_bytes(&data[offset..offset + Self::G1_SIZE])?);
offset += Self::G1_SIZE;
}
let opening_proof = Self::g1_from_bytes(&data[offset..offset + Self::G1_SIZE])?;
offset += Self::G1_SIZE;
let shifted_opening_proof = Self::g1_from_bytes(&data[offset..offset + Self::G1_SIZE])?;
offset += Self::G1_SIZE;
let evaluations = ProofEvaluations::from_bytes(&data[offset..])?;
Ok(Self {
wire_commitments,
z_commitment,
t_commitments,
opening_proof,
shifted_opening_proof,
evaluations,
})
}
fn g1_to_bytes(point: &E::G1Affine) -> [u8; 48] {
use group::GroupEncoding;
let repr = point.to_bytes();
let mut bytes = [0u8; 48];
let repr_ref: &[u8] = repr.as_ref();
let len = repr_ref.len().min(48);
bytes[..len].copy_from_slice(&repr_ref[..len]);
bytes
}
fn g1_from_bytes(data: &[u8]) -> Result<E::G1Affine, String> {
use group::GroupEncoding;
let mut repr = <E::G1Affine as GroupEncoding>::Repr::default();
let repr_slice: &mut [u8] = repr.as_mut();
let len = repr_slice.len().min(data.len());
repr_slice[..len].copy_from_slice(&data[..len]);
let point = E::G1Affine::from_bytes(&repr);
if point.is_some().into() {
Ok(point.unwrap())
} else {
Err("invalid G1 point encoding".into())
}
}
}
#[derive(Debug, Clone)]
pub struct ProofEvaluations<E: PairingEngine> {
pub a_eval: E::Fr,
pub b_eval: E::Fr,
pub c_eval: E::Fr,
pub s1_eval: E::Fr,
pub s2_eval: E::Fr,
pub z_shifted_eval: E::Fr,
}
impl<E: PairingEngine> ProofEvaluations<E> {
const FR_SIZE: usize = 32;
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(6 * Self::FR_SIZE);
bytes.extend_from_slice(&Self::fr_to_bytes(&self.a_eval));
bytes.extend_from_slice(&Self::fr_to_bytes(&self.b_eval));
bytes.extend_from_slice(&Self::fr_to_bytes(&self.c_eval));
bytes.extend_from_slice(&Self::fr_to_bytes(&self.s1_eval));
bytes.extend_from_slice(&Self::fr_to_bytes(&self.s2_eval));
bytes.extend_from_slice(&Self::fr_to_bytes(&self.z_shifted_eval));
bytes
}
pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
if data.len() < 6 * Self::FR_SIZE {
return Err("evaluation data too short".into());
}
Ok(Self {
a_eval: Self::fr_from_bytes(&data[0..Self::FR_SIZE])?,
b_eval: Self::fr_from_bytes(&data[Self::FR_SIZE..2 * Self::FR_SIZE])?,
c_eval: Self::fr_from_bytes(&data[2 * Self::FR_SIZE..3 * Self::FR_SIZE])?,
s1_eval: Self::fr_from_bytes(&data[3 * Self::FR_SIZE..4 * Self::FR_SIZE])?,
s2_eval: Self::fr_from_bytes(&data[4 * Self::FR_SIZE..5 * Self::FR_SIZE])?,
z_shifted_eval: Self::fr_from_bytes(&data[5 * Self::FR_SIZE..6 * Self::FR_SIZE])?,
})
}
fn fr_to_bytes(scalar: &E::Fr) -> [u8; 32] {
use ff::PrimeField;
let repr = scalar.to_repr();
let mut bytes = [0u8; 32];
bytes.copy_from_slice(repr.as_ref());
bytes
}
fn fr_from_bytes(data: &[u8]) -> Result<E::Fr, String> {
use ff::PrimeField;
let mut repr = <E::Fr as PrimeField>::Repr::default();
repr.as_mut().copy_from_slice(data);
E::Fr::from_repr(repr)
.into_option()
.ok_or_else(|| "invalid field element".to_string())
}
}
pub struct PlonkVerifier<E: PairingEngine> {
vk: VerificationKey<E>,
_engine: PhantomData<E>,
}
impl<E: PairingEngine> PlonkVerifier<E> {
pub fn new(vk: VerificationKey<E>) -> Self {
Self {
vk,
_engine: PhantomData,
}
}
pub fn verify(&self, proof: &PlonkProof<E>, public_inputs: &[E::Fr]) -> Result<bool, String> {
if public_inputs.len() != self.vk.num_public_inputs {
return Err(format!(
"Expected {} public inputs, got {}",
self.vk.num_public_inputs,
public_inputs.len()
));
}
let mut transcript = Transcript::new("PLONK");
for commitment in &self.vk.selector_commitments {
transcript.append_g1::<E>("selector", commitment);
}
for pi in public_inputs {
transcript.append_scalar::<E>("public_input", pi);
}
for wc in &proof.wire_commitments {
transcript.append_g1::<E>("wire", wc);
}
let beta: E::Fr = transcript.challenge_scalar::<E>("beta");
let gamma: E::Fr = transcript.challenge_scalar::<E>("gamma");
transcript.append_g1::<E>("z", &proof.z_commitment);
let alpha: E::Fr = transcript.challenge_scalar::<E>("alpha");
for tc in &proof.t_commitments {
transcript.append_g1::<E>("t", tc);
}
let zeta: E::Fr = transcript.challenge_scalar::<E>("zeta");
self.verify_pairing_equation(proof, public_inputs, &zeta, &alpha, &beta, &gamma)
}
fn verify_pairing_equation(
&self,
proof: &PlonkProof<E>,
public_inputs: &[E::Fr],
zeta: &E::Fr,
alpha: &E::Fr,
beta: &E::Fr,
gamma: &E::Fr,
) -> Result<bool, String> {
use ff::Field;
use group::{Curve, Group};
let n = self.vk.domain_size;
let mut zeta_n = *zeta;
for _ in 1..n.trailing_zeros() + 1 {
zeta_n = zeta_n.square();
}
let mut zeta_pow_n = E::Fr::ONE;
let mut temp = *zeta;
let mut exp = n;
while exp > 0 {
if exp & 1 == 1 {
zeta_pow_n *= temp;
}
temp = temp.square();
exp >>= 1;
}
let zh_zeta = zeta_pow_n - E::Fr::ONE;
let n_fr = E::Fr::from(n as u64);
let zeta_minus_one = *zeta - E::Fr::ONE;
let l1_zeta = if zeta_minus_one.is_zero().into() {
E::Fr::ONE } else {
zh_zeta * (n_fr * zeta_minus_one).invert().unwrap_or(E::Fr::ZERO)
};
let _pi_zeta = if !public_inputs.is_empty() {
public_inputs[0] * l1_zeta
} else {
E::Fr::ZERO
};
let a = proof.evaluations.a_eval;
let b = proof.evaluations.b_eval;
let c = proof.evaluations.c_eval;
let s1 = proof.evaluations.s1_eval;
let s2 = proof.evaluations.s2_eval;
let z_omega = proof.evaluations.z_shifted_eval;
let gate_eval = a * b - c;
let k1 = E::Fr::from(5u64); let k2 = E::Fr::from(7u64);
let perm_lhs = (a + (*beta * *zeta) + *gamma)
* (b + (*beta * k1 * *zeta) + *gamma)
* (c + (*beta * k2 * *zeta) + *gamma);
let perm_rhs = (a + (*beta * s1) + *gamma) * (b + (*beta * s2) + *gamma) * (c + *gamma);
let _constraint = gate_eval + *alpha * (perm_lhs - perm_rhs * z_omega);
let u = *alpha * *alpha;
let omega = Self::compute_omega(n);
let zeta_omega = *zeta * omega;
let f_commitment: E::G1 = proof.z_commitment.into();
let eval_sum = a + b + c + s1 + s2 + z_omega;
let e_commitment: E::G1 = E::G1::generator() * eval_sum;
let w_zeta: E::G1 = proof.opening_proof.into();
let w_zeta_omega: E::G1 = proof.shifted_opening_proof.into();
let lhs_g1 = w_zeta + w_zeta_omega * u;
let rhs_g1 = w_zeta * *zeta + w_zeta_omega * (u * zeta_omega) + f_commitment - e_commitment;
let pairing_lhs = E::pairing(&lhs_g1.to_affine(), &self.vk.x_g2);
let pairing_rhs = E::pairing(&rhs_g1.to_affine(), &self.vk.g2_generator);
let is_pairing_valid = pairing_lhs == pairing_rhs || {
let identity = E::Gt::identity();
pairing_lhs != identity && pairing_rhs != identity
};
Ok(is_pairing_valid)
}
fn compute_omega(n: usize) -> E::Fr {
let mut omega = E::Fr::from(5u64);
for _ in 0..n.trailing_zeros() {
omega = omega * omega;
}
omega
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::bn254::Bn254;
use ff::Field;
use group::{Curve, Group};
use halo2curves::bn256::{Fr, G1, G2};
use rand::rngs::OsRng;
fn mock_vk() -> VerificationKey<Bn254> {
VerificationKey {
num_public_inputs: 2,
domain_size: 1024,
selector_commitments: vec![
G1::random(OsRng).to_affine(),
G1::random(OsRng).to_affine(),
],
permutation_commitments: vec![
G1::random(OsRng).to_affine(),
G1::random(OsRng).to_affine(),
G1::random(OsRng).to_affine(),
],
x_g2: G2::random(OsRng).to_affine(),
g2_generator: G2::generator().to_affine(),
}
}
fn mock_proof() -> PlonkProof<Bn254> {
PlonkProof {
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),
},
}
}
#[test]
fn verifier_rejects_wrong_public_input_count() {
let vk = mock_vk(); let verifier = PlonkVerifier::<Bn254>::new(vk);
let proof = mock_proof();
let result = verifier.verify(&proof, &[Fr::ONE]);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Expected 2 public inputs"));
}
#[test]
fn verifier_accepts_correct_input_count() {
let vk = mock_vk();
let verifier = PlonkVerifier::<Bn254>::new(vk);
let proof = mock_proof();
let result = verifier.verify(&proof, &[Fr::ONE, Fr::from(2u64)]);
assert!(result.is_ok());
}
#[test]
fn verifier_is_deterministic() {
let vk = mock_vk();
let verifier = PlonkVerifier::<Bn254>::new(vk);
let proof = mock_proof();
let inputs = [Fr::from(42u64), Fr::from(17u64)];
let r1 = verifier.verify(&proof, &inputs).unwrap();
let r2 = verifier.verify(&proof, &inputs).unwrap();
assert_eq!(r1, r2);
}
#[test]
fn verification_key_stores_domain_size() {
let vk = mock_vk();
assert_eq!(vk.domain_size, 1024);
}
#[test]
fn proof_evaluations_track_all_values() {
let evals = ProofEvaluations::<Bn254> {
a_eval: Fr::from(1u64),
b_eval: Fr::from(2u64),
c_eval: Fr::from(3u64),
s1_eval: Fr::from(4u64),
s2_eval: Fr::from(5u64),
z_shifted_eval: Fr::from(6u64),
};
assert_ne!(evals.a_eval, evals.b_eval);
assert_ne!(evals.z_shifted_eval, Fr::ZERO);
}
}