use super::external::{AdapterError, ExternalProof, ProofMetadata};
use super::parsing;
use crate::backend::bn254::Bn254;
use crate::crypto::AccumulatorInstance;
use group::Curve;
use halo2curves::bn256::{Fr, G1Affine, G1};
#[derive(Debug, Clone)]
pub struct GnarkPlonkProof {
pub lro: [G1Affine; 3],
pub z: G1Affine,
pub h: [G1Affine; 3],
pub linearized_commitment: G1Affine,
pub opening_proof: G1Affine,
pub shifted_opening_proof: G1Affine,
pub evaluations: Vec<Fr>,
pub public_inputs: Vec<Fr>,
}
impl GnarkPlonkProof {
#[allow(clippy::too_many_arguments)]
pub fn new(
lro: [G1Affine; 3],
z: G1Affine,
h: [G1Affine; 3],
linearized_commitment: G1Affine,
opening_proof: G1Affine,
shifted_opening_proof: G1Affine,
evaluations: Vec<Fr>,
public_inputs: Vec<Fr>,
) -> Self {
Self {
lro,
z,
h,
linearized_commitment,
opening_proof,
shifted_opening_proof,
evaluations,
public_inputs,
}
}
pub fn mock(public_inputs: Vec<Fr>) -> Self {
let g1 = G1::generator().to_affine();
let g1_2 = (G1::generator() * Fr::from(2u64)).to_affine();
Self {
lro: [g1, g1_2, g1],
z: g1_2,
h: [g1, g1, g1],
linearized_commitment: g1_2,
opening_proof: g1,
shifted_opening_proof: g1_2,
evaluations: vec![Fr::from(1u64), Fr::from(2u64), Fr::from(3u64)],
public_inputs,
}
}
pub fn from_bytes(data: &[u8]) -> Result<Self, AdapterError> {
const G1_SIZE: usize = 64;
const MIN_SIZE: usize = G1_SIZE * 10;
if data.len() < MIN_SIZE {
return Err(AdapterError::InvalidFormat(format!(
"Data too short: {} < {}",
data.len(),
MIN_SIZE
)));
}
let mut offset = 0;
let mut lro = [G1::generator().to_affine(); 3];
for point in lro.iter_mut() {
*point = parsing::parse_g1_uncompressed(&data[offset..offset + G1_SIZE])
.map_err(|e| AdapterError::InvalidPoint(format!("LRO: {}", e)))?;
offset += G1_SIZE;
}
let z = parsing::parse_g1_uncompressed(&data[offset..offset + G1_SIZE])
.map_err(|e| AdapterError::InvalidPoint(format!("Z: {}", e)))?;
offset += G1_SIZE;
let mut h = [G1::generator().to_affine(); 3];
for point in h.iter_mut() {
*point = parsing::parse_g1_uncompressed(&data[offset..offset + G1_SIZE])
.map_err(|e| AdapterError::InvalidPoint(format!("H: {}", e)))?;
offset += G1_SIZE;
}
let linearized_commitment = parsing::parse_g1_uncompressed(&data[offset..offset + G1_SIZE])
.map_err(|e| AdapterError::InvalidPoint(format!("Linearized: {}", e)))?;
offset += G1_SIZE;
let opening_proof = parsing::parse_g1_uncompressed(&data[offset..offset + G1_SIZE])
.map_err(|e| AdapterError::InvalidPoint(format!("Opening: {}", e)))?;
offset += G1_SIZE;
let shifted_opening_proof = parsing::parse_g1_uncompressed(&data[offset..offset + G1_SIZE])
.map_err(|e| AdapterError::InvalidPoint(format!("ShiftedOpening: {}", e)))?;
offset += G1_SIZE;
let mut evaluations = Vec::new();
while offset + 32 <= data.len() {
let eval = parsing::parse_fr_bytes(&data[offset..offset + 32])
.map_err(|e| AdapterError::ParseError(format!("Evaluation: {}", e)))?;
evaluations.push(eval);
offset += 32;
}
Ok(Self {
lro,
z,
h,
linearized_commitment,
opening_proof,
shifted_opening_proof,
evaluations,
public_inputs: vec![],
})
}
pub fn with_public_inputs(mut self, inputs: Vec<Fr>) -> Self {
self.public_inputs = inputs;
self
}
}
impl ExternalProof<Bn254> for GnarkPlonkProof {
fn to_accumulator_instances(&self) -> Result<Vec<AccumulatorInstance<Bn254>>, AdapterError> {
let mut instances = Vec::new();
let zeta = Fr::from(7u64);
for (i, wire) in self.lro.iter().enumerate() {
let eval = self.evaluations.get(i).copied().unwrap_or(Fr::from(1u64));
instances.push(AccumulatorInstance {
commitment: *wire,
evaluation: eval,
point: zeta,
quotient: self.opening_proof,
});
}
instances.push(AccumulatorInstance {
commitment: self.z,
evaluation: self.evaluations.get(3).copied().unwrap_or(Fr::from(1u64)),
point: zeta,
quotient: self.shifted_opening_proof,
});
Ok(instances)
}
fn public_inputs(&self) -> &[Fr] {
&self.public_inputs
}
fn metadata(&self) -> ProofMetadata {
ProofMetadata {
system: "gnark",
proof_type: "plonk",
curve: "bn254",
num_public_inputs: self.public_inputs.len(),
}
}
fn validate_format(&self) -> Result<(), AdapterError> {
use group::prime::PrimeCurveAffine;
for (i, comm) in self.lro.iter().enumerate() {
if comm.is_identity().into() {
return Err(AdapterError::InvalidPoint(format!("lro[{}] is identity", i)));
}
}
if self.z.is_identity().into() {
return Err(AdapterError::InvalidPoint("z is identity".to_string()));
}
if self.opening_proof.is_identity().into() {
return Err(AdapterError::InvalidPoint("opening_proof is identity".to_string()));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gnark_proof_creation() {
let proof = GnarkPlonkProof::mock(vec![Fr::from(42u64)]);
assert_eq!(proof.public_inputs().len(), 1);
assert_eq!(proof.metadata().system, "gnark");
assert_eq!(proof.metadata().proof_type, "plonk");
}
#[test]
fn gnark_to_accumulator() {
let proof = GnarkPlonkProof::mock(vec![Fr::from(100u64)]);
let instances = proof.to_accumulator_instances().unwrap();
assert_eq!(instances.len(), 4); }
#[test]
fn gnark_validate_format() {
let proof = GnarkPlonkProof::mock(vec![]);
assert!(proof.validate_format().is_ok());
}
#[test]
fn gnark_with_evaluations() {
let mut proof = GnarkPlonkProof::mock(vec![]);
proof.evaluations = vec![Fr::from(10u64), Fr::from(20u64), Fr::from(30u64), Fr::from(40u64)];
let instances = proof.to_accumulator_instances().unwrap();
assert_eq!(instances[0].evaluation, Fr::from(10u64));
assert_eq!(instances[3].evaluation, Fr::from(40u64));
}
}