Skip to content

Commit

Permalink
Merge pull request #2263 from grumbach/vault_with_user_data
Browse files Browse the repository at this point in the history
feat: user data stored in vault
  • Loading branch information
grumbach authored Oct 18, 2024
2 parents 84324eb + 88694f7 commit 2437a88
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 58 deletions.
4 changes: 2 additions & 2 deletions autonomi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ repository = "https://github.com/maidsafe/safe_network"
crate-type = ["cdylib", "rlib"]

[features]
default = ["data"]
default = ["data", "vault"]
full = ["data", "registers", "vault"]
data = []
vault = ["data"]
vault = ["data", "registers"]
fs = ["tokio/fs", "data"]
local = ["sn_networking/local", "test_utils/local", "sn_evm/local"]
registers = ["data"]
Expand Down
23 changes: 12 additions & 11 deletions autonomi/src/client/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ use std::{
use sn_networking::target_arch::{Duration, SystemTime, UNIX_EPOCH};

use super::{
data::DataAddr,
data::{GetError, PutError},
data::{CostError, DataAddr, GetError, PutError},
Client,
};
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use sn_evm::EvmWallet;
use sn_evm::{AttoTokens, EvmWallet};
use xor_name::XorName;

/// The address of an archive on the network. Points to an [`Archive`].
Expand All @@ -36,13 +35,13 @@ pub enum RenameError {

/// An archive of files that containing file paths, their metadata and the files data addresses
/// Using archives is useful for uploading entire directories to the network, only needing to keep track of a single address.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct Archive {
map: HashMap<PathBuf, (DataAddr, Metadata)>,
}

/// Metadata for a file in an archive
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Metadata {
pub created: u64,
pub modified: u64,
Expand Down Expand Up @@ -147,12 +146,6 @@ impl Archive {
}
}

impl Default for Archive {
fn default() -> Self {
Self::new()
}
}

impl Client {
/// Fetch an archive from the network
pub async fn archive_get(&self, addr: ArchiveAddr) -> Result<Archive, GetError> {
Expand All @@ -171,4 +164,12 @@ impl Client {
.map_err(|e| PutError::Serialization(format!("Failed to serialize archive: {e:?}")))?;
self.data_put(bytes, wallet).await
}

/// Get the cost to upload an archive
pub async fn archive_cost(&self, archive: Archive) -> Result<AttoTokens, CostError> {
let bytes = archive
.into_bytes()
.map_err(|e| CostError::Serialization(format!("Failed to serialize archive: {e:?}")))?;
self.data_cost(bytes).await
}
}
4 changes: 3 additions & 1 deletion autonomi/src/client/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub enum PutError {
Network(#[from] NetworkError),
#[error("Error occurred during payment.")]
PayError(#[from] PayError),
#[error("Failed to serialize {0}")]
#[error("Serialization error: {0}")]
Serialization(String),
#[error("A wallet error occurred.")]
Wallet(#[from] sn_evm::EvmError),
Expand Down Expand Up @@ -82,6 +82,8 @@ pub enum CostError {
CouldNotGetStoreQuote(XorName),
#[error("Could not get store costs: {0:?}")]
CouldNotGetStoreCosts(NetworkError),
#[error("Failed to serialize {0}")]
Serialization(String),
}

impl Client {
Expand Down
2 changes: 2 additions & 0 deletions autonomi/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pub mod fs;
pub mod registers;
#[cfg(feature = "vault")]
pub mod vault;
#[cfg(feature = "vault")]
pub mod vault_user_data;

#[cfg(target_arch = "wasm32")]
pub mod wasm;
Expand Down
37 changes: 28 additions & 9 deletions autonomi/src/client/vault.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@
// permissions and limitations relating to use of the SAFE Network Software.

use std::collections::HashSet;
use std::hash::{DefaultHasher, Hash, Hasher};

use crate::client::data::PutError;
use crate::client::Client;
use bls::SecretKey;
use bytes::Bytes;
use libp2p::kad::{Quorum, Record};
use sn_evm::EvmWallet;
use sn_networking::{GetRecordCfg, NetworkError, PutRecordCfg, VerificationKind};
use sn_protocol::storage::{
try_serialize_record, RecordKind, RetryStrategy, Scratchpad, ScratchpadAddress,
};
use sn_protocol::Bytes;
use sn_protocol::{storage::try_deserialize_record, NetworkAddress};
use tracing::info;

Expand All @@ -33,16 +34,32 @@ pub enum VaultError {
Network(#[from] NetworkError),
}

/// The content type of the vault data
/// The number is used to determine the type of the contents of the bytes contained in a vault
/// Custom apps can use this to store their own custom types of data in vaults
/// It is recommended to use the hash of the app name or an unique identifier as the content type using [`app_name_to_vault_content_type`]
/// The value 0 is reserved for tests
pub type VaultContentType = u64;

/// For custom apps using Scratchpad, this function converts an app identifier or name to a [`VaultContentType`]
pub fn app_name_to_vault_content_type<T: Hash>(s: T) -> VaultContentType {
let mut hasher = DefaultHasher::new();
s.hash(&mut hasher);
hasher.finish()
}

impl Client {
/// Retrieves and returns a decrypted vault if one exists.
/// Returns the content type of the bytes in the vault
pub async fn fetch_and_decrypt_vault(
&self,
secret_key: &SecretKey,
) -> Result<Option<Bytes>, VaultError> {
) -> Result<(Bytes, VaultContentType), VaultError> {
info!("Fetching and decrypting vault");
let pad = self.get_vault_from_network(secret_key).await?;

Ok(pad.decrypt_data(secret_key)?)
let data = pad.decrypt_data(secret_key)?;
Ok((data, pad.data_encoding()))
}

/// Gets the vault Scratchpad from a provided client public key
Expand Down Expand Up @@ -81,14 +98,16 @@ impl Client {

/// Put data into the client's VaultPacket
///
/// Pays for a new VaultPacket if none yet created for the client. Returns the current version
/// of the data on success.
/// Pays for a new VaultPacket if none yet created for the client.
/// Provide the bytes to be written to the vault and the content type of those bytes.
/// It is recommended to use the hash of the app name or unique identifier as the content type.
pub async fn write_bytes_to_vault(
&self,
data: Bytes,
wallet: &EvmWallet,
secret_key: &SecretKey,
) -> Result<u64, PutError> {
content_type: VaultContentType,
) -> Result<(), PutError> {
let client_pk = secret_key.public_key();

let pad_res = self.get_vault_from_network(secret_key).await;
Expand All @@ -106,10 +125,10 @@ impl Client {
existing_data
} else {
trace!("new scratchpad creation");
Scratchpad::new(client_pk)
Scratchpad::new(client_pk, content_type)
};

let next_count = scratch.update_and_sign(data, secret_key);
let _next_count = scratch.update_and_sign(data, secret_key);
let scratch_address = scratch.network_address();
let scratch_key = scratch_address.to_record_key();

Expand Down Expand Up @@ -181,6 +200,6 @@ impl Client {
)
})?;

Ok(next_count)
Ok(())
}
}
123 changes: 123 additions & 0 deletions autonomi/src/client/vault_user_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2024 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use std::collections::HashMap;
use std::collections::HashSet;

use super::archive::ArchiveAddr;
use super::data::GetError;
use super::data::PutError;
use super::registers::RegisterAddress;
use super::vault::VaultError;
use super::Client;
use crate::client::vault::{app_name_to_vault_content_type, VaultContentType};
use bls::SecretKey;
use serde::{Deserialize, Serialize};
use sn_evm::EvmWallet;
use sn_protocol::Bytes;

use std::sync::LazyLock;

/// Vault content type for UserDataVault
pub static USER_DATA_VAULT_CONTENT_IDENTIFIER: LazyLock<VaultContentType> =
LazyLock::new(|| app_name_to_vault_content_type("UserData"));

/// UserData is stored in Vaults and contains most of a user's private data:
/// It allows users to keep track of only the key to their User Data Vault
/// while having the rest kept on the Network encrypted in a Vault for them
/// Using User Data Vault is optional, one can decide to keep all their data locally instead.
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct UserData {
/// The register secret key hex encoded
pub register_sk: Option<String>,
/// Owned register addresses
pub registers: HashSet<RegisterAddress>,
/// Owned file archive addresses
pub file_archives: HashSet<ArchiveAddr>,

/// Owner register names, providing it is optional
pub register_names: HashMap<String, RegisterAddress>,
/// Owned file archive addresses along with a name for that archive providing it is optional
pub file_archive_names: HashMap<String, ArchiveAddr>,
}

/// Errors that can occur during the get operation.
#[derive(Debug, thiserror::Error)]
pub enum UserDataVaultGetError {
#[error("Vault error: {0}")]
Vault(#[from] VaultError),
#[error("Unsupported vault content type: {0}")]
UnsupportedVaultContentType(VaultContentType),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Get error: {0}")]
GetError(#[from] GetError),
}

impl UserData {
/// Create a new empty UserData
pub fn new() -> Self {
Self::default()
}

/// To bytes
pub fn to_bytes(&self) -> Result<Bytes, rmp_serde::encode::Error> {
let bytes = rmp_serde::to_vec(&self)?;
Ok(Bytes::from(bytes))
}

/// From bytes
pub fn from_bytes(bytes: Bytes) -> Result<Self, rmp_serde::decode::Error> {
let vault_content = rmp_serde::from_slice(&bytes)?;
Ok(vault_content)
}
}

impl Client {
/// Get the user data from the vault
pub async fn get_user_data_from_vault(
&self,
secret_key: &SecretKey,
) -> Result<UserData, UserDataVaultGetError> {
let (bytes, content_type) = self.fetch_and_decrypt_vault(secret_key).await?;

if content_type != *USER_DATA_VAULT_CONTENT_IDENTIFIER {
return Err(UserDataVaultGetError::UnsupportedVaultContentType(
content_type,
));
}

let vault = UserData::from_bytes(bytes).map_err(|e| {
UserDataVaultGetError::Serialization(format!(
"Failed to deserialize vault content: {e}"
))
})?;

Ok(vault)
}

/// Put the user data to the vault
pub async fn put_user_data_to_vault(
&self,
secret_key: &SecretKey,
wallet: &EvmWallet,
user_data: UserData,
) -> Result<(), PutError> {
let bytes = user_data
.to_bytes()
.map_err(|e| PutError::Serialization(format!("Failed to serialize user data: {e}")))?;
self.write_bytes_to_vault(
bytes,
wallet,
secret_key,
*USER_DATA_VAULT_CONTENT_IDENTIFIER,
)
.await?;
Ok(())
}
}
24 changes: 13 additions & 11 deletions autonomi/src/client/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ use libp2p::Multiaddr;
use wasm_bindgen::prelude::*;

use super::address::{addr_to_str, str_to_addr};
use super::vault_user_data::UserData;

#[wasm_bindgen(js_name = UserData)]
pub struct JsUserData(UserData);

#[wasm_bindgen(js_name = Client)]
pub struct JsClient(super::Client);
Expand Down Expand Up @@ -115,33 +119,31 @@ mod vault {

#[wasm_bindgen(js_class = Client)]
impl JsClient {
#[wasm_bindgen(js_name = fetchAndDecryptVault)]
pub async fn fetch_and_decrypt_vault(
#[wasm_bindgen(js_name = getUserDataFromVault)]
pub async fn get_user_data_from_vault(
&self,
secret_key: Vec<u8>,
) -> Result<Option<Vec<u8>>, JsError> {
) -> Result<JsUserData, JsError> {
let secret_key: [u8; 32] = secret_key[..].try_into()?;
let secret_key = SecretKey::from_bytes(secret_key)?;

let vault = self.0.fetch_and_decrypt_vault(&secret_key).await?;
let vault = vault.map(|v| v.to_vec());
let user_data = self.0.get_user_data_from_vault(&secret_key).await?;

Ok(vault)
Ok(JsUserData(user_data))
}

#[wasm_bindgen(js_name = writeBytesToVault)]
pub async fn write_bytes_to_vault(
#[wasm_bindgen(js_name = putUserDataToVault)]
pub async fn put_user_data_to_vault(
&self,
vault: Vec<u8>,
user_data: JsUserData,
wallet: &mut JsWallet,
secret_key: Vec<u8>,
) -> Result<(), JsError> {
let secret_key: [u8; 32] = secret_key[..].try_into()?;
let secret_key = SecretKey::from_bytes(secret_key)?;

let vault = bytes::Bytes::from(vault);
self.0
.write_bytes_to_vault(vault, &mut wallet.0, &secret_key)
.put_user_data_to_vault(&secret_key, &wallet.0, user_data.0)
.await?;

Ok(())
Expand Down
Loading

0 comments on commit 2437a88

Please sign in to comment.