1use axum::{
4 extract::{Path, Query, State},
5 Json,
6};
7use serde::{Deserialize, Serialize};
8
9use crate::error::{Error, Result};
10use crate::middleware::{is_in_namespace, RequestNamespace};
11use crate::proofs::{ProofId, ProofMetadata, ProofType, StoredProof, SubmitProofRequest};
12use crate::state::AppState;
13
14pub async fn submit_proof(
18 State(state): State<AppState>,
19 ns_ext: Option<axum::Extension<RequestNamespace>>,
20 Json(request): Json<SubmitProofRequest>,
21) -> Result<Json<SubmitProofResponse>> {
22 if let Some(axum::Extension(RequestNamespace(Some(ref ns)))) = ns_ext {
24 if let Some(ref meta) = request.metadata {
25 if let Some(ref submitter) = meta.submitter {
26 if !is_in_namespace(submitter, ns) {
27 return Err(Error::Forbidden(format!(
28 "Submitter \"{}\" is not in namespace \"{}\"",
29 submitter, ns
30 )));
31 }
32 }
33 }
34 }
35
36 let proof_id = state
37 .proof_store
38 .submit(request)
39 .await
40 .map_err(|e| Error::ValidationError(e.to_string()))?;
41
42 let proof = state
43 .proof_store
44 .get(&proof_id)
45 .await
46 .ok_or_else(|| Error::Internal("Failed to retrieve submitted proof".to_string()))?;
47
48 Ok(Json(SubmitProofResponse {
49 proof_id,
50 submitted_at: proof.created_at,
51 }))
52}
53
54pub async fn submit_proofs_batch(
58 State(state): State<AppState>,
59 Json(request): Json<BatchSubmitRequest>,
60) -> Result<Json<BatchSubmitResponse>> {
61 let results = state.proof_store.submit_batch(request.proofs).await;
62
63 let mut successful = Vec::new();
64 let mut failed = Vec::new();
65
66 for (idx, result) in results.into_iter().enumerate() {
67 match result {
68 Ok(proof_id) => successful.push(proof_id),
69 Err(e) => failed.push(BatchError {
70 index: idx,
71 error: e.to_string(),
72 }),
73 }
74 }
75
76 Ok(Json(BatchSubmitResponse {
77 successful_count: successful.len(),
78 failed_count: failed.len(),
79 successful,
80 failed,
81 }))
82}
83
84pub async fn get_proof(
88 State(state): State<AppState>,
89 Path(proof_id): Path<ProofId>,
90) -> Result<Json<ProofResponse>> {
91 let proof = state
92 .proof_store
93 .get(&proof_id)
94 .await
95 .ok_or_else(|| Error::NotFound(format!("Proof {} not found", proof_id)))?;
96
97 Ok(Json(ProofResponse::from(proof)))
98}
99
100pub async fn verify_proof_by_id(
104 State(state): State<AppState>,
105 Path(proof_id): Path<ProofId>,
106) -> Result<Json<VerifyProofResponse>> {
107 let result = state
108 .proof_store
109 .verify(&proof_id)
110 .await
111 .map_err(|e| Error::ValidationError(e.to_string()))?;
112
113 Ok(Json(VerifyProofResponse {
114 proof_id: proof_id.clone(),
115 valid: result.valid,
116 verified_at: result.verified_at,
117 details: result.details,
118 verification_time_us: result.verification_time_us,
119 }))
120}
121
122pub async fn verify_proofs_batch(
126 State(state): State<AppState>,
127 Json(request): Json<BatchVerifyRequest>,
128) -> Result<Json<BatchVerifyResponse>> {
129 let results = state.proof_store.batch_verify(&request.proof_ids).await;
130
131 let mut verifications = Vec::new();
132 for (proof_id, result) in request.proof_ids.iter().zip(results.into_iter()) {
133 match result {
134 Ok(verification) => {
135 verifications.push(VerifyProofResponse {
136 proof_id: proof_id.clone(),
137 valid: verification.valid,
138 verified_at: verification.verified_at,
139 details: verification.details,
140 verification_time_us: verification.verification_time_us,
141 });
142 }
143 Err(e) => {
144 verifications.push(VerifyProofResponse {
145 proof_id: proof_id.clone(),
146 valid: false,
147 verified_at: chrono::Utc::now(),
148 details: vec![format!("Verification error: {}", e)],
149 verification_time_us: 0,
150 });
151 }
152 }
153 }
154
155 let valid_count = verifications.iter().filter(|v| v.valid).count();
156
157 Ok(Json(BatchVerifyResponse {
158 total: verifications.len(),
159 valid_count,
160 invalid_count: verifications.len() - valid_count,
161 verifications,
162 }))
163}
164
165pub async fn list_proofs(
169 State(state): State<AppState>,
170 ns_ext: Option<axum::Extension<RequestNamespace>>,
171 Query(params): Query<ListProofsQuery>,
172) -> Result<Json<ListProofsResponse>> {
173 let proofs = state.proof_store.list(params.proof_type).await;
174
175 let mut filtered_proofs = proofs;
176
177 if let Some(axum::Extension(RequestNamespace(Some(ref ns)))) = ns_ext {
179 filtered_proofs.retain(|p| {
180 p.metadata
181 .submitter
182 .as_deref()
183 .map(|s| is_in_namespace(s, ns))
184 .unwrap_or(false)
185 });
186 }
187
188 if let Some(verified) = params.verified {
190 filtered_proofs.retain(|p| p.verified == verified);
191 }
192
193 let limit = params.limit.unwrap_or(100).min(1000);
195 filtered_proofs.truncate(limit);
196
197 let proofs_response: Vec<ProofResponse> = filtered_proofs
198 .into_iter()
199 .map(ProofResponse::from)
200 .collect();
201
202 Ok(Json(ListProofsResponse {
203 count: proofs_response.len(),
204 proofs: proofs_response,
205 }))
206}
207
208pub async fn delete_proof(
212 State(state): State<AppState>,
213 ns_ext: Option<axum::Extension<RequestNamespace>>,
214 Path(proof_id): Path<ProofId>,
215) -> Result<Json<DeleteProofResponse>> {
216 if let Some(axum::Extension(RequestNamespace(Some(ref ns)))) = ns_ext {
218 if let Some(proof) = state.proof_store.get(&proof_id).await {
219 if let Some(ref submitter) = proof.metadata.submitter {
220 if !is_in_namespace(submitter, ns) {
221 return Err(Error::Forbidden(format!(
222 "Proof submitter is not in namespace \"{}\"",
223 ns
224 )));
225 }
226 }
227 }
228 }
229
230 let deleted = state.proof_store.delete(&proof_id).await;
231
232 if deleted {
233 Ok(Json(DeleteProofResponse {
234 proof_id,
235 deleted: true,
236 }))
237 } else {
238 Err(Error::NotFound(format!("Proof {} not found", proof_id)))
239 }
240}
241
242pub async fn get_proof_stats(State(state): State<AppState>) -> Result<Json<ProofStatsResponse>> {
246 let stats = state.proof_store.stats().await;
247
248 Ok(Json(ProofStatsResponse {
249 total_proofs: stats.total_proofs,
250 proofs_by_type: stats.proofs_by_type,
251 total_verifications: stats.total_verifications,
252 successful_verifications: stats.successful_verifications,
253 failed_verifications: stats.failed_verifications,
254 cache_hits: stats.cache_hits,
255 cache_misses: stats.cache_misses,
256 cache_hit_rate: if stats.cache_hits + stats.cache_misses > 0 {
257 stats.cache_hits as f64 / (stats.cache_hits + stats.cache_misses) as f64
258 } else {
259 0.0
260 },
261 total_size_bytes: stats.total_size_bytes,
262 }))
263}
264
265#[derive(Debug, Serialize)]
268pub struct SubmitProofResponse {
269 pub proof_id: ProofId,
270 pub submitted_at: chrono::DateTime<chrono::Utc>,
271}
272
273#[derive(Debug, Deserialize)]
274pub struct BatchSubmitRequest {
275 pub proofs: Vec<SubmitProofRequest>,
276}
277
278#[derive(Debug, Serialize)]
279pub struct BatchSubmitResponse {
280 pub successful_count: usize,
281 pub failed_count: usize,
282 pub successful: Vec<ProofId>,
283 pub failed: Vec<BatchError>,
284}
285
286#[derive(Debug, Serialize)]
287pub struct BatchError {
288 pub index: usize,
289 pub error: String,
290}
291
292#[derive(Debug, Serialize)]
293pub struct ProofResponse {
294 pub id: ProofId,
295 pub proof_type: ProofType,
296 pub created_at: chrono::DateTime<chrono::Utc>,
297 pub verified: bool,
298 pub verified_at: Option<chrono::DateTime<chrono::Utc>>,
299 pub metadata: ProofMetadata,
300 pub size_bytes: usize,
301}
302
303impl From<StoredProof> for ProofResponse {
304 fn from(proof: StoredProof) -> Self {
305 let size_bytes = proof.size_bytes();
306 Self {
307 id: proof.id,
308 proof_type: proof.proof_type,
309 created_at: proof.created_at,
310 verified: proof.verified,
311 verified_at: proof.verified_at,
312 metadata: proof.metadata,
313 size_bytes,
314 }
315 }
316}
317
318#[derive(Debug, Serialize)]
319pub struct VerifyProofResponse {
320 pub proof_id: ProofId,
321 pub valid: bool,
322 pub verified_at: chrono::DateTime<chrono::Utc>,
323 pub details: Vec<String>,
324 pub verification_time_us: u64,
325}
326
327#[derive(Debug, Deserialize)]
328pub struct BatchVerifyRequest {
329 pub proof_ids: Vec<ProofId>,
330}
331
332#[derive(Debug, Serialize)]
333pub struct BatchVerifyResponse {
334 pub total: usize,
335 pub valid_count: usize,
336 pub invalid_count: usize,
337 pub verifications: Vec<VerifyProofResponse>,
338}
339
340#[derive(Debug, Deserialize)]
341pub struct ListProofsQuery {
342 pub proof_type: Option<ProofType>,
343 pub verified: Option<bool>,
344 pub limit: Option<usize>,
345}
346
347#[derive(Debug, Serialize)]
348pub struct ListProofsResponse {
349 pub count: usize,
350 pub proofs: Vec<ProofResponse>,
351}
352
353#[derive(Debug, Serialize)]
354pub struct DeleteProofResponse {
355 pub proof_id: ProofId,
356 pub deleted: bool,
357}
358
359#[derive(Debug, Serialize)]
360pub struct ProofStatsResponse {
361 pub total_proofs: usize,
362 pub proofs_by_type: std::collections::HashMap<String, usize>,
363 pub total_verifications: usize,
364 pub successful_verifications: usize,
365 pub failed_verifications: usize,
366 pub cache_hits: usize,
367 pub cache_misses: usize,
368 pub cache_hit_rate: f64,
369 pub total_size_bytes: usize,
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use crate::state::AppState;
376 use axum::extract::State as AxumState;
377
378 #[tokio::test]
379 async fn test_submit_and_get_proof() {
380 let state = AppState::new();
381
382 let request = SubmitProofRequest {
383 proof_type: ProofType::Knowledge,
384 proof_data: serde_json::json!({
385 "commitment": vec![0u8; 32],
386 "challenge": vec![1u8; 32],
387 "response": vec![2u8; 32],
388 }),
389 metadata: None,
390 };
391
392 let response = submit_proof(AxumState(state.clone()), None, Json(request))
393 .await
394 .unwrap();
395
396 let proof_id = response.0.proof_id.clone();
397 assert!(!proof_id.is_empty());
398
399 let get_response = get_proof(AxumState(state), Path(proof_id)).await.unwrap();
400
401 assert_eq!(get_response.0.proof_type, ProofType::Knowledge);
402 }
403
404 #[tokio::test]
405 async fn test_list_proofs() {
406 let state = AppState::new();
407
408 for _ in 0..3 {
410 let request = SubmitProofRequest {
411 proof_type: ProofType::Schnorr,
412 proof_data: serde_json::json!({"test": "data"}),
413 metadata: None,
414 };
415 submit_proof(AxumState(state.clone()), None, Json(request))
416 .await
417 .unwrap();
418 }
419
420 let query = ListProofsQuery {
421 proof_type: None,
422 verified: None,
423 limit: Some(10),
424 };
425
426 let response = list_proofs(AxumState(state), None, Query(query)).await.unwrap();
427
428 assert_eq!(response.0.count, 3);
429 }
430
431 #[tokio::test]
432 async fn test_proof_stats() {
433 let state = AppState::new();
434
435 let request = SubmitProofRequest {
436 proof_type: ProofType::Equality,
437 proof_data: serde_json::json!({"test": "data"}),
438 metadata: None,
439 };
440
441 submit_proof(AxumState(state.clone()), None, Json(request))
442 .await
443 .unwrap();
444
445 let response = get_proof_stats(AxumState(state)).await.unwrap();
446
447 assert_eq!(response.0.total_proofs, 1);
448 }
449}