Skip to content

Instantly share code, notes, and snippets.

@prasad-kumkar
Created February 3, 2026 17:05
Show Gist options
  • Select an option

  • Save prasad-kumkar/600c60e2bccd3b2788ef7dcaaa80650f to your computer and use it in GitHub Desktop.

Select an option

Save prasad-kumkar/600c60e2bccd3b2788ef7dcaaa80650f to your computer and use it in GitHub Desktop.
use ark_serialize::CanonicalDeserialize;
use serde_json::Value;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use ark_bls12_381::Bls12_381;
use ark_ed_on_bls12_381_bandersnatch::{EdwardsAffine, Fq};
use w3f_pcs::pcs::kzg::KZG;
use w3f_plonk_common::domain::Domain;
use w3f_ring_proof::{ArkTranscript, PiopParams, RingProof, VerifierKey};
use w3f_ring_proof::ring_verifier::RingVerifier;
fn from_hex(hex: &str) -> Result<Vec<u8>, String> {
if hex.len() % 2 != 0 {
return Err("Hex string has odd length".to_string());
}
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16))
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("Invalid hex: {}", e))
}
fn get_str<'a>(value: &'a Value, path: &[&str]) -> Result<&'a str, String> {
let mut current = value;
for key in path {
current = current
.get(key)
.ok_or_else(|| format!("Missing key: {}", key))?;
}
current
.as_str()
.ok_or_else(|| format!("Expected string at {:?}", path))
}
fn get_u64(value: &Value, path: &[&str]) -> Result<u64, String> {
let mut current = value;
for key in path {
current = current
.get(key)
.ok_or_else(|| format!("Missing key: {}", key))?;
}
current
.as_u64()
.ok_or_else(|| format!("Expected u64 at {:?}", path))
}
fn verify_file(path: &Path) -> Result<(), String> {
let json_content = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
let data: Value = serde_json::from_str(&json_content)
.map_err(|e| format!("Failed to parse JSON: {}", e))?;
// Parse metadata
let domain_size = get_u64(&data, &["metadata", "parameters", "domain_size"])? as usize;
// Parse result point (instance)
let result_x_hex = get_str(&data, &["metadata", "parameters", "result", "x"])?;
let result_y_hex = get_str(&data, &["metadata", "parameters", "result", "y"])?;
let result_x_bytes = from_hex(result_x_hex)?;
let result_y_bytes = from_hex(result_y_hex)?;
// Deserialize as little-endian field elements (Bandersnatch uses Fq)
let result_x = Fq::deserialize_uncompressed(&result_x_bytes[..])
.map_err(|e| format!("Failed to deserialize result.x: {:?}", e))?;
let result_y = Fq::deserialize_uncompressed(&result_y_bytes[..])
.map_err(|e| format!("Failed to deserialize result.y: {:?}", e))?;
let result = EdwardsAffine::new_unchecked(result_x, result_y);
// Parse seed and h points
let seed_x_bytes = from_hex(get_str(&data, &["metadata", "parameters", "seed", "x"])? )?;
let seed_y_bytes = from_hex(get_str(&data, &["metadata", "parameters", "seed", "y"])? )?;
let seed_x = Fq::deserialize_uncompressed(&seed_x_bytes[..])
.map_err(|e| format!("Failed to deserialize seed.x: {:?}", e))?;
let seed_y = Fq::deserialize_uncompressed(&seed_y_bytes[..])
.map_err(|e| format!("Failed to deserialize seed.y: {:?}", e))?;
let seed = EdwardsAffine::new_unchecked(seed_x, seed_y);
let h_x_bytes = from_hex(get_str(&data, &["metadata", "parameters", "h", "x"])? )?;
let h_y_bytes = from_hex(get_str(&data, &["metadata", "parameters", "h", "y"])? )?;
let h_x = Fq::deserialize_uncompressed(&h_x_bytes[..])
.map_err(|e| format!("Failed to deserialize h.x: {:?}", e))?;
let h_y = Fq::deserialize_uncompressed(&h_y_bytes[..])
.map_err(|e| format!("Failed to deserialize h.y: {:?}", e))?;
let h = EdwardsAffine::new_unchecked(h_x, h_y);
// Padding point may not be in JSON, use identity point as default
let padding = EdwardsAffine::default();
// Parse verifier key
let vk_hex = get_str(&data, &["verifier_key", "verification_key"])?;
let vk_bytes = from_hex(vk_hex)?;
let verifier_key = VerifierKey::<Fq, KZG<Bls12_381>>::deserialize_compressed(&vk_bytes[..])
.map_err(|e| format!("Failed to deserialize verifier key: {:?}", e))?;
// Parse proof components
let column_commitments_hex = get_str(&data, &["proof", "column_commitments"])?;
let columns_at_zeta_hex = get_str(&data, &["proof", "columns_at_zeta"])?;
let quotient_commitment_hex = get_str(&data, &["proof", "quotient_commitment"])?;
let lin_at_zeta_omega_hex = get_str(&data, &["proof", "lin_at_zeta_omega"])?;
let agg_at_zeta_proof_hex = get_str(&data, &["proof", "agg_at_zeta_proof"])?;
let lin_at_zeta_omega_proof_hex = get_str(&data, &["proof", "lin_at_zeta_omega_proof"])?;
// Concatenate all proof components in the exact order of Proof's fields:
// 1. column_commitments (RingCommitments)
// 2. columns_at_zeta (RingEvaluations)
// 3. quotient_commitment (G1)
// 4. lin_at_zeta_omega (Fq)
// 5. agg_at_zeta_proof (G1)
// 6. lin_at_zeta_omega_proof (G1)
let mut proof_bytes = Vec::new();
proof_bytes.extend(from_hex(column_commitments_hex)?);
proof_bytes.extend(from_hex(columns_at_zeta_hex)?);
proof_bytes.extend(from_hex(quotient_commitment_hex)?);
proof_bytes.extend(from_hex(lin_at_zeta_omega_hex)?);
proof_bytes.extend(from_hex(agg_at_zeta_proof_hex)?);
proof_bytes.extend(from_hex(lin_at_zeta_omega_proof_hex)?);
// Deserialize the proof
let proof = RingProof::<Fq, KZG<Bls12_381>>::deserialize_compressed(&proof_bytes[..])
.map_err(|e| format!("Failed to deserialize proof: {:?}", e))?;
// Create PIOP params
let domain = Domain::new(domain_size, true);
let piop_params = PiopParams::setup(domain, h, seed, padding);
// Verify the proof
let ring_verifier = RingVerifier::init(
verifier_key,
piop_params,
ArkTranscript::new(b"w3f-ring-proof-test"),
);
let is_valid = ring_verifier.verify(proof, result);
if !is_valid {
return Err("Verification failed".to_string());
}
Ok(())
}
fn collect_json_paths(target: &Path) -> Result<Vec<PathBuf>, String> {
if target.is_file() {
return Ok(vec![target.to_path_buf()]);
}
if !target.is_dir() {
return Err(format!("Path is not a file or directory: {}", target.display()));
}
let mut paths = Vec::new();
for entry in fs::read_dir(target)
.map_err(|e| format!("Failed to read directory {}: {}", target.display(), e))?
{
let entry = entry.map_err(|e| format!("Failed to read entry: {}", e))?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("json") {
paths.push(path);
}
}
paths.sort();
Ok(paths)
}
fn main() {
let args: Vec<String> = env::args().collect();
let target = if args.len() > 1 {
PathBuf::from(&args[1])
} else {
PathBuf::from("../../dot-ring/tests/vectors/others")
};
println!("Target: {}", target.display());
let paths = match collect_json_paths(&target) {
Ok(paths) => paths,
Err(err) => {
eprintln!("ERROR: {}", err);
std::process::exit(1);
}
};
if paths.is_empty() {
eprintln!("ERROR: No JSON files found at {}", target.display());
std::process::exit(1);
}
let mut ok = 0usize;
let mut failed = 0usize;
for path in paths {
match verify_file(&path) {
Ok(()) => {
println!("[OK] {}", path.display());
ok += 1;
}
Err(err) => {
eprintln!("[FAIL] {}", path.display());
eprintln!(" {}", err);
failed += 1;
}
}
}
println!("\nSummary: {} ok, {} failed", ok, failed);
if failed > 0 {
std::process::exit(1);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment