1use axum::{
6 extract::State,
7 http::StatusCode,
8 response::{IntoResponse, Response},
9 routing::post,
10 Json, Router,
11};
12use serde::Deserialize;
13use std::sync::Arc;
14use zk402_core::*;
15use zk402_facilitator::ZkSettleFacilitator;
16
17#[derive(Debug, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct VerifyRequest {
21 pub requirements: PaymentRequirements,
22 pub payload: PaymentPayload,
23}
24
25#[derive(Debug, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub struct SettleRequest {
29 pub requirements: PaymentRequirements,
30 pub payload: PaymentPayload,
31}
32
33pub struct AppState<PS: ProofSystem, VKR: VerifyingKeyRegistry> {
35 pub facilitator: Arc<ZkSettleFacilitator<PS, VKR>>,
36}
37
38pub fn app_router<PS, VKR>(facilitator: Arc<ZkSettleFacilitator<PS, VKR>>) -> Router
40where
41 PS: ProofSystem + Send + Sync + 'static,
42 VKR: VerifyingKeyRegistry<VerifyingKeyHandle = PS::VerifyingKey> + Send + Sync + 'static,
43{
44 let state = Arc::new(AppState { facilitator });
45
46 Router::new()
47 .route("/verify", post(verify_handler::<PS, VKR>))
48 .route("/settle", post(settle_handler::<PS, VKR>))
49 .with_state(state)
50}
51
52async fn verify_handler<PS, VKR>(
54 State(state): State<Arc<AppState<PS, VKR>>>,
55 Json(request): Json<VerifyRequest>,
56) -> Result<Json<VerifyResponse>, AppError>
57where
58 PS: ProofSystem + Send + Sync,
59 VKR: VerifyingKeyRegistry<VerifyingKeyHandle = PS::VerifyingKey> + Send + Sync,
60{
61 let response = state
62 .facilitator
63 .verify(&request.requirements, &request.payload)
64 .await
65 .map_err(AppError::from)?;
66
67 Ok(Json(response))
68}
69
70async fn settle_handler<PS, VKR>(
72 State(state): State<Arc<AppState<PS, VKR>>>,
73 Json(request): Json<SettleRequest>,
74) -> Result<Json<SettleResponse>, AppError>
75where
76 PS: ProofSystem + Send + Sync,
77 VKR: VerifyingKeyRegistry<VerifyingKeyHandle = PS::VerifyingKey> + Send + Sync,
78{
79 let response = state
80 .facilitator
81 .settle(&request.requirements, &request.payload)
82 .await
83 .map_err(AppError::from)?;
84
85 Ok(Json(response))
86}
87
88#[derive(Debug)]
90pub struct AppError(SchemeError);
91
92impl From<SchemeError> for AppError {
93 fn from(err: SchemeError) -> Self {
94 Self(err)
95 }
96}
97
98impl IntoResponse for AppError {
99 fn into_response(self) -> Response {
100 let (status, message) = match self.0 {
101 SchemeError::StructuralMismatch(msg) => (StatusCode::BAD_REQUEST, msg),
102 SchemeError::UnsupportedCircuit => {
103 (StatusCode::BAD_REQUEST, "unsupported circuit".to_string())
104 }
105 SchemeError::CryptographicVerificationFailed => {
106 (StatusCode::BAD_REQUEST, "proof verification failed".to_string())
107 }
108 SchemeError::Expired => {
109 (StatusCode::BAD_REQUEST, "payment window expired".to_string())
110 }
111 SchemeError::ChainError(msg) => (StatusCode::BAD_GATEWAY, msg),
112 SchemeError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
113 };
114
115 let body = serde_json::json!({
116 "error": message
117 });
118
119 (status, Json(body)).into_response()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use axum::http::Request;
127 use tower::ServiceExt;
128 use zk402_facilitator::StaticRegistry;
129
130 struct MockProofSystem;
131
132 impl ProofSystem for MockProofSystem {
133 type ProvingKey = Vec<u8>;
134 type VerifyingKey = Vec<u8>;
135
136 fn prove(
137 _pk: &Self::ProvingKey,
138 _witness: &Witness,
139 _public_inputs: &PublicInputs,
140 ) -> Result<Vec<u8>, ProofSystemError> {
141 Ok(vec![0x42])
142 }
143
144 fn verify(
145 _vk: &Self::VerifyingKey,
146 proof: &[u8],
147 _public_inputs: &PublicInputs,
148 ) -> Result<bool, ProofSystemError> {
149 Ok(proof == &[0x42])
150 }
151 }
152
153 fn test_facilitator() -> Arc<ZkSettleFacilitator<MockProofSystem, StaticRegistry<Vec<u8>>>> {
154 let mut registry = StaticRegistry::new();
155 registry.register(
156 "groth16".to_string(),
157 "zk-settle-transfer-v1".to_string(),
158 vec![0u8],
159 );
160 Arc::new(ZkSettleFacilitator::new(registry))
161 }
162
163 fn test_payload() -> PaymentPayload {
164 PaymentPayload {
165 x402_version: 1,
166 accepted: PaymentRequirements {
167 scheme: "zk-settle".to_string(),
168 network: "eip155:8453".to_string(),
169 amount: "100".to_string(),
170 asset: [1u8; 20],
171 pay_to: [2u8; 20],
172 max_timeout_seconds: 3600,
173 extra: ZkExtra {
174 proof_system: "groth16".to_string(),
175 circuit_id: "zk-settle-transfer-v1".to_string(),
176 verifier_address: [3u8; 20],
177 },
178 },
179 proof: vec![0x42],
180 public_inputs: PublicInputs {
181 amount: "100".to_string(),
182 asset: [1u8; 20],
183 pay_to: [2u8; 20],
184 from: [4u8; 20],
185 network: "eip155:8453".to_string(),
186 nonce: [5u8; 32],
187 valid_after: 1000000000,
188 valid_before: 9999999999,
189 public_key: vec![6u8; 32],
190 },
191 proof_system_extra: None,
192 }
193 }
194
195 #[tokio::test]
196 async fn verify_endpoint_returns_valid_response() {
197 let app = app_router(test_facilitator());
198 let payload = test_payload();
199
200 let request = Request::builder()
201 .uri("/verify")
202 .method("POST")
203 .header("content-type", "application/json")
204 .body(
205 serde_json::to_string(&VerifyRequest {
206 requirements: payload.accepted.clone(),
207 payload: payload.clone(),
208 })
209 .unwrap(),
210 )
211 .unwrap();
212
213 let response = app.oneshot(request).await.unwrap();
214 assert_eq!(response.status(), StatusCode::OK);
215 }
216
217 #[tokio::test]
218 async fn verify_endpoint_rejects_invalid_proof() {
219 let app = app_router(test_facilitator());
220 let mut payload = test_payload();
221 payload.proof = vec![0x99]; let request = Request::builder()
224 .uri("/verify")
225 .method("POST")
226 .header("content-type", "application/json")
227 .body(
228 serde_json::to_string(&VerifyRequest {
229 requirements: payload.accepted.clone(),
230 payload: payload.clone(),
231 })
232 .unwrap(),
233 )
234 .unwrap();
235
236 let response = app.oneshot(request).await.unwrap();
237 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
238 }
239}