Rust implementation of NextGraph, a Decentralized and local-first web 3.0 ecosystem
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.
 
 
 
 
 
 
nextgraph-rs/p2p-repo/src/types.rs

1337 lines
39 KiB

// 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
// <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.
//! 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<Self, NgError> {
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<Self, NgError> {
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<Self, NgError> {
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<Self, NgError> {
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<u8>,
}
/// 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<RepoHash> 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<PubKey>,
}
/// 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<PubKey>,
}
/// BLOCKS common types
/// Internal node of a Merkle tree
pub type InternalNode = Vec<BlockKey>;
/// 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<u8>),
}
#[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<ObjectId>,
/// dependency that is removed after this commit. used for reverts
pub ndeps: Vec<ObjectId>,
/// current valid commits in head
pub acks: Vec<ObjectId>,
/// head commits that are invalid
pub nacks: Vec<ObjectId>,
/// list of Files that are referenced in this commit
pub refs: Vec<ObjectId>,
/// 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<ObjectId>,
}
#[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<ObjectId> {
match self {
CommitHeader::V0(v0) => v0.deps.clone(),
}
}
pub fn acks(&self) -> Vec<ObjectId> {
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<ObjectRef>,
ndeps: Vec<ObjectRef>,
acks: Vec<ObjectRef>,
nacks: Vec<ObjectRef>,
refs: Vec<ObjectRef>,
nrefs: Vec<ObjectRef>,
) -> (Option<Self>, Option<CommitHeaderKeysV0>) {
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<ObjectId> = vec![];
let mut indeps: Vec<ObjectId> = vec![];
let mut iacks: Vec<ObjectId> = vec![];
let mut inacks: Vec<ObjectId> = vec![];
let mut irefs: Vec<ObjectId> = vec![];
let mut inrefs: Vec<ObjectId> = vec![];
let mut kdeps: Vec<ObjectKey> = vec![];
let mut kndeps: Vec<ObjectKey> = vec![];
let mut kacks: Vec<ObjectKey> = vec![];
let mut knacks: Vec<ObjectKey> = 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<ObjectId>) -> Option<Self> {
assert!(!deps.is_empty());
let mut n = Self::new_empty();
n.deps = deps;
Some(n)
}
pub fn new_with_deps_and_acks(deps: Vec<ObjectId>, acks: Vec<ObjectId>) -> Option<Self> {
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<ObjectKey>,
/// dependencies that are removed after this commit. used for reverts
pub ndeps: Vec<ObjectKey>,
/// current valid commits in head
pub acks: Vec<ObjectKey>,
/// head commits that are invalid
pub nacks: Vec<ObjectKey>,
/// 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<ObjectRef>,
// 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<ObjectId>,
/// Block IDs for child nodes in the Merkle tree, can be empty if ObjectContent fits in one block
pub children: Vec<BlockId>,
/// 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<u8>,
}
/// 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<BlockId>,
/// Block Key
#[serde(skip)]
pub key: Option<SymKey>,
/// Header
// #[serde(skip)]
// pub header: Option<CommitHeaderV0>,
/// 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<ObjectKey>,
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<u8>,
/// User ID who created this repo
pub creator: Option<UserId>,
// 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<u8>,
}
/// 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<u8>,
/// 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<StoreRootRepo>,
/// 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<Sig>,
/// Mutable App-specific metadata
#[serde(with = "serde_bytes")]
pub metadata: Vec<u8>,
}
/// 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<UserId>,
/// 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<UserId>,
/// cryptographic material for Threshold signature
#[serde(with = "serde_bytes")]
pub metadata: Vec<u8>,
}
/// 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<u8>,
/// App-specific metadata
#[serde(with = "serde_bytes")]
pub metadata: Vec<u8>,
}
/// 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<u8>,
}
#[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<u8>,
}
#[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<u8>,
}
#[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<u8>,
}
#[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<u8>,
}
#[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<u8>,
}
#[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<u8>,
}
#[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<repo_id>
pub type TransactionV0 = Vec<u8>;
#[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<String>,
/// Metadata
#[serde(with = "serde_bytes")]
pub metadata: Vec<u8>,
}
#[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<ObjectId>,
/// 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<u8>,
}
/// 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<u8>,
}
/// 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<CommitHeaderKeysV0>,
/// 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<u8>,
/// 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<ObjectId>,
/// Key of containing Object
#[serde(skip)]
pub key: Option<SymKey>,
/// optional Commit Header
#[serde(skip)]
pub header: Option<CommitHeaderV0>,
/// 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<u8>,
#[serde(with = "serde_bytes")]
pub content: Vec<u8>,
}
/// 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),
}