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 JfPlonkProof {
pub wire_commits: Vec<G1Affine>,
pub prod_perm_poly_commit: G1Affine,
pub split_quot_poly_commits: Vec<G1Affine>,
pub opening_proof: G1Affine,
pub shifted_opening_proof: G1Affine,
pub poly_evals: Vec<Fr>,
pub public_inputs: Vec<Fr>,
}
impl JfPlonkProof {
pub fn new(
wire_commits: Vec<G1Affine>,
prod_perm_poly_commit: G1Affine,
split_quot_poly_commits: Vec<G1Affine>,
opening_proof: G1Affine,
shifted_opening_proof: G1Affine,
poly_evals: Vec<Fr>,
public_inputs: Vec<Fr>,
) -> Self {
Self {
wire_commits,
prod_perm_poly_commit,
split_quot_poly_commits,
opening_proof,
shifted_opening_proof,
poly_evals,
public_inputs,
}
}
pub fn mock(num_wires: usize, public_inputs: Vec<Fr>) -> Self {
let g1 = G1::generator().to_affine();
let g1_2 = (G1::generator() * Fr::from(2u64)).to_affine();
Self {
wire_commits: vec![g1; num_wires],
prod_perm_poly_commit: g1_2,
split_quot_poly_commits: vec![g1, g1, g1],
opening_proof: g1,
shifted_opening_proof: g1_2,
poly_evals: vec![Fr::from(1u64); num_wires + 1], public_inputs,
}
}
pub fn from_bytes(data: &[u8], num_wires: usize) -> Result<Self, AdapterError> {
const G1_SIZE: usize = 64;
const FR_SIZE: usize = 32;
let min_size = G1_SIZE * (num_wires + 5); if data.len() < min_size {
return Err(AdapterError::InvalidFormat(format!(
"Data too short for jf-plonk: {} < {}",
data.len(),
min_size
)));
}
let mut offset = 0;
let mut wire_commits = Vec::with_capacity(num_wires);
for i in 0..num_wires {
let wire = parsing::parse_g1_uncompressed(&data[offset..offset + G1_SIZE])
.map_err(|e| AdapterError::InvalidPoint(format!("wire[{}]: {}", i, e)))?;
wire_commits.push(wire);
offset += G1_SIZE;
}
let prod_perm_poly_commit = parsing::parse_g1_uncompressed(&data[offset..offset + G1_SIZE])
.map_err(|e| AdapterError::InvalidPoint(format!("perm: {}", e)))?;
offset += G1_SIZE;
let mut split_quot_poly_commits = Vec::with_capacity(3);
for i in 0..3 {
let quot = parsing::parse_g1_uncompressed(&data[offset..offset + G1_SIZE])
.map_err(|e| AdapterError::InvalidPoint(format!("quot[{}]: {}", i, e)))?;
split_quot_poly_commits.push(quot);
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!("shifted: {}", e)))?;
offset += G1_SIZE;
let mut poly_evals = Vec::new();
while offset + FR_SIZE <= data.len() {
let eval = parsing::parse_fr_bytes(&data[offset..offset + FR_SIZE])
.map_err(|e| AdapterError::ParseError(format!("eval: {}", e)))?;
poly_evals.push(eval);
offset += FR_SIZE;
}
Ok(Self {
wire_commits,
prod_perm_poly_commit,
split_quot_poly_commits,
opening_proof,
shifted_opening_proof,
poly_evals,
public_inputs: vec![],
})
}
pub fn from_jf_bytes(data: &[u8]) -> Result<Self, AdapterError> {
Self::from_bytes(data, 5)
}
pub fn with_public_inputs(mut self, inputs: Vec<Fr>) -> Self {
self.public_inputs = inputs;
self
}
}
impl ExternalProof<Bn254> for JfPlonkProof {
fn to_accumulator_instances(&self) -> Result<Vec<AccumulatorInstance<Bn254>>, AdapterError> {
if self.wire_commits.is_empty() {
return Err(AdapterError::InvalidFormat("No wire commitments".to_string()));
}
let mut instances = Vec::new();
let zeta = Fr::from(7u64);
for (i, wire_commit) in self.wire_commits.iter().enumerate() {
let eval = self.poly_evals.get(i)
.copied()
.ok_or_else(|| AdapterError::InvalidFormat(
format!("Missing evaluation for wire {}", i)
))?;
instances.push(AccumulatorInstance {
commitment: *wire_commit,
evaluation: eval,
point: zeta,
quotient: self.opening_proof,
});
}
let perm_eval = self.poly_evals.get(self.wire_commits.len())
.copied()
.ok_or_else(|| AdapterError::InvalidFormat(
"Missing permutation evaluation".to_string()
))?;
instances.push(AccumulatorInstance {
commitment: self.prod_perm_poly_commit,
evaluation: perm_eval,
point: zeta,
quotient: self.shifted_opening_proof,
});
Ok(instances)
}
fn public_inputs(&self) -> &[Fr] {
&self.public_inputs
}
fn metadata(&self) -> ProofMetadata {
ProofMetadata {
system: "jf-plonk",
proof_type: "ultraplonk",
curve: "bn254",
num_public_inputs: self.public_inputs.len(),
}
}
fn validate_format(&self) -> Result<(), AdapterError> {
use group::prime::PrimeCurveAffine;
if self.wire_commits.is_empty() {
return Err(AdapterError::InvalidFormat("No wire commitments".to_string()));
}
for (i, comm) in self.wire_commits.iter().enumerate() {
if comm.is_identity().into() {
return Err(AdapterError::InvalidPoint(format!(
"wire_commits[{}] is identity",
i
)));
}
}
if self.prod_perm_poly_commit.is_identity().into() {
return Err(AdapterError::InvalidPoint(
"prod_perm_poly_commit is identity".to_string(),
));
}
if self.opening_proof.is_identity().into() {
return Err(AdapterError::InvalidPoint("opening_proof is identity".to_string()));
}
if self.poly_evals.len() < self.wire_commits.len() {
return Err(AdapterError::InvalidFormat(format!(
"Not enough evaluations: {} < {}",
self.poly_evals.len(),
self.wire_commits.len()
)));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn jf_plonk_proof_creation() {
let proof = JfPlonkProof::mock(5, vec![Fr::from(42u64)]);
assert_eq!(proof.wire_commits.len(), 5);
assert_eq!(proof.public_inputs().len(), 1);
assert_eq!(proof.metadata().system, "jf-plonk");
assert_eq!(proof.metadata().proof_type, "ultraplonk");
}
#[test]
fn jf_plonk_to_accumulator() {
let proof = JfPlonkProof::mock(5, vec![Fr::from(100u64)]);
let instances = proof.to_accumulator_instances().unwrap();
assert_eq!(instances.len(), 6);
}
#[test]
fn jf_plonk_validate_format() {
let proof = JfPlonkProof::mock(3, vec![]);
assert!(proof.validate_format().is_ok());
}
#[test]
fn jf_plonk_validate_empty_wires_fails() {
let proof = JfPlonkProof::new(
vec![],
G1::generator().to_affine(),
vec![],
G1::generator().to_affine(),
G1::generator().to_affine(),
vec![],
vec![],
);
assert!(proof.validate_format().is_err());
}
#[test]
fn jf_plonk_with_public_inputs() {
let proof = JfPlonkProof::mock(3, vec![])
.with_public_inputs(vec![Fr::from(1u64), Fr::from(2u64)]);
assert_eq!(proof.public_inputs.len(), 2);
}
}