// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers // All rights reserved. // This code is partly derived from work written by TG x Thoth from P2Pcollab. // Copyright 2022 TG x Thoth // Licensed under the Apache License, Version 2.0 // // or the MIT license , // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. //! P2P Repo types //! //! Corresponds to the BARE schema use crate::errors::NgError; use crate::utils::{ decode_key, dh_pubkey_array_from_ed_pubkey_slice, dh_pubkey_from_ed_pubkey_slice, ed_privkey_to_ed_pubkey, from_ed_privkey_to_dh_privkey, random_key, }; use core::fmt; use serde::{Deserialize, Serialize}; use serde_bare::to_vec; use std::collections::{HashMap, HashSet}; use std::hash::Hash; use zeroize::{Zeroize, ZeroizeOnDrop}; // // COMMON TYPES // /// 32-byte Blake3 hash digest pub type Blake3Digest32 = [u8; 32]; /// Hash digest #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum Digest { Blake3Digest32(Blake3Digest32), } impl fmt::Display for Digest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Digest::Blake3Digest32(d) => write!(f, "{}", base64_url::encode(d)), } } } /// ChaCha20 symmetric key pub type ChaCha20Key = [u8; 32]; /// Symmetric cryptographic key #[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum SymKey { ChaCha20Key(ChaCha20Key), } impl SymKey { pub fn slice(&self) -> &[u8; 32] { match self { SymKey::ChaCha20Key(o) => o, } } pub fn random() -> Self { SymKey::ChaCha20Key(random_key()) } pub fn from_array(array: [u8; 32]) -> Self { SymKey::ChaCha20Key(array) } } impl fmt::Display for SymKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ChaCha20Key(k) => write!(f, "{}", base64_url::encode(k)), } } } impl TryFrom<&[u8]> for SymKey { type Error = NgError; fn try_from(buf: &[u8]) -> Result { let sym_key_array = *slice_as_array!(buf, [u8; 32]).ok_or(NgError::InvalidKey)?; let sym_key = SymKey::ChaCha20Key(sym_key_array); Ok(sym_key) } } /// Curve25519 public key Edwards form pub type Ed25519PubKey = [u8; 32]; /// Curve25519 public key Montgomery form pub type X25519PubKey = [u8; 32]; /// Curve25519 private key Edwards form pub type Ed25519PrivKey = [u8; 32]; /// Curve25519 private key Montgomery form pub type X25519PrivKey = [u8; 32]; /// Public key #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum PubKey { Ed25519PubKey(Ed25519PubKey), X25519PubKey(X25519PubKey), } impl PubKey { pub fn slice(&self) -> &[u8; 32] { match self { PubKey::Ed25519PubKey(o) | PubKey::X25519PubKey(o) => o, } } pub fn to_dh_from_ed(&self) -> PubKey { match self { PubKey::Ed25519PubKey(ed) => dh_pubkey_from_ed_pubkey_slice(ed), _ => panic!( "there is no need to convert a Montgomery key to Montgomery. it is already one. check your code" ), } } // pub fn dh_from_ed_slice(slice: &[u8]) -> PubKey { // dh_pubkey_from_ed_pubkey_slice(slice) // } pub fn to_dh_slice(&self) -> [u8; 32] { match self { PubKey::Ed25519PubKey(o) => dh_pubkey_array_from_ed_pubkey_slice(o), _ => panic!("can only convert an edward key to montgomery"), } } pub fn nil() -> Self { PubKey::Ed25519PubKey([0u8; 32]) } } impl fmt::Display for PubKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { PubKey::Ed25519PubKey(d) | PubKey::X25519PubKey(d) => { write!(f, "{}", base64_url::encode(d)) } } } } impl TryFrom<&str> for PubKey { type Error = NgError; fn try_from(str: &str) -> Result { let key = decode_key(str).map_err(|_| NgError::InvalidKey)?; Ok(PubKey::Ed25519PubKey(key)) } } /// Private key #[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum PrivKey { Ed25519PrivKey(Ed25519PrivKey), X25519PrivKey(X25519PrivKey), } impl PrivKey { pub fn slice(&self) -> &[u8; 32] { match self { PrivKey::Ed25519PrivKey(o) | PrivKey::X25519PrivKey(o) => o, } } pub fn to_pub(&self) -> PubKey { match self { PrivKey::Ed25519PrivKey(_) => ed_privkey_to_ed_pubkey(self), _ => panic!("X25519PrivKey to pub not implemented"), } } #[deprecated(note = "**Don't use dummy method**")] pub fn dummy() -> PrivKey { PrivKey::Ed25519PrivKey([0u8; 32]) } pub fn to_dh(&self) -> PrivKey { from_ed_privkey_to_dh_privkey(self) } pub fn random_ed() -> Self { PrivKey::Ed25519PrivKey(random_key()) } } impl From<[u8; 32]> for PrivKey { fn from(buf: [u8; 32]) -> Self { let priv_key = PrivKey::Ed25519PrivKey(buf); priv_key } } impl TryFrom<&[u8]> for PrivKey { type Error = NgError; fn try_from(buf: &[u8]) -> Result { let priv_key_array = *slice_as_array!(buf, [u8; 32]).ok_or(NgError::InvalidKey)?; let priv_key = PrivKey::Ed25519PrivKey(priv_key_array); Ok(priv_key) } } impl TryFrom<&str> for PrivKey { type Error = NgError; fn try_from(str: &str) -> Result { let key = decode_key(str).map_err(|_| NgError::InvalidKey)?; Ok(PrivKey::Ed25519PrivKey(key)) } } impl fmt::Display for PrivKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Ed25519PrivKey(ed) => { //let priv_key_ser = serde_bare::to_vec(ed).unwrap(); let prix_key_encoded = base64_url::encode(ed); write!(f, "{}", prix_key_encoded) } _ => { unimplemented!(); } } } } /// Ed25519 signature pub type Ed25519Sig = [[u8; 32]; 2]; /// Cryptographic signature #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum Sig { Ed25519Sig(Ed25519Sig), } /// Timestamp: absolute time in minutes since 2022-02-22 22:22 UTC pub type Timestamp = u32; pub const EPOCH_AS_UNIX_TIMESTAMP: u64 = 1645568520; /// Relative time (e.g. delay from current time) #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum RelTime { Seconds(u8), Minutes(u8), Hours(u8), Days(u8), } /// Bloom filter (variable size) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct BloomFilter { /// Number of hash functions pub k: u32, /// Filter #[serde(with = "serde_bytes")] pub f: Vec, } /// Bloom filter (128 B) /// /// (m=1024; k=7; p=0.01; n=107) pub type BloomFilter128 = [[u8; 32]; 4]; /// Bloom filter (1 KiB) /// /// (m=8192; k=7; p=0.01; n=855) pub type BloomFilter1K = [[u8; 32]; 32]; // // REPOSITORY TYPES // /// RepoId is a PubKey pub type RepoId = PubKey; /// RepoHash is the BLAKE3 Digest over the RepoId pub type RepoHash = Digest; // impl From for String { // fn from(id: RepoHash) -> Self { // hex::encode(to_vec(&id).unwrap()) // } // } /// Topic ID: public key of the topic pub type TopicId = PubKey; /// User ID: user account for broker pub type UserId = PubKey; /// BranchId is a PubKey pub type BranchId = PubKey; /// Block ID: /// BLAKE3 hash over the serialized BlockContent (contains encrypted content) pub type BlockId = Digest; pub type BlockKey = SymKey; /// Block reference #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct BlockRef { /// Object ID pub id: BlockId, /// Key for decrypting the Object pub key: BlockKey, } impl BlockRef { #[deprecated(note = "**Don't use dummy method**")] pub fn dummy() -> Self { BlockRef { id: Digest::Blake3Digest32([0u8; 32]), key: SymKey::ChaCha20Key([0u8; 32]), } } pub fn from_id_key(id: BlockId, key: BlockKey) -> Self { BlockRef { id, key } } } impl From<(&BlockId, &BlockKey)> for BlockRef { fn from(id_key: (&BlockId, &BlockKey)) -> Self { BlockRef { id: id_key.0.clone(), key: id_key.1.clone(), } } } /// Object ID pub type ObjectId = BlockId; /// Object Key pub type ObjectKey = BlockKey; /// Object reference pub type ObjectRef = BlockRef; /// IDENTITY, SITE, STORE, OVERLAY common types /// List of Identity types #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum Identity { OrgSite(PubKey), IndividualSite(PubKey), OrgPublicStore(PubKey), OrgProtectedStore(PubKey), OrgPrivateStore(PubKey), IndividualPublicStore(PubKey), IndividualProtectedStore(PubKey), IndividualPrivateStore(PubKey), } /// List of Store Overlay types #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum StoreOverlay { PublicStore(PubKey), ProtectedStore(PubKey), PrivateStore(PubKey), Group(PubKey), Dialog(Digest), //Document(RepoId), } /// List of Store Root Repo types #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum StoreRootRepo { PublicStore(RepoId), ProtectedStore(RepoId), PrivateStore(RepoId), Group(RepoId), Dialog(RepoId), } /// Site type #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum SiteType { Org, Individual, // formerly Personal } /// Site Store #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct SiteStore { // pub identity: Identity, pub key: PrivKey, // signature with site_key // pub sig: Sig, pub root_branch_def_ref: ObjectRef, pub repo_secret: SymKey, } /// Site Store type #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum SiteStoreType { Public, Protected, Private, } /// Site V0 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct SiteV0 { pub site_type: SiteType, // Identity::OrgSite or Identity::IndividualSite // pub site_identity: Identity, pub site_key: PrivKey, // Identity::OrgPublicStore or Identity::IndividualPublicStore pub public: SiteStore, // Identity::OrgProtectedStore or Identity::IndividualProtectedStore pub protected: SiteStore, // Identity::OrgPrivateStore or Identity::IndividualPrivateStore pub private: SiteStore, pub cores: Vec<(PubKey, Option<[u8; 32]>)>, pub bootstraps: Vec, } /// Reduced Site (for QRcode) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct ReducedSiteV0 { pub site_key: PrivKey, pub private_site_key: PrivKey, pub private_site_root_branch_def_ref: ObjectRef, pub private_site_repo_secret: SymKey, pub core: PubKey, pub bootstraps: Vec, } /// BLOCKS common types /// Internal node of a Merkle tree pub type InternalNode = Vec; /// encrypted_content of BlockContentV0: a Merkle tree node #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum ChunkContentV0 { /// Internal node with references to children InternalNode(InternalNode), #[serde(with = "serde_bytes")] DataChunk(Vec), } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct CommitHeaderV0 { /// Other objects this commit strongly depends on (ex: ADD for a REMOVE, refs for an nrefs) pub deps: Vec, /// dependency that is removed after this commit. used for reverts pub ndeps: Vec, /// current valid commits in head pub acks: Vec, /// head commits that are invalid pub nacks: Vec, /// list of Files that are referenced in this commit pub refs: Vec, /// list of Files that are not referenced anymore after this commit /// the commit(s) that created the refs should be in deps pub nrefs: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum CommitHeader { V0(CommitHeaderV0), } impl CommitHeader { pub fn is_root(&self) -> bool { match self { CommitHeader::V0(v0) => v0.is_root(), } } pub fn deps(&self) -> Vec { match self { CommitHeader::V0(v0) => v0.deps.clone(), } } pub fn acks(&self) -> Vec { match self { CommitHeader::V0(v0) => v0.acks.clone(), } } } impl CommitHeaderV0 { fn new_empty() -> Self { Self { deps: vec![], ndeps: vec![], acks: vec![], nacks: vec![], refs: vec![], nrefs: vec![], } } pub fn new_with( deps: Vec, ndeps: Vec, acks: Vec, nacks: Vec, refs: Vec, nrefs: Vec, ) -> (Option, Option) { if deps.is_empty() && ndeps.is_empty() && acks.is_empty() && nacks.is_empty() && refs.is_empty() && nrefs.is_empty() { (None, None) } else { let mut ideps: Vec = vec![]; let mut indeps: Vec = vec![]; let mut iacks: Vec = vec![]; let mut inacks: Vec = vec![]; let mut irefs: Vec = vec![]; let mut inrefs: Vec = vec![]; let mut kdeps: Vec = vec![]; let mut kndeps: Vec = vec![]; let mut kacks: Vec = vec![]; let mut knacks: Vec = vec![]; for d in deps { ideps.push(d.id); kdeps.push(d.key); } for d in ndeps { indeps.push(d.id); kndeps.push(d.key); } for d in acks { iacks.push(d.id); kacks.push(d.key); } for d in nacks { inacks.push(d.id); knacks.push(d.key); } for d in refs.clone() { irefs.push(d.id); } for d in nrefs { inrefs.push(d.id); } ( Some(Self { deps: ideps, ndeps: indeps, acks: iacks, nacks: inacks, refs: irefs, nrefs: inrefs, }), Some(CommitHeaderKeysV0 { deps: kdeps, ndeps: kndeps, acks: kacks, nacks: knacks, refs, }), ) } } pub fn new_with_deps(deps: Vec) -> Option { assert!(!deps.is_empty()); let mut n = Self::new_empty(); n.deps = deps; Some(n) } pub fn new_with_deps_and_acks(deps: Vec, acks: Vec) -> Option { assert!(!deps.is_empty() || !acks.is_empty()); let mut n = Self::new_empty(); n.deps = deps; n.acks = acks; Some(n) } pub fn is_root(&self) -> bool { //self.deps.is_empty() // && self.ndeps.is_empty() self.acks.is_empty() && self.nacks.is_empty() } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct CommitHeaderKeysV0 { /// Other objects this commit strongly depends on (ex: ADD for a REMOVE, refs for an nrefs) pub deps: Vec, /// dependencies that are removed after this commit. used for reverts pub ndeps: Vec, /// current valid commits in head pub acks: Vec, /// head commits that are invalid pub nacks: Vec, /// list of Files that are referenced in this commit. Exceptionally this is an ObjectRef, because /// even if the CommitHeader is omitted, we want the Files to be openable. pub refs: Vec, // nrefs keys are not included because we don't need the keys to access the files we will not need anymore // the keys are in the deps anyway } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum CommitHeaderKeys { V0(CommitHeaderKeysV0), } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct BlockContentV0 { /// Reference (actually, only its ID) to a CommitHeader of the root Block of a commit that contains references to other objects (e.g. Commit deps & acks) /// Only set if the block is a commit (and it is the root block of the Object). /// It is an easy way to know if the Block is a commit. /// And ObjectRef to an Object containing a CommitHeaderV0 pub commit_header_id: Option, /// Block IDs for child nodes in the Merkle tree, can be empty if ObjectContent fits in one block pub children: Vec, /// Encrypted ChunkContentV0 (entirety or chunks of ObjectContentV0) /// /// Encrypted using convergent encryption with ChaCha20: /// - convergence_key: BLAKE3 derive_key ("NextGraph Data BLAKE3 key", /// repo_pubkey + repo_secret) /// - key: BLAKE3 keyed hash (convergence_key, plain_chunk_content) /// - nonce: 0 #[serde(with = "serde_bytes")] pub encrypted_content: Vec, } /// Immutable object with encrypted content #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum BlockContent { V0(BlockContentV0), } /// Immutable block with encrypted content /// /// `ObjectContent` is chunked and stored as `Block`s in a Merkle tree. /// A Block is a Merkle tree node. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct BlockV0 { /// Block ID #[serde(skip)] pub id: Option, /// Block Key #[serde(skip)] pub key: Option, /// Header // #[serde(skip)] // pub header: Option, /// Key needed to open the CommitHeader. can be omitted if the Commit is shared without its ancestors, /// or if the block is not a root block of commit, or that commit is a root commit (first in branch) pub commit_header_key: Option, pub content: BlockContent, } /// Immutable block with encrypted content #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Block { V0(BlockV0), } /// REPO IMPLEMENTATION /// Repository definition /// /// First commit published in root branch, where: /// - branch_pubkey: repo_pubkey /// - branch_secret: BLAKE3 derive_key ("NextGraph Root Branch secret", /// repo_pubkey + repo_secret) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct RepositoryV0 { /// Repo public key ID pub id: RepoId, /// Verification program (WASM) #[serde(with = "serde_bytes")] pub verification_program: Vec, /// User ID who created this repo pub creator: Option, // TODO: discrete doc type // TODO: order (partial order, total order, partial sign all commits, fsm, smart contract ) /// Immutable App-specific metadata #[serde(with = "serde_bytes")] pub metadata: Vec, } /// Repository definition #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Repository { V0(RepositoryV0), } /// Root Branch definition V0 /// /// Second commit in the root branch, signed by repository key /// is used also to update the root branch definition when users are removed /// DEPS: Reference to the repository commit, to get the verification_program and repo_id #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct RootBranchV0 { /// Branch public key ID, equal to the repo_id pub id: PubKey, // Reference to the repository commit, to get the verification_program and repo_id //pub repo_ref: ObjectRef, // this can be omitted as the ref to repo is in deps. /// Store ID the repo belongs to /// the identity is checked by verifiers (check members, check overlay is matching) pub store: StoreOverlay, /// Pub/sub topic ID for publishing events pub topic: PubKey, /// topic private key, encrypted with the repo_secret, topic_id, branch_id #[serde(with = "serde_bytes")] pub topic_privkey: Vec, /// Permissions are inherited from Store Root Repo. Optional /// (only if this repo is not a root repo itself). /// check that it matches the self.store pub inherit_perms: Option, /// BEC periodic reconciliation interval. zero deactivates it pub reconciliation_interval: RelTime, /// signature of repoId with MODIFY_STORE_KEY privkey of store /// in order to verify that the store recognizes this repo as part of itself. /// only if not a store root repo itself pub store_sig: Option, /// Mutable App-specific metadata #[serde(with = "serde_bytes")] pub metadata: Vec, } /// RootBranch definition #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum RootBranch { V0(RootBranchV0), } /// Quorum change V0 /// /// Sent after RemoveUser, AddUser #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct QuorumV0 { /// Number of signatures required for an partial order commit to be valid pub partial_order_quorum: u32, /// List of the users who can sign for partial order pub partial_order_users: Vec, /// Number of signatures required for a total order commit to be valid pub total_order_quorum: u32, /// List of the users who can sign for total order pub total_order_users: Vec, /// cryptographic material for Threshold signature #[serde(with = "serde_bytes")] pub metadata: Vec, } /// Quorum change #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Quorum { V0(QuorumV0), } /// Branch definition /// /// First commit in a branch, signed by branch key /// In case of a fork, the commit DEPS indicate /// the previous branch heads, and the ACKS are empty. /// /// Can be used also to update the branch definition when users are removed /// In this case, the total_order quorum is needed, and DEPS indicates the previous branch definition, ACKS indicate the current HEAD #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct BranchV0 { /// Branch public key ID pub id: PubKey, /// Reference to the repository commit pub repo: ObjectRef, /// object ID of the current root_branch commit, in order to keep in sync the branch with root_branch pub root_branch_def_id: ObjectId, /// Pub/sub topic for publishing events pub topic: PubKey, /// topic private key, encrypted with the repo_secret, branch_id, topic_id #[serde(with = "serde_bytes")] pub topic_privkey: Vec, /// App-specific metadata #[serde(with = "serde_bytes")] pub metadata: Vec, } /// Branch definition #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Branch { V0(BranchV0), } /// Add a branch to the repository /// DEPS: if update branch: previous AddBranch or UpdateBranch commit #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct AddBranchV0 { /// the new topic_id (will be needed immediately by future readers /// in order to subscribe to the pub/sub) topic_id: TopicId, // the new branch definition commit // (we need the ObjectKey in order to open the pub/sub Event) branch_def: ObjectRef, } /// Add a branch to the repository #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum AddBranch { V0(AddBranchV0), } pub type RemoveBranchV0 = (); /// Remove a branch from the repository /// /// DEPS: should point to the previous AddBranch/UpdateBranch, can be several in case of concurrent AddBranch. ORset logiv) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum RemoveBranch { V0(RemoveBranchV0), } /// Add member to a repo #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct AddMemberV0 { /// Member to add pub member: UserId, /// App-specific metadata /// (role, app level permissions, cryptographic material, etc) #[serde(with = "serde_bytes")] pub metadata: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum AddMember { V0(AddMemberV0), } /// Remove member from a repo #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct RemoveMemberV0 { /// Member to remove pub member: UserId, /// Should the overlay been refreshed. This is used on the last repo, when User is removed from all the repos of the store, because user was malicious. pub refresh_overlay: bool, /// should this user be banned and prevented from being invited again by anybody else pub banned: bool, /// Metadata /// (reason, etc...) #[serde(with = "serde_bytes")] pub metadata: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum RemoveMember { V0(RemoveMemberV0), } /// Permissions #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum Permission { Create, // Used internally by the creator at creation time. Not part of the permission set that can added and removed MoveToStore, // moves the repo to another store AddBranch, RemoveBranch, ChangeName, AddMember, RemoveMember, ChangeQuorum, ChangePermission, ChangeMainBranch, Transaction, Snapshot, Chat, Inbox, Share, UpdateStore, // only for store root repo (add doc, remove doc) } /// Add permission to a member in a repo #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct AddPermissionV0 { /// Member receiving the permission pub member: UserId, /// Permission given to user pub permission: Permission, /// Metadata /// (role, app level permissions, cryptographic material, etc) /// Can be some COMMON KEY privkey encrypted with the user pubkey /// If a PROOF for the common key is needed, should be sent here too #[serde(with = "serde_bytes")] pub metadata: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum AddPermission { V0(AddPermissionV0), } /// Remove permission from a user in a repo #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct RemovePermissionV0 { /// Member to remove pub member: UserId, /// Permission removed from user pub permission: Permission, /// Metadata /// (reason, new cryptographic materials...) /// If the permission was linked to a COMMON KEY, a new privkey should be generated /// and sent to all users that still have this permission, encrypted with their respective pubkey /// If a PROOF for the common key is needed, should be sent here too #[serde(with = "serde_bytes")] pub metadata: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum RemovePermission { V0(RemovePermissionV0), } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum RepoNamedItemV0 { Branch(BranchId), Commit(ObjectId), File(ObjectId), } /// Add a new name in the repo that can point to a branch, a commit or a file /// Or change the value of a name /// DEPS: if it is a change of value: all the previous AddName commits seen for this name #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct AddNameV0 { /// the name. in case of conflict, the smallest Id is taken. /// names `main`, `chat`, `store` are reserved pub name: String, /// A branch, commit or file pub item: RepoNamedItemV0, /// Metadata #[serde(with = "serde_bytes")] pub metadata: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum AddName { V0(AddNameV0), } /// Change the main branch /// DEPS: previous ChangeMainBranchV0 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct ChangeMainBranchV0 { pub branch: BranchId, /// Metadata #[serde(with = "serde_bytes")] pub metadata: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum ChangeMainBranch { V0(ChangeMainBranchV0), } /// Remove a name from the repo, using ORset CRDT logic /// DEPS: all the AddName commits seen for this name #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct RemoveNameV0 { /// Member to remove /// names `main`, `chat`, `store` are reserved pub name: String, /// Permission removed from user pub permission: Permission, /// Metadata /// (reason, new cryptographic materials...) /// If the permission was linked to a COMMON KEY, a new privkey should be generated /// and sent to all users that still have this permission, encrypted with their respective pubkey /// If a PROOF for the common key is needed, should be sent here too #[serde(with = "serde_bytes")] pub metadata: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum RemoveName { V0(RemoveNameV0), } /// Transaction with CRDT operations // TODO: edeps: List<(repo_id,ObjectRef)> // TODO: rcpts: List pub type TransactionV0 = Vec; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Transaction { #[serde(with = "serde_bytes")] V0(TransactionV0), } /// Add a new binary file in a branch /// REFS: the file ObjectRef #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct AddFileV0 { /// an optional name. does not conflict (not unique across the branch nor repo) pub name: Option, /// Metadata #[serde(with = "serde_bytes")] pub metadata: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum AddFile { V0(AddFileV0), } /// Remove a file from the branch, using ORset CRDT logic /// (removes the ref counting. not necessarily the file itself) /// NREFS: the file ObjectRef /// DEPS: all the visible AddFile commits in the branch (ORset) pub type RemoveFileV0 = (); #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum RemoveFile { V0(RemoveFileV0), } /// Snapshot of a Branch /// /// Contains a data structure /// computed from the commits at the specified head. /// ACKS contains the head the snapshot was made from #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct SnapshotV0 { // FIXME: why do we need this? // Branch heads the snapshot was made from // pub heads: Vec, /// hard snapshot will erase all the CommitBody of ancestors in the branch /// the acks will be present in header, but the CommitContent.header_keys will be set to None so the access to the acks will be lost /// the commit_header_key of BlockV0 can be safely shared outside of the repo, as the header_keys is empty, so the heads will not be readable anyway /// If a branch is based on a hard snapshot, it cannot be merged back into the branch where the hard snapshot was made. pub hard: bool, /// Snapshot data structure #[serde(with = "serde_bytes")] pub content: Vec, } /// Snapshot of a Branch #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Snapshot { V0(SnapshotV0), } /// Threshold Signature of a commit /// mandatory for UpdateRootBranch, AddMember, RemoveMember, Quorum, UpdateBranch, hard Snapshot, /// DEPS: the signed commit #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct ThresholdSignatureV0 { // TODO: pub chain_of_trust: , /// Threshold signature #[serde(with = "serde_bytes")] pub signature: Vec, } /// Snapshot of a Branch #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum ThresholdSignature { V0(ThresholdSignatureV0), } /// Commit body V0 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum CommitBodyV0 { // // for root branch: // Repository(RepositoryV0), // singleton and should be first in root_branch RootBranch(RootBranchV0), // singleton and should be second in root_branch UpdateRootBranch(RootBranchV0), // total order enforced with total_order_quorum AddMember(AddMemberV0), // total order enforced with total_order_quorum RemoveMember(RemoveMemberV0), // total order enforced with total_order_quorum Quorum(QuorumV0), // total order enforced with total_order_quorum AddPermission(AddPermissionV0), RemovePermission(RemovePermissionV0), AddBranch(AddBranchV0), ChangeMainBranch(ChangeMainBranchV0), RemoveBranch(RemoveBranchV0), AddName(AddNameV0), RemoveName(RemoveNameV0), // // For regular branches: // Branch(BranchV0), // singleton and should be first in branch UpdateBranch(BranchV0), // total order enforced with total_order_quorum Snapshot(SnapshotV0), // if hard snapshot, total order enforced with total_order_quorum Transaction(TransactionV0), AddFile(AddFileV0), RemoveFile(RemoveFileV0), //Merge(MergeV0), //Revert(RevertV0), // only possible on partial order commit // // For both // ThresholdSignature(ThresholdSignatureV0), } /// Commit body #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum CommitBody { V0(CommitBodyV0), } impl CommitBody { pub fn must_be_root_commit_in_branch(&self) -> bool { match self { Self::V0(v0) => match v0 { CommitBodyV0::Repository(_) => true, CommitBodyV0::Branch(_) => true, _ => false, }, } } pub fn total_order_required(&self) -> bool { match self { Self::V0(v0) => match v0 { CommitBodyV0::UpdateRootBranch(_) => true, CommitBodyV0::AddMember(_) => true, CommitBodyV0::RemoveMember(_) => true, CommitBodyV0::Quorum(_) => true, CommitBodyV0::UpdateBranch(_) => true, CommitBodyV0::Snapshot(s) => s.hard, _ => false, }, } } pub fn required_permission(&self) -> HashSet<&Permission> { let res: &[Permission]; res = match self { Self::V0(v0) => match v0 { CommitBodyV0::Repository(_) => &[Permission::Create], CommitBodyV0::RootBranch(_) => &[Permission::Create], CommitBodyV0::UpdateRootBranch(_) => { &[Permission::RemoveMember, Permission::MoveToStore] } CommitBodyV0::AddMember(_) => &[Permission::Create, Permission::AddMember], CommitBodyV0::RemoveMember(_) => &[Permission::RemoveMember], CommitBodyV0::Quorum(_) => &[ Permission::Create, Permission::AddMember, Permission::RemoveMember, Permission::ChangeQuorum, ], CommitBodyV0::AddPermission(_) => { &[Permission::Create, Permission::ChangePermission] } CommitBodyV0::RemovePermission(_) => &[Permission::ChangePermission], CommitBodyV0::AddBranch(_) => &[Permission::Create, Permission::AddBranch], CommitBodyV0::RemoveBranch(_) => &[Permission::RemoveBranch], CommitBodyV0::UpdateBranch(_) => { &[Permission::RemoveMember, Permission::MoveToStore] } CommitBodyV0::AddName(_) => &[Permission::AddBranch, Permission::ChangeName], CommitBodyV0::RemoveName(_) => &[Permission::ChangeName, Permission::RemoveBranch], CommitBodyV0::Branch(_) => &[Permission::Create, Permission::AddBranch], CommitBodyV0::ChangeMainBranch(_) => { &[Permission::Create, Permission::ChangeMainBranch] } CommitBodyV0::Snapshot(_) => &[Permission::Snapshot], CommitBodyV0::Transaction(_) => &[Permission::Transaction], CommitBodyV0::AddFile(_) => &[Permission::Transaction], CommitBodyV0::RemoveFile(_) => &[Permission::Transaction], CommitBodyV0::ThresholdSignature(_) => &[ Permission::AddMember, Permission::ChangeQuorum, Permission::RemoveMember, Permission::Snapshot, Permission::MoveToStore, Permission::Transaction, ], }, }; HashSet::from_iter(res.iter()) } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum QuorumType { NoSigning, PartialOrder, TotalOrder, } /// Content of a Commit #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct CommitContentV0 { /// Commit author (a ForwardedPeerId) pub author: PubKey, /// Author's commit sequence number pub seq: u64, /// BranchId the commit belongs to (not a ref, as readers do not need to access the branch definition) pub branch: BranchId, /// Keys to be able to open all the references (deps, acks, refs, etc...) pub header_keys: Option, /// This commit can only be accepted if signed by this quorum pub quorum: QuorumType, /// App-specific metadata (commit message, creation time, etc) #[serde(with = "serde_bytes")] pub metadata: Vec, /// reference to an Object with a CommitBody inside. /// When the commit is reverted or erased (after compaction/snapshot), the CommitBody is deleted, creating a dangling reference pub body: ObjectRef, } /// Commit object /// Signed by branch key, or a member key authorized to publish this commit type #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct CommitV0 { /// ID of containing Object #[serde(skip)] pub id: Option, /// Key of containing Object #[serde(skip)] pub key: Option, /// optional Commit Header #[serde(skip)] pub header: Option, /// Commit content pub content: CommitContentV0, /// Signature over the content by the author pub sig: Sig, } /// Commit Object #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Commit { V0(CommitV0), } /// File Object #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct FileV0 { pub content_type: String, #[serde(with = "serde_bytes")] pub metadata: Vec, #[serde(with = "serde_bytes")] pub content: Vec, } /// A file stored in an Object #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum File { V0(FileV0), } /// Immutable data stored encrypted in a Merkle tree V0 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum ObjectContentV0 { Commit(CommitV0), CommitBody(CommitBodyV0), CommitHeader(CommitHeaderV0), File(FileV0), } /// Immutable data stored encrypted in a Merkle tree #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum ObjectContent { V0(ObjectContentV0), }