walletkit_core/
credential.rs1use std::ops::Deref;
4
5use world_id_core::Credential as CoreCredential;
6
7use crate::error::WalletKitError;
8use crate::FieldElement;
9
10#[derive(Debug, Clone, uniffi::Object)]
15pub struct Credential(CoreCredential);
16
17#[uniffi::export]
18impl Credential {
19 #[uniffi::constructor]
25 #[allow(clippy::needless_pass_by_value)]
26 pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, WalletKitError> {
27 let credential: CoreCredential =
28 serde_json::from_slice(&bytes).map_err(|e| {
29 WalletKitError::InvalidInput {
30 attribute: "credential_bytes".to_string(),
31 reason: format!("Failed to deserialize credential: {e}"),
32 }
33 })?;
34 Ok(Self(credential))
35 }
36
37 #[must_use]
39 pub fn sub(&self) -> FieldElement {
40 self.0.sub.into()
41 }
42
43 #[must_use]
45 pub const fn issuer_schema_id(&self) -> u64 {
46 self.0.issuer_schema_id
47 }
48
49 #[must_use]
51 pub const fn expires_at(&self) -> u64 {
52 self.0.expires_at
53 }
54
55 #[must_use]
59 pub fn associated_data_commitment(&self) -> FieldElement {
60 self.0.associated_data_commitment.into()
61 }
62
63 #[must_use]
71 pub fn claims(&self) -> Vec<std::sync::Arc<FieldElement>> {
72 self.0
73 .claims
74 .iter()
75 .map(|claim| std::sync::Arc::new((*claim).into()))
76 .collect()
77 }
78
79 #[must_use]
85 pub fn claims_hex(&self) -> Vec<String> {
86 self.0.claims.iter().map(ToString::to_string).collect()
87 }
88}
89
90impl Credential {
91 pub fn to_bytes(&self) -> Result<Vec<u8>, WalletKitError> {
97 serde_json::to_vec(&self.0).map_err(|e| WalletKitError::SerializationError {
98 error: format!("Failed to serialize credential: {e}"),
99 })
100 }
101
102 #[must_use]
104 pub const fn genesis_issued_at(&self) -> u64 {
105 self.0.genesis_issued_at
106 }
107}
108
109impl From<CoreCredential> for Credential {
110 fn from(val: CoreCredential) -> Self {
111 Self(val)
112 }
113}
114
115impl From<Credential> for CoreCredential {
116 fn from(val: Credential) -> Self {
117 val.0
118 }
119}
120
121impl Deref for Credential {
122 type Target = CoreCredential;
123
124 fn deref(&self) -> &Self::Target {
125 &self.0
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use ruint::aliases::U256;
132 use world_id_core::Credential as CoreCredential;
133
134 use super::Credential;
135
136 fn credential_with_claims() -> Credential {
137 let core = CoreCredential::new()
138 .claim_hash(0, U256::from(1u64))
139 .expect("claim 0 in bounds")
140 .claim_hash(1, U256::from(2u64))
141 .expect("claim 1 in bounds");
142 Credential(core)
143 }
144
145 #[test]
146 fn claims_expose_field_elements_in_schema_order() {
147 let credential = credential_with_claims();
148
149 let claims = credential.claims();
150 assert_eq!(claims.len(), credential.claims_hex().len());
151 assert_eq!(
152 claims[0].to_hex_string(),
153 "0x0000000000000000000000000000000000000000000000000000000000000001"
154 );
155 assert_eq!(
156 claims[1].to_hex_string(),
157 "0x0000000000000000000000000000000000000000000000000000000000000002"
158 );
159 }
160
161 #[test]
162 fn claims_hex_matches_field_element_encoding() {
163 let credential = credential_with_claims();
164
165 let hex = credential.claims_hex();
166 let from_elements: Vec<String> = credential
167 .claims()
168 .iter()
169 .map(|claim| claim.to_hex_string())
170 .collect();
171 assert_eq!(hex, from_elements);
172
173 assert!(hex[2..].iter().all(|claim| claim
175 == "0x0000000000000000000000000000000000000000000000000000000000000000"));
176 }
177}