Skip to main content

zk402_prover/
lib.rs

1//! Client-side proof generation
2//!
3//! Takes private witness + PaymentRequirements, generates proof, assembles PaymentPayload.
4
5use 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
27/// Client witness data for proof generation
28pub struct ClientWitness {
29    /// Private signing key (raw bytes)
30    pub secret_key: Vec<u8>,
31    /// Payment source address (must match derived public key)
32    pub from: [u8; 20],
33    /// Random nonce for this payment (32 bytes)
34    pub payment_nonce: [u8; 32],
35}
36
37/// Configuration for proof generation
38pub struct ProverConfig {
39    /// Groth16 proving key (serialized)
40    pub proving_key: Vec<u8>,
41    /// Validity window start (unix timestamp)
42    pub valid_after: u64,
43    /// Validity window end (unix timestamp)
44    pub valid_before: u64,
45}
46
47impl ProverConfig {
48    /// Create config with a time window from now
49    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
63/// Build a complete PaymentPayload from requirements and witness
64///
65/// This is the main entry point for client-side proof generation.
66pub fn build_payload(
67    requirements: &PaymentRequirements,
68    witness: &ClientWitness,
69    config: &ProverConfig,
70) -> Result<PaymentPayload, ProverError> {
71    // Derive public key from secret key
72    let public_key = zk402_groth16::derive_public_key(&witness.secret_key)
73        .map_err(|e| ProverError::KeyDerivationFailed(e.to_string()))?;
74
75    // Build PublicInputs
76    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    // Build witness for proof system
89    let proof_witness = Witness {
90        secret_key: witness.secret_key.clone(),
91        nonce_scalar: vec![0u8; 32], // Random nonce for signature
92    };
93
94    // Generate proof
95    let proof = Groth16ProofSystem::prove(&config.proving_key, &proof_witness, &public_inputs)
96        .map_err(|e| ProverError::ProofGenerationFailed(e.to_string()))?;
97
98    // Assemble complete payload
99    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        // This test validates the structure without actual proving (which requires setup)
115        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        // Would need real proving key from setup()
136        // For now, just verify error handling works
137        let config = ProverConfig::with_validity_window(vec![0u8; 100], 3600);
138
139        let result = build_payload(&requirements, &witness, &config);
140
141        // Expect it to fail without real proving key, but not panic
142        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}