Skip to main content

zk402_core/
lib.rs

1//! Core types and traits for the zk402 payment scheme.
2//!
3//! This crate defines the wire format types, the `ProofSystem` trait boundary,
4//! the `VerifyingKeyRegistry` trust model, and the `Scheme` trait that facilitators
5//! implement. It has zero proving-library dependencies — all cryptographic backends
6//! are swappable.
7
8use serde::{Deserialize, Serialize};
9
10pub type Address = [u8; 20];
11
12/// Payment requirements from the payee, matching the x402 wire format.
13#[derive(Clone, Debug, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct PaymentRequirements {
16    pub scheme: String,
17    pub network: String,
18    pub amount: String,
19    #[serde(with = "hex_address")]
20    pub asset: Address,
21    #[serde(with = "hex_address")]
22    pub pay_to: Address,
23    pub max_timeout_seconds: u64,
24    pub extra: ZkExtra,
25}
26
27/// ZK-specific fields within `PaymentRequirements.extra`.
28#[derive(Clone, Debug, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct ZkExtra {
31    pub proof_system: String,
32    pub circuit_id: String,
33    #[serde(with = "hex_address")]
34    pub verifier_address: Address,
35}
36
37/// Public inputs to the zero-knowledge proof.
38#[derive(Clone, Debug, Serialize, Deserialize)]
39#[serde(rename_all = "camelCase")]
40pub struct PublicInputs {
41    pub amount: String,
42    #[serde(with = "hex_address")]
43    pub asset: Address,
44    #[serde(with = "hex_address")]
45    pub pay_to: Address,
46    #[serde(with = "hex_address")]
47    pub from: Address,
48    pub network: String,
49    #[serde(with = "hex_nonce")]
50    pub nonce: [u8; 32],
51    pub valid_after: u64,
52    pub valid_before: u64,
53    /// Compressed public key (EdDSA) for signature verification
54    #[serde(with = "hex_bytes")]
55    pub public_key: Vec<u8>,
56}
57
58/// Complete payment payload sent by the payer.
59#[derive(Clone, Debug, Serialize, Deserialize)]
60#[serde(rename_all = "camelCase")]
61pub struct PaymentPayload {
62    pub x402_version: u8,
63    pub accepted: PaymentRequirements,
64    #[serde(with = "hex_bytes")]
65    pub proof: Vec<u8>,
66    pub public_inputs: PublicInputs,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub proof_system_extra: Option<serde_json::Value>,
69}
70
71/// Response from the verify endpoint.
72#[derive(Clone, Debug, Serialize, Deserialize)]
73#[serde(rename_all = "camelCase")]
74pub struct VerifyResponse {
75    pub is_valid: bool,
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub invalid_reason: Option<String>,
78}
79
80/// Response from the settle endpoint.
81#[derive(Clone, Debug, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct SettleResponse {
84    pub success: bool,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub tx_hash: Option<String>,
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub error: Option<String>,
89}
90
91/// Abstract proof system backend — the primary swap point in the architecture.
92///
93/// `zk402-groth16` implements this; future backends (Halo2, etc.) implement the same
94/// trait without changes to consumers.
95pub trait ProofSystem {
96    type ProvingKey;
97    type VerifyingKey;
98
99    fn prove(
100        pk: &Self::ProvingKey,
101        witness: &Witness,
102        public_inputs: &PublicInputs,
103    ) -> Result<Vec<u8>, ProofSystemError>;
104
105    fn verify(
106        vk: &Self::VerifyingKey,
107        proof: &[u8],
108        public_inputs: &PublicInputs,
109    ) -> Result<bool, ProofSystemError>;
110}
111
112/// Witness data for proof generation (private inputs).
113#[derive(Clone, Debug)]
114pub struct Witness {
115    pub secret_key: Vec<u8>,
116    pub nonce_scalar: Vec<u8>,
117}
118
119#[derive(Debug)]
120pub enum ProofSystemError {
121    InvalidParameters,
122    ProvingFailed(String),
123    VerificationFailed(String),
124    Encoding(String),
125}
126
127impl std::fmt::Display for ProofSystemError {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        match self {
130            Self::InvalidParameters => write!(f, "invalid proof system parameters"),
131            Self::ProvingFailed(msg) => write!(f, "proving failed: {msg}"),
132            Self::VerificationFailed(msg) => write!(f, "verification failed: {msg}"),
133            Self::Encoding(msg) => write!(f, "encoding error: {msg}"),
134        }
135    }
136}
137
138impl std::error::Error for ProofSystemError {}
139
140/// Verifying key registry — the trust model swap point.
141///
142/// v1 is `StaticRegistry` (config file); future versions might use on-chain lookups.
143pub trait VerifyingKeyRegistry {
144    type VerifyingKeyHandle;
145
146    fn lookup(
147        &self,
148        proof_system: &str,
149        circuit_id: &str,
150    ) -> Option<Self::VerifyingKeyHandle>;
151}
152
153/// Scheme trait matching the shape existing facilitator implementations use.
154#[async_trait::async_trait]
155pub trait Scheme {
156    fn scheme_name(&self) -> &str;
157
158    async fn verify(
159        &self,
160        req: &PaymentRequirements,
161        payload: &PaymentPayload,
162    ) -> Result<VerifyResponse, SchemeError>;
163
164    async fn settle(
165        &self,
166        req: &PaymentRequirements,
167        payload: &PaymentPayload,
168    ) -> Result<SettleResponse, SchemeError>;
169}
170
171#[derive(Debug)]
172pub enum SchemeError {
173    StructuralMismatch(String),
174    UnsupportedCircuit,
175    CryptographicVerificationFailed,
176    Expired,
177    ChainError(String),
178    Internal(String),
179}
180
181impl std::fmt::Display for SchemeError {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        match self {
184            Self::StructuralMismatch(msg) => write!(f, "structural mismatch: {msg}"),
185            Self::UnsupportedCircuit => write!(f, "unsupported circuit"),
186            Self::CryptographicVerificationFailed => write!(f, "proof verification failed"),
187            Self::Expired => write!(f, "payment window expired or not yet valid"),
188            Self::ChainError(msg) => write!(f, "chain error: {msg}"),
189            Self::Internal(msg) => write!(f, "internal error: {msg}"),
190        }
191    }
192}
193
194impl std::error::Error for SchemeError {}
195
196// Hex serialization helpers for addresses and nonces
197mod hex_address {
198    use serde::{Deserialize, Deserializer, Serializer};
199
200    pub fn serialize<S>(bytes: &[u8; 20], serializer: S) -> Result<S::Ok, S::Error>
201    where
202        S: Serializer,
203    {
204        let hex_string = format!("0x{}", hex::encode(bytes));
205        serializer.serialize_str(&hex_string)
206    }
207
208    pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 20], D::Error>
209    where
210        D: Deserializer<'de>,
211    {
212        let s = String::deserialize(deserializer)?;
213        let s = s.strip_prefix("0x").unwrap_or(&s);
214        let bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
215        bytes.try_into().map_err(|_| {
216            serde::de::Error::custom("expected 20 bytes for address")
217        })
218    }
219}
220
221mod hex_nonce {
222    use serde::{Deserialize, Deserializer, Serializer};
223
224    pub fn serialize<S>(bytes: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
225    where
226        S: Serializer,
227    {
228        let hex_string = format!("0x{}", hex::encode(bytes));
229        serializer.serialize_str(&hex_string)
230    }
231
232    pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
233    where
234        D: Deserializer<'de>,
235    {
236        let s = String::deserialize(deserializer)?;
237        let s = s.strip_prefix("0x").unwrap_or(&s);
238        let bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
239        bytes.try_into().map_err(|_| {
240            serde::de::Error::custom("expected 32 bytes for nonce")
241        })
242    }
243}
244
245mod hex_bytes {
246    use serde::{Deserialize, Deserializer, Serializer};
247
248    pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
249    where
250        S: Serializer,
251    {
252        let hex_string = format!("0x{}", hex::encode(bytes));
253        serializer.serialize_str(&hex_string)
254    }
255
256    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
257    where
258        D: Deserializer<'de>,
259    {
260        let s = String::deserialize(deserializer)?;
261        let s = s.strip_prefix("0x").unwrap_or(&s);
262        hex::decode(s).map_err(serde::de::Error::custom)
263    }
264}