use ark_bls12_381::{G1Affine as BlsG1Affine, G2Affine as BlsG2Affine};
use ark_bn254::{Fr as Bn254Fr, G1Affine as Bn254G1Affine, G2Affine as Bn254G2Affine};
use ark_ff::PrimeField;
use ark_serialize::CanonicalDeserialize;
use serde::Deserialize;
use serde_json::Value;
use std::fs;
use std::path::Path;
use crate::curves::{create_adapter, CurveAdapter};
use crate::error::{Error, Result};
use crate::model::{
CurveKind, Groth16G1Point, Groth16G2Point, Groth16Proof, Groth16VerificationKey,
Groth16VerifierInputs, SourceFormat,
};
use crate::snarkjs::parse_decimal;
#[derive(Debug, Deserialize)]
struct GnarkVerificationKeyJson {
#[serde(rename = "G1")]
g1: GnarkVkG1Json,
#[serde(rename = "G2")]
g2: GnarkVkG2Json,
#[serde(rename = "CommitmentKeys", default)]
commitment_keys: Option<Value>,
#[serde(rename = "PublicAndCommitmentCommitted", default)]
public_and_commitment_committed: Vec<Value>,
}
#[derive(Debug, Deserialize)]
struct GnarkVkG1Json {
#[serde(rename = "Alpha")]
alpha: GnarkG1Json,
#[serde(rename = "K")]
k: Vec<GnarkG1Json>,
}
#[derive(Debug, Deserialize)]
struct GnarkVkG2Json {
#[serde(rename = "Beta")]
beta: GnarkG2Json,
#[serde(rename = "Gamma")]
gamma: GnarkG2Json,
#[serde(rename = "Delta")]
delta: GnarkG2Json,
}
#[derive(Debug, Deserialize)]
struct GnarkProofJson {
#[serde(rename = "Ar")]
ar: GnarkG1Json,
#[serde(rename = "Bs")]
bs: GnarkG2Json,
#[serde(rename = "Krs")]
krs: GnarkG1Json,
#[serde(rename = "Commitments", default)]
commitments: Vec<Value>,
}
#[derive(Debug, Clone, Deserialize)]
struct GnarkG1Json {
#[serde(rename = "X")]
x: Value,
#[serde(rename = "Y")]
y: Value,
}
#[derive(Debug, Clone, Deserialize)]
struct GnarkG2Json {
#[serde(rename = "X")]
x: GnarkFq2Json,
#[serde(rename = "Y")]
y: GnarkFq2Json,
}
#[derive(Debug, Clone, Deserialize)]
struct GnarkFq2Json {
#[serde(rename = "A0")]
a0: Value,
#[serde(rename = "A1")]
a1: Value,
}
#[derive(Debug, Clone, Copy)]
struct GnarkEncoding {
curve: CurveKind,
field_bytes: usize,
flag_mask: u8,
compressed_smallest: u8,
compressed_largest: u8,
compressed_infinity: u8,
uncompressed: u8,
uncompressed_infinity: Option<u8>,
}
impl GnarkEncoding {
fn for_curve(curve: CurveKind) -> Self {
match curve {
CurveKind::Bn254 => Self {
curve,
field_bytes: 32,
flag_mask: 0b11 << 6,
compressed_smallest: 0b10 << 6,
compressed_largest: 0b11 << 6,
compressed_infinity: 0b01 << 6,
uncompressed: 0,
uncompressed_infinity: None,
},
CurveKind::Bls12_381 => Self {
curve,
field_bytes: 48,
flag_mask: 0b111 << 5,
compressed_smallest: 0b100 << 5,
compressed_largest: 0b101 << 5,
compressed_infinity: 0b110 << 5,
uncompressed: 0,
uncompressed_infinity: Some(0b010 << 5),
},
}
}
fn g1_compressed_size(self) -> usize {
self.field_bytes
}
fn g1_uncompressed_size(self) -> usize {
self.field_bytes * 2
}
fn g2_compressed_size(self) -> usize {
self.field_bytes * 2
}
fn g2_uncompressed_size(self) -> usize {
self.field_bytes * 4
}
fn is_compressed_flag(self, flag: u8) -> bool {
flag == self.compressed_smallest
|| flag == self.compressed_largest
|| flag == self.compressed_infinity
}
fn is_uncompressed_flag(self, flag: u8) -> bool {
flag == self.uncompressed || Some(flag) == self.uncompressed_infinity
}
fn ark_flag(self, flag: u8) -> Result<u8> {
if flag == self.compressed_smallest {
Ok(0)
} else if flag == self.compressed_largest {
Ok(0b10 << 6)
} else if flag == self.compressed_infinity {
Ok(0b01 << 6)
} else {
Err(Error::Serialization(format!(
"unsupported gnark compressed point flag 0x{flag:02x}"
)))
}
}
}
pub fn load_gnark_json_inputs(
vk_path: &Path,
proof_path: Option<&Path>,
public_path: Option<&Path>,
curve_hint: Option<&str>,
) -> Result<Groth16VerifierInputs> {
let vk: GnarkVerificationKeyJson = read_json(vk_path)?;
ensure_no_vk_commitments(&vk)?;
let verifying_key = vk_from_json(vk)?;
let proof = proof_path.map(load_gnark_json_proof).transpose()?;
let public_inputs = load_optional_public_inputs(public_path)?;
match curve_hint {
Some(raw_curve) => Groth16VerifierInputs::from_parts(
CurveKind::from_name(raw_curve)?,
verifying_key,
proof,
public_inputs,
SourceFormat::GnarkJson,
),
None => infer_curve(verifying_key, proof, public_inputs, SourceFormat::GnarkJson),
}
}
pub fn load_gnark_binary_inputs(
vk_path: &Path,
proof_path: Option<&Path>,
public_path: Option<&Path>,
curve_hint: &str,
) -> Result<Groth16VerifierInputs> {
let curve = CurveKind::from_name(curve_hint)?;
load_gnark_binary_inputs_for_curve(vk_path, proof_path, public_path, curve)
}
pub fn load_gnark_binary_inputs_auto(
vk_path: &Path,
proof_path: Option<&Path>,
public_path: Option<&Path>,
) -> Result<Groth16VerifierInputs> {
let mut matches = Vec::new();
let mut errors = Vec::new();
for curve in [CurveKind::Bn254, CurveKind::Bls12_381] {
match load_gnark_binary_inputs_for_curve(vk_path, proof_path, public_path, curve).and_then(
|inputs| {
validate_candidate(&inputs)?;
Ok(inputs)
},
) {
Ok(inputs) => matches.push(inputs),
Err(err) => errors.push(format!("{}: {err}", curve.canonical_name())),
}
}
match matches.len() {
1 => Ok(matches.remove(0)),
0 => Err(Error::MissingInput(format!(
"could not auto-detect gnark binary curve: {}",
errors.join("; ")
))),
_ => Err(Error::CurveMismatch(
"gnark binary artifact is ambiguous between supported curves".to_string(),
)),
}
}
pub fn load_sp1_groth16_inputs(vk_path: &Path, proof_path: &Path) -> Result<Groth16VerifierInputs> {
let verifying_key = load_gnark_binary_vk(vk_path, CurveKind::Bn254)?;
let parsed_proof = load_sp1_groth16_proof(proof_path)?;
Groth16VerifierInputs::from_parts(
CurveKind::Bn254,
verifying_key,
Some(parsed_proof.proof),
parsed_proof.public_inputs,
SourceFormat::Sp1Groth16,
)
}
fn load_gnark_binary_inputs_for_curve(
vk_path: &Path,
proof_path: Option<&Path>,
public_path: Option<&Path>,
curve: CurveKind,
) -> Result<Groth16VerifierInputs> {
let verifying_key = load_gnark_binary_vk(vk_path, curve)?;
let proof = proof_path
.map(|path| load_gnark_binary_proof(path, curve))
.transpose()?;
let public_inputs = load_optional_public_inputs(public_path)?;
Groth16VerifierInputs::from_parts(
curve,
verifying_key,
proof,
public_inputs,
SourceFormat::GnarkBinary,
)
}
fn infer_curve(
verifying_key: Groth16VerificationKey,
proof: Option<Groth16Proof>,
public_inputs: Vec<String>,
source_format: SourceFormat,
) -> Result<Groth16VerifierInputs> {
let mut matches = Vec::new();
let mut errors = Vec::new();
for curve in [CurveKind::Bn254, CurveKind::Bls12_381] {
let inputs = Groth16VerifierInputs::from_parts(
curve,
verifying_key.clone(),
proof.clone(),
public_inputs.clone(),
source_format,
);
let inputs = match inputs {
Ok(inputs) => inputs,
Err(err) => {
errors.push(format!("{}: {err}", curve.canonical_name()));
continue;
}
};
match validate_candidate(&inputs) {
Ok(()) => matches.push(inputs),
Err(err) => errors.push(format!("{}: {err}", curve.canonical_name())),
}
}
match matches.len() {
1 => Ok(matches.remove(0)),
0 => Err(Error::MissingInput(format!(
"could not infer gnark curve: {}",
errors.join("; ")
))),
_ => Err(Error::CurveMismatch(
"gnark JSON artifact is valid for more than one supported curve".to_string(),
)),
}
}
fn validate_candidate(inputs: &Groth16VerifierInputs) -> Result<()> {
let adapter = create_adapter(inputs.curve.canonical_name())?;
if inputs.has_test_vectors() {
return match adapter.local_verify(inputs) {
Ok(true) => Ok(()),
Ok(false) => Err(Error::LocalProofVerificationFailed(
"local verification returned false".to_string(),
)),
Err(err) => Err(err),
};
}
serialize_verifying_key(adapter.as_ref(), &inputs.verifying_key)
}
fn serialize_verifying_key(adapter: &dyn CurveAdapter, vk: &Groth16VerificationKey) -> Result<()> {
adapter.serialize_g1_vk(&vk.vk_alpha_1)?;
adapter.serialize_g2_vk(&vk.vk_beta_2)?;
adapter.serialize_g2_vk(&vk.vk_gamma_2)?;
adapter.serialize_g2_vk(&vk.vk_delta_2)?;
for point in &vk.ic {
adapter.serialize_g1_vk(point)?;
}
Ok(())
}
fn load_gnark_json_proof(path: &Path) -> Result<Groth16Proof> {
let proof: GnarkProofJson = read_json(path)?;
if !proof.commitments.is_empty() {
return Err(Error::UnsupportedProtocol(
"gnark proof commitments are not supported by Aptos groth16 verifier generation"
.to_string(),
));
}
Ok(Groth16Proof {
pi_a: g1_from_json(proof.ar, "Ar")?,
pi_b: g2_from_json(proof.bs, "Bs")?,
pi_c: g1_from_json(proof.krs, "Krs")?,
})
}
fn vk_from_json(vk: GnarkVerificationKeyJson) -> Result<Groth16VerificationKey> {
let ic = vk
.g1
.k
.into_iter()
.enumerate()
.map(|(idx, point)| g1_from_json(point, &format!("G1.K[{idx}]")))
.collect::<Result<Vec<_>>>()?;
Ok(Groth16VerificationKey {
n_public: ic.len().saturating_sub(1),
vk_alpha_1: g1_from_json(vk.g1.alpha, "G1.Alpha")?,
vk_beta_2: g2_from_json(vk.g2.beta, "G2.Beta")?,
vk_gamma_2: g2_from_json(vk.g2.gamma, "G2.Gamma")?,
vk_delta_2: g2_from_json(vk.g2.delta, "G2.Delta")?,
ic,
})
}
fn ensure_no_vk_commitments(vk: &GnarkVerificationKeyJson) -> Result<()> {
if !is_empty_or_null(&vk.commitment_keys) || !vk.public_and_commitment_committed.is_empty() {
return Err(Error::UnsupportedProtocol(
"gnark commitment keys are not supported by Aptos groth16 verifier generation"
.to_string(),
));
}
Ok(())
}
fn is_empty_or_null(value: &Option<Value>) -> bool {
match value {
None | Some(Value::Null) => true,
Some(Value::Array(items)) => items.is_empty(),
_ => false,
}
}
fn g1_from_json(point: GnarkG1Json, name: &str) -> Result<Groth16G1Point> {
Ok(Groth16G1Point {
x: decimal_from_value(&point.x, &format!("{name}.X"))?,
y: decimal_from_value(&point.y, &format!("{name}.Y"))?,
z: "1".to_string(),
})
}
fn g2_from_json(point: GnarkG2Json, name: &str) -> Result<Groth16G2Point> {
Ok(Groth16G2Point {
x0: decimal_from_value(&point.x.a0, &format!("{name}.X.A0"))?,
x1: decimal_from_value(&point.x.a1, &format!("{name}.X.A1"))?,
y0: decimal_from_value(&point.y.a0, &format!("{name}.Y.A0"))?,
y1: decimal_from_value(&point.y.a1, &format!("{name}.Y.A1"))?,
z0: "1".to_string(),
z1: "0".to_string(),
})
}
fn load_gnark_binary_vk(path: &Path, curve: CurveKind) -> Result<Groth16VerificationKey> {
let bytes = read_bytes(path)?;
let encoding = GnarkEncoding::for_curve(curve);
let mut reader = GnarkBinaryReader::new(&bytes, path);
let alpha = reader.read_g1(encoding, "G1.Alpha")?;
let _beta_g1 = reader.read_g1(encoding, "G1.Beta")?;
let beta = reader.read_g2(encoding, "G2.Beta")?;
let gamma = reader.read_g2(encoding, "G2.Gamma")?;
let _delta_g1 = reader.read_g1(encoding, "G1.Delta")?;
let delta = reader.read_g2(encoding, "G2.Delta")?;
let ic_len = reader.read_u32("G1.K length")? as usize;
let mut ic = Vec::with_capacity(ic_len);
for idx in 0..ic_len {
ic.push(reader.read_g1(encoding, &format!("G1.K[{idx}]"))?);
}
let public_and_commitment_committed =
reader.read_u64_slice_slice("PublicAndCommitmentCommitted")?;
let commitment_count = reader.read_u32("CommitmentKeys length")?;
if !public_and_commitment_committed.is_empty() || commitment_count != 0 {
return Err(Error::UnsupportedProtocol(
"gnark commitment keys are not supported by Aptos groth16 verifier generation"
.to_string(),
));
}
reader.finish()?;
Ok(Groth16VerificationKey {
n_public: ic.len().saturating_sub(1),
vk_alpha_1: alpha,
vk_beta_2: beta,
vk_gamma_2: gamma,
vk_delta_2: delta,
ic,
})
}
fn load_gnark_binary_proof(path: &Path, curve: CurveKind) -> Result<Groth16Proof> {
let bytes = read_bytes(path)?;
let encoding = GnarkEncoding::for_curve(curve);
let mut reader = GnarkBinaryReader::new(&bytes, path);
let ar = reader.read_g1(encoding, "Ar")?;
let bs = reader.read_g2(encoding, "Bs")?;
let krs = reader.read_g1(encoding, "Krs")?;
let commitment_count = reader.read_u32("Commitments length")?;
if commitment_count != 0 {
return Err(Error::UnsupportedProtocol(
"gnark proof commitments are not supported by Aptos groth16 verifier generation"
.to_string(),
));
}
let _commitment_pok = reader.read_g1(encoding, "CommitmentPok")?;
reader.finish()?;
Ok(Groth16Proof {
pi_a: ar,
pi_b: bs,
pi_c: krs,
})
}
struct Sp1Groth16Proof {
proof: Groth16Proof,
public_inputs: Vec<String>,
}
fn load_sp1_groth16_proof(path: &Path) -> Result<Sp1Groth16Proof> {
let bytes = read_bytes(path)?;
let mut reader = Sp1ProofReader::new(&bytes, path);
let variant = reader.read_u32("SP1Proof variant")?;
if variant != 3 {
return Err(Error::UnsupportedProtocol(format!(
"expected SP1 Groth16 proof variant 3, got {variant}"
)));
}
let vkey_hash = reader.read_string("SP1 vkey hash public input")?;
let committed_values_digest = reader.read_string("SP1 committed values digest public input")?;
let next = reader.read_string("SP1 Groth16 raw proof or exit code public input")?;
if let Ok(proof) = decode_sp1_groth16_raw_proof_hex(&next, path) {
return Ok(Sp1Groth16Proof {
proof,
public_inputs: sp1_public_inputs(&vkey_hash, &committed_values_digest)?,
});
}
let mut public_inputs = vec![vkey_hash, committed_values_digest, next];
public_inputs.push(reader.read_string("SP1 vk root public input")?);
public_inputs.push(reader.read_string("SP1 proof nonce public input")?);
let encoded_proof_hex = reader.read_string("SP1 Groth16 encoded proof")?;
let _raw_proof_hex = reader.read_string("SP1 Groth16 raw proof")?;
let _groth16_vkey_hash = reader.read_exact(32, "SP1 Groth16 vkey hash")?;
let encoded_proof = hex::decode(&encoded_proof_hex).map_err(|e| {
Error::HexParse(format!(
"SP1 Groth16 encoded proof must be hex encoded: {e}"
))
})?;
const SP1_GROTH16_METADATA_BYTES: usize = 96;
if encoded_proof.len() < SP1_GROTH16_METADATA_BYTES {
return Err(Error::Serialization(
"SP1 Groth16 encoded proof is shorter than its metadata prefix".to_string(),
));
}
let proof = decode_sp1_groth16_proof_bytes(&encoded_proof[SP1_GROTH16_METADATA_BYTES..], path)?;
Ok(Sp1Groth16Proof {
proof,
public_inputs,
})
}
fn decode_sp1_groth16_raw_proof_hex(raw_proof_hex: &str, path: &Path) -> Result<Groth16Proof> {
let raw_proof = hex::decode(raw_proof_hex)
.map_err(|e| Error::HexParse(format!("SP1 Groth16 raw proof must be hex encoded: {e}")))?;
decode_sp1_groth16_proof_bytes(&raw_proof, path)
}
fn decode_sp1_groth16_proof_bytes(raw_proof: &[u8], path: &Path) -> Result<Groth16Proof> {
let encoding = GnarkEncoding::for_curve(CurveKind::Bn254);
let mut proof_reader = GnarkBinaryReader::new(raw_proof, path);
let proof = Groth16Proof {
pi_a: proof_reader.read_g1(encoding, "SP1 proof.Ar")?,
pi_b: proof_reader.read_g2(encoding, "SP1 proof.Bs")?,
pi_c: proof_reader.read_g1(encoding, "SP1 proof.Krs")?,
};
proof_reader.finish()?;
Ok(proof)
}
fn sp1_public_inputs(vkey_hash: &str, committed_values_digest: &str) -> Result<Vec<String>> {
let vkey_hash = parse_decimal_bytes_be(vkey_hash, "SP1 vkey hash")?;
let committed_values_digest =
parse_decimal_bytes_be(committed_values_digest, "SP1 committed values digest")?;
let mut vkey_hash = {
let mut padded = vec![0u8];
padded.extend_from_slice(&vkey_hash);
padded
};
if vkey_hash.len() > 32 {
return Err(Error::FieldOverflow(
"SP1 vkey hash does not fit into 32 bytes".to_string(),
));
}
vkey_hash.resize(32, 0);
let vkey_hash: [u8; 32] = vkey_hash.try_into().unwrap();
let mut padded_committed_values_digest = [0u8; 32];
if committed_values_digest.len() > padded_committed_values_digest.len() {
return Err(Error::FieldOverflow(
"SP1 committed values digest does not fit into 32 bytes".to_string(),
));
}
padded_committed_values_digest[..committed_values_digest.len()]
.copy_from_slice(&committed_values_digest);
Ok(vec![
fr_from_be_bytes(&vkey_hash),
fr_from_be_bytes(&padded_committed_values_digest),
])
}
fn parse_decimal_bytes_be(raw: &str, field: &str) -> Result<Vec<u8>> {
let value = parse_decimal(raw, field)?;
Ok(value.to_bytes_be())
}
fn fr_from_be_bytes(bytes: &[u8; 32]) -> String {
let fr = Bn254Fr::from_be_bytes_mod_order(bytes);
fr.into_bigint().to_string()
}
struct Sp1ProofReader<'a> {
bytes: &'a [u8],
offset: usize,
path: &'a Path,
}
impl<'a> Sp1ProofReader<'a> {
fn new(bytes: &'a [u8], path: &'a Path) -> Self {
Self {
bytes,
offset: 0,
path,
}
}
fn read_u32(&mut self, field: &str) -> Result<u32> {
let bytes = self.read_exact(4, field)?;
Ok(u32::from_le_bytes(bytes.try_into().unwrap()))
}
fn read_u64(&mut self, field: &str) -> Result<u64> {
let bytes = self.read_exact(8, field)?;
Ok(u64::from_le_bytes(bytes.try_into().unwrap()))
}
fn read_string(&mut self, field: &str) -> Result<String> {
let len = self.read_u64(&format!("{field} length"))?;
let len: usize = len
.try_into()
.map_err(|_| Error::Serialization(format!("{field} length does not fit into usize")))?;
let bytes = self.read_exact(len, field)?;
String::from_utf8(bytes.to_vec()).map_err(|e| {
Error::Serialization(format!("{field} is not valid UTF-8 in SP1 proof: {e}"))
})
}
fn read_exact(&mut self, len: usize, field: &str) -> Result<&'a [u8]> {
let end = self.offset.checked_add(len).ok_or_else(|| {
Error::Serialization(format!("overflow while reading {field} from SP1 proof"))
})?;
if end > self.bytes.len() {
return Err(Error::Serialization(format!(
"{} ended while reading {field}: need {len} bytes at offset {}, remaining {}",
self.path.display(),
self.offset,
self.bytes.len().saturating_sub(self.offset)
)));
}
let slice = &self.bytes[self.offset..end];
self.offset = end;
Ok(slice)
}
}
struct GnarkBinaryReader<'a> {
bytes: &'a [u8],
offset: usize,
path: &'a Path,
}
impl<'a> GnarkBinaryReader<'a> {
fn new(bytes: &'a [u8], path: &'a Path) -> Self {
Self {
bytes,
offset: 0,
path,
}
}
fn read_g1(&mut self, encoding: GnarkEncoding, field: &str) -> Result<Groth16G1Point> {
let first = self.peek_byte(field)?;
let flag = first & encoding.flag_mask;
let compressed = if encoding.is_compressed_flag(flag) {
true
} else if encoding.is_uncompressed_flag(flag) {
false
} else {
return Err(Error::Serialization(format!(
"{field} has unsupported gnark point flag 0x{flag:02x}"
)));
};
let len = if compressed {
encoding.g1_compressed_size()
} else {
encoding.g1_uncompressed_size()
};
let bytes = self.read_exact(len, field)?;
decode_gnark_g1(bytes, encoding, compressed, field)
}
fn read_g2(&mut self, encoding: GnarkEncoding, field: &str) -> Result<Groth16G2Point> {
let first = self.peek_byte(field)?;
let flag = first & encoding.flag_mask;
let compressed = if encoding.is_compressed_flag(flag) {
true
} else if encoding.is_uncompressed_flag(flag) {
false
} else {
return Err(Error::Serialization(format!(
"{field} has unsupported gnark point flag 0x{flag:02x}"
)));
};
let len = if compressed {
encoding.g2_compressed_size()
} else {
encoding.g2_uncompressed_size()
};
let bytes = self.read_exact(len, field)?;
decode_gnark_g2(bytes, encoding, compressed, field)
}
fn read_u32(&mut self, field: &str) -> Result<u32> {
let bytes = self.read_exact(4, field)?;
Ok(u32::from_be_bytes(bytes.try_into().unwrap()))
}
fn read_u64(&mut self, field: &str) -> Result<u64> {
let bytes = self.read_exact(8, field)?;
Ok(u64::from_be_bytes(bytes.try_into().unwrap()))
}
fn read_u64_slice_slice(&mut self, field: &str) -> Result<Vec<Vec<u64>>> {
let outer_len = self.read_u32(&format!("{field} length"))? as usize;
let mut outer = Vec::with_capacity(outer_len);
for outer_idx in 0..outer_len {
let inner_len = self.read_u32(&format!("{field}[{outer_idx}] length"))? as usize;
let mut inner = Vec::with_capacity(inner_len);
for inner_idx in 0..inner_len {
inner.push(self.read_u64(&format!("{field}[{outer_idx}][{inner_idx}]"))?);
}
outer.push(inner);
}
Ok(outer)
}
fn finish(&self) -> Result<()> {
if self.offset == self.bytes.len() {
return Ok(());
}
Err(Error::Serialization(format!(
"{} has {} trailing bytes after gnark artifact",
self.path.display(),
self.bytes.len() - self.offset
)))
}
fn peek_byte(&self, field: &str) -> Result<u8> {
self.bytes.get(self.offset).copied().ok_or_else(|| {
Error::Serialization(format!(
"{} ended before reading {field}",
self.path.display()
))
})
}
fn read_exact(&mut self, len: usize, field: &str) -> Result<&'a [u8]> {
let end = self.offset.checked_add(len).ok_or_else(|| {
Error::Serialization(format!(
"overflow while reading {field} from gnark artifact"
))
})?;
if end > self.bytes.len() {
return Err(Error::Serialization(format!(
"{} ended while reading {field}: need {len} bytes at offset {}, remaining {}",
self.path.display(),
self.offset,
self.bytes.len().saturating_sub(self.offset)
)));
}
let slice = &self.bytes[self.offset..end];
self.offset = end;
Ok(slice)
}
}
fn decode_gnark_g1(
bytes: &[u8],
encoding: GnarkEncoding,
compressed: bool,
field: &str,
) -> Result<Groth16G1Point> {
match (encoding.curve, compressed) {
(CurveKind::Bn254, true) => {
let ark = gnark_compressed_g1_to_ark(bytes, encoding)?;
let mut reader = ark.as_slice();
let point = Bn254G1Affine::deserialize_compressed(&mut reader).map_err(|e| {
Error::Serialization(format!("failed to deserialize BN254 {field}: {e:?}"))
})?;
Ok(point_from_bn254_g1(&point))
}
(CurveKind::Bn254, false) => {
let ark = gnark_uncompressed_g1_to_ark(bytes, encoding);
let mut reader = ark.as_slice();
let point = Bn254G1Affine::deserialize_uncompressed(&mut reader).map_err(|e| {
Error::Serialization(format!("failed to deserialize BN254 {field}: {e:?}"))
})?;
Ok(point_from_bn254_g1(&point))
}
(CurveKind::Bls12_381, true) => {
let ark = gnark_compressed_g1_to_ark(bytes, encoding)?;
let mut reader = ark.as_slice();
let point = BlsG1Affine::deserialize_compressed(&mut reader).map_err(|e| {
Error::Serialization(format!("failed to deserialize BLS12-381 {field}: {e:?}"))
})?;
Ok(point_from_bls_g1(&point))
}
(CurveKind::Bls12_381, false) => {
let ark = gnark_uncompressed_g1_to_ark(bytes, encoding);
let mut reader = ark.as_slice();
let point = BlsG1Affine::deserialize_uncompressed(&mut reader).map_err(|e| {
Error::Serialization(format!("failed to deserialize BLS12-381 {field}: {e:?}"))
})?;
Ok(point_from_bls_g1(&point))
}
}
}
fn decode_gnark_g2(
bytes: &[u8],
encoding: GnarkEncoding,
compressed: bool,
field: &str,
) -> Result<Groth16G2Point> {
match (encoding.curve, compressed) {
(CurveKind::Bn254, true) => {
let ark = gnark_compressed_g2_to_ark(bytes, encoding)?;
let mut reader = ark.as_slice();
let point = Bn254G2Affine::deserialize_compressed(&mut reader).map_err(|e| {
Error::Serialization(format!("failed to deserialize BN254 {field}: {e:?}"))
})?;
Ok(point_from_bn254_g2(&point))
}
(CurveKind::Bn254, false) => {
let ark = gnark_uncompressed_g2_to_ark(bytes, encoding);
let mut reader = ark.as_slice();
let point = Bn254G2Affine::deserialize_uncompressed(&mut reader).map_err(|e| {
Error::Serialization(format!("failed to deserialize BN254 {field}: {e:?}"))
})?;
Ok(point_from_bn254_g2(&point))
}
(CurveKind::Bls12_381, true) => {
let ark = gnark_compressed_g2_to_ark(bytes, encoding)?;
let mut reader = ark.as_slice();
let point = BlsG2Affine::deserialize_compressed(&mut reader).map_err(|e| {
Error::Serialization(format!("failed to deserialize BLS12-381 {field}: {e:?}"))
})?;
Ok(point_from_bls_g2(&point))
}
(CurveKind::Bls12_381, false) => {
let ark = gnark_uncompressed_g2_to_ark(bytes, encoding);
let mut reader = ark.as_slice();
let point = BlsG2Affine::deserialize_uncompressed(&mut reader).map_err(|e| {
Error::Serialization(format!("failed to deserialize BLS12-381 {field}: {e:?}"))
})?;
Ok(point_from_bls_g2(&point))
}
}
}
fn gnark_compressed_g1_to_ark(bytes: &[u8], encoding: GnarkEncoding) -> Result<Vec<u8>> {
if encoding.curve == CurveKind::Bls12_381 {
return Ok(bytes.to_vec());
}
let flag = bytes[0] & encoding.flag_mask;
let mut cleaned = bytes.to_vec();
cleaned[0] &= !encoding.flag_mask;
let mut out = reverse_field_bytes(&cleaned);
let last = out.len() - 1;
out[last] |= encoding.ark_flag(flag)?;
Ok(out)
}
fn gnark_uncompressed_g1_to_ark(bytes: &[u8], encoding: GnarkEncoding) -> Vec<u8> {
if encoding.curve == CurveKind::Bls12_381 {
return bytes.to_vec();
}
let coordinate_size = encoding.field_bytes;
let mut x = bytes[..coordinate_size].to_vec();
x[0] &= !encoding.flag_mask;
let y = &bytes[coordinate_size..coordinate_size * 2];
let mut out = Vec::with_capacity(bytes.len());
out.extend(reverse_field_bytes(&x));
out.extend(reverse_field_bytes(y));
out
}
fn gnark_compressed_g2_to_ark(bytes: &[u8], encoding: GnarkEncoding) -> Result<Vec<u8>> {
if encoding.curve == CurveKind::Bls12_381 {
return Ok(bytes.to_vec());
}
let coordinate_size = encoding.field_bytes;
let flag = bytes[0] & encoding.flag_mask;
let mut x1 = bytes[..coordinate_size].to_vec();
x1[0] &= !encoding.flag_mask;
let x0 = &bytes[coordinate_size..coordinate_size * 2];
let mut out = Vec::with_capacity(bytes.len());
out.extend(reverse_field_bytes(x0));
out.extend(reverse_field_bytes(&x1));
let last = out.len() - 1;
out[last] |= encoding.ark_flag(flag)?;
Ok(out)
}
fn gnark_uncompressed_g2_to_ark(bytes: &[u8], encoding: GnarkEncoding) -> Vec<u8> {
if encoding.curve == CurveKind::Bls12_381 {
return bytes.to_vec();
}
let coordinate_size = encoding.field_bytes;
let mut x1 = bytes[..coordinate_size].to_vec();
x1[0] &= !encoding.flag_mask;
let x0 = &bytes[coordinate_size..coordinate_size * 2];
let y1 = &bytes[coordinate_size * 2..coordinate_size * 3];
let y0 = &bytes[coordinate_size * 3..coordinate_size * 4];
let mut out = Vec::with_capacity(bytes.len());
out.extend(reverse_field_bytes(x0));
out.extend(reverse_field_bytes(&x1));
out.extend(reverse_field_bytes(y0));
out.extend(reverse_field_bytes(y1));
out
}
fn reverse_field_bytes(bytes: &[u8]) -> Vec<u8> {
bytes.iter().rev().copied().collect()
}
fn point_from_bn254_g1(point: &Bn254G1Affine) -> Groth16G1Point {
Groth16G1Point {
x: point.x.to_string(),
y: point.y.to_string(),
z: "1".to_string(),
}
}
fn point_from_bn254_g2(point: &Bn254G2Affine) -> Groth16G2Point {
Groth16G2Point {
x0: point.x.c0.to_string(),
x1: point.x.c1.to_string(),
y0: point.y.c0.to_string(),
y1: point.y.c1.to_string(),
z0: "1".to_string(),
z1: "0".to_string(),
}
}
fn point_from_bls_g1(point: &BlsG1Affine) -> Groth16G1Point {
Groth16G1Point {
x: point.x.to_string(),
y: point.y.to_string(),
z: "1".to_string(),
}
}
fn point_from_bls_g2(point: &BlsG2Affine) -> Groth16G2Point {
Groth16G2Point {
x0: point.x.c0.to_string(),
x1: point.x.c1.to_string(),
y0: point.y.c0.to_string(),
y1: point.y.c1.to_string(),
z0: "1".to_string(),
z1: "0".to_string(),
}
}
fn load_optional_public_inputs(path: Option<&Path>) -> Result<Vec<String>> {
match path {
Some(path) => parse_public_inputs_value(&read_json_value(path)?, "public"),
None => Ok(Vec::new()),
}
}
fn parse_public_inputs_value(value: &Value, field: &str) -> Result<Vec<String>> {
match value {
Value::Array(values) => values
.iter()
.enumerate()
.map(|(idx, value)| decimal_from_value(value, &format!("{field}[{idx}]")))
.collect(),
Value::Object(map) => {
for key in ["public_inputs", "public", "publicSignals"] {
if let Some(inner) = map.get(key) {
return parse_public_inputs_value(inner, key);
}
}
if map.len() == 1 {
let (key, value) = map.iter().next().unwrap();
return decimal_from_value(value, key).map(|value| vec![value]);
}
Err(Error::MissingInput(
"gnark public input object with multiple fields must use an ordered public_inputs array"
.to_string(),
))
}
_ => decimal_from_value(value, field).map(|value| vec![value]),
}
}
fn decimal_from_value(value: &Value, field: &str) -> Result<String> {
match value {
Value::String(raw) => {
parse_decimal(raw, field)?;
Ok(raw.clone())
}
Value::Number(num) => {
let decimal = num.to_string();
parse_decimal(&decimal, field)?;
Ok(decimal)
}
_ => Err(Error::DecimalParse(format!(
"{field} must be a decimal string or number"
))),
}
}
fn read_json<T: for<'de> Deserialize<'de>>(path: &Path) -> Result<T> {
let content = fs::read_to_string(path).map_err(|e| Error::Io {
source: e,
context: format!("failed to read file {}", path.display()),
})?;
serde_json::from_str::<T>(&content).map_err(|e| Error::JsonParse {
source: e,
context: format!("invalid gnark json in file {}", path.display()),
})
}
fn read_json_value(path: &Path) -> Result<Value> {
read_json(path)
}
fn read_bytes(path: &Path) -> Result<Vec<u8>> {
fs::read(path).map_err(|e| Error::Io {
source: e,
context: format!("failed to read file {}", path.display()),
})
}