1use zk402_core::*;
6use zk402_groth16::proof_system::Groth16ProofSystem;
7
8#[derive(Debug)]
9pub enum ProverError {
10 InvalidRequirements(String),
11 ProofGenerationFailed(String),
12 KeyDerivationFailed(String),
13}
14
15impl std::fmt::Display for ProverError {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 match self {
18 Self::InvalidRequirements(msg) => write!(f, "invalid requirements: {msg}"),
19 Self::ProofGenerationFailed(msg) => write!(f, "proof generation failed: {msg}"),
20 Self::KeyDerivationFailed(msg) => write!(f, "key derivation failed: {msg}"),
21 }
22 }
23}
24
25impl std::error::Error for ProverError {}
26
27pub struct ClientWitness {
29 pub secret_key: Vec<u8>,
31 pub from: [u8; 20],
33 pub payment_nonce: [u8; 32],
35}
36
37pub struct ProverConfig {
39 pub proving_key: Vec<u8>,
41 pub valid_after: u64,
43 pub valid_before: u64,
45}
46
47impl ProverConfig {
48 pub fn with_validity_window(proving_key: Vec<u8>, duration_secs: u64) -> Self {
50 let now = std::time::SystemTime::now()
51 .duration_since(std::time::UNIX_EPOCH)
52 .unwrap()
53 .as_secs();
54
55 Self {
56 proving_key,
57 valid_after: now,
58 valid_before: now + duration_secs,
59 }
60 }
61}
62
63pub fn build_payload(
67 requirements: &PaymentRequirements,
68 witness: &ClientWitness,
69 config: &ProverConfig,
70) -> Result<PaymentPayload, ProverError> {
71 let public_key = zk402_groth16::derive_public_key(&witness.secret_key)
73 .map_err(|e| ProverError::KeyDerivationFailed(e.to_string()))?;
74
75 let public_inputs = PublicInputs {
77 amount: requirements.amount.clone(),
78 asset: requirements.asset,
79 pay_to: requirements.pay_to,
80 from: witness.from,
81 network: requirements.network.clone(),
82 nonce: witness.payment_nonce,
83 valid_after: config.valid_after,
84 valid_before: config.valid_before,
85 public_key,
86 };
87
88 let proof_witness = Witness {
90 secret_key: witness.secret_key.clone(),
91 nonce_scalar: vec![0u8; 32], };
93
94 let proof = Groth16ProofSystem::prove(&config.proving_key, &proof_witness, &public_inputs)
96 .map_err(|e| ProverError::ProofGenerationFailed(e.to_string()))?;
97
98 Ok(PaymentPayload {
100 x402_version: 1,
101 accepted: requirements.clone(),
102 proof,
103 public_inputs,
104 proof_system_extra: None,
105 })
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn builds_valid_payload_structure() {
114 let requirements = PaymentRequirements {
116 scheme: "zk-settle".to_string(),
117 network: "eip155:8453".to_string(),
118 amount: "100".to_string(),
119 asset: [1u8; 20],
120 pay_to: [2u8; 20],
121 max_timeout_seconds: 3600,
122 extra: ZkExtra {
123 proof_system: "groth16".to_string(),
124 circuit_id: "zk-settle-transfer-v1".to_string(),
125 verifier_address: [3u8; 20],
126 },
127 };
128
129 let witness = ClientWitness {
130 secret_key: b"test-secret-key-32-bytes-long!!!".to_vec(),
131 from: [4u8; 20],
132 payment_nonce: [5u8; 32],
133 };
134
135 let config = ProverConfig::with_validity_window(vec![0u8; 100], 3600);
138
139 let result = build_payload(&requirements, &witness, &config);
140
141 assert!(result.is_err());
143 }
144
145 #[test]
146 fn derives_public_key_correctly() {
147 let secret = b"test-secret-key-32-bytes-long!!!";
148 let result = zk402_groth16::derive_public_key(secret);
149 assert!(result.is_ok());
150 let pubkey = result.unwrap();
151 assert!(!pubkey.is_empty());
152 }
153}