Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: user data stored in vault #2263

Merged
merged 4 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading