autonomi/client/high_level/vault/
mod.rs

1// Copyright 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9pub mod key;
10pub mod user_data;
11
12pub use key::{VaultSecretKey, vault_derive_key};
13pub use user_data::UserData;
14
15use crate::client::config::FILE_UPLOAD_BATCH_SIZE;
16use crate::client::data_types::scratchpad::ScratchpadError;
17use crate::client::key_derivation::{DerivationIndex, MainSecretKey};
18use crate::client::payment::PaymentOption;
19use crate::client::quote::CostError;
20use crate::client::{Client, GetError};
21use crate::graph::GraphError;
22use crate::utils::process_tasks_with_max_concurrency;
23use ant_evm::{AttoTokens, U256};
24use ant_protocol::Bytes;
25use ant_protocol::storage::{
26    GraphContent, GraphEntry, GraphEntryAddress, Scratchpad, ScratchpadAddress,
27};
28use bls::PublicKey;
29use std::hash::{DefaultHasher, Hash, Hasher};
30use tracing::info;
31
32/// The content type of the vault data
33/// The number is used to determine the type of the contents of the bytes contained in a vault
34/// Custom apps can use this to store their own custom types of data in vaults
35/// It is recommended to use the hash of the app name or an unique identifier as the content type using [`vault_content_type_from_app_name`]
36/// The value 0 is reserved for tests
37pub type VaultContentType = u64;
38
39/// Defines the max size of content can be written into per ScratchPad
40pub const MAX_CONTENT_PER_SCRATCHPAD: usize = Scratchpad::MAX_SIZE - 1024;
41
42/// Defines the max number of Scratchpads that one GraphEntry can point to
43/// The current value is assuming GraphEntry max_size to be 100KB.
44pub const NUM_OF_SCRATCHPADS_PER_GRAPH_ENTRY: usize = 1_000;
45
46/// Hard coded derivation index for the Vault's root GraphEntry.
47/// Derive the Vault's main secret/public key by it to get the root GraphEntry owner/address
48pub const VAULT_HEAD_DERIVATION_INDEX: [u8; 32] = [0; 32];
49
50/// For custom apps using Vault, this function converts an app identifier or name to a [`VaultContentType`]
51pub fn vault_content_type_from_app_name<T: Hash>(s: T) -> VaultContentType {
52    let mut hasher = DefaultHasher::new();
53    s.hash(&mut hasher);
54    hasher.finish()
55}
56
57/// @deprecated Use [`vault_content_type_from_app_name`] instead. This function will be removed in a future version.
58#[deprecated(
59    since = "0.6.0",
60    note = "Use `vault_content_type_from_app_name` instead"
61)]
62pub fn app_name_to_vault_content_type<T: Hash>(s: T) -> VaultContentType {
63    vault_content_type_from_app_name(s)
64}
65
66#[derive(Debug, thiserror::Error)]
67pub enum VaultError {
68    #[error("Vault Scratchpad related error: {0}")]
69    Scratchpad(#[from] ScratchpadError),
70    #[error("Vault GraphEntry related error: {0}")]
71    GraphEntry(#[from] GraphError),
72    #[error("Vault Cost related error: {0}")]
73    Cost(#[from] CostError),
74    #[error("Protocol: {0}")]
75    Protocol(#[from] ant_protocol::Error),
76    #[error("Vault doesn't have enough graph descendants: {0}")]
77    VaultNotEnoughGraphDescendants(String),
78    #[error("Vault with empty content")]
79    VaultWithZeroContentSize,
80}
81
82impl Client {
83    /// Retrieves and returns a decrypted vault if one exists.
84    ///
85    /// Returns the content type of the bytes in the vault.
86    pub async fn vault_get(
87        &self,
88        secret_key: &VaultSecretKey,
89    ) -> Result<(Bytes, VaultContentType), VaultError> {
90        info!("Fetching and decrypting vault...");
91        let main_secret_key = MainSecretKey::new(secret_key.clone());
92        let public_key = main_secret_key
93            .derive_key(&DerivationIndex::from_bytes(VAULT_HEAD_DERIVATION_INDEX))
94            .public_key();
95
96        let mut cur_graph_entry_addr = GraphEntryAddress::new(public_key.into());
97        let mut decrypted_full_text = vec![];
98        let mut content_type = 0;
99        let mut has_end_reached = false;
100
101        while !has_end_reached {
102            let graph_entry = self.graph_entry_get(&cur_graph_entry_addr).await?;
103
104            // The first descendant is reserved for `expand GraphEntry`.
105            match graph_entry.descendants.split_first() {
106                Some((&(first, _), rest)) => {
107                    cur_graph_entry_addr = GraphEntryAddress::new(first);
108                    let scratchpad_addresses = rest.to_vec();
109
110                    let (decrypt_data, cur_content_type, is_end_reached) = self
111                        .fetch_scratchpads_of_one_graph_entry_and_decrypt(
112                            &main_secret_key,
113                            scratchpad_addresses,
114                        )
115                        .await?;
116                    decrypted_full_text.push(decrypt_data);
117                    content_type = cur_content_type;
118                    has_end_reached = is_end_reached;
119                }
120                None => {
121                    let msg = format!(
122                        "Vault's GraphEntry at {cur_graph_entry_addr:?} only has {} descendants.",
123                        graph_entry.descendants.len()
124                    );
125                    return Err(VaultError::VaultNotEnoughGraphDescendants(msg));
126                }
127            }
128        }
129
130        debug!("vault data is successfully fetched and decrypted");
131        Ok((Bytes::from(decrypted_full_text.concat()), content_type))
132    }
133
134    /// Get the cost of creating a new vault
135    /// A quick estimation of cost:
136    ///   num_of_graph_entry * graph_entry_cost + num_of_scratchpad * scratchpad_cost
137    pub async fn vault_cost(
138        &self,
139        owner: &VaultSecretKey,
140        max_size: u64,
141    ) -> Result<AttoTokens, VaultError> {
142        if max_size == 0 {
143            return Err(VaultError::VaultWithZeroContentSize);
144        }
145
146        info!("Getting cost for vault");
147        let public_key = MainSecretKey::new(owner.clone())
148            .derive_key(&DerivationIndex::from_bytes(VAULT_HEAD_DERIVATION_INDEX))
149            .public_key();
150        let graph_entry_cost = self.graph_entry_cost(&public_key.into()).await?;
151        if graph_entry_cost.is_zero() {
152            // Has been created, assuming all Scratchpads have been created and paid
153            Ok(graph_entry_cost)
154        } else {
155            let scratchpad_cost = self.scratchpad_cost(&public_key.into()).await?;
156
157            let num_of_scratchpads = max_size / MAX_CONTENT_PER_SCRATCHPAD as u64 + 1;
158            let num_of_graph_entry =
159                num_of_scratchpads / NUM_OF_SCRATCHPADS_PER_GRAPH_ENTRY as u64 + 1;
160
161            let total_cost = U256::from(num_of_graph_entry) * graph_entry_cost.as_atto()
162                + U256::from(num_of_scratchpads) * scratchpad_cost.as_atto();
163            Ok(AttoTokens::from_atto(total_cost))
164        }
165    }
166
167    /// Put data into the client's VaultPacket
168    ///
169    /// Dynamically expand the vault capacity by paying for more space (Scratchpad) when needed.
170    ///
171    /// It is recommended to use the hash of the app name or unique identifier as the content type.
172    pub async fn vault_put(
173        &self,
174        data: Bytes,
175        payment_option: PaymentOption,
176        secret_key: &VaultSecretKey,
177        content_type: VaultContentType,
178    ) -> Result<AttoTokens, VaultError> {
179        if data.is_empty() {
180            return Err(VaultError::VaultWithZeroContentSize);
181        }
182
183        info!("Writing {} bytes to vault ...", data.len());
184        let mut total_cost = AttoTokens::zero();
185        let main_secret_key = MainSecretKey::new(secret_key.clone());
186
187        // scratchpad_derivations ordered by the collection order
188        let (mut cur_free_graphentry_derivation, mut scratchpad_derivations) = self
189            .vault_claimed_capacity(
190                &main_secret_key,
191                DerivationIndex::from_bytes(VAULT_HEAD_DERIVATION_INDEX),
192            )
193            .await?;
194
195        let contents = vault_split_bytes(data);
196
197        info!(
198            "Current capacity is {}, meanwhile requiring {}",
199            scratchpad_derivations.len(),
200            contents.len()
201        );
202
203        // claim more capacity if short of.
204        // Note: as the Scratchpad is `created on use`, hence during the `claim stage`,
205        //       NUM_OF_SCRATCHPADS_PER_GRAPHENTRY to be claimed in one newly created GraphEntry.
206        while scratchpad_derivations.len() < contents.len() {
207            let (new_free_graphentry_derivation, new_scratchpad_derivations, graph_cost) = self
208                .vault_expand_capacity(
209                    &main_secret_key,
210                    &cur_free_graphentry_derivation,
211                    payment_option.clone(),
212                )
213                .await?;
214            cur_free_graphentry_derivation = new_free_graphentry_derivation;
215            scratchpad_derivations.extend(&new_scratchpad_derivations);
216            total_cost = AttoTokens::from_atto(total_cost.as_atto() + graph_cost.as_atto());
217        }
218
219        // Convert to Vec of futures
220        let update_futures: Vec<_> = contents
221            .into_iter()
222            .enumerate()
223            .map(|(i, content)| {
224                let sp_secret_key = main_secret_key
225                    .derive_key(&DerivationIndex::from_bytes(scratchpad_derivations[i].1));
226                let client = self.clone();
227                let payment_option_clone = payment_option.clone();
228
229                async move {
230                    let target_addr = ScratchpadAddress::new(sp_secret_key.public_key().into());
231                    let already_exists = self.scratchpad_check_existence(&target_addr).await?;
232
233                    if already_exists {
234                        info!(
235                            "Updating Scratchpad at {target_addr:?} with content of {} bytes",
236                            content.len()
237                        );
238                        match client
239                            .scratchpad_update(&sp_secret_key.clone().into(), content_type, &content)
240                            .await
241                        {
242                            Ok(()) => {
243                                info!(
244                                    "Updated Scratchpad at {target_addr:?} with content of {} bytes",
245                                    content.len()
246                                );
247                                Ok(None)
248                            }
249                            Err(err) => Err(err.into()),
250                        }
251                    } else {
252                        info!("Creating Scratchpad at {target_addr:?}");
253                        let (price, addr) = client
254                            .scratchpad_create(
255                                &sp_secret_key.into(),
256                                content_type,
257                                &content,
258                                payment_option_clone,
259                            )
260                            .await?;
261                        info!("Created Scratchpad at {addr:?} with cost of {price:?}");
262                        Ok(Some(price))
263                    }
264                }
265            })
266            .collect();
267
268        let update_results =
269            process_tasks_with_max_concurrency(update_futures, *FILE_UPLOAD_BATCH_SIZE).await;
270
271        // Process results
272        for result in update_results {
273            match result {
274                Ok(Some(price)) => {
275                    total_cost = AttoTokens::from_atto(total_cost.as_atto() + price.as_atto());
276                }
277                Ok(None) => (),
278                Err(e) => return Err(e),
279            }
280        }
281
282        Ok(total_cost)
283    }
284
285    // Expand the capacity, i.e. upload one GraphEntry
286    // The returned value is:
287    //   * cur_free_graphentry_derivation: the output[0] of the tail of the linked GraphEntry
288    //   * scratchpad_derivations: ordered by the creating order
289    //   * graph_cost: cost paid to upload the GraphEntry
290    pub async fn vault_expand_capacity(
291        &self,
292        main_secret_key: &MainSecretKey,
293        cur_graphentry_derivation: &DerivationIndex,
294        payment_option: PaymentOption,
295    ) -> Result<(DerivationIndex, Vec<(PublicKey, GraphContent)>, AttoTokens), VaultError> {
296        let own_secret_key = main_secret_key.derive_key(cur_graphentry_derivation);
297
298        // For Vault, doesn't need the backward poining. i.e. one-direction link shall be enough.
299        let parents = vec![];
300        // For Vault, doesn't need this field to be populated.
301        let initial_value = [0u8; 32];
302
303        // Poining to the next GraphEntry
304        let new_graphentry_derivation = DerivationIndex::random(&mut rand::thread_rng());
305        let public_key: PublicKey = main_secret_key
306            .derive_key(&new_graphentry_derivation)
307            .public_key()
308            .into();
309        let mut descendants = vec![(public_key, new_graphentry_derivation.into_bytes())];
310
311        // Pointing to other future Scrachpads
312        descendants.extend((0..NUM_OF_SCRATCHPADS_PER_GRAPH_ENTRY).map(|_| {
313            let derivation_index = DerivationIndex::random(&mut rand::thread_rng());
314            let public_key: PublicKey = main_secret_key
315                .derive_key(&derivation_index)
316                .public_key()
317                .into();
318            (public_key, derivation_index.into_bytes())
319        }));
320
321        let graph_entry = GraphEntry::new(
322            &own_secret_key.into(),
323            parents,
324            initial_value,
325            descendants.clone(),
326        );
327
328        // Upload the GraphEntry
329        let (graph_cost, _addr) = self.graph_entry_put(graph_entry, payment_option).await?;
330
331        let scratchpad_derivations = descendants.split_off(1);
332        Ok((
333            new_graphentry_derivation,
334            scratchpad_derivations,
335            graph_cost,
336        ))
337    }
338
339    // Collects the current claimed capacity (i.e. the uploaded `GrapthEntry`s)
340    // The returned value is:
341    //   * cur_free_graphentry_derivation: i.e. the root if no graph_entry uploaded,
342    //       otherwise, the first un-used one (the output[0] of the tail of the linked GraphEntry)
343    //   * scratchpad_derivations: ordered by the collection order
344    pub async fn vault_claimed_capacity(
345        &self,
346        main_secret_key: &MainSecretKey,
347        mut cur_free_graphentry_derivation: DerivationIndex,
348    ) -> Result<(DerivationIndex, Vec<(PublicKey, GraphContent)>), VaultError> {
349        let mut scratchpad_derivations = vec![];
350        loop {
351            let public_key = main_secret_key
352                .derive_key(&cur_free_graphentry_derivation)
353                .public_key();
354            let cur_graph_entry_addr = GraphEntryAddress::new(public_key.into());
355
356            match self.graph_entry_get(&cur_graph_entry_addr).await {
357                Ok(entry) => {
358                    // A GraphEntry was created with all NUM_OF_SCRATCHPADS_PER_GRAPHENTRY
359                    // scratchpad claimed:
360                    //   * the first descendant pointing to next GraphEntry.
361                    //   * other descendants pointing to Scratchpads for content.
362                    if entry.descendants.len() <= NUM_OF_SCRATCHPADS_PER_GRAPH_ENTRY {
363                        let msg = format!(
364                            "Vault's GraphEntry at {cur_graph_entry_addr:?} only has {} descendants.",
365                            entry.descendants.len()
366                        );
367                        return Err(VaultError::VaultNotEnoughGraphDescendants(msg));
368                    }
369                    cur_free_graphentry_derivation =
370                        DerivationIndex::from_bytes(entry.descendants[0].1);
371                    scratchpad_derivations.extend(&entry.descendants[1..]);
372                }
373                Err(GraphError::GetError(GetError::RecordNotFound)) => {
374                    // GraphEntry not existed, return the current snapshot.
375                    info!(
376                        "vault capacity is successfully fetched, with {} scratchpads",
377                        scratchpad_derivations.len()
378                    );
379                    return Ok((cur_free_graphentry_derivation, scratchpad_derivations));
380                }
381                Err(err) => {
382                    return Err(err.into());
383                }
384            }
385        }
386    }
387
388    async fn fetch_scratchpads_of_one_graph_entry_and_decrypt(
389        &self,
390        main_secret_key: &MainSecretKey,
391        scratchpad_addresses: Vec<(PublicKey, [u8; 32])>,
392    ) -> Result<(Bytes, VaultContentType, bool), VaultError> {
393        let mut decrypted_full_text = vec![];
394        let mut content_type = 0;
395        let mut has_end_reached = false;
396        // Any non-max-sized ScratchPad indicates the end-of-vault-content.
397        for (pub_key, derive_bytes) in scratchpad_addresses {
398            let addr = ScratchpadAddress::new(pub_key);
399            let secret_key = main_secret_key.derive_key(&DerivationIndex::from_bytes(derive_bytes));
400
401            let sp = self.scratchpad_get(&addr).await?;
402            content_type = sp.data_encoding();
403            let decrypt_data = sp.decrypt_data(&secret_key.into())?;
404            decrypted_full_text.push(decrypt_data);
405            if sp.encrypted_data().len() < MAX_CONTENT_PER_SCRATCHPAD {
406                has_end_reached = true;
407                break;
408            }
409        }
410
411        Ok((
412            Bytes::from(decrypted_full_text.concat()),
413            content_type,
414            has_end_reached,
415        ))
416    }
417
418    /// @deprecated Use `vault_get` instead. This function will be removed in a future version.
419    #[deprecated(since = "0.2.0", note = "Use `vault_get` instead")]
420    pub async fn fetch_and_decrypt_vault(
421        &self,
422        secret_key: &VaultSecretKey,
423    ) -> Result<(Bytes, VaultContentType), VaultError> {
424        self.vault_get(secret_key).await
425    }
426
427    /// @deprecated Use `vault_put` instead. This function will be removed in a future version.
428    #[deprecated(since = "0.2.0", note = "Use `vault_put` instead")]
429    pub async fn write_bytes_to_vault(
430        &self,
431        data: Bytes,
432        payment_option: PaymentOption,
433        secret_key: &VaultSecretKey,
434        content_type: VaultContentType,
435    ) -> Result<AttoTokens, VaultError> {
436        self.vault_put(data, payment_option, secret_key, content_type)
437            .await
438    }
439}
440
441pub fn vault_split_bytes(input: Bytes) -> Vec<Bytes> {
442    let mut contents = Vec::new();
443    let mut offset = 0;
444
445    while offset < input.len() {
446        let end = (offset + MAX_CONTENT_PER_SCRATCHPAD).min(input.len());
447        contents.push(input.slice(offset..end));
448        offset = end;
449    }
450
451    contents
452}