use ark_ec::pairing::Pairing;
use ark_ff::PrimeField;
use ark_poly::{Polynomial, univariate::DensePolynomial};
use ark_serde_compat::CheckElement;
use ark_serialize::CanonicalDeserialize;
use std::io::{Cursor, Read};
use crate::{
binfile::{BinFile, ZkeyParserError, ZkeyParserResult},
traits::CircomArkworksPairingBridge,
};
macro_rules! u32_to_usize {
($x: expr) => {
usize::try_from($x).expect("u32 fits into usize")
};
}
#[derive(Clone)]
pub struct Zkey<P: Pairing> {
pub n_vars: usize,
pub n_public: usize,
pub domain_size: usize,
pub pow: usize,
pub n_additions: usize,
pub n_constraints: usize,
pub verifying_key: VerifyingKey<P>,
pub additions: Vec<Additions<P>>,
pub map_a: Vec<usize>,
pub map_b: Vec<usize>,
pub map_c: Vec<usize>,
pub qm_poly: CircomPolynomial<P::ScalarField>,
pub ql_poly: CircomPolynomial<P::ScalarField>,
pub qr_poly: CircomPolynomial<P::ScalarField>,
pub qo_poly: CircomPolynomial<P::ScalarField>,
pub qc_poly: CircomPolynomial<P::ScalarField>,
pub s1_poly: CircomPolynomial<P::ScalarField>,
pub s2_poly: CircomPolynomial<P::ScalarField>,
pub s3_poly: CircomPolynomial<P::ScalarField>,
pub lagrange: Vec<CircomPolynomial<P::ScalarField>>,
pub p_tau: Vec<P::G1Affine>,
}
#[derive(Clone)]
pub struct CircomPolynomial<F: PrimeField> {
pub coeffs: DensePolynomial<F>,
pub evaluations: Vec<F>,
}
impl<F: PrimeField> CircomPolynomial<F> {
pub fn evaluate(&self, point: &F) -> F {
self.coeffs.evaluate(point)
}
}
#[derive(Clone)]
pub struct Additions<P: Pairing> {
pub signal_id1: u32,
pub signal_id2: u32,
pub factor1: P::ScalarField,
pub factor2: P::ScalarField,
}
#[derive(Default, Clone, Debug)]
pub struct VerifyingKey<P: Pairing> {
pub k1: P::ScalarField,
pub k2: P::ScalarField,
pub qm: P::G1Affine,
pub ql: P::G1Affine,
pub qr: P::G1Affine,
pub qo: P::G1Affine,
pub qc: P::G1Affine,
pub s1: P::G1Affine,
pub s2: P::G1Affine,
pub s3: P::G1Affine,
pub x_2: P::G2Affine,
}
impl<P: Pairing + CircomArkworksPairingBridge> Zkey<P> {
pub fn from_reader<R: Read>(mut reader: R, check: CheckElement) -> ZkeyParserResult<Self> {
let mut binfile = BinFile::<P>::new(&mut reader)?;
tracing::debug!("start transforming bin file into zkey...");
let header = PlonkHeader::<P>::read(&mut binfile.take_section(2))?;
let n_vars = header.n_vars;
let n_additions = header.n_additions;
let n_constraints = header.n_constraints;
let n_public = header.n_public;
let domain_size = header.domain_size;
let sigma_section_size = domain_size * header.n8r + domain_size * 4 * header.n8r;
let add_section = binfile.take_section(3);
let a_section = binfile.take_section(4);
let b_section = binfile.take_section(5);
let c_section = binfile.take_section(6);
let qm_section = binfile.take_section(7);
let ql_section = binfile.take_section(8);
let qr_section = binfile.take_section(9);
let q0_section = binfile.take_section(10);
let qc_section = binfile.take_section(11);
let sigma_sections = binfile.take_section_raw(12);
let l_section = binfile.take_section(13);
let t_section = binfile.take_section(14);
let sigma1_section = Cursor::new(&sigma_sections[..sigma_section_size]);
let sigma2_section =
Cursor::new(&sigma_sections[sigma_section_size..sigma_section_size * 2]);
let sigma3_section = Cursor::new(&sigma_sections[sigma_section_size * 2..]);
let mut additions = None;
let mut map_a = None;
let mut map_b = None;
let mut map_c = None;
let mut qm = None;
let mut ql = None;
let mut qr = None;
let mut q0 = None;
let mut qc = None;
let mut sigma1 = None;
let mut sigma2 = None;
let mut sigma3 = None;
let mut lagrange = None;
let mut p_tau = None;
tracing::debug!("parsing zkey sections...");
std::thread::scope(|s| {
s.spawn(|| additions = Some(Self::additions_indices(n_additions, add_section)));
s.spawn(|| map_a = Some(Self::id_map(n_constraints, a_section)));
s.spawn(|| map_b = Some(Self::id_map(n_constraints, b_section)));
s.spawn(|| map_c = Some(Self::id_map(n_constraints, c_section)));
s.spawn(|| qm = Some(Self::evaluations(domain_size, qm_section)));
s.spawn(|| ql = Some(Self::evaluations(domain_size, ql_section)));
s.spawn(|| qr = Some(Self::evaluations(domain_size, qr_section)));
s.spawn(|| q0 = Some(Self::evaluations(domain_size, q0_section)));
s.spawn(|| qc = Some(Self::evaluations(domain_size, qc_section)));
s.spawn(|| sigma1 = Some(Self::evaluations(domain_size, sigma1_section)));
s.spawn(|| sigma2 = Some(Self::evaluations(domain_size, sigma2_section)));
s.spawn(|| sigma3 = Some(Self::evaluations(domain_size, sigma3_section)));
s.spawn(|| lagrange = Some(Self::lagrange(n_public, domain_size, l_section)));
s.spawn(|| p_tau = Some(Self::taus(domain_size, t_section, check)));
});
tracing::debug!("we are done with parsing sections!");
Ok(Self {
n_vars,
n_public,
domain_size,
pow: header.power,
n_additions,
n_constraints,
verifying_key: header.verifying_key,
additions: additions.unwrap()?,
map_a: map_a.unwrap()?,
map_b: map_b.unwrap()?,
map_c: map_c.unwrap()?,
qm_poly: qm.unwrap()?,
ql_poly: ql.unwrap()?,
qr_poly: qr.unwrap()?,
qo_poly: q0.unwrap()?,
qc_poly: qc.unwrap()?,
s1_poly: sigma1.unwrap()?,
s2_poly: sigma2.unwrap()?,
s3_poly: sigma3.unwrap()?,
lagrange: lagrange.unwrap()?,
p_tau: p_tau.unwrap()?,
})
}
fn additions_indices<R: Read>(
n_additions: usize,
mut reader: R,
) -> ZkeyParserResult<Vec<Additions<P>>> {
let mut additions = Vec::with_capacity(n_additions);
for _ in 0..n_additions {
let signal_id1 = u32::deserialize_uncompressed(&mut reader)?;
let signal_id2 = u32::deserialize_uncompressed(&mut reader)?;
let factor1 = P::fr_from_montgomery_reader(&mut reader)?;
let factor2 = P::fr_from_montgomery_reader(&mut reader)?;
additions.push(Additions {
signal_id1,
signal_id2,
factor1,
factor2,
})
}
Ok(additions)
}
fn id_map<R: Read>(n_constraints: usize, mut reader: R) -> ZkeyParserResult<Vec<usize>> {
let mut map = Vec::with_capacity(n_constraints);
for _ in 0..n_constraints {
map.push(u32_to_usize!(u32::deserialize_uncompressed(&mut reader)?));
}
Ok(map)
}
fn evaluations<R: Read>(
domain_size: usize,
mut reader: R,
) -> ZkeyParserResult<CircomPolynomial<P::ScalarField>> {
let mut coeffs = Vec::with_capacity(domain_size);
for _ in 0..domain_size {
coeffs.push(P::fr_from_montgomery_reader(&mut reader)?);
}
let mut evaluations = Vec::with_capacity(domain_size * 4);
for _ in 0..domain_size * 4 {
evaluations.push(P::fr_from_montgomery_reader(&mut reader)?);
}
Ok(CircomPolynomial {
coeffs: DensePolynomial { coeffs },
evaluations,
})
}
fn lagrange<R: Read>(
n_public: usize,
domain_size: usize,
mut reader: R,
) -> ZkeyParserResult<Vec<CircomPolynomial<P::ScalarField>>> {
let mut lagrange = Vec::with_capacity(n_public);
for _ in 0..n_public {
lagrange.push(Self::evaluations(domain_size, &mut reader)?);
}
Ok(lagrange)
}
fn taus<R: Read>(
domain_size: usize,
reader: R,
check: CheckElement,
) -> ZkeyParserResult<Vec<P::G1Affine>> {
Ok(P::g1_vec_from_reader(reader, domain_size + 6, check)?)
}
}
impl<P: Pairing + CircomArkworksPairingBridge> VerifyingKey<P> {
fn new<R: Read>(mut reader: R) -> ZkeyParserResult<Self> {
let k1 = P::fr_from_montgomery_reader(&mut reader)?;
let k2 = P::fr_from_montgomery_reader(&mut reader)?;
let qm = P::g1_from_reader(&mut reader, CheckElement::Yes)?;
let ql = P::g1_from_reader(&mut reader, CheckElement::Yes)?;
let qr = P::g1_from_reader(&mut reader, CheckElement::Yes)?;
let qo = P::g1_from_reader(&mut reader, CheckElement::Yes)?;
let qc = P::g1_from_reader(&mut reader, CheckElement::Yes)?;
let s1 = P::g1_from_reader(&mut reader, CheckElement::Yes)?;
let s2 = P::g1_from_reader(&mut reader, CheckElement::Yes)?;
let s3 = P::g1_from_reader(&mut reader, CheckElement::Yes)?;
let x2 = P::g2_from_reader(&mut reader, CheckElement::Yes)?;
Ok(Self {
k1,
k2,
qm,
ql,
qr,
qo,
qc,
s1,
s2,
s3,
x_2: x2,
})
}
}
#[derive(Clone)]
struct PlonkHeader<P: Pairing> {
n8r: usize,
n_vars: usize,
n_public: usize,
domain_size: usize,
power: usize,
n_additions: usize,
n_constraints: usize,
verifying_key: VerifyingKey<P>,
}
impl<P: Pairing + CircomArkworksPairingBridge> PlonkHeader<P> {
fn read<R: Read>(mut reader: &mut R) -> ZkeyParserResult<Self> {
let n8q = u32::deserialize_uncompressed(&mut reader)?;
let q = <P::BaseField as PrimeField>::BigInt::deserialize_uncompressed(&mut reader)?;
let expected_n8q = P::BaseField::MODULUS_BIT_SIZE.div_ceil(8);
tracing::debug!("base field byte size: {n8q}");
if n8q != expected_n8q {
return Err(ZkeyParserError::UnexpectedByteSize(expected_n8q, n8q));
}
let modulus = <P::BaseField as PrimeField>::MODULUS;
if q != modulus {
return Err(ZkeyParserError::InvalidPrimeInHeader);
}
let n8r = u32::deserialize_uncompressed(&mut reader)?;
let r = <P::ScalarField as PrimeField>::BigInt::deserialize_uncompressed(&mut reader)?;
tracing::debug!("scalar field byte size: {n8r}");
let expected_n8r = P::ScalarField::MODULUS_BIT_SIZE.div_ceil(8);
if n8r != expected_n8r {
return Err(ZkeyParserError::UnexpectedByteSize(expected_n8r, n8r));
}
let n8r = usize::try_from(n8r).expect("works after check");
let modulus = <P::ScalarField as PrimeField>::MODULUS;
if r != modulus {
return Err(ZkeyParserError::InvalidPrimeInHeader);
}
let n_vars = u32::deserialize_uncompressed(&mut reader)?;
let n_public = u32::deserialize_uncompressed(&mut reader)?;
let domain_size = u32::deserialize_uncompressed(&mut reader)?;
let n_additions = u32::deserialize_uncompressed(&mut reader)?;
let n_constraints = u32::deserialize_uncompressed(&mut reader)?;
tracing::debug!("n_vars: {n_vars}; n_public: {n_public}, domain_size: {domain_size}");
let verifying_key = VerifyingKey::new(&mut reader)?;
if domain_size & (domain_size - 1) == 0 && domain_size > 0 {
tracing::debug!("read header done!");
Ok(Self {
n8r,
n_vars: u32_to_usize!(n_vars),
n_public: u32_to_usize!(n_public),
domain_size: u32_to_usize!(domain_size),
power: u32_to_usize!(domain_size.ilog2()),
n_additions: u32_to_usize!(n_additions),
n_constraints: u32_to_usize!(n_constraints),
verifying_key,
})
} else {
Err(ZkeyParserError::CorruptedBinFile(format!(
"Invalid domain size {domain_size}. Must be power of 2"
)))
}
}
}