Rust implementation of NextGraph, a Decentralized and local-first web 3.0 ecosystem
https://nextgraph.org
byzantine-fault-tolerancecrdtsdappsdecentralizede2eeeventual-consistencyjson-ldlocal-firstmarkdownocapoffline-firstp2pp2p-networkprivacy-protectionrdfrich-text-editorself-hostedsemantic-websparqlweb3collaboration
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1461 lines
50 KiB
1461 lines
50 KiB
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
|
// All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0
|
|
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
|
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
|
// at your option. All files in the project carrying such
|
|
// notice may not be copied, modified, or distributed except
|
|
// according to those terms.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_big_array::BigArray;
|
|
use std::collections::hash_map::{DefaultHasher, Keys};
|
|
use std::hash::{Hash, Hasher};
|
|
use std::{collections::HashMap, fmt};
|
|
use web_time::SystemTime;
|
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
|
|
|
use ng_repo::errors::NgError;
|
|
#[allow(unused_imports)]
|
|
use ng_repo::log::*;
|
|
use ng_repo::types::*;
|
|
use ng_repo::utils::{encrypt_in_place, generate_keypair};
|
|
|
|
use ng_net::types::*;
|
|
|
|
use ng_verifier::site::SiteV0;
|
|
|
|
/// WalletId is a PubKey
|
|
pub type WalletId = PubKey;
|
|
|
|
/// BootstrapId is a WalletId
|
|
pub type BootstrapId = WalletId;
|
|
|
|
/// Bootstrap Version 0
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct BootstrapV0 {
|
|
/// ID
|
|
pub id: BootstrapId,
|
|
|
|
/// Content
|
|
pub content: BootstrapContentV0,
|
|
|
|
/// Signature over content by wallet's private key
|
|
pub sig: Sig,
|
|
}
|
|
|
|
/// Bootstrap info
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum Bootstrap {
|
|
V0(BootstrapV0),
|
|
}
|
|
|
|
impl Bootstrap {
|
|
pub fn id(&self) -> BootstrapId {
|
|
match self {
|
|
Bootstrap::V0(v0) => v0.id,
|
|
}
|
|
}
|
|
pub fn content_as_bytes(&self) -> Vec<u8> {
|
|
match self {
|
|
Bootstrap::V0(v0) => serde_bare::to_vec(&v0.content).unwrap(),
|
|
}
|
|
}
|
|
pub fn sig(&self) -> Sig {
|
|
match self {
|
|
Bootstrap::V0(v0) => v0.sig,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct SessionWalletStorageV0 {
|
|
// string is base64_url encoding of userId(pubkey)
|
|
pub users: HashMap<String, SessionPeerStorageV0>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum SessionWalletStorage {
|
|
V0(SessionWalletStorageV0),
|
|
}
|
|
|
|
impl SessionWalletStorageV0 {
|
|
pub fn new() -> Self {
|
|
SessionWalletStorageV0 {
|
|
users: HashMap::new(),
|
|
}
|
|
}
|
|
pub fn dec_session(
|
|
wallet_key: PrivKey,
|
|
vec: &Vec<u8>,
|
|
) -> Result<SessionWalletStorageV0, NgWalletError> {
|
|
let session_ser = crypto_box::seal_open(&(*wallet_key.to_dh().slice()).into(), vec)
|
|
.map_err(|_| NgWalletError::DecryptionError)?;
|
|
let session: SessionWalletStorage =
|
|
serde_bare::from_slice(&session_ser).map_err(|_| NgWalletError::SerializationError)?;
|
|
let SessionWalletStorage::V0(v0) = session;
|
|
Ok(v0)
|
|
}
|
|
|
|
pub fn create_new_session(
|
|
wallet_id: &PubKey,
|
|
user: PubKey,
|
|
) -> Result<(SessionPeerStorageV0, Vec<u8>), NgWalletError> {
|
|
let mut sws = SessionWalletStorageV0::new();
|
|
let sps = SessionPeerStorageV0::new(user);
|
|
sws.users.insert(sps.user.to_string(), sps.clone());
|
|
let cipher = sws.enc_session(wallet_id)?;
|
|
Ok((sps, cipher))
|
|
}
|
|
|
|
pub fn enc_session(&self, wallet_id: &PubKey) -> Result<Vec<u8>, NgWalletError> {
|
|
let sws_ser = serde_bare::to_vec(&SessionWalletStorage::V0(self.clone())).unwrap();
|
|
let mut rng = crypto_box::aead::OsRng {};
|
|
let cipher = crypto_box::seal(&mut rng, &wallet_id.to_dh_slice().into(), &sws_ser)
|
|
.map_err(|_| NgWalletError::EncryptionError)?;
|
|
Ok(cipher)
|
|
}
|
|
|
|
// pub fn get_first_user_peer_nonce(&self) -> Result<(PubKey, u64), NgWalletError> {
|
|
// if self.users.len() > 1 {
|
|
// panic!("get_first_user_peer_nonce does not work as soon as there are more than one user in SessionWalletStorageV0")
|
|
// };
|
|
// let first = self.users.values().next();
|
|
// if first.is_none() {
|
|
// return Err(NgWalletError::InternalError);
|
|
// }
|
|
// let sps = first.unwrap();
|
|
// Ok((sps.peer_key.to_pub(), sps.last_wallet_nonce))
|
|
// }
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct SessionInfoString {
|
|
pub session_id: u64,
|
|
pub user: String,
|
|
pub private_store_id: String,
|
|
}
|
|
|
|
impl From<SessionInfo> for SessionInfoString {
|
|
fn from(f: SessionInfo) -> Self {
|
|
SessionInfoString {
|
|
session_id: f.session_id,
|
|
private_store_id: f.private_store_id,
|
|
user: f.user.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct SessionInfo {
|
|
pub session_id: u64,
|
|
pub user: UserId,
|
|
pub private_store_id: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct SessionPeerStorageV0 {
|
|
pub user: UserId,
|
|
pub peer_key: PrivKey,
|
|
/// The current nonce used for encrypting this wallet by the user on this device.
|
|
/// It should be incremented BEFORE encrypting the wallet again
|
|
/// when some new operations have been added to the log of the Wallet.
|
|
/// The nonce is by PeerId. It is saved together with the PeerId in the SessionPeerStorage.
|
|
/// If the session is not saved (in-memory) it is lost, but it is fine, as the PeerId is also lost, and a new one
|
|
/// will be generated for the next session.
|
|
pub last_wallet_nonce: u64,
|
|
}
|
|
|
|
impl SessionPeerStorageV0 {
|
|
pub fn new(user: UserId) -> Self {
|
|
let peer = generate_keypair();
|
|
SessionPeerStorageV0 {
|
|
user,
|
|
peer_key: peer.0,
|
|
last_wallet_nonce: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
|
pub struct LocalClientStorageV0 {
|
|
pub priv_key: PrivKey,
|
|
pub storage_master_key: SymKey,
|
|
}
|
|
|
|
impl LocalClientStorageV0 {
|
|
fn crypt(text: &mut Vec<u8>, client: ClientId, wallet_privkey: PrivKey) {
|
|
let client_ser = serde_bare::to_vec(&client).unwrap();
|
|
let wallet_privkey_ser = serde_bare::to_vec(&wallet_privkey).unwrap();
|
|
let mut key_material = [client_ser, wallet_privkey_ser].concat();
|
|
|
|
let mut key: [u8; 32] = blake3::derive_key(
|
|
"NextGraph LocalClientStorageV0 BLAKE3 key",
|
|
key_material.as_slice(),
|
|
);
|
|
|
|
encrypt_in_place(text, key, [0; 12]);
|
|
key.zeroize();
|
|
key_material.zeroize();
|
|
}
|
|
|
|
pub fn decrypt(
|
|
ciphertext: &mut Vec<u8>,
|
|
client: ClientId,
|
|
wallet_privkey: PrivKey,
|
|
) -> Result<LocalClientStorageV0, NgWalletError> {
|
|
Self::crypt(ciphertext, client, wallet_privkey);
|
|
|
|
let res =
|
|
serde_bare::from_slice(&ciphertext).map_err(|_| NgWalletError::DecryptionError)?;
|
|
|
|
ciphertext.zeroize();
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
pub fn encrypt(
|
|
&self,
|
|
client: ClientId,
|
|
wallet_privkey: PrivKey,
|
|
) -> Result<Vec<u8>, NgWalletError> {
|
|
let mut ser = serde_bare::to_vec(self).map_err(|_| NgWalletError::EncryptionError)?;
|
|
|
|
Self::crypt(&mut ser, client, wallet_privkey);
|
|
|
|
Ok(ser)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct LocalWalletStorageV0 {
|
|
pub in_memory: bool,
|
|
pub bootstrap: BootstrapContent,
|
|
pub wallet: Wallet,
|
|
pub client_id: ClientId,
|
|
pub client_auto_open: Vec<PubKey>,
|
|
pub client_name: Option<String>,
|
|
#[serde(with = "serde_bytes")]
|
|
pub encrypted_client_storage: Vec<u8>,
|
|
}
|
|
|
|
impl From<&CreateWalletIntermediaryV0> for LocalWalletStorageV0 {
|
|
fn from(res: &CreateWalletIntermediaryV0) -> Self {
|
|
LocalWalletStorageV0 {
|
|
bootstrap: BootstrapContent::V0(BootstrapContentV0::new_empty()),
|
|
wallet: Wallet::TemporarilyEmpty,
|
|
in_memory: res.in_memory,
|
|
client_id: res.client.id,
|
|
client_auto_open: res.client.auto_open.clone(),
|
|
client_name: res.client.name.clone(),
|
|
encrypted_client_storage: res
|
|
.client
|
|
.sensitive_client_storage
|
|
.encrypt(res.client.id, res.wallet_privkey.clone())
|
|
.unwrap(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&CreateWalletIntermediaryV0> for SensitiveWalletV0 {
|
|
fn from(res: &CreateWalletIntermediaryV0) -> Self {
|
|
SensitiveWalletV0 {
|
|
wallet_privkey: res.wallet_privkey.clone(),
|
|
wallet_id: res.wallet_name.clone(),
|
|
save_to_ng_one: if res.send_wallet {
|
|
SaveToNGOne::Wallet
|
|
} else if res.send_bootstrap {
|
|
SaveToNGOne::Bootstrap
|
|
} else {
|
|
SaveToNGOne::No
|
|
},
|
|
// for now, personal_site is null. will be replaced later
|
|
personal_site: PubKey::nil(),
|
|
personal_site_id: "".to_string(),
|
|
sites: HashMap::new(),
|
|
brokers: HashMap::new(),
|
|
overlay_core_overrides: HashMap::new(),
|
|
third_parties: HashMap::new(),
|
|
log: None,
|
|
master_key: None,
|
|
client: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&CreateWalletIntermediaryV0> for SensitiveWallet {
|
|
fn from(res: &CreateWalletIntermediaryV0) -> SensitiveWallet {
|
|
SensitiveWallet::V0(res.into())
|
|
}
|
|
}
|
|
|
|
impl LocalWalletStorageV0 {
|
|
#[doc(hidden)]
|
|
pub fn new(
|
|
encrypted_wallet: Wallet,
|
|
wallet_priv_key: PrivKey,
|
|
client: &ClientV0,
|
|
in_memory: bool,
|
|
) -> Result<Self, NgWalletError> {
|
|
Ok(LocalWalletStorageV0 {
|
|
bootstrap: BootstrapContent::V0(BootstrapContentV0::new_empty()),
|
|
wallet: encrypted_wallet,
|
|
in_memory,
|
|
client_id: client.id,
|
|
client_auto_open: client.auto_open.clone(),
|
|
client_name: client.name.clone(),
|
|
encrypted_client_storage: client
|
|
.sensitive_client_storage
|
|
.encrypt(client.id, wallet_priv_key)?,
|
|
})
|
|
}
|
|
#[doc(hidden)]
|
|
pub fn to_client_v0(&self, wallet_privkey: PrivKey) -> Result<ClientV0, NgWalletError> {
|
|
Ok(ClientV0 {
|
|
id: self.client_id,
|
|
auto_open: self.client_auto_open.clone(),
|
|
name: self.client_name.clone(),
|
|
sensitive_client_storage: self.local_client_storage_v0(wallet_privkey)?,
|
|
})
|
|
}
|
|
|
|
/// decrypts the client_storage field, given the wallet PrivKey
|
|
pub fn local_client_storage_v0(
|
|
&self,
|
|
wallet_privkey: PrivKey,
|
|
) -> Result<LocalClientStorageV0, NgWalletError> {
|
|
let mut cipher = self.encrypted_client_storage.clone();
|
|
LocalClientStorageV0::decrypt(&mut cipher, self.client_id, wallet_privkey)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum LocalWalletStorage {
|
|
V0(HashMap<String, LocalWalletStorageV0>),
|
|
}
|
|
|
|
impl LocalWalletStorage {
|
|
pub fn v0_from_vec(vec: &Vec<u8>) -> Result<Self, NgError> {
|
|
let wallets: LocalWalletStorage = serde_bare::from_slice(vec)?;
|
|
Ok(wallets)
|
|
}
|
|
pub fn v0_to_vec(wallets: &HashMap<String, LocalWalletStorageV0>) -> Vec<u8> {
|
|
serde_bare::to_vec(&LocalWalletStorage::V0(wallets.clone())).unwrap()
|
|
}
|
|
}
|
|
|
|
/// Device info Version 0
|
|
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
|
pub struct ClientV0 {
|
|
#[zeroize(skip)]
|
|
/// ClientID
|
|
pub id: PubKey,
|
|
|
|
/// list of users that should be opened automatically (at launch, after wallet opened) on this device
|
|
#[zeroize(skip)]
|
|
pub auto_open: Vec<PubKey>,
|
|
|
|
/// user supplied Device name. can be useful to distinguish between several devices (phone, tablet, laptop, office desktop, etc...)
|
|
#[zeroize(skip)]
|
|
pub name: Option<String>,
|
|
|
|
/// contains the decrypted information needed when user is opening their wallet on this client.
|
|
pub sensitive_client_storage: LocalClientStorageV0,
|
|
}
|
|
|
|
impl ClientV0 {
|
|
pub fn id(&self) -> String {
|
|
self.id.to_string()
|
|
}
|
|
|
|
#[deprecated(note = "**Don't use nil method**")]
|
|
#[allow(deprecated)]
|
|
pub fn nil() -> Self {
|
|
ClientV0 {
|
|
id: PubKey::nil(),
|
|
sensitive_client_storage: LocalClientStorageV0 {
|
|
priv_key: PrivKey::nil(),
|
|
storage_master_key: SymKey::nil(),
|
|
},
|
|
auto_open: vec![],
|
|
name: None,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[allow(deprecated)]
|
|
pub fn dummy() -> Self {
|
|
Self::nil()
|
|
}
|
|
|
|
pub fn new_with_auto_open(user: PubKey) -> Self {
|
|
let (priv_key, id) = generate_keypair();
|
|
ClientV0 {
|
|
id,
|
|
sensitive_client_storage: LocalClientStorageV0 {
|
|
priv_key,
|
|
storage_master_key: SymKey::random(),
|
|
},
|
|
auto_open: vec![user],
|
|
name: None,
|
|
}
|
|
}
|
|
|
|
pub fn new() -> Self {
|
|
let (priv_key, id) = generate_keypair();
|
|
ClientV0 {
|
|
id,
|
|
sensitive_client_storage: LocalClientStorageV0 {
|
|
priv_key,
|
|
storage_master_key: SymKey::random(),
|
|
},
|
|
auto_open: vec![],
|
|
name: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Save to nextgraph.one
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum SaveToNGOne {
|
|
No,
|
|
Bootstrap,
|
|
Wallet,
|
|
}
|
|
|
|
/// SensitiveWallet block Version 0
|
|
#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)]
|
|
pub struct SensitiveWalletV0 {
|
|
pub wallet_privkey: PrivKey,
|
|
|
|
#[zeroize(skip)]
|
|
pub wallet_id: String,
|
|
|
|
//#[serde(with = "serde_bytes")]
|
|
//pub pazzle: Vec<u8>,
|
|
|
|
//pub mnemonic: [u16; 12],
|
|
|
|
//pub pin: [u8; 4],
|
|
#[zeroize(skip)]
|
|
pub save_to_ng_one: SaveToNGOne,
|
|
|
|
#[zeroize(skip)]
|
|
pub personal_site: PubKey,
|
|
|
|
#[zeroize(skip)]
|
|
pub personal_site_id: String,
|
|
|
|
#[zeroize(skip)]
|
|
pub sites: HashMap<String, SiteV0>,
|
|
|
|
// map of brokers and their connection details
|
|
#[zeroize(skip)]
|
|
pub brokers: HashMap<String, Vec<BrokerInfoV0>>,
|
|
|
|
// map of all devices of the user
|
|
//#[zeroize(skip)]
|
|
//pub clients: HashMap<String, ClientV0>,
|
|
#[zeroize(skip)]
|
|
pub overlay_core_overrides: HashMap<String, Vec<PubKey>>,
|
|
|
|
/// third parties data saved in the wallet. the string (key) in the hashmap should be unique among vendors.
|
|
/// the format of the byte array (value) is up to the vendor, to serde as needed.
|
|
#[zeroize(skip)]
|
|
pub third_parties: HashMap<String, serde_bytes::ByteBuf>,
|
|
|
|
#[zeroize(skip)]
|
|
pub log: Option<WalletLogV0>,
|
|
|
|
pub master_key: Option<[u8; 32]>,
|
|
|
|
pub client: Option<ClientV0>,
|
|
}
|
|
|
|
/// SensitiveWallet block
|
|
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
|
pub enum SensitiveWallet {
|
|
V0(SensitiveWalletV0),
|
|
}
|
|
|
|
impl SensitiveWallet {
|
|
pub fn privkey(&self) -> PrivKey {
|
|
match self {
|
|
Self::V0(v0) => v0.wallet_privkey.clone(),
|
|
}
|
|
}
|
|
pub fn id(&self) -> String {
|
|
match self {
|
|
Self::V0(v0) => v0.wallet_id.clone(),
|
|
}
|
|
}
|
|
// TODO: this is unfortunate. id should return the PubKey, name should return the String
|
|
pub fn name(&self) -> String {
|
|
self.id()
|
|
}
|
|
pub fn client(&self) -> &Option<ClientV0> {
|
|
match self {
|
|
Self::V0(v0) => &v0.client,
|
|
}
|
|
}
|
|
pub fn site_names(&self) -> Keys<String, SiteV0> {
|
|
match self {
|
|
Self::V0(v0) => v0.sites.keys(),
|
|
}
|
|
}
|
|
pub fn site(&self, user_id: &UserId) -> Result<&SiteV0, NgError> {
|
|
match self {
|
|
Self::V0(v0) => match v0.sites.get(&user_id.to_string()) {
|
|
Some(site) => Ok(site),
|
|
None => Err(NgError::UserNotFound),
|
|
},
|
|
}
|
|
}
|
|
pub fn set_client(&mut self, client: ClientV0) {
|
|
match self {
|
|
Self::V0(v0) => v0.client = Some(client),
|
|
}
|
|
}
|
|
pub fn individual_site(
|
|
&self,
|
|
user_id: &UserId,
|
|
) -> Option<(
|
|
PrivKey,
|
|
Option<ReadCap>,
|
|
Option<RepoId>,
|
|
Option<RepoId>,
|
|
Option<RepoId>,
|
|
)> {
|
|
match self {
|
|
Self::V0(v0) => match v0.sites.get(&user_id.to_string()) {
|
|
Some(site) => match &site.site_type {
|
|
SiteType::Individual((user, readcap)) => Some((
|
|
user.clone(),
|
|
Some(readcap.clone()),
|
|
Some(site.private.id),
|
|
Some(site.protected.id),
|
|
Some(site.public.id),
|
|
)),
|
|
_ => None,
|
|
},
|
|
None => None,
|
|
},
|
|
}
|
|
}
|
|
pub fn has_user(&self, user_id: &UserId) -> bool {
|
|
match self {
|
|
Self::V0(v0) => v0.sites.get(&user_id.to_string()).is_some(),
|
|
}
|
|
}
|
|
pub fn personal_identity(&self) -> UserId {
|
|
match self {
|
|
Self::V0(v0) => v0.personal_site,
|
|
}
|
|
}
|
|
pub fn import_v0(
|
|
&mut self,
|
|
encrypted_wallet: Wallet,
|
|
in_memory: bool,
|
|
) -> Result<LocalWalletStorageV0, NgWalletError> {
|
|
match self {
|
|
Self::V0(v0) => v0.import(encrypted_wallet, in_memory),
|
|
}
|
|
}
|
|
|
|
pub fn complete_with_site_and_brokers(
|
|
&mut self,
|
|
site: SiteV0,
|
|
brokers: HashMap<String, Vec<BrokerInfoV0>>,
|
|
) {
|
|
match self {
|
|
Self::V0(v0) => v0.complete_with_site_and_brokers(site, brokers),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SensitiveWalletV0 {
|
|
pub fn import(
|
|
&mut self,
|
|
encrypted_wallet: Wallet,
|
|
in_memory: bool,
|
|
) -> Result<LocalWalletStorageV0, NgWalletError> {
|
|
// Creating a new client
|
|
// TODO, create client with auto_open taken from wallet log ?
|
|
let client = ClientV0::new_with_auto_open(self.personal_site);
|
|
|
|
let lws = LocalWalletStorageV0::new(
|
|
encrypted_wallet,
|
|
self.wallet_privkey.clone(),
|
|
&client,
|
|
in_memory,
|
|
)?;
|
|
|
|
self.client = Some(client);
|
|
|
|
Ok(lws)
|
|
}
|
|
pub fn add_site(&mut self, site: SiteV0) {
|
|
let site_id = site.id;
|
|
let _ = self.sites.insert(site_id.to_string(), site);
|
|
}
|
|
pub fn add_brokers(&mut self, brokers: Vec<BrokerInfoV0>) {
|
|
for broker in brokers {
|
|
let key = broker.get_id().to_string();
|
|
let mut list = self.brokers.get_mut(&key);
|
|
if list.is_none() {
|
|
let new_list = vec![];
|
|
self.brokers.insert(key.clone(), new_list);
|
|
list = self.brokers.get_mut(&key);
|
|
}
|
|
list.unwrap().push(broker);
|
|
}
|
|
}
|
|
// pub fn add_client(&mut self, client: ClientV0) {
|
|
// let client_id = client.priv_key.to_pub().to_string();
|
|
// let _ = self.clients.insert(client_id, client);
|
|
// }
|
|
pub fn add_overlay_core_overrides(&mut self, overlay: &OverlayId, cores: &Vec<PubKey>) {
|
|
let _ = self
|
|
.overlay_core_overrides
|
|
.insert(overlay.to_string(), cores.to_vec());
|
|
}
|
|
|
|
pub fn complete_with_site_and_brokers(
|
|
&mut self,
|
|
site: SiteV0,
|
|
brokers: HashMap<String, Vec<BrokerInfoV0>>,
|
|
) {
|
|
let personal_site = site.id;
|
|
let personal_site_id = personal_site.to_string();
|
|
self.personal_site = personal_site;
|
|
self.personal_site_id = personal_site_id.clone();
|
|
self.sites.insert(personal_site_id, site);
|
|
self.brokers = brokers;
|
|
}
|
|
}
|
|
|
|
/// Wallet content Version 0
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct WalletContentV0 {
|
|
#[serde(with = "serde_bytes")]
|
|
pub security_img: Vec<u8>,
|
|
|
|
pub security_txt: String,
|
|
|
|
/// can be 9, 12 or 15 (or 0, in this case salt_pazzle and enc_master_key_pazzle are filled with zeros and should not be used)
|
|
pub pazzle_length: u8,
|
|
|
|
pub salt_pazzle: [u8; 16],
|
|
|
|
pub salt_mnemonic: [u8; 16],
|
|
|
|
// encrypted master keys. first is encrypted with pazzle, second is encrypted with mnemonic
|
|
// AD = wallet_id
|
|
#[serde(with = "BigArray")]
|
|
pub enc_master_key_pazzle: [u8; 48],
|
|
#[serde(with = "BigArray")]
|
|
pub enc_master_key_mnemonic: [u8; 48],
|
|
|
|
// nonce for the encryption of masterkey
|
|
// incremented only if the masterkey changes
|
|
// be very careful with incrementing this, as a conflict would result in total loss of crypto guarantees.
|
|
pub master_nonce: u8,
|
|
|
|
pub timestamp: Timestamp,
|
|
|
|
// the peerId that updated this version of the Wallet. this value is truncated by half and concatenated with the nonce
|
|
pub peer_id: PubKey,
|
|
pub nonce: u64,
|
|
|
|
// WalletLog content encrypted with XChaCha20Poly1305, AD = timestamp and walletID
|
|
#[serde(with = "serde_bytes")]
|
|
pub encrypted: Vec<u8>,
|
|
}
|
|
|
|
/// Wallet Log V0
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct WalletLogV0 {
|
|
pub log: Vec<(u128, WalletOperation)>,
|
|
}
|
|
|
|
/// Wallet Log
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum WalletLog {
|
|
V0(WalletLogV0),
|
|
}
|
|
|
|
impl WalletLog {
|
|
pub fn new_v0(create_op: WalletOpCreateV0) -> Self {
|
|
WalletLog::V0(WalletLogV0::new(create_op))
|
|
}
|
|
pub fn add(&mut self, op: WalletOperation) {
|
|
match self {
|
|
Self::V0(v0) => v0.add(op),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl WalletLogV0 {
|
|
pub fn new(create_op: WalletOpCreateV0) -> Self {
|
|
let mut wallet = WalletLogV0 { log: vec![] };
|
|
wallet.add(WalletOperation::CreateWalletV0(create_op));
|
|
wallet
|
|
}
|
|
|
|
pub fn add(&mut self, op: WalletOperation) {
|
|
let duration = SystemTime::now()
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
self.log.push((duration, op));
|
|
}
|
|
|
|
/// applies all the operation and produces an encrypted wallet object.
|
|
pub fn reduce(self, master_key: [u8; 32]) -> Result<SensitiveWalletV0, NgWalletError> {
|
|
if self.log.len() < 1 {
|
|
Err(NgWalletError::NoCreateWalletPresent)
|
|
} else if let (_, WalletOperation::CreateWalletV0(create_op)) = &self.log[0] {
|
|
let mut wallet: SensitiveWalletV0 = create_op.into();
|
|
wallet.master_key = Some(master_key);
|
|
|
|
for op in &self.log {
|
|
match &op.1 {
|
|
WalletOperation::CreateWalletV0(_) => { /* intentionally left blank. this op is already reduced */
|
|
}
|
|
WalletOperation::AddSiteV0(o) => {
|
|
if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteV0") {
|
|
wallet.add_site(o.clone());
|
|
}
|
|
}
|
|
WalletOperation::RemoveSiteV0(_) => {}
|
|
WalletOperation::AddBrokerServerV0(o) => {
|
|
if self.is_last_and_not_deleted_afterwards(op, "RemoveBrokerServerV0") {
|
|
wallet.add_brokers(vec![BrokerInfoV0::ServerV0(o.clone())]);
|
|
}
|
|
}
|
|
WalletOperation::RemoveBrokerServerV0(_) => {}
|
|
WalletOperation::SetSaveToNGOneV0(o) => {
|
|
if self.is_last_occurrence(op.0, &op.1) != 0 {
|
|
wallet.save_to_ng_one = o.clone();
|
|
}
|
|
}
|
|
WalletOperation::SetBrokerCoreV0(o) => {
|
|
if self.is_last_occurrence(op.0, &op.1) != 0 {
|
|
wallet.add_brokers(vec![BrokerInfoV0::CoreV0(o.clone())]);
|
|
}
|
|
}
|
|
// WalletOperation::SetClientV0(o) => {
|
|
// if self.is_last_occurrence(op.0, &op.1) != 0 {
|
|
// wallet.add_client(o.clone());
|
|
// }
|
|
// }
|
|
WalletOperation::AddOverlayCoreOverrideV0((overlay, cores)) => {
|
|
if self
|
|
.is_last_and_not_deleted_afterwards(op, "RemoveOverlayCoreOverrideV0")
|
|
{
|
|
wallet.add_overlay_core_overrides(overlay, cores);
|
|
}
|
|
}
|
|
WalletOperation::RemoveOverlayCoreOverrideV0(_) => {}
|
|
WalletOperation::AddSiteCoreV0((site, core, registration)) => {
|
|
if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteCoreV0") {
|
|
let _ = wallet.sites.get_mut(&site.to_string()).and_then(|site| {
|
|
site.cores.push((*core, *registration));
|
|
None::<SiteV0>
|
|
});
|
|
}
|
|
}
|
|
WalletOperation::RemoveSiteCoreV0(_) => {}
|
|
WalletOperation::AddSiteBootstrapV0((site, server)) => {
|
|
if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteBootstrapV0") {
|
|
let _ = wallet.sites.get_mut(&site.to_string()).and_then(|site| {
|
|
site.bootstraps.push(*server);
|
|
None::<SiteV0>
|
|
});
|
|
}
|
|
}
|
|
WalletOperation::RemoveSiteBootstrapV0(_) => {}
|
|
WalletOperation::AddThirdPartyDataV0((key, value)) => {
|
|
if self.is_last_and_not_deleted_afterwards(op, "RemoveThirdPartyDataV0") {
|
|
let _ = wallet.third_parties.insert(key.to_string(), value.clone());
|
|
}
|
|
}
|
|
WalletOperation::RemoveThirdPartyDataV0(_) => {} // WalletOperation::SetSiteRBDRefV0((site, store_type, rbdr)) => {
|
|
// if self.is_last_occurrence(op.0, &op.1) != 0 {
|
|
// let _ = wallet.sites.get_mut(&site.to_string()).and_then(|site| {
|
|
// match store_type {
|
|
// SiteStoreType::Public => site.public.read_cap = rbdr.clone(),
|
|
// SiteStoreType::Protected => {
|
|
// site.protected.read_cap = rbdr.clone()
|
|
// }
|
|
// SiteStoreType::Private => site.private.read_cap = rbdr.clone(),
|
|
// };
|
|
// None::<SiteV0>
|
|
// });
|
|
// }
|
|
// }
|
|
// WalletOperation::SetSiteRepoSecretV0((site, store_type, secret)) => {
|
|
// if self.is_last_occurrence(op.0, &op.1) != 0 {
|
|
// let _ = wallet.sites.get_mut(&site.to_string()).and_then(|site| {
|
|
// match store_type {
|
|
// SiteStoreType::Public => site.public.write_cap = secret.clone(),
|
|
// SiteStoreType::Protected => {
|
|
// site.protected.write_cap = secret.clone()
|
|
// }
|
|
// SiteStoreType::Private => {
|
|
// site.private.write_cap = secret.clone()
|
|
// }
|
|
// };
|
|
// None::<SiteV0>
|
|
// });
|
|
// }
|
|
// }
|
|
}
|
|
}
|
|
//log_debug!("reduced {:?}", wallet);
|
|
wallet.log = Some(self);
|
|
Ok(wallet)
|
|
} else {
|
|
Err(NgWalletError::NoCreateWalletPresent)
|
|
}
|
|
}
|
|
|
|
pub fn is_first_and_not_deleted_afterwards(
|
|
&self,
|
|
item: &(u128, WalletOperation),
|
|
delete_type: &str,
|
|
) -> bool {
|
|
let hash = self.is_first_occurrence(item.0, &item.1);
|
|
if hash != 0 {
|
|
// check that it hasn't been deleted since the first occurrence
|
|
let deleted = self.find_first_occurrence_of_type_and_hash_after_timestamp(
|
|
delete_type,
|
|
hash,
|
|
item.0,
|
|
);
|
|
return deleted.is_none();
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn is_last_and_not_deleted_afterwards(
|
|
&self,
|
|
item: &(u128, WalletOperation),
|
|
delete_type: &str,
|
|
) -> bool {
|
|
let hash = self.is_last_occurrence(item.0, &item.1);
|
|
if hash != 0 {
|
|
// check that it hasn't been deleted since the last occurrence
|
|
let deleted = self.find_first_occurrence_of_type_and_hash_after_timestamp(
|
|
delete_type,
|
|
hash,
|
|
item.0,
|
|
);
|
|
return deleted.is_none();
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn is_first_occurrence(&self, timestamp: u128, searched_op: &WalletOperation) -> u64 {
|
|
let searched_hash = searched_op.hash();
|
|
//let mut timestamp = u128::MAX;
|
|
//let mut found = searched_op;
|
|
for op in &self.log {
|
|
let hash = op.1.hash();
|
|
if hash.0 == searched_hash.0 && op.0 < timestamp && hash.1 == searched_hash.1 {
|
|
//timestamp = op.0;
|
|
//found = &op.1;
|
|
return 0;
|
|
}
|
|
}
|
|
searched_hash.0
|
|
}
|
|
|
|
pub fn is_last_occurrence(&self, timestamp: u128, searched_op: &WalletOperation) -> u64 {
|
|
let searched_hash = searched_op.hash();
|
|
//let mut timestamp = 0u128;
|
|
//let mut found = searched_op;
|
|
for op in &self.log {
|
|
let hash = op.1.hash();
|
|
if hash.0 == searched_hash.0 && op.0 > timestamp && hash.1 == searched_hash.1 {
|
|
//timestamp = op.0;
|
|
//found = &op.1;
|
|
return 0;
|
|
}
|
|
}
|
|
searched_hash.0
|
|
}
|
|
|
|
pub fn find_first_occurrence_of_type_and_hash_after_timestamp(
|
|
&self,
|
|
searched_type: &str,
|
|
searched_hash: u64,
|
|
after: u128,
|
|
) -> Option<(u128, &WalletOperation)> {
|
|
let mut timestamp = u128::MAX;
|
|
let mut found = None;
|
|
for op in &self.log {
|
|
let hash = op.1.hash();
|
|
if hash.0 == searched_hash
|
|
&& op.0 > after
|
|
&& op.0 < timestamp
|
|
&& hash.1 == searched_type
|
|
{
|
|
timestamp = op.0;
|
|
found = Some(&op.1);
|
|
}
|
|
}
|
|
found.map(|f| (timestamp, f))
|
|
}
|
|
}
|
|
|
|
/// WalletOperation
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum WalletOperation {
|
|
CreateWalletV0(WalletOpCreateV0),
|
|
AddSiteV0(SiteV0),
|
|
RemoveSiteV0(PrivKey),
|
|
AddBrokerServerV0(BrokerServerV0),
|
|
RemoveBrokerServerV0(BrokerServerV0),
|
|
SetSaveToNGOneV0(SaveToNGOne),
|
|
SetBrokerCoreV0(BrokerCoreV0),
|
|
//SetClientV0(ClientV0),
|
|
AddOverlayCoreOverrideV0((OverlayId, Vec<PubKey>)),
|
|
RemoveOverlayCoreOverrideV0(OverlayId),
|
|
AddSiteCoreV0((PubKey, PubKey, Option<[u8; 32]>)),
|
|
RemoveSiteCoreV0((PubKey, PubKey)),
|
|
AddSiteBootstrapV0((PubKey, PubKey)),
|
|
RemoveSiteBootstrapV0((PubKey, PubKey)),
|
|
AddThirdPartyDataV0((String, serde_bytes::ByteBuf)),
|
|
RemoveThirdPartyDataV0(String),
|
|
//SetSiteRBDRefV0((PubKey, SiteStoreType, ObjectRef)),
|
|
//SetSiteRepoSecretV0((PubKey, SiteStoreType, RepoWriteCapSecret)),
|
|
}
|
|
|
|
impl WalletOperation {
|
|
pub fn hash(&self) -> (u64, &str) {
|
|
let mut s = DefaultHasher::new();
|
|
match self {
|
|
Self::CreateWalletV0(_t) => (0, "CreateWalletV0"),
|
|
Self::AddSiteV0(t) => {
|
|
t.id.hash(&mut s);
|
|
(s.finish(), "AddSiteV0")
|
|
}
|
|
Self::RemoveSiteV0(t) => {
|
|
t.hash(&mut s);
|
|
(s.finish(), "RemoveSiteV0")
|
|
}
|
|
Self::AddBrokerServerV0(t) => {
|
|
t.hash(&mut s);
|
|
(s.finish(), "AddBrokerServerV0")
|
|
}
|
|
Self::RemoveBrokerServerV0(t) => {
|
|
t.hash(&mut s);
|
|
(s.finish(), "RemoveBrokerServerV0")
|
|
}
|
|
Self::SetSaveToNGOneV0(_t) => (0, "SetSaveToNGOneV0"),
|
|
Self::SetBrokerCoreV0(t) => {
|
|
t.peer_id.hash(&mut s);
|
|
(s.finish(), "SetBrokerCoreV0")
|
|
}
|
|
// Self::SetClientV0(t) => {
|
|
// t.priv_key.hash(&mut s);
|
|
// (s.finish(), "SetClientV0")
|
|
// }
|
|
Self::AddOverlayCoreOverrideV0(t) => {
|
|
t.0.hash(&mut s);
|
|
(s.finish(), "AddOverlayCoreOverrideV0")
|
|
}
|
|
Self::RemoveOverlayCoreOverrideV0(t) => {
|
|
t.hash(&mut s);
|
|
(s.finish(), "RemoveOverlayCoreOverrideV0")
|
|
}
|
|
Self::AddSiteCoreV0(t) => {
|
|
t.0.hash(&mut s);
|
|
t.1.hash(&mut s);
|
|
(s.finish(), "AddSiteCoreV0")
|
|
}
|
|
Self::RemoveSiteCoreV0(t) => {
|
|
t.0.hash(&mut s);
|
|
t.1.hash(&mut s);
|
|
(s.finish(), "RemoveSiteCoreV0")
|
|
}
|
|
Self::AddSiteBootstrapV0(t) => {
|
|
t.0.hash(&mut s);
|
|
t.1.hash(&mut s);
|
|
(s.finish(), "AddSiteBootstrapV0")
|
|
}
|
|
Self::RemoveSiteBootstrapV0(t) => {
|
|
t.0.hash(&mut s);
|
|
t.1.hash(&mut s);
|
|
(s.finish(), "RemoveSiteBootstrapV0")
|
|
}
|
|
Self::AddThirdPartyDataV0(t) => {
|
|
t.0.hash(&mut s);
|
|
(s.finish(), "AddThirdPartyDataV0")
|
|
}
|
|
Self::RemoveThirdPartyDataV0(t) => {
|
|
t.hash(&mut s);
|
|
(s.finish(), "RemoveThirdPartyDataV0")
|
|
} // Self::SetSiteRBDRefV0(t) => {
|
|
// t.0.hash(&mut s);
|
|
// t.1.hash(&mut s);
|
|
// (s.finish(), "SetSiteRBDRefV0")
|
|
// }
|
|
// Self::SetSiteRepoSecretV0(t) => {
|
|
// t.0.hash(&mut s);
|
|
// t.1.hash(&mut s);
|
|
// (s.finish(), "SetSiteRepoSecretV0")
|
|
// }
|
|
}
|
|
}
|
|
}
|
|
|
|
/// WalletOp Create V0
|
|
/// first operation in the log
|
|
/// also serialized and encoded in Rescue QRcode
|
|
#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)]
|
|
pub struct WalletOpCreateV0 {
|
|
pub wallet_privkey: PrivKey,
|
|
|
|
// #[serde(skip)]
|
|
// pub pazzle: Vec<u8>,
|
|
|
|
// #[serde(skip)]
|
|
// pub mnemonic: [u16; 12],
|
|
|
|
// #[serde(skip)]
|
|
// pub pin: [u8; 4],
|
|
#[zeroize(skip)]
|
|
pub save_to_ng_one: SaveToNGOne,
|
|
|
|
#[zeroize(skip)]
|
|
pub personal_site: SiteV0,
|
|
// list of brokers and their connection details
|
|
//#[zeroize(skip)]
|
|
//pub brokers: Vec<BrokerInfoV0>,
|
|
//#[serde(skip)]
|
|
//pub client: ClientV0,
|
|
}
|
|
|
|
impl From<&WalletOpCreateV0> for SensitiveWalletV0 {
|
|
fn from(op: &WalletOpCreateV0) -> Self {
|
|
let personal_site = op.personal_site.id;
|
|
let mut wallet = SensitiveWalletV0 {
|
|
wallet_privkey: op.wallet_privkey.clone(),
|
|
wallet_id: op.wallet_privkey.to_pub().to_string(),
|
|
//pazzle: op.pazzle.clone(),
|
|
//mnemonic: op.mnemonic.clone(),
|
|
//pin: op.pin.clone(),
|
|
save_to_ng_one: op.save_to_ng_one.clone(),
|
|
personal_site,
|
|
personal_site_id: personal_site.to_string(),
|
|
sites: HashMap::new(),
|
|
brokers: HashMap::new(),
|
|
//clients: HashMap::new(),
|
|
overlay_core_overrides: HashMap::new(),
|
|
third_parties: HashMap::new(),
|
|
log: None,
|
|
master_key: None,
|
|
client: None, //Some(op.client.clone()),
|
|
};
|
|
wallet.add_site(op.personal_site.clone());
|
|
//wallet.add_brokers(op.brokers.clone());
|
|
//wallet.add_client(op.client.clone());
|
|
wallet
|
|
}
|
|
}
|
|
|
|
/// Reduced Wallet content Version 0, for Login QRcode
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct ReducedWalletContentV0 {
|
|
/// can be 9, 12 or 15 (or 0, in this case salt_pazzle and enc_master_key_pazzle are filled with zeros and should not be used)
|
|
pub pazzle_length: u8,
|
|
|
|
pub salt_pazzle: [u8; 16],
|
|
|
|
pub salt_mnemonic: [u8; 16],
|
|
|
|
// encrypted master keys. first is encrypted with pazzle, second is encrypted with mnemonic
|
|
// AD = wallet_id
|
|
#[serde(with = "BigArray")]
|
|
pub enc_master_key_pazzle: [u8; 48],
|
|
#[serde(with = "BigArray")]
|
|
pub enc_master_key_mnemonic: [u8; 48],
|
|
|
|
// nonce for the encryption of masterkey
|
|
// incremented only if the masterkey changes
|
|
// be very careful with incrementing this, as a conflict would result in total loss of crypto guarantees.
|
|
pub master_nonce: u8,
|
|
|
|
pub timestamp: Timestamp,
|
|
|
|
// the peerId that updated this version of the Wallet. this value is truncated by half and concatenated with the nonce
|
|
pub peer_id: PubKey,
|
|
pub nonce: u64,
|
|
|
|
// ReducedSensitiveWalletV0 content encrypted with XChaCha20Poly1305, AD = timestamp and walletID
|
|
#[serde(with = "serde_bytes")]
|
|
pub encrypted: Vec<u8>,
|
|
}
|
|
|
|
/// Broker Info Version 0
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum BrokerInfoV0 {
|
|
ServerV0(BrokerServerV0),
|
|
CoreV0(BrokerCoreV0),
|
|
}
|
|
|
|
impl BrokerInfoV0 {
|
|
pub fn get_id(&self) -> PubKey {
|
|
match self {
|
|
Self::CoreV0(c) => c.peer_id,
|
|
Self::ServerV0(s) => s.peer_id,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// ReducedSensitiveWallet block Version 0
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct ReducedSensitiveWalletV0 {
|
|
pub save_to_ng_one: SaveToNGOne,
|
|
|
|
// main Site (Personal)
|
|
pub personal_site: ReducedSiteV0,
|
|
|
|
// list of brokers and their connection details
|
|
pub brokers: Vec<BrokerInfoV0>,
|
|
|
|
pub client: ClientV0,
|
|
}
|
|
|
|
/// ReducedSensitiveWallet block
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum ReducedSensitiveWallet {
|
|
V0(ReducedSensitiveWalletV0),
|
|
}
|
|
|
|
/// Wallet Version 0
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct WalletV0 {
|
|
/// ID
|
|
pub id: WalletId,
|
|
|
|
/// Content
|
|
pub content: WalletContentV0,
|
|
|
|
/// Signature over content by wallet's private key
|
|
pub sig: Sig,
|
|
}
|
|
|
|
/// Wallet info
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum Wallet {
|
|
V0(WalletV0),
|
|
TemporarilyEmpty,
|
|
}
|
|
|
|
/// Add Wallet Version 0
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct AddWalletV0 {
|
|
/// wallet. optional (for those who chose not to upload their wallet to nextgraph.one server)
|
|
pub wallet: Option<Wallet>,
|
|
|
|
/// bootstrap
|
|
pub bootstrap: Bootstrap,
|
|
}
|
|
|
|
/// Add Wallet
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum AddWallet {
|
|
V0(AddWalletV0),
|
|
}
|
|
|
|
impl AddWallet {
|
|
pub fn id(&self) -> BootstrapId {
|
|
match self {
|
|
AddWallet::V0(v0) => v0.bootstrap.id(),
|
|
}
|
|
}
|
|
pub fn bootstrap(&self) -> &Bootstrap {
|
|
match self {
|
|
AddWallet::V0(v0) => &v0.bootstrap,
|
|
}
|
|
}
|
|
pub fn wallet(&self) -> Option<&Wallet> {
|
|
match self {
|
|
AddWallet::V0(v0) => v0.wallet.as_ref(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create Wallet Version 0, used by the API create_wallet_v0 as a list of arguments
|
|
#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)]
|
|
pub struct CreateWalletV0 {
|
|
/// a vector containing the binary content of an image file that will be used at every login, displayed (on devices that can)
|
|
/// to the user so they can check the wallet is theirs and that entering their pazzle and PIN is safe and there is no phishing attack.
|
|
/// an attacker would redirect the user to a clone of the wallet opener app, and would try to steal what the user enters
|
|
/// but this attacker would not possess the security_img of the user as it is only present locally in the wallet file.
|
|
/// the image should be bigger than 150x150px. There is no need to provide more than 400x400px as it will be scaled down anyway.
|
|
/// We accept several formats like JPEG, PNG, GIF, WEBP and more.
|
|
/// The image should be unique to the user. But it should not be too personal neither. Do not upload face picture, this is not a profile pic.
|
|
/// The best would be any picture that the user recognizes as unique.
|
|
/// Please be aware that other users who are sharing the same device, will be able to see this image.
|
|
#[zeroize(skip)]
|
|
#[serde(with = "serde_bytes")]
|
|
pub security_img: Vec<u8>,
|
|
/// A string of characters of minimum length 10.
|
|
/// This phrase will be presented to the user every time they are about to enter their pazzle and PIN in order to unlock their wallet.
|
|
/// It should be something the user will remember, but not something too personal.
|
|
/// Do not enter full name, nor address, nor phone number.
|
|
/// Instead, the user can enter a quote, a small phrase that they like, or something meaningless to others, but unique to them.
|
|
/// Please be aware that other users who are sharing the same device, will be able to see this phrase.
|
|
pub security_txt: String,
|
|
/// chose a PIN code.
|
|
/// We recommend the user to choose a PIN code they already know very well (unlock phone, credit card).
|
|
/// The PIN and the rest of the Wallet will never be sent to NextGraph or any other third party (check the source code if you don't believe us).
|
|
/// It cannot be a series like 1234 or 8765. The same digit cannot repeat more than once. By example 4484 is invalid.
|
|
/// Try to avoid birth date, last digits of phone number, or zip code for privacy concern
|
|
pub pin: [u8; 4],
|
|
/// For now, only 9 is supported. 12 and 15 are planned.
|
|
/// A value of 0 will deactivate the pazzle mechanism on this Wallet, and only the mnemonic could be used to open it.
|
|
pub pazzle_length: u8,
|
|
#[zeroize(skip)]
|
|
/// Not implemented yet. Will send the bootstrap to our cloud servers, if needed
|
|
pub send_bootstrap: bool,
|
|
#[zeroize(skip)]
|
|
/// Not implemented yet. Will send an encrypted Wallet file to our cloud servers, if needed. (and no, it does not contain the user's pazzle nor PIN)
|
|
pub send_wallet: bool,
|
|
#[zeroize(skip)]
|
|
/// Do you want a binary file containing the whole Wallet ?
|
|
pub result_with_wallet_file: bool,
|
|
#[zeroize(skip)]
|
|
/// Should the wallet be saved locally on disk, by the LocalBroker. It will not work on a in-memory LocalBroker, obviously.
|
|
pub local_save: bool,
|
|
#[zeroize(skip)]
|
|
/// What Broker Server to contact when there is internet and we want to sync.
|
|
pub core_bootstrap: BootstrapContentV0,
|
|
#[zeroize(skip)]
|
|
/// What is the registration code at that Broker Server. Only useful the first time you connect to the Server.
|
|
/// Can be None the rest of the time, or if your server does not need an Invitation.
|
|
pub core_registration: Option<[u8; 32]>,
|
|
#[zeroize(skip)]
|
|
/// Bootstrap of another server that you might use in order to connect to NextGraph network. It can be another interface on the same `core` server.
|
|
pub additional_bootstrap: Option<BootstrapContentV0>,
|
|
}
|
|
|
|
impl CreateWalletV0 {
|
|
pub fn new(
|
|
security_img: Vec<u8>,
|
|
security_txt: String,
|
|
pin: [u8; 4],
|
|
pazzle_length: u8,
|
|
send_bootstrap: bool,
|
|
send_wallet: bool,
|
|
core_bootstrap: BootstrapContentV0,
|
|
core_registration: Option<[u8; 32]>,
|
|
additional_bootstrap: Option<BootstrapContentV0>,
|
|
) -> Self {
|
|
CreateWalletV0 {
|
|
result_with_wallet_file: false,
|
|
local_save: true,
|
|
security_img,
|
|
security_txt,
|
|
pin,
|
|
pazzle_length,
|
|
send_bootstrap,
|
|
send_wallet,
|
|
core_bootstrap,
|
|
core_registration,
|
|
additional_bootstrap,
|
|
}
|
|
}
|
|
}
|
|
|
|
// #[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)]
|
|
// pub struct WalletCreationSiteEventsV0 {
|
|
// store_id: RepoId,
|
|
// store_read_cap: ReadCap,
|
|
// topic_id: TopicId,
|
|
// topic_priv_key: BranchWriteCapSecret,
|
|
// events: Vec<(Commit, Vec<Digest>)>,
|
|
// }
|
|
|
|
// #[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)]
|
|
// pub struct WalletCreationEventsV0 {}
|
|
|
|
#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)]
|
|
pub struct CreateWalletResultV0 {
|
|
#[zeroize(skip)]
|
|
/// The encrypted form of the Wallet object that was created.
|
|
/// basically the same as what the file contains.
|
|
pub wallet: Wallet,
|
|
// #[serde(skip)]
|
|
// /// The private key of the Wallet. Used for signing the wallet and other internal purposes.
|
|
// /// it is contained in the opened wallet. No need to save it anywhere.
|
|
// pub wallet_privkey: PrivKey,
|
|
#[serde(with = "serde_bytes")]
|
|
#[zeroize(skip)]
|
|
/// The binary file that can be saved to disk and given to the user
|
|
pub wallet_file: Vec<u8>,
|
|
/// randomly generated pazzle
|
|
pub pazzle: Vec<u8>,
|
|
/// randomly generated mnemonic. It is an alternate way to open the wallet.
|
|
/// A BIP39 list of 12 words. We argue that the Pazzle is easier to remember than this.
|
|
pub mnemonic: [u16; 12],
|
|
/// The words of the mnemonic, in a human readable form.
|
|
pub mnemonic_str: Vec<String>,
|
|
#[zeroize(skip)]
|
|
/// a string identifying uniquely the wallet
|
|
pub wallet_name: String,
|
|
/// newly created Client that uniquely identifies the device where the wallet has been created.
|
|
pub client: ClientV0,
|
|
#[zeroize(skip)]
|
|
/// UserId of the "personal identity" of the user
|
|
pub user: PubKey,
|
|
#[zeroize(skip)]
|
|
/// is this an in_memory wallet that should not be saved to disk by the LocalBroker?
|
|
pub in_memory: bool,
|
|
|
|
pub session_id: u64,
|
|
}
|
|
|
|
impl CreateWalletResultV0 {
|
|
pub fn personal_identity(&self) -> UserId {
|
|
self.user
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug)]
|
|
pub struct CreateWalletIntermediaryV0 {
|
|
/// The private key of the Wallet. Used for signing the wallet and other internal purposes.
|
|
/// it is contained in the opened wallet. No need to save it anywhere.
|
|
pub wallet_privkey: PrivKey,
|
|
#[zeroize(skip)]
|
|
/// a string identifying uniquely the wallet
|
|
pub wallet_name: String,
|
|
/// newly created Client that uniquely identifies the device where the wallet has been created.
|
|
pub client: ClientV0,
|
|
|
|
/// User priv key of the "personal identity" of the user
|
|
pub user_privkey: PrivKey,
|
|
#[zeroize(skip)]
|
|
/// is this an in_memory wallet that should not be saved to disk by the LocalBroker?
|
|
pub in_memory: bool,
|
|
|
|
#[zeroize(skip)]
|
|
pub security_img: Vec<u8>,
|
|
|
|
pub security_txt: String,
|
|
|
|
pub pazzle_length: u8,
|
|
|
|
pub pin: [u8; 4],
|
|
|
|
#[zeroize(skip)]
|
|
pub send_bootstrap: bool,
|
|
#[zeroize(skip)]
|
|
pub send_wallet: bool,
|
|
#[zeroize(skip)]
|
|
pub result_with_wallet_file: bool,
|
|
#[zeroize(skip)]
|
|
pub core_bootstrap: BootstrapContentV0,
|
|
pub core_registration: Option<[u8; 32]>,
|
|
#[zeroize(skip)]
|
|
pub additional_bootstrap: Option<BootstrapContentV0>,
|
|
}
|
|
|
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
|
pub enum NgWalletError {
|
|
InvalidPin,
|
|
InvalidPazzle,
|
|
InvalidPazzleLength,
|
|
InvalidMnemonic,
|
|
InvalidSecurityImage,
|
|
InvalidSecurityText,
|
|
SubmissionError,
|
|
InternalError,
|
|
EncryptionError,
|
|
DecryptionError,
|
|
InvalidSignature,
|
|
NoCreateWalletPresent,
|
|
InvalidBootstrap,
|
|
SerializationError,
|
|
}
|
|
|
|
impl From<NgWalletError> for NgError {
|
|
fn from(wallet_error: NgWalletError) -> NgError {
|
|
match wallet_error {
|
|
NgWalletError::SerializationError => NgError::SerializationError,
|
|
NgWalletError::InvalidSignature => NgError::InvalidSignature,
|
|
NgWalletError::EncryptionError | NgWalletError::DecryptionError => {
|
|
NgError::EncryptionError
|
|
}
|
|
_ => NgError::WalletError(wallet_error.to_string()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for NgWalletError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{:?}", self)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum NgFileV0 {
|
|
Wallet(Wallet),
|
|
Other,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum NgFile {
|
|
V0(NgFileV0),
|
|
}
|
|
|
|
impl TryFrom<Vec<u8>> for NgFile {
|
|
type Error = NgError;
|
|
fn try_from(file: Vec<u8>) -> Result<Self, Self::Error> {
|
|
let ngf: Self = serde_bare::from_slice(&file).map_err(|_| NgError::InvalidFileFormat)?;
|
|
Ok(ngf)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct ShuffledPazzle {
|
|
pub category_indices: Vec<u8>,
|
|
pub emoji_indices: Vec<Vec<u8>>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct NgQRCodeWalletTransferV0 {
|
|
pub broker: BrokerServerV0,
|
|
pub rendezvous: SymKey, // Rendez-vous ID
|
|
pub secret_key: SymKey,
|
|
pub is_rendezvous: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct NgQRCodeWalletRecoveryV0 {
|
|
pub wallet: WalletContentV0, //of which security_img is emptied
|
|
pub pazzle: Vec<u8>,
|
|
pub mnemonic: [u16; 12],
|
|
pub pin: [u8; 4],
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub enum NgQRCode {
|
|
WalletTransferV0(NgQRCodeWalletTransferV0),
|
|
WalletRecoveryV0(NgQRCodeWalletRecoveryV0),
|
|
}
|
|
|
|
impl NgQRCode {
|
|
pub fn from_code(code: String) -> Result<Self, NgError> {
|
|
let decoded = base64_url::decode(&code).map_err(|_| NgError::SerializationError)?;
|
|
Ok(serde_bare::from_slice(&decoded)?)
|
|
}
|
|
pub fn to_code(&self) -> String {
|
|
let ser = serde_bare::to_vec(self).unwrap();
|
|
base64_url::encode(&ser)
|
|
}
|
|
}
|
|
|