walletkit_core/authenticator/
with_storage.rs1use serde::{Deserialize, Serialize};
2use world_id_core::primitives::authenticator::AuthenticatorPublicKeySet;
3use world_id_core::primitives::merkle::MerkleInclusionProof;
4use world_id_core::primitives::TREE_DEPTH;
5
6use crate::error::WalletKitError;
7
8use super::Authenticator;
9
10const MERKLE_PROOF_VALIDITY_SECONDS: u64 = 60 * 15;
12
13#[uniffi::export]
14impl Authenticator {
15 pub fn init_storage(&self, now: u64) -> Result<(), WalletKitError> {
21 self.store.init(self.leaf_index(), now)?;
22 Ok(())
23 }
24}
25
26impl Authenticator {
27 pub(crate) async fn fetch_inclusion_proof_with_cache(
33 &self,
34 now: u64,
35 ) -> Result<
36 (MerkleInclusionProof<TREE_DEPTH>, AuthenticatorPublicKeySet),
37 WalletKitError,
38 > {
39 if let Some(bytes) = self.store.merkle_cache_get(now)? {
41 if let Some(cached) = CachedInclusionProof::deserialize(&bytes) {
42 if cached.inclusion_proof.leaf_index == self.leaf_index() {
43 return Ok((cached.inclusion_proof, cached.authenticator_keyset));
44 }
45 }
46 }
47
48 let (inclusion_proof, authenticator_keyset) =
50 self.inner.fetch_inclusion_proof().await?;
51 let payload = CachedInclusionProof {
52 inclusion_proof: inclusion_proof.clone(),
53 authenticator_keyset: authenticator_keyset.clone(),
54 };
55 let payload = payload.serialize()?;
56
57 if let Err(e) =
58 self.store
59 .merkle_cache_put(payload, now, MERKLE_PROOF_VALIDITY_SECONDS)
60 {
61 log::error!("Failed to cache Merkle inclusion proof: {e}");
62 }
63
64 Ok((inclusion_proof, authenticator_keyset))
65 }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69struct CachedInclusionProof {
70 inclusion_proof: MerkleInclusionProof<TREE_DEPTH>,
71 authenticator_keyset: AuthenticatorPublicKeySet,
72}
73
74impl CachedInclusionProof {
75 fn serialize(&self) -> Result<Vec<u8>, WalletKitError> {
76 let mut bytes = Vec::new();
77 ciborium::ser::into_writer(self, &mut bytes).map_err(|err| {
78 WalletKitError::SerializationError {
79 error: err.to_string(),
80 }
81 })?;
82 Ok(bytes)
83 }
84
85 fn deserialize(bytes: &[u8]) -> Option<Self> {
86 ciborium::de::from_reader(bytes).ok()
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::storage::tests_utils::{
94 cleanup_test_storage, temp_root_path, InMemoryStorageProvider,
95 };
96 use crate::storage::CredentialStore;
97 use world_id_core::FieldElement;
98
99 #[test]
100 fn test_cached_inclusion_round_trip() {
101 let root = temp_root_path();
102 let provider = InMemoryStorageProvider::new(&root);
103 let store = CredentialStore::from_provider(&provider).expect("store");
104 store.init(42, 100).expect("init storage");
105
106 let siblings = [FieldElement::from(0u64); TREE_DEPTH];
107 let root_fe = FieldElement::from(123u64);
108 let inclusion_proof = MerkleInclusionProof::new(root_fe, 42, siblings);
109 let authenticator_keyset =
110 AuthenticatorPublicKeySet::new(vec![]).expect("key set");
111 let payload = CachedInclusionProof {
112 inclusion_proof,
113 authenticator_keyset,
114 };
115 let payload_bytes = payload.serialize().expect("serialize");
116
117 store
118 .merkle_cache_put(payload_bytes, 100, 60)
119 .expect("cache put");
120 let now = 110;
121 let cached = store
122 .merkle_cache_get(now)
123 .expect("cache get")
124 .expect("cache hit");
125 let decoded = CachedInclusionProof::deserialize(&cached).expect("decode");
126 assert_eq!(decoded.inclusion_proof.leaf_index, 42);
127 assert_eq!(decoded.inclusion_proof.root, root_fe);
128 assert_eq!(decoded.authenticator_keyset.len(), 0);
129 cleanup_test_storage(&root);
130 }
131}