1use std::collections::HashMap;
7use std::time::{SystemTime, UNIX_EPOCH};
8use zk402_core::*;
9
10pub mod settlement;
11pub use settlement::SettlementConfig;
12
13pub struct StaticRegistry<VK> {
15 keys: HashMap<(String, String), VK>,
16}
17
18impl<VK: Clone> StaticRegistry<VK> {
19 pub fn new() -> Self {
20 Self {
21 keys: HashMap::new(),
22 }
23 }
24
25 pub fn register(&mut self, proof_system: String, circuit_id: String, vk: VK) {
26 self.keys.insert((proof_system, circuit_id), vk);
27 }
28}
29
30impl<VK: Clone> Default for StaticRegistry<VK> {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl<VK: Clone> VerifyingKeyRegistry for StaticRegistry<VK> {
37 type VerifyingKeyHandle = VK;
38
39 fn lookup(
40 &self,
41 proof_system: &str,
42 circuit_id: &str,
43 ) -> Option<Self::VerifyingKeyHandle> {
44 self.keys
45 .get(&(proof_system.to_string(), circuit_id.to_string()))
46 .cloned()
47 }
48}
49
50pub struct ZkSettleFacilitator<PS: ProofSystem, VKR: VerifyingKeyRegistry> {
52 registry: VKR,
53 settlement_config: Option<SettlementConfig>,
54 _phantom: std::marker::PhantomData<PS>,
55}
56
57impl<PS: ProofSystem, VKR: VerifyingKeyRegistry<VerifyingKeyHandle = PS::VerifyingKey>>
58 ZkSettleFacilitator<PS, VKR>
59{
60 pub fn new(registry: VKR) -> Self {
61 Self {
62 registry,
63 settlement_config: None,
64 _phantom: std::marker::PhantomData,
65 }
66 }
67
68 pub fn with_settlement(mut self, config: SettlementConfig) -> Self {
69 self.settlement_config = Some(config);
70 self
71 }
72
73 fn validate_structural_match(
75 req: &PaymentRequirements,
76 payload: &PaymentPayload,
77 ) -> Result<(), SchemeError> {
78 let pi = &payload.public_inputs;
79
80 if pi.amount != req.amount {
81 return Err(SchemeError::StructuralMismatch(format!(
82 "amount mismatch: expected {}, got {}",
83 req.amount, pi.amount
84 )));
85 }
86
87 if pi.asset != req.asset {
88 return Err(SchemeError::StructuralMismatch(format!(
89 "asset mismatch: expected 0x{}, got 0x{}",
90 hex::encode(req.asset),
91 hex::encode(pi.asset)
92 )));
93 }
94
95 if pi.pay_to != req.pay_to {
96 return Err(SchemeError::StructuralMismatch(format!(
97 "payTo mismatch: expected 0x{}, got 0x{}",
98 hex::encode(req.pay_to),
99 hex::encode(pi.pay_to)
100 )));
101 }
102
103 if pi.network != req.network {
104 return Err(SchemeError::StructuralMismatch(format!(
105 "network mismatch: expected {}, got {}",
106 req.network, pi.network
107 )));
108 }
109
110 Ok(())
111 }
112
113 fn trust_check(&self, extra: &ZkExtra) -> Result<PS::VerifyingKey, SchemeError> {
115 self.registry
116 .lookup(&extra.proof_system, &extra.circuit_id)
117 .ok_or(SchemeError::UnsupportedCircuit)
118 }
119
120 fn cryptographic_verification(
122 vk: &PS::VerifyingKey,
123 payload: &PaymentPayload,
124 ) -> Result<(), SchemeError> {
125 PS::verify(vk, &payload.proof, &payload.public_inputs)
126 .map_err(|_| SchemeError::Internal("proof verification error".to_string()))?
127 .then_some(())
128 .ok_or(SchemeError::CryptographicVerificationFailed)
129 }
130
131 fn freshness_check(public_inputs: &PublicInputs) -> Result<(), SchemeError> {
133 let now = SystemTime::now()
134 .duration_since(UNIX_EPOCH)
135 .map_err(|e| SchemeError::Internal(format!("system time error: {}", e)))?
136 .as_secs();
137
138 if now < public_inputs.valid_after {
139 return Err(SchemeError::Expired);
140 }
141
142 if now >= public_inputs.valid_before {
143 return Err(SchemeError::Expired);
144 }
145
146 Ok(())
147 }
148}
149
150#[async_trait::async_trait]
151impl<PS: ProofSystem + Send + Sync, VKR: VerifyingKeyRegistry<VerifyingKeyHandle = PS::VerifyingKey> + Send + Sync>
152 Scheme for ZkSettleFacilitator<PS, VKR>
153{
154 fn scheme_name(&self) -> &str {
155 "zk-settle"
156 }
157
158 async fn verify(
159 &self,
160 req: &PaymentRequirements,
161 payload: &PaymentPayload,
162 ) -> Result<VerifyResponse, SchemeError> {
163 Self::validate_structural_match(req, payload)?;
165
166 let vk = self.trust_check(&req.extra)?;
168
169 Self::cryptographic_verification(&vk, payload)?;
171
172 Self::freshness_check(&payload.public_inputs)?;
174
175 Ok(VerifyResponse {
178 is_valid: true,
179 invalid_reason: None,
180 })
181 }
182
183 async fn settle(
184 &self,
185 _req: &PaymentRequirements,
186 payload: &PaymentPayload,
187 ) -> Result<SettleResponse, SchemeError> {
188 let config = self
189 .settlement_config
190 .as_ref()
191 .ok_or_else(|| SchemeError::Internal("settlement not configured".to_string()))?;
192
193 match settlement::submit_settlement(config, payload).await {
194 Ok(tx_hash) => Ok(SettleResponse {
195 success: true,
196 tx_hash: Some(tx_hash),
197 error: None,
198 }),
199 Err(e) => Ok(SettleResponse {
200 success: false,
201 tx_hash: None,
202 error: Some(e.to_string()),
203 }),
204 }
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 struct MockProofSystem;
213
214 impl ProofSystem for MockProofSystem {
215 type ProvingKey = Vec<u8>;
216 type VerifyingKey = Vec<u8>;
217
218 fn prove(
219 _pk: &Self::ProvingKey,
220 _witness: &Witness,
221 _public_inputs: &PublicInputs,
222 ) -> Result<Vec<u8>, ProofSystemError> {
223 Ok(vec![0x42])
224 }
225
226 fn verify(
227 _vk: &Self::VerifyingKey,
228 proof: &[u8],
229 _public_inputs: &PublicInputs,
230 ) -> Result<bool, ProofSystemError> {
231 Ok(proof == &[0x42])
233 }
234 }
235
236 fn test_requirements() -> PaymentRequirements {
237 PaymentRequirements {
238 scheme: "zk-settle".to_string(),
239 network: "eip155:8453".to_string(),
240 amount: "100".to_string(),
241 asset: [1u8; 20],
242 pay_to: [2u8; 20],
243 max_timeout_seconds: 3600,
244 extra: ZkExtra {
245 proof_system: "groth16".to_string(),
246 circuit_id: "zk-settle-transfer-v1".to_string(),
247 verifier_address: [3u8; 20],
248 },
249 }
250 }
251
252 fn test_payload(proof: Vec<u8>) -> PaymentPayload {
253 let req = test_requirements();
254 PaymentPayload {
255 x402_version: 1,
256 accepted: req.clone(),
257 proof,
258 public_inputs: PublicInputs {
259 amount: req.amount.clone(),
260 asset: req.asset,
261 pay_to: req.pay_to,
262 from: [4u8; 20],
263 network: req.network.clone(),
264 nonce: [5u8; 32],
265 valid_after: current_timestamp() - 60,
266 valid_before: current_timestamp() + 3600,
267 public_key: vec![6u8; 32],
268 },
269 proof_system_extra: None,
270 }
271 }
272
273 fn current_timestamp() -> u64 {
274 SystemTime::now()
275 .duration_since(UNIX_EPOCH)
276 .unwrap()
277 .as_secs()
278 }
279
280 #[tokio::test]
281 async fn valid_proof_passes_verification() {
282 let mut registry = StaticRegistry::new();
283 registry.register(
284 "groth16".to_string(),
285 "zk-settle-transfer-v1".to_string(),
286 vec![0u8],
287 );
288
289 let facilitator = ZkSettleFacilitator::<MockProofSystem, _>::new(registry);
290 let req = test_requirements();
291 let payload = test_payload(vec![0x42]);
292
293 let result = facilitator.verify(&req, &payload).await;
294 assert!(result.is_ok());
295 assert!(result.unwrap().is_valid);
296 }
297
298 #[tokio::test]
299 async fn tampered_amount_fails_structural_match() {
300 let mut registry = StaticRegistry::new();
301 registry.register(
302 "groth16".to_string(),
303 "zk-settle-transfer-v1".to_string(),
304 vec![0u8],
305 );
306
307 let facilitator = ZkSettleFacilitator::<MockProofSystem, _>::new(registry);
308 let req = test_requirements();
309 let mut payload = test_payload(vec![0x42]);
310 payload.public_inputs.amount = "101".to_string();
311
312 let result = facilitator.verify(&req, &payload).await;
313 assert!(matches!(result, Err(SchemeError::StructuralMismatch(_))));
314 }
315
316 #[tokio::test]
317 async fn unknown_circuit_id_fails_trust_check() {
318 let registry = StaticRegistry::<Vec<u8>>::new();
319 let facilitator = ZkSettleFacilitator::<MockProofSystem, _>::new(registry);
320 let req = test_requirements();
321 let payload = test_payload(vec![0x42]);
322
323 let result = facilitator.verify(&req, &payload).await;
324 assert!(matches!(result, Err(SchemeError::UnsupportedCircuit)));
325 }
326
327 #[tokio::test]
328 async fn invalid_proof_fails_crypto_verification() {
329 let mut registry = StaticRegistry::new();
330 registry.register(
331 "groth16".to_string(),
332 "zk-settle-transfer-v1".to_string(),
333 vec![0u8],
334 );
335
336 let facilitator = ZkSettleFacilitator::<MockProofSystem, _>::new(registry);
337 let req = test_requirements();
338 let payload = test_payload(vec![0x99]); let result = facilitator.verify(&req, &payload).await;
341 assert!(matches!(
342 result,
343 Err(SchemeError::CryptographicVerificationFailed)
344 ));
345 }
346
347 #[tokio::test]
348 async fn expired_window_fails_freshness() {
349 let mut registry = StaticRegistry::new();
350 registry.register(
351 "groth16".to_string(),
352 "zk-settle-transfer-v1".to_string(),
353 vec![0u8],
354 );
355
356 let facilitator = ZkSettleFacilitator::<MockProofSystem, _>::new(registry);
357 let req = test_requirements();
358 let mut payload = test_payload(vec![0x42]);
359 payload.public_inputs.valid_before = current_timestamp() - 10; let result = facilitator.verify(&req, &payload).await;
362 assert!(matches!(result, Err(SchemeError::Expired)));
363 }
364}