use std::marker::PhantomData;
use crate::traits::PairingEngine;
#[derive(Debug, Clone)]
pub struct SolidityConfig {
pub contract_name: String,
pub solidity_version: String,
pub use_assembly: bool,
pub add_natspec: bool,
}
impl Default for SolidityConfig {
fn default() -> Self {
Self {
contract_name: "Verifier".to_string(),
solidity_version: "^0.8.20".to_string(),
use_assembly: true,
add_natspec: true,
}
}
}
impl SolidityConfig {
pub fn with_name(name: impl Into<String>) -> Self {
Self {
contract_name: name.into(),
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub struct SolidityVk {
pub commitments: Vec<(String, String)>,
pub num_public_inputs: usize,
pub proof_length: usize,
}
pub struct SolidityGenerator<E: PairingEngine> {
config: SolidityConfig,
_engine: PhantomData<E>,
}
impl<E: PairingEngine> SolidityGenerator<E> {
pub fn new() -> Self {
Self::with_config(SolidityConfig::default())
}
pub fn with_config(config: SolidityConfig) -> Self {
Self {
config,
_engine: PhantomData,
}
}
pub fn generate(&self, vk: &SolidityVk) -> String {
let template = super::template::VERIFIER_TEMPLATE;
let mut output = template.to_string();
output = output.replace("{{CONTRACT_NAME}}", &self.config.contract_name);
output = output.replace("{{SOLIDITY_VERSION}}", &self.config.solidity_version);
output = output.replace("{{NUM_PUBLIC_INPUTS}}", &vk.num_public_inputs.to_string());
output = output.replace("{{PROOF_LENGTH}}", &vk.proof_length.to_string());
let vk_points = self.generate_vk_points(vk);
output = output.replace("{{VK_POINTS}}", &vk_points);
let proof_decode = r#"
// Decode proof elements from calldata
uint256[2] memory a;
uint256[2][2] memory b;
uint256[2] memory c;
a[0] = uint256(bytes32(proof[0:32]));
a[1] = uint256(bytes32(proof[32:64]));
b[0][0] = uint256(bytes32(proof[64:96]));
b[0][1] = uint256(bytes32(proof[96:128]));
b[1][0] = uint256(bytes32(proof[128:160]));
b[1][1] = uint256(bytes32(proof[160:192]));
c[0] = uint256(bytes32(proof[192:224]));
c[1] = uint256(bytes32(proof[224:256]));"#;
output = output.replace("{{PROOF_DECODE}}", proof_decode);
let pi_compute = r#"
// Compute public input contribution
uint256 pi = 0;
for (uint256 i = 0; i < publicInputs.length; i++) {
pi = addmod(pi, mulmod(publicInputs[i], lagrangeBasis[i], FIELD_MODULUS), FIELD_MODULUS);
}"#;
output = output.replace("{{PUBLIC_INPUT_COMPUTATION}}", pi_compute);
let pairing_check = r#"
// Pairing check: e(A, B) == e(C, D) * e(α, β) * e(PI, γ) * e(vk, δ)
uint256[12] memory input;
// -A
input[0] = a[0];
input[1] = (FIELD_MODULUS - a[1]) % FIELD_MODULUS;
// B
input[2] = b[0][0];
input[3] = b[0][1];
input[4] = b[1][0];
input[5] = b[1][1];
// C
input[6] = c[0];
input[7] = c[1];
// Delta (from VK)
input[8] = VK_DELTA_X1;
input[9] = VK_DELTA_X2;
input[10] = VK_DELTA_Y1;
input[11] = VK_DELTA_Y2;
uint256[1] memory out;
bool success;
assembly {
success := staticcall(gas(), 0x08, input, 384, out, 32)
}
require(success, "pairing failed");
return out[0] == 1;"#;
output = output.replace("{{PAIRING_CHECK}}", pairing_check);
output
}
pub fn generate_complete(&self, vk: &super::serialization::SerializedVk) -> String {
let template = super::complete_verifier::COMPLETE_VERIFIER_TEMPLATE;
let _vk_constants = super::serialization::generate_vk_constants(vk);
let mut output = template.to_string();
output = output.replace("{{CONTRACT_NAME}}", &self.config.contract_name);
output = output.replace("{{SOLIDITY_VERSION}}", &self.config.solidity_version);
output = output.replace("{{VK_CONSTANTS}}", ""); output = output.replace("{{PROOF_SIZE}}", "576");
output = output.replace("{{VK_ALPHA_X}}", &vk.alpha.0);
output = output.replace("{{VK_ALPHA_Y}}", &vk.alpha.1);
output = output.replace("{{VK_BETA_X1}}", &vk.beta.0);
output = output.replace("{{VK_BETA_X2}}", &vk.beta.1);
output = output.replace("{{VK_BETA_Y1}}", &vk.beta.2);
output = output.replace("{{VK_BETA_Y2}}", &vk.beta.3);
output = output.replace("{{VK_GAMMA_X1}}", &vk.gamma.0);
output = output.replace("{{VK_GAMMA_X2}}", &vk.gamma.1);
output = output.replace("{{VK_GAMMA_Y1}}", &vk.gamma.2);
output = output.replace("{{VK_GAMMA_Y2}}", &vk.gamma.3);
output = output.replace("{{VK_DELTA_X1}}", &vk.delta.0);
output = output.replace("{{VK_DELTA_X2}}", &vk.delta.1);
output = output.replace("{{VK_DELTA_Y1}}", &vk.delta.2);
output = output.replace("{{VK_DELTA_Y2}}", &vk.delta.3);
output = output.replace("{{VK_DOMAIN_SIZE}}", &vk.domain_size.to_string());
output = output.replace("{{VK_OMEGA}}", &vk.omega);
output
}
pub fn generate_aggregated(&self, vk: &super::serialization::SerializedVk) -> String {
let template = super::template::AGGREGATED_VERIFIER_TEMPLATE;
let vk_constants = super::serialization::generate_vk_constants(vk);
let mut output = template.to_string();
output = output.replace("{{CONTRACT_NAME}}", &self.config.contract_name);
output = output.replace("{{SOLIDITY_VERSION}}", &self.config.solidity_version);
output = output.replace("{{VK_CONSTANTS}}", &vk_constants);
output
}
pub fn generate_stub(&self) -> String {
super::template::MINIMAL_TEMPLATE.replace("{{CONTRACT_NAME}}", &self.config.contract_name)
}
fn generate_vk_points(&self, vk: &SolidityVk) -> String {
let mut lines = Vec::new();
for (i, (x, y)) in vk.commitments.iter().enumerate() {
lines.push(format!(" uint256 internal constant VK_{}_X = {};", i, x));
lines.push(format!(" uint256 internal constant VK_{}_Y = {};", i, y));
}
lines.join("\n")
}
}
impl<E: PairingEngine> Default for SolidityGenerator<E> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::bn254::Bn254;
#[test]
fn generator_creates_with_default_config() {
let gen = SolidityGenerator::<Bn254>::new();
assert_eq!(gen.config.contract_name, "Verifier");
}
#[test]
fn generator_creates_with_custom_name() {
let config = SolidityConfig::with_name("AggregatedVerifier");
let gen = SolidityGenerator::<Bn254>::with_config(config);
assert_eq!(gen.config.contract_name, "AggregatedVerifier");
}
#[test]
fn generator_produces_valid_solidity() {
let gen = SolidityGenerator::<Bn254>::new();
let vk = SolidityVk {
commitments: vec![("0x1234".to_string(), "0x5678".to_string())],
num_public_inputs: 2,
proof_length: 256,
};
let output = gen.generate(&vk);
assert!(output.contains("pragma solidity"));
assert!(output.contains("contract Verifier"));
assert!(output.contains("VK_0_X"));
assert!(output.contains("publicInputs.length != 2"));
assert!(output.contains("proof.length != 256"));
}
#[test]
fn generator_stub_is_valid_solidity() {
let gen =
SolidityGenerator::<Bn254>::with_config(SolidityConfig::with_name("TestVerifier"));
let stub = gen.generate_stub();
assert!(stub.contains("pragma solidity ^0.8.20"));
assert!(stub.contains("contract TestVerifier"));
assert!(stub.contains("function verify"));
}
#[test]
fn generator_complete_embeds_vk() {
use super::super::serialization::SerializedVk;
let gen =
SolidityGenerator::<Bn254>::with_config(SolidityConfig::with_name("CompleteVerifier"));
let vk = SerializedVk {
alpha: ("0x1".to_string(), "0x2".to_string()),
beta: (
"0x3".to_string(),
"0x4".to_string(),
"0x5".to_string(),
"0x6".to_string(),
),
gamma: (
"0x7".to_string(),
"0x8".to_string(),
"0x9".to_string(),
"0xa".to_string(),
),
delta: (
"0xb".to_string(),
"0xc".to_string(),
"0xd".to_string(),
"0xe".to_string(),
),
tau_g2: (
"0xf".to_string(),
"0x10".to_string(),
"0x11".to_string(),
"0x12".to_string(),
),
ic: vec![],
num_public_inputs: 2,
domain_size: 1024,
omega: "0xabc".to_string(),
};
let output = gen.generate_complete(&vk);
assert!(output.contains("contract CompleteVerifier"));
assert!(output.contains("_pairing"));
assert!(output.contains("_scalarMul"));
assert!(output.contains("VK_ALPHA_X = 0x1"));
}
}