Skip to main content

walletkit_core/authenticator/
with_storage.rs

1use crate::error::WalletKitError;
2
3use super::Authenticator;
4
5use world_id_core::primitives::merkle::AccountInclusionProof;
6use world_id_core::primitives::TREE_DEPTH;
7
8/// The amount of time a Merkle inclusion proof remains valid in the cache.
9const MERKLE_PROOF_VALIDITY_SECONDS: u64 = 60 * 15;
10
11#[uniffi::export]
12impl Authenticator {
13    /// Initializes storage using the authenticator's leaf index.
14    ///
15    /// # Errors
16    ///
17    /// Returns an error if the leaf index is invalid or storage initialization fails.
18    pub fn init_storage(&self, now: u64) -> Result<(), WalletKitError> {
19        self.store.init(self.leaf_index(), now)?;
20        Ok(())
21    }
22
23    /// Permanently destroys all credential storage data.
24    ///
25    /// Removes the encryption keys, vault database, and cache database.
26    /// After this call the authenticator can no longer generate proofs or
27    /// access stored credentials. Intended for logout or account deletion.
28    ///
29    /// # Errors
30    ///
31    /// Returns an error if the storage destruction fails.
32    pub fn destroy_storage(&self) -> Result<(), WalletKitError> {
33        self.store.destroy_storage()?;
34        Ok(())
35    }
36}
37
38impl Authenticator {
39    /// Fetches a [`MerkleInclusionProof`] from the indexer, or from cache if it's available and fresh.
40    ///
41    /// # Errors
42    ///
43    /// Returns an error if fetching or caching the proof fails.
44    #[tracing::instrument(
45        target = "walletkit_latency",
46        name = "indexer_inclusion_proof",
47        skip_all
48    )]
49    pub(crate) async fn fetch_inclusion_proof_with_cache(
50        &self,
51        now: u64,
52    ) -> Result<AccountInclusionProof<TREE_DEPTH>, WalletKitError> {
53        // If there is a cached inclusion proof, return it
54        if let Some(account_inclusion_proof) = self.store.merkle_cache_get(now)? {
55            if account_inclusion_proof.inclusion_proof.leaf_index == self.leaf_index() {
56                return Ok(account_inclusion_proof);
57            }
58        }
59
60        // Otherwise, fetch from the indexer and cache it
61        let account_inclusion_proof = self.inner.fetch_inclusion_proof().await?;
62
63        if let Err(e) = self.store.merkle_cache_put(
64            &account_inclusion_proof,
65            now,
66            MERKLE_PROOF_VALIDITY_SECONDS,
67        ) {
68            tracing::error!("Failed to cache Merkle inclusion proof: {e}");
69        }
70
71        Ok(account_inclusion_proof)
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::storage::tests_utils::{
79        cleanup_test_storage, temp_root_path, InMemoryStorageProvider,
80    };
81    use crate::storage::CredentialStore;
82    use world_id_core::primitives::merkle::MerkleInclusionProof;
83    use world_id_core::primitives::AuthenticatorPublicKeySet;
84    use world_id_core::FieldElement;
85
86    #[test]
87    fn test_cached_inclusion_round_trip() {
88        let root = temp_root_path();
89        let provider = InMemoryStorageProvider::new(&root);
90        let store = CredentialStore::from_provider(&provider).expect("store");
91        store.init(42, 100).expect("init storage");
92
93        let siblings = [FieldElement::from(0u64); TREE_DEPTH];
94        let root_fe = FieldElement::from(123u64);
95        let inclusion_proof = MerkleInclusionProof::new(root_fe, 42, siblings);
96        let authenticator_pubkeys =
97            AuthenticatorPublicKeySet::new(vec![]).expect("key set");
98        let account_inclusion_proof = AccountInclusionProof {
99            inclusion_proof,
100            authenticator_pubkeys,
101        };
102
103        store
104            .merkle_cache_put(&account_inclusion_proof, 100, 60)
105            .expect("cache put");
106        let now = 110;
107        let decoded = store
108            .merkle_cache_get(now)
109            .expect("cache get")
110            .expect("cache hit");
111        assert_eq!(decoded.inclusion_proof.leaf_index, 42);
112        assert_eq!(decoded.inclusion_proof.root, root_fe);
113        assert_eq!(decoded.authenticator_pubkeys.len(), 0);
114        cleanup_test_storage(&root);
115    }
116}