Skip to main content

walletkit_core/
requests.rs

1use world_id_core::requests::{
2    ProofRequest as CoreProofRequest, ProofResponse as CoreProofResponse,
3};
4
5use crate::error::WalletKitError;
6
7/// A request from the RP to the Authenticator. See [`CoreProofRequest`] for more details.
8/// This is a wrapper type to expose to foreign language bindings.
9#[derive(Debug, Clone, uniffi::Object)]
10pub struct ProofRequest(pub(crate) CoreProofRequest);
11
12#[uniffi::export]
13impl ProofRequest {
14    /// Deserializes a `ProofRequest` from a JSON string.
15    ///
16    /// # Errors
17    /// Returns an error if the JSON is invalid or cannot be parsed.
18    #[uniffi::constructor]
19    pub fn from_json(json: &str) -> Result<Self, WalletKitError> {
20        let core_request = CoreProofRequest::from_json(json).map_err(|e| {
21            WalletKitError::InvalidInput {
22                attribute: "proof_request".to_string(),
23                reason: format!("invalid proof request json: {e}"),
24            }
25        })?;
26        Ok(Self(core_request))
27    }
28
29    /// Serializes the proof request to a JSON string.
30    ///
31    /// # Errors
32    /// Returns an error if serialization fails.
33    pub fn to_json(&self) -> Result<String, WalletKitError> {
34        serde_json::to_string(&self.0).map_err(|e| WalletKitError::Generic {
35            error: format!("critical unexpected error serializing to json: {e}"),
36        })
37    }
38
39    /// Returns the unique identifier for this request.
40    #[must_use]
41    pub fn id(&self) -> String {
42        self.0.id.clone()
43    }
44
45    /// Returns the request format version as a `u8`.
46    #[must_use]
47    pub const fn version(&self) -> u8 {
48        self.0.version as u8
49    }
50}
51
52/// A response from the Authenticator to the RP. See [`CoreProofResponse`] for more details.
53///
54/// This is a wrapper type to expose to foreign language bindings.
55#[derive(Debug, Clone, uniffi::Object)]
56pub struct ProofResponse(pub CoreProofResponse);
57
58#[uniffi::export]
59impl ProofResponse {
60    /// Serializes the proof response to a JSON string.
61    ///
62    /// # Errors
63    /// Returns an error if serialization fails.
64    pub fn to_json(&self) -> Result<String, WalletKitError> {
65        serde_json::to_string(&self.0).map_err(|e| WalletKitError::Generic {
66            error: format!("critical unexpected error serializing to json: {e}"),
67        })
68    }
69
70    /// Returns the unique identifier for this response.
71    #[must_use]
72    pub fn id(&self) -> String {
73        self.0.id.clone()
74    }
75
76    /// Returns the response format version as a `u8`.
77    #[must_use]
78    pub const fn version(&self) -> u8 {
79        self.0.version as u8
80    }
81
82    /// Returns the top-level error message, if the entire proof request failed.
83    #[must_use]
84    pub fn error(&self) -> Option<String> {
85        self.0.error.clone()
86    }
87}
88
89impl ProofResponse {
90    /// Consumes the wrapper and returns the inner `CoreProofResponse`.
91    #[must_use]
92    pub fn into_inner(self) -> CoreProofResponse {
93        self.0
94    }
95}
96
97impl From<CoreProofRequest> for ProofRequest {
98    fn from(core_request: CoreProofRequest) -> Self {
99        Self(core_request)
100    }
101}
102
103impl From<CoreProofResponse> for ProofResponse {
104    fn from(core_response: CoreProofResponse) -> Self {
105        Self(core_response)
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use alloy::signers::{local::PrivateKeySigner, SignerSync};
112    use alloy_core::primitives::U160;
113    use serde_json::Value;
114    use taceo_oprf::types::OprfKeyId;
115    use world_id_core::{
116        primitives::{rp::RpId, FieldElement},
117        requests::{ProofType, RequestItem, RequestVersion},
118    };
119
120    use super::*;
121
122    fn test_signature() -> alloy::signers::Signature {
123        let signer = PrivateKeySigner::from_bytes(&[1u8; 32].into())
124            .expect("test signer should be valid");
125        signer
126            .sign_message_sync(b"test")
127            .expect("test signature should sign")
128    }
129
130    fn base_core_request(proof_type: ProofType) -> CoreProofRequest {
131        CoreProofRequest {
132            id: "test_request".to_string(),
133            version: RequestVersion::V1,
134            proof_type,
135            created_at: 1_700_000_000,
136            expires_at: 1_700_000_300,
137            rp_id: RpId::new(1),
138            oprf_key_id: OprfKeyId::new(U160::from(1)),
139            session_id: None,
140            action: Some(FieldElement::from(1u64)),
141            signature: test_signature(),
142            nonce: FieldElement::from(2u64),
143            requests: vec![RequestItem {
144                identifier: "credential".to_string(),
145                issuer_schema_id: 1,
146                signal: None,
147                genesis_issued_at_min: None,
148                expires_at_min: None,
149            }],
150            constraints: None,
151        }
152    }
153
154    #[test]
155    fn from_json_defaults_missing_proof_type_to_uniqueness() {
156        let core_request = base_core_request(ProofType::Uniqueness);
157        let mut value =
158            serde_json::to_value(core_request).expect("request should serialize");
159        value
160            .as_object_mut()
161            .expect("request should be an object")
162            .remove("proof_type");
163
164        let json =
165            serde_json::to_string(&value).expect("request json should serialize");
166        let request = ProofRequest::from_json(&json).expect("request should parse");
167
168        assert_eq!(request.0.proof_type, ProofType::Uniqueness);
169    }
170
171    #[test]
172    fn from_json_rejects_invalid_proof_type_fields() {
173        let mut value = serde_json::to_value(base_core_request(ProofType::Uniqueness))
174            .expect("request should serialize");
175        let object = value.as_object_mut().expect("request should be an object");
176        object.insert(
177            "proof_type".to_string(),
178            Value::String("session".to_string()),
179        );
180        object.remove("action");
181
182        let json =
183            serde_json::to_string(&value).expect("request json should serialize");
184        let error = ProofRequest::from_json(&json)
185            .expect_err("session request needs session_id");
186
187        match error {
188            WalletKitError::InvalidInput { attribute, reason } => {
189                assert_eq!(attribute, "proof_request");
190                assert!(reason.contains("session_id"));
191            }
192            other => panic!("expected invalid input error, got {other:?}"),
193        }
194    }
195}