use crate::error::ProverError;
use crate::witness::{DepositWitness, TransferWitness, WithdrawWitness};
use std::path::{Path, PathBuf};
use std::process::Command;
use tokio::fs;
use tracing::{debug, info};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CircuitType {
Deposit,
Withdraw,
Transfer,
}
impl CircuitType {
pub fn name(&self) -> &'static str {
match self {
CircuitType::Deposit => "deposit",
CircuitType::Withdraw => "withdraw",
CircuitType::Transfer => "transfer",
}
}
}
pub struct NoirProver {
circuits_dir: PathBuf,
target_dir: PathBuf,
work_dir: PathBuf,
}
impl NoirProver {
pub fn new(circuits_dir: PathBuf) -> Self {
let target_dir = circuits_dir.join("target");
let work_dir = circuits_dir.join("work");
Self {
circuits_dir,
target_dir,
work_dir,
}
}
pub fn default_paths() -> Self {
let circuits_dir = PathBuf::from("circuits");
Self::new(circuits_dir)
}
async fn ensure_work_dir(&self) -> Result<(), ProverError> {
fs::create_dir_all(&self.work_dir).await?;
Ok(())
}
pub fn is_compiled(&self) -> bool {
self.target_dir.join("veilocity_circuits.json").exists()
}
pub async fn compile(&self) -> Result<(), ProverError> {
info!("Compiling Noir circuits...");
let output = Command::new("nargo")
.current_dir(&self.circuits_dir)
.arg("compile")
.output()
.map_err(|e| ProverError::CommandFailed(format!("Failed to run nargo: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(ProverError::CommandFailed(format!(
"nargo compile failed: {}",
stderr
)));
}
info!("Circuits compiled successfully");
Ok(())
}
pub async fn prove_deposit(&self, witness: &DepositWitness) -> Result<Vec<u8>, ProverError> {
self.ensure_work_dir().await?;
let prover_toml = self.work_dir.join("Prover.toml");
fs::write(&prover_toml, witness.to_toml()).await?;
self.generate_proof(CircuitType::Deposit).await
}
pub async fn prove_withdraw(&self, witness: &WithdrawWitness) -> Result<Vec<u8>, ProverError> {
self.ensure_work_dir().await?;
let prover_toml = self.work_dir.join("Prover.toml");
fs::write(&prover_toml, witness.to_toml()).await?;
self.generate_proof(CircuitType::Withdraw).await
}
pub async fn prove_transfer(&self, witness: &TransferWitness) -> Result<Vec<u8>, ProverError> {
self.ensure_work_dir().await?;
let prover_toml = self.work_dir.join("Prover.toml");
fs::write(&prover_toml, witness.to_toml()).await?;
self.generate_proof(CircuitType::Transfer).await
}
async fn generate_proof(&self, circuit_type: CircuitType) -> Result<Vec<u8>, ProverError> {
if !self.is_compiled() {
return Err(ProverError::CircuitNotCompiled);
}
let circuit_path = self.target_dir.join("veilocity_circuits.json");
let witness_path = self.work_dir.join("witness.gz");
let proof_path = self.work_dir.join("proof");
let prover_toml = self.work_dir.join("Prover.toml");
debug!("Generating witness for {:?}...", circuit_type);
let output = Command::new("nargo")
.current_dir(&self.circuits_dir)
.arg("execute")
.arg("--prover-toml")
.arg(&prover_toml)
.arg("--witness-path")
.arg(&witness_path)
.output()
.map_err(|e| ProverError::CommandFailed(format!("Failed to run nargo execute: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(ProverError::WitnessGeneration(stderr.to_string()));
}
debug!("Generating proof...");
let output = Command::new("bb")
.arg("prove")
.arg("-b")
.arg(&circuit_path)
.arg("-w")
.arg(&witness_path)
.arg("-o")
.arg(&proof_path)
.output()
.map_err(|e| ProverError::CommandFailed(format!("Failed to run bb prove: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(ProverError::ProofGeneration(stderr.to_string()));
}
let proof = fs::read(&proof_path).await?;
info!(
"Proof generated successfully ({} bytes)",
proof.len()
);
Ok(proof)
}
pub async fn verify_proof(
&self,
proof: &[u8],
_circuit_type: CircuitType,
) -> Result<bool, ProverError> {
if !self.is_compiled() {
return Err(ProverError::CircuitNotCompiled);
}
self.ensure_work_dir().await?;
let circuit_path = self.target_dir.join("veilocity_circuits.json");
let vk_path = self.target_dir.join("vk");
let proof_path = self.work_dir.join("proof_to_verify");
fs::write(&proof_path, proof).await?;
let output = Command::new("bb")
.arg("verify")
.arg("-b")
.arg(&circuit_path)
.arg("-k")
.arg(&vk_path)
.arg("-p")
.arg(&proof_path)
.output()
.map_err(|e| ProverError::CommandFailed(format!("Failed to run bb verify: {}", e)))?;
Ok(output.status.success())
}
pub async fn generate_vk(&self) -> Result<(), ProverError> {
if !self.is_compiled() {
return Err(ProverError::CircuitNotCompiled);
}
let circuit_path = self.target_dir.join("veilocity_circuits.json");
let vk_path = self.target_dir.join("vk");
info!("Generating verification key...");
let output = Command::new("bb")
.arg("write_vk")
.arg("-b")
.arg(&circuit_path)
.arg("-o")
.arg(&vk_path)
.arg("--oracle_hash")
.arg("keccak")
.output()
.map_err(|e| ProverError::CommandFailed(format!("Failed to run bb write_vk: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(ProverError::CommandFailed(format!(
"bb write_vk failed: {}",
stderr
)));
}
info!("Verification key generated");
Ok(())
}
pub async fn generate_solidity_verifier(&self, output_path: &Path) -> Result<(), ProverError> {
let vk_path = self.target_dir.join("vk");
if !vk_path.exists() {
self.generate_vk().await?;
}
info!("Generating Solidity verifier...");
let output = Command::new("bb")
.arg("write_solidity_verifier")
.arg("-k")
.arg(&vk_path)
.arg("-o")
.arg(output_path)
.output()
.map_err(|e| {
ProverError::CommandFailed(format!("Failed to run bb write_solidity_verifier: {}", e))
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(ProverError::CommandFailed(format!(
"bb write_solidity_verifier failed: {}",
stderr
)));
}
info!("Solidity verifier generated at {:?}", output_path);
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Proof {
pub proof: Vec<u8>,
pub public_inputs: Vec<String>,
pub circuit_type: String,
}
impl Proof {
pub fn new(proof: Vec<u8>, public_inputs: Vec<String>, circuit_type: CircuitType) -> Self {
Self {
proof,
public_inputs,
circuit_type: circuit_type.name().to_string(),
}
}
pub fn proof_hex(&self) -> String {
format!("0x{}", hex::encode(&self.proof))
}
}
use serde::{Deserialize, Serialize};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_circuit_type_names() {
assert_eq!(CircuitType::Deposit.name(), "deposit");
assert_eq!(CircuitType::Withdraw.name(), "withdraw");
assert_eq!(CircuitType::Transfer.name(), "transfer");
}
#[test]
fn test_prover_default_paths() {
let prover = NoirProver::default_paths();
assert!(prover.circuits_dir.ends_with("circuits"));
}
#[test]
fn test_proof_hex() {
let proof = Proof::new(vec![1, 2, 3, 4], vec![], CircuitType::Deposit);
assert_eq!(proof.proof_hex(), "0x01020304");
}
}