use crate::{ConclaveError, ConclaveResult};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ProofSystem {
Snark,
Stark,
Auto,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZkmlProofRequest {
pub model_id: String,
pub input_commitment: String,
pub compliance_rule: String,
pub proof_system: Option<ProofSystem>,
pub expected_output_hash: Option<String>,
}
impl ZkmlProofRequest {
pub fn new(model_id: &str, input_commitment: &str, compliance_rule: &str) -> Self {
Self {
model_id: model_id.to_string(),
input_commitment: input_commitment.to_string(),
compliance_rule: compliance_rule.to_string(),
proof_system: None,
expected_output_hash: None,
}
}
pub fn with_proof_system(mut self, system: ProofSystem) -> Self {
self.proof_system = Some(system);
self
}
pub fn with_expected_output(mut self, hash: &str) -> Self {
self.expected_output_hash = Some(hash.to_string());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZkmlProofResponse {
pub proof_hex: String,
pub verified: bool,
pub output_commitment: String,
pub proof_system: ProofSystem,
pub verification_time_ms: Option<u64>,
pub proof_size_bytes: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZkmlModelInference {
pub model_id: String,
pub input_hash: String,
pub output_hash: String,
pub rules_passed: Vec<String>,
pub rules_failed: Vec<String>,
}
pub struct ZkmlService {
pub gateway_url: String,
pub http_client: reqwest::Client,
}
impl ZkmlService {
pub fn new(gateway_url: String, http_client: reqwest::Client) -> Self {
Self {
gateway_url,
http_client,
}
}
pub async fn generate_compliance_proof(
&self,
request: ZkmlProofRequest,
) -> ConclaveResult<ZkmlProofResponse> {
let url = format!("{}/v1/zkml/prove", self.gateway_url);
let response = self
.http_client
.post(&url)
.json(&request)
.send()
.await
.map_err(|e| ConclaveError::NetworkError(format!("ZKML request failed: {}", e)))?;
if !response.status().is_success() {
return Err(ConclaveError::EnclaveFailure(format!(
"Gateway ZKML error: {}",
response.status()
)));
}
let proof = response
.json::<ZkmlProofResponse>()
.await
.map_err(|e| ConclaveError::CryptoError(format!("Invalid ZKML response: {}", e)))?;
Ok(proof)
}
pub async fn verify_proof_locally(
&self,
proof_hex: &str,
public_inputs: &[String],
) -> ConclaveResult<bool> {
let url = format!("{}/v1/zkml/verify", self.gateway_url);
#[derive(Serialize)]
struct VerifyRequest<'a> {
proof_hex: &'a str,
public_inputs: &'a [String],
}
let response = self
.http_client
.post(&url)
.json(&VerifyRequest {
proof_hex,
public_inputs,
})
.send()
.await
.map_err(|e| ConclaveError::NetworkError(format!("ZKML verify failed: {}", e)))?;
#[derive(Deserialize)]
struct VerifyResponse {
valid: bool,
}
let result = response
.json::<VerifyResponse>()
.await
.map_err(|e| ConclaveError::CryptoError(format!("Invalid verify response: {}", e)))?;
Ok(result.valid)
}
pub async fn get_supported_proof_systems(
&self,
model_id: &str,
) -> ConclaveResult<Vec<ProofSystem>> {
let url = format!(
"{}/v1/zkml/models/{}/proof-systems",
self.gateway_url, model_id
);
let response = self
.http_client
.get(&url)
.send()
.await
.map_err(|e| ConclaveError::NetworkError(format!("ZKML request failed: {}", e)))?;
if !response.status().is_success() {
return Err(ConclaveError::EnclaveFailure(format!(
"Gateway error: {}",
response.status()
)));
}
let systems: Vec<String> = response
.json()
.await
.map_err(|e| ConclaveError::CryptoError(format!("Invalid response: {}", e)))?;
let proof_systems = systems
.iter()
.filter_map(|s| match s.as_str() {
"snark" => Some(ProofSystem::Snark),
"stark" => Some(ProofSystem::Stark),
_ => None,
})
.collect();
Ok(proof_systems)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zkml_service_new() {
let client = reqwest::Client::new();
let service = ZkmlService::new("https://gateway.conxian-labs.com".to_string(), client);
assert_eq!(service.gateway_url, "https://gateway.conxian-labs.com");
}
#[tokio::test]
async fn test_zkml_request_construction() {
let req = ZkmlProofRequest {
model_id: "compliance_v1".to_string(),
input_commitment: "0xabc".to_string(),
compliance_rule: "KYC_AML".to_string(),
proof_system: None,
expected_output_hash: None,
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("compliance_v1"));
}
}