Skip to main content

walletkit_core/authenticator/
with_storage.rs

1use 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
10/// The amount of time a Merkle inclusion proof remains valid in the cache.
11const MERKLE_PROOF_VALIDITY_SECONDS: u64 = 60 * 15;
12
13#[uniffi::export]
14impl Authenticator {
15    /// Initializes storage using the authenticator's leaf index.
16    ///
17    /// # Errors
18    ///
19    /// Returns an error if the leaf index is invalid or storage initialization fails.
20    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    /// Fetches a [`MerkleInclusionProof`] from the indexer, or from cache if it's available and fresh.
28    ///
29    /// # Errors
30    ///
31    /// Returns an error if fetching or caching the proof fails.
32    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 there is a cached inclusion proof, return it
40        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        // Otherwise, fetch from the indexer and cache it
49        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}