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.
2346 lines
76 KiB
2346 lines
76 KiB
8 months ago
|
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||
2 years ago
|
// All rights reserved.
|
||
|
// Licensed under the Apache License, Version 2.0
|
||
1 year ago
|
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||
2 years ago
|
// 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
|
||
|
|
||
1 year ago
|
use crate::errors::NgError;
|
||
1 year ago
|
use crate::utils::{
|
||
1 year ago
|
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,
|
||
1 year ago
|
};
|
||
2 years ago
|
use core::fmt;
|
||
7 months ago
|
use once_cell::sync::OnceCell;
|
||
2 years ago
|
use serde::{Deserialize, Serialize};
|
||
|
use std::hash::Hash;
|
||
6 months ago
|
use threshold_crypto::serde_impl::SerdeSecret;
|
||
1 year ago
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||
2 years ago
|
|
||
|
//
|
||
|
// 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),
|
||
|
}
|
||
|
|
||
6 months ago
|
impl Digest {
|
||
|
pub fn from_slice(slice: [u8; 32]) -> Digest {
|
||
|
Digest::Blake3Digest32(slice)
|
||
|
}
|
||
|
}
|
||
|
|
||
2 years ago
|
impl fmt::Display for Digest {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
1 year ago
|
Digest::Blake3Digest32(d) => write!(f, "{}", base64_url::encode(d)),
|
||
2 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
impl From<&Vec<u8>> for Digest {
|
||
|
fn from(ser: &Vec<u8>) -> Self {
|
||
|
let hash = blake3::hash(ser.as_slice());
|
||
|
Digest::Blake3Digest32(hash.as_bytes().clone())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl From<&[u8; 32]> for Digest {
|
||
|
fn from(ser: &[u8; 32]) -> Self {
|
||
|
let hash = blake3::hash(ser);
|
||
|
Digest::Blake3Digest32(hash.as_bytes().clone())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl From<&PubKey> for Digest {
|
||
|
fn from(key: &PubKey) -> Self {
|
||
|
key.slice().into()
|
||
|
}
|
||
|
}
|
||
|
|
||
2 years ago
|
/// ChaCha20 symmetric key
|
||
|
pub type ChaCha20Key = [u8; 32];
|
||
|
|
||
|
/// Symmetric cryptographic key
|
||
1 year ago
|
#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
2 years ago
|
pub enum SymKey {
|
||
|
ChaCha20Key(ChaCha20Key),
|
||
|
}
|
||
|
|
||
|
impl SymKey {
|
||
|
pub fn slice(&self) -> &[u8; 32] {
|
||
|
match self {
|
||
|
SymKey::ChaCha20Key(o) => o,
|
||
|
}
|
||
|
}
|
||
1 year ago
|
pub fn random() -> Self {
|
||
|
SymKey::ChaCha20Key(random_key())
|
||
|
}
|
||
1 year ago
|
pub fn from_array(array: [u8; 32]) -> Self {
|
||
|
SymKey::ChaCha20Key(array)
|
||
|
}
|
||
7 months ago
|
#[deprecated(note = "**Don't use nil method**")]
|
||
|
pub fn nil() -> Self {
|
||
|
SymKey::ChaCha20Key([0; 32])
|
||
|
}
|
||
|
#[cfg(test)]
|
||
7 months ago
|
pub fn dummy() -> Self {
|
||
|
SymKey::ChaCha20Key([0; 32])
|
||
|
}
|
||
2 years ago
|
}
|
||
|
|
||
1 year ago
|
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)),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
1 year ago
|
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)
|
||
|
}
|
||
|
}
|
||
|
|
||
1 year ago
|
/// Curve25519 public key Edwards form
|
||
2 years ago
|
pub type Ed25519PubKey = [u8; 32];
|
||
|
|
||
1 year ago
|
/// Curve25519 public key Montgomery form
|
||
1 year ago
|
pub type X25519PubKey = [u8; 32];
|
||
1 year ago
|
|
||
1 year ago
|
/// Curve25519 private key Edwards form
|
||
2 years ago
|
pub type Ed25519PrivKey = [u8; 32];
|
||
|
|
||
1 year ago
|
/// Curve25519 private key Montgomery form
|
||
|
pub type X25519PrivKey = [u8; 32];
|
||
|
|
||
2 years ago
|
/// Public key
|
||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
|
pub enum PubKey {
|
||
|
Ed25519PubKey(Ed25519PubKey),
|
||
1 year ago
|
X25519PubKey(X25519PubKey),
|
||
2 years ago
|
}
|
||
|
|
||
|
impl PubKey {
|
||
|
pub fn slice(&self) -> &[u8; 32] {
|
||
|
match self {
|
||
1 year ago
|
PubKey::Ed25519PubKey(o) | PubKey::X25519PubKey(o) => o,
|
||
2 years ago
|
}
|
||
|
}
|
||
1 year ago
|
pub fn to_dh_from_ed(&self) -> PubKey {
|
||
|
match self {
|
||
1 year ago
|
PubKey::Ed25519PubKey(ed) => dh_pubkey_from_ed_pubkey_slice(ed),
|
||
1 year ago
|
_ => panic!(
|
||
1 year ago
|
"there is no need to convert a Montgomery key to Montgomery. it is already one. check your code"
|
||
1 year ago
|
),
|
||
|
}
|
||
|
}
|
||
1 year ago
|
// pub fn dh_from_ed_slice(slice: &[u8]) -> PubKey {
|
||
|
// dh_pubkey_from_ed_pubkey_slice(slice)
|
||
|
// }
|
||
1 year ago
|
pub fn to_dh_slice(&self) -> [u8; 32] {
|
||
1 year ago
|
match self {
|
||
|
PubKey::Ed25519PubKey(o) => dh_pubkey_array_from_ed_pubkey_slice(o),
|
||
|
_ => panic!("can only convert an edward key to montgomery"),
|
||
|
}
|
||
1 year ago
|
}
|
||
7 months ago
|
|
||
|
#[deprecated(note = "**Don't use nil method**")]
|
||
1 year ago
|
pub fn nil() -> Self {
|
||
|
PubKey::Ed25519PubKey([0u8; 32])
|
||
|
}
|
||
2 years ago
|
}
|
||
|
|
||
|
impl fmt::Display for PubKey {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
1 year ago
|
PubKey::Ed25519PubKey(d) | PubKey::X25519PubKey(d) => {
|
||
|
write!(f, "{}", base64_url::encode(d))
|
||
|
}
|
||
2 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
1 year ago
|
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))
|
||
|
}
|
||
|
}
|
||
|
|
||
2 years ago
|
/// Private key
|
||
1 year ago
|
#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
2 years ago
|
pub enum PrivKey {
|
||
|
Ed25519PrivKey(Ed25519PrivKey),
|
||
1 year ago
|
X25519PrivKey(X25519PrivKey),
|
||
2 years ago
|
}
|
||
|
|
||
6 months ago
|
#[allow(deprecated)]
|
||
|
impl Default for PrivKey {
|
||
|
fn default() -> Self {
|
||
|
Self::nil()
|
||
|
}
|
||
|
}
|
||
|
|
||
1 year ago
|
impl PrivKey {
|
||
|
pub fn slice(&self) -> &[u8; 32] {
|
||
|
match self {
|
||
1 year ago
|
PrivKey::Ed25519PrivKey(o) | PrivKey::X25519PrivKey(o) => o,
|
||
1 year ago
|
}
|
||
|
}
|
||
1 year ago
|
pub fn to_pub(&self) -> PubKey {
|
||
1 year ago
|
match self {
|
||
|
PrivKey::Ed25519PrivKey(_) => ed_privkey_to_ed_pubkey(self),
|
||
|
_ => panic!("X25519PrivKey to pub not implemented"),
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
#[deprecated(note = "**Don't use nil method**")]
|
||
|
pub fn nil() -> PrivKey {
|
||
|
PrivKey::Ed25519PrivKey([0u8; 32])
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
1 year ago
|
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())
|
||
1 year ago
|
}
|
||
1 year ago
|
}
|
||
|
|
||
1 year ago
|
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)
|
||
|
}
|
||
|
}
|
||
|
|
||
1 year ago
|
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))
|
||
|
}
|
||
|
}
|
||
|
|
||
1 year ago
|
impl fmt::Display for PrivKey {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
1 year ago
|
match self {
|
||
|
Self::Ed25519PrivKey(ed) => {
|
||
|
//let priv_key_ser = serde_bare::to_vec(ed).unwrap();
|
||
7 months ago
|
let priv_key_encoded = base64_url::encode(ed);
|
||
|
write!(f, "{}", priv_key_encoded)
|
||
1 year ago
|
}
|
||
|
_ => {
|
||
|
unimplemented!();
|
||
|
}
|
||
|
}
|
||
1 year ago
|
}
|
||
|
}
|
||
|
|
||
2 years ago
|
/// Ed25519 signature
|
||
|
pub type Ed25519Sig = [[u8; 32]; 2];
|
||
|
|
||
|
/// Cryptographic signature
|
||
1 year ago
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
2 years ago
|
pub enum Sig {
|
||
|
Ed25519Sig(Ed25519Sig),
|
||
|
}
|
||
|
|
||
7 months ago
|
impl fmt::Display for Sig {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
|
Self::Ed25519Sig(ed) => {
|
||
|
write!(
|
||
|
f,
|
||
|
"{} {}",
|
||
|
base64_url::encode(&ed[0]),
|
||
|
base64_url::encode(&ed[1])
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
6 months ago
|
impl Sig {
|
||
|
pub fn nil() -> Self {
|
||
|
Sig::Ed25519Sig([[0; 32]; 2])
|
||
|
}
|
||
|
}
|
||
|
|
||
2 years ago
|
/// 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),
|
||
6 months ago
|
None,
|
||
2 years ago
|
}
|
||
|
|
||
7 months ago
|
impl fmt::Display for RelTime {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
|
Self::Seconds(s) => writeln!(f, "{} sec.", s),
|
||
|
Self::Minutes(s) => writeln!(f, "{} min.", s),
|
||
|
Self::Hours(s) => writeln!(f, "{} h.", s),
|
||
|
Self::Days(s) => writeln!(f, "{} d.", s),
|
||
6 months ago
|
Self::None => writeln!(f, "None"),
|
||
7 months ago
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
2 years ago
|
/// 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
|
||
|
//
|
||
|
|
||
7 months ago
|
/// RepoId is a PubKey
|
||
|
pub type RepoId = PubKey;
|
||
|
|
||
|
/// RepoHash is the BLAKE3 Digest over the RepoId
|
||
2 years ago
|
pub type RepoHash = Digest;
|
||
|
|
||
1 year ago
|
// impl From<RepoHash> for String {
|
||
|
// fn from(id: RepoHash) -> Self {
|
||
|
// hex::encode(to_vec(&id).unwrap())
|
||
|
// }
|
||
|
// }
|
||
2 years ago
|
|
||
7 months ago
|
/// 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;
|
||
2 years ago
|
|
||
2 years ago
|
/// Block ID:
|
||
7 months ago
|
/// BLAKE3 hash over the serialized BlockContent (contains encrypted content)
|
||
2 years ago
|
pub type BlockId = Digest;
|
||
|
|
||
7 months ago
|
pub type BlockKey = SymKey;
|
||
|
|
||
2 years ago
|
/// Block reference
|
||
1 year ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
2 years ago
|
pub struct BlockRef {
|
||
|
/// Object ID
|
||
|
pub id: BlockId,
|
||
|
|
||
|
/// Key for decrypting the Object
|
||
7 months ago
|
pub key: BlockKey,
|
||
2 years ago
|
}
|
||
|
|
||
7 months ago
|
impl BlockId {
|
||
7 months ago
|
#[cfg(test)]
|
||
7 months ago
|
pub fn dummy() -> Self {
|
||
|
Digest::Blake3Digest32([0u8; 32])
|
||
|
}
|
||
7 months ago
|
|
||
|
#[deprecated(note = "**Don't use nil method**")]
|
||
|
pub fn nil() -> Self {
|
||
|
Digest::Blake3Digest32([0u8; 32])
|
||
|
}
|
||
7 months ago
|
}
|
||
|
|
||
1 year ago
|
impl BlockRef {
|
||
7 months ago
|
#[cfg(test)]
|
||
1 year ago
|
pub fn dummy() -> Self {
|
||
|
BlockRef {
|
||
|
id: Digest::Blake3Digest32([0u8; 32]),
|
||
|
key: SymKey::ChaCha20Key([0u8; 32]),
|
||
|
}
|
||
|
}
|
||
7 months ago
|
|
||
|
#[deprecated(note = "**Don't use nil method**")]
|
||
|
pub fn nil() -> Self {
|
||
|
BlockRef {
|
||
|
id: Digest::Blake3Digest32([0u8; 32]),
|
||
|
key: SymKey::ChaCha20Key([0u8; 32]),
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
pub fn from_id_key(id: BlockId, key: BlockKey) -> Self {
|
||
|
BlockRef { id, key }
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
impl From<BlockRef> for (BlockId, BlockKey) {
|
||
|
fn from(blockref: BlockRef) -> (BlockId, BlockKey) {
|
||
|
(blockref.id.clone(), blockref.key.clone())
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
impl From<(&BlockId, &BlockKey)> for BlockRef {
|
||
|
fn from(id_key: (&BlockId, &BlockKey)) -> Self {
|
||
|
BlockRef {
|
||
|
id: id_key.0.clone(),
|
||
|
key: id_key.1.clone(),
|
||
|
}
|
||
|
}
|
||
1 year ago
|
}
|
||
|
|
||
7 months ago
|
impl fmt::Display for BlockRef {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
write!(f, "{} {}", self.id, self.key)
|
||
|
}
|
||
|
}
|
||
|
|
||
2 years ago
|
/// Object ID
|
||
|
pub type ObjectId = BlockId;
|
||
|
|
||
7 months ago
|
/// Object Key
|
||
|
pub type ObjectKey = BlockKey;
|
||
|
|
||
2 years ago
|
/// Object reference
|
||
|
pub type ObjectRef = BlockRef;
|
||
|
|
||
7 months ago
|
/// Read capability (for a commit, branch, whole repo, or store)
|
||
|
/// For a store: A ReadCap to the root repo of the store
|
||
|
/// For a repo: A reference to the latest RootBranch definition commit
|
||
|
/// For a branch: A reference to the latest Branch definition commit
|
||
|
/// For a commit or object, the ObjectRef is itself the read capability
|
||
|
pub type ReadCap = ObjectRef;
|
||
|
|
||
|
/// Read capability secret (for a commit, branch, whole repo, or store)
|
||
|
/// it is already included in the ReadCap (it is the key part of the reference)
|
||
|
pub type ReadCapSecret = ObjectKey;
|
||
|
|
||
|
/// Write capability secret (for a whole repo)
|
||
|
pub type RepoWriteCapSecret = SymKey;
|
||
|
|
||
|
/// Write capability secret (for a branch's topic)
|
||
|
pub type BranchWriteCapSecret = PrivKey;
|
||
|
|
||
6 months ago
|
//TODO: PermaCap (involves sending an InboxPost to some verifiers)
|
||
7 months ago
|
|
||
|
//
|
||
|
// IDENTITY, SITE, STORE, OVERLAY common types
|
||
|
//
|
||
7 months ago
|
|
||
|
/// 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),
|
||
6 months ago
|
}
|
||
|
|
||
|
pub type OuterOverlayId = Digest;
|
||
|
|
||
|
pub type InnerOverlayId = Digest;
|
||
|
|
||
|
/// Overlay ID
|
||
|
///
|
||
|
/// - for outer overlays that need to be discovered by public key:
|
||
|
/// BLAKE3 hash over the public key of the store repo
|
||
|
/// - for inner overlays:
|
||
|
/// BLAKE3 keyed hash over the public key of the store repo
|
||
|
/// - key: BLAKE3 derive_key ("NextGraph Overlay ReadCapSecret BLAKE3 key", store repo's overlay's branch ReadCapSecret)
|
||
|
/// except for Dialog Overlays where the Hash is computed from 2 secrets.
|
||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
|
pub enum OverlayId {
|
||
|
Outer(OuterOverlayId),
|
||
|
Inner(InnerOverlayId),
|
||
|
Global,
|
||
|
}
|
||
|
|
||
|
impl fmt::Display for OverlayId {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
let overlay_ser = serde_bare::to_vec(&self).unwrap();
|
||
|
write!(f, "{}", base64_url::encode(&overlay_ser))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl OverlayId {
|
||
|
pub fn inner(
|
||
|
store_id: &PubKey,
|
||
|
store_overlay_branch_readcap_secret: ReadCapSecret,
|
||
|
) -> OverlayId {
|
||
|
let store_id = serde_bare::to_vec(store_id).unwrap();
|
||
|
let mut store_overlay_branch_readcap_secret =
|
||
|
serde_bare::to_vec(&store_overlay_branch_readcap_secret).unwrap();
|
||
|
let mut key: [u8; 32] = blake3::derive_key(
|
||
|
"NextGraph Overlay ReadCapSecret BLAKE3 key",
|
||
|
store_overlay_branch_readcap_secret.as_slice(),
|
||
|
);
|
||
|
let key_hash = blake3::keyed_hash(&key, &store_id);
|
||
|
store_overlay_branch_readcap_secret.zeroize();
|
||
|
key.zeroize();
|
||
|
OverlayId::Inner(Digest::from_slice(*key_hash.as_bytes()))
|
||
|
}
|
||
|
|
||
|
pub fn outer(store_id: &PubKey) -> OverlayId {
|
||
|
let store_id = serde_bare::to_vec(store_id).unwrap();
|
||
|
OverlayId::Outer((&store_id).into())
|
||
|
}
|
||
|
#[cfg(test)]
|
||
|
pub fn dummy() -> OverlayId {
|
||
|
OverlayId::Outer(Digest::dummy())
|
||
|
}
|
||
|
#[deprecated(note = "**Don't use nil method**")]
|
||
|
pub fn nil() -> OverlayId {
|
||
|
OverlayId::Outer(Digest::nil())
|
||
|
}
|
||
7 months ago
|
}
|
||
|
|
||
|
/// List of Store Overlay types
|
||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
7 months ago
|
pub enum StoreOverlayV0 {
|
||
7 months ago
|
PublicStore(PubKey),
|
||
|
ProtectedStore(PubKey),
|
||
6 months ago
|
PrivateStore(PubKey),
|
||
7 months ago
|
Group(PubKey),
|
||
|
Dialog(Digest),
|
||
7 months ago
|
}
|
||
|
|
||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
|
pub enum StoreOverlay {
|
||
|
V0(StoreOverlayV0),
|
||
|
Own(BranchId), // The repo is a store, so the overlay can be derived from its own ID. In this case, the branchId of the `overlay` branch is entered here.
|
||
7 months ago
|
}
|
||
|
|
||
7 months ago
|
impl fmt::Display for StoreOverlay {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
|
Self::V0(v0) => {
|
||
|
writeln!(f, "StoreOverlay V0")?;
|
||
|
match v0 {
|
||
|
StoreOverlayV0::PublicStore(k) => writeln!(f, "PublicStore: {}", k),
|
||
|
StoreOverlayV0::ProtectedStore(k) => writeln!(f, "ProtectedStore: {}", k),
|
||
6 months ago
|
StoreOverlayV0::PrivateStore(k) => writeln!(f, "PrivateStore: {}", k),
|
||
7 months ago
|
StoreOverlayV0::Group(k) => writeln!(f, "Group: {}", k),
|
||
|
StoreOverlayV0::Dialog(k) => writeln!(f, "Dialog: {}", k),
|
||
|
}
|
||
|
}
|
||
|
Self::Own(b) => writeln!(f, "Own: {}", b),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
6 months ago
|
impl StoreOverlay {
|
||
|
pub fn from_store_repo(overlay_branch: BranchId) -> StoreOverlay {
|
||
|
StoreOverlay::Own(overlay_branch)
|
||
|
}
|
||
|
|
||
|
pub fn overlay_id_for_read_purpose(&self) -> OverlayId {
|
||
|
match self {
|
||
|
StoreOverlay::V0(StoreOverlayV0::PublicStore(id))
|
||
|
| StoreOverlay::V0(StoreOverlayV0::ProtectedStore(id))
|
||
|
| StoreOverlay::V0(StoreOverlayV0::PrivateStore(id))
|
||
|
| StoreOverlay::V0(StoreOverlayV0::Group(id)) => OverlayId::outer(id),
|
||
|
StoreOverlay::V0(StoreOverlayV0::Dialog(d)) => unimplemented!(),
|
||
|
StoreOverlay::Own(_) => unimplemented!(),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl From<&StoreRepo> for StoreOverlay {
|
||
|
fn from(store_repo: &StoreRepo) -> Self {
|
||
|
match store_repo {
|
||
|
StoreRepo::V0(v0) => match v0 {
|
||
|
StoreRepoV0::PublicStore(id) => {
|
||
|
StoreOverlay::V0(StoreOverlayV0::PublicStore(id.clone()))
|
||
|
}
|
||
|
StoreRepoV0::ProtectedStore(id) => {
|
||
|
StoreOverlay::V0(StoreOverlayV0::ProtectedStore(id.clone()))
|
||
|
}
|
||
|
StoreRepoV0::PrivateStore(id) => {
|
||
|
StoreOverlay::V0(StoreOverlayV0::PrivateStore(id.clone()))
|
||
|
}
|
||
|
StoreRepoV0::Group(id) => StoreOverlay::V0(StoreOverlayV0::Group(id.clone())),
|
||
|
StoreRepoV0::Dialog((_, d)) => StoreOverlay::V0(StoreOverlayV0::Dialog(d.clone())),
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
/// List of Store Root Repo types
|
||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
7 months ago
|
pub enum StoreRepoV0 {
|
||
7 months ago
|
PublicStore(RepoId),
|
||
|
ProtectedStore(RepoId),
|
||
6 months ago
|
PrivateStore(RepoId),
|
||
7 months ago
|
Group(RepoId),
|
||
6 months ago
|
Dialog((RepoId, Digest)),
|
||
7 months ago
|
}
|
||
|
|
||
7 months ago
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
|
pub enum StoreRepo {
|
||
|
V0(StoreRepoV0),
|
||
|
}
|
||
|
|
||
|
impl StoreRepo {
|
||
|
pub fn repo_id(&self) -> &RepoId {
|
||
|
match self {
|
||
|
Self::V0(v0) => match v0 {
|
||
|
StoreRepoV0::PublicStore(id)
|
||
|
| StoreRepoV0::ProtectedStore(id)
|
||
6 months ago
|
| StoreRepoV0::PrivateStore(id)
|
||
7 months ago
|
| StoreRepoV0::Group(id)
|
||
6 months ago
|
| StoreRepoV0::Dialog((id, _)) => id,
|
||
7 months ago
|
},
|
||
|
}
|
||
|
}
|
||
7 months ago
|
#[cfg(test)]
|
||
|
#[allow(deprecated)]
|
||
7 months ago
|
pub fn dummy_public_v0() -> (Self, SymKey) {
|
||
|
let readcap = SymKey::dummy();
|
||
|
let store_pubkey = PubKey::nil();
|
||
|
(
|
||
|
StoreRepo::V0(StoreRepoV0::PublicStore(store_pubkey)),
|
||
|
readcap,
|
||
|
)
|
||
|
}
|
||
6 months ago
|
|
||
|
pub fn overlay_id_for_read_purpose(&self) -> OverlayId {
|
||
|
let store_overlay: StoreOverlay = self.into();
|
||
|
store_overlay.overlay_id_for_read_purpose()
|
||
|
}
|
||
7 months ago
|
}
|
||
|
|
||
7 months ago
|
/// Site type
|
||
6 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
7 months ago
|
pub enum SiteType {
|
||
|
Org,
|
||
6 months ago
|
Individual((PrivKey, ReadCap)), // the priv_key of the user, and the read_cap of the private store
|
||
7 months ago
|
}
|
||
|
|
||
|
/// Site Store
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||
|
pub struct SiteStore {
|
||
6 months ago
|
pub id: PubKey,
|
||
7 months ago
|
|
||
6 months ago
|
pub store_type: SiteStoreType,
|
||
7 months ago
|
}
|
||
|
|
||
|
/// Site Store type
|
||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
|
pub enum SiteStoreType {
|
||
|
Public,
|
||
|
Protected,
|
||
|
Private,
|
||
|
}
|
||
|
|
||
6 months ago
|
/// Site Name
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
|
pub enum SiteName {
|
||
|
Personal,
|
||
|
Name(String),
|
||
|
}
|
||
|
|
||
7 months ago
|
/// Site V0
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||
|
pub struct SiteV0 {
|
||
|
pub site_type: SiteType,
|
||
6 months ago
|
|
||
|
pub id: PubKey,
|
||
|
|
||
|
pub name: SiteName,
|
||
7 months ago
|
|
||
|
// Identity::OrgPublicStore or Identity::IndividualPublicStore
|
||
|
pub public: SiteStore,
|
||
|
|
||
|
// Identity::OrgProtectedStore or Identity::IndividualProtectedStore
|
||
|
pub protected: SiteStore,
|
||
|
|
||
|
// Identity::OrgPrivateStore or Identity::IndividualPrivateStore
|
||
|
pub private: SiteStore,
|
||
|
|
||
6 months ago
|
/// Only for IndividualSite: TODO reorganize those 2 fields
|
||
7 months ago
|
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,
|
||
|
|
||
7 months ago
|
pub private_site_read_cap: ReadCap,
|
||
7 months ago
|
|
||
7 months ago
|
pub private_site_write_cap: RepoWriteCapSecret,
|
||
7 months ago
|
|
||
|
pub core: PubKey,
|
||
|
|
||
|
pub bootstraps: Vec<PubKey>,
|
||
|
}
|
||
|
|
||
|
/// BLOCKS common types
|
||
|
|
||
2 years ago
|
/// Internal node of a Merkle tree
|
||
7 months ago
|
pub type InternalNode = Vec<BlockKey>;
|
||
2 years ago
|
|
||
7 months ago
|
/// encrypted_content of BlockContentV0: a Merkle tree node
|
||
2 years ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub enum ChunkContentV0 {
|
||
2 years ago
|
/// Internal node with references to children
|
||
|
InternalNode(InternalNode),
|
||
|
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
DataChunk(Vec<u8>),
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub struct CommitHeaderV0 {
|
||
7 months ago
|
/// optional Commit Header ID
|
||
|
#[serde(skip)]
|
||
|
pub id: Option<ObjectId>,
|
||
|
|
||
7 months ago
|
/// Other objects this commit strongly depends on (ex: ADD for a REMOVE, files for an nfiles)
|
||
7 months ago
|
pub deps: Vec<ObjectId>,
|
||
|
|
||
|
/// dependency that is removed after this commit. used for reverts
|
||
|
pub ndeps: Vec<ObjectId>,
|
||
|
|
||
7 months ago
|
/// tells brokers that this is a hard snapshot and that all the ACKs and full causal past should be treated as ndeps (their body removed)
|
||
|
/// brokers will only perform the deletion of bodies after this commit has been ACKed by at least one subsequent commit
|
||
|
/// but if the next commit is a nack, the deletion is prevented.
|
||
|
pub compact: bool,
|
||
|
|
||
7 months ago
|
/// 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
|
||
7 months ago
|
pub files: Vec<ObjectId>,
|
||
7 months ago
|
|
||
|
/// list of Files that are not referenced anymore after this commit
|
||
7 months ago
|
/// the commit(s) that created the files should be in deps
|
||
|
pub nfiles: Vec<ObjectId>,
|
||
2 years ago
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub enum CommitHeader {
|
||
|
V0(CommitHeaderV0),
|
||
|
}
|
||
2 years ago
|
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct CommitHeaderKeysV0 {
|
||
7 months ago
|
/// Other objects this commit strongly depends on (ex: ADD for a REMOVE, files for an nfiles)
|
||
7 months ago
|
pub deps: Vec<ObjectKey>,
|
||
|
|
||
7 months ago
|
// ndeps keys are not included because we don't need the keys to access the commits we will not need anymore
|
||
|
// the keys are in the deps of their respective subsequent commits in the DAG anyway
|
||
7 months ago
|
/// 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.
|
||
7 months ago
|
pub files: Vec<ObjectRef>,
|
||
|
// nfiles keys are not included because we don't need the keys to access the files we will not need anymore
|
||
7 months ago
|
// the keys are in the deps of the respective commits that added them anyway
|
||
7 months ago
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum CommitHeaderKeys {
|
||
|
V0(CommitHeaderKeysV0),
|
||
|
}
|
||
|
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum CommitHeaderObject {
|
||
|
Id(ObjectId),
|
||
|
EncryptedContent(Vec<u8>),
|
||
|
None,
|
||
7 months ago
|
RandomAccess,
|
||
7 months ago
|
}
|
||
|
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub struct CommitHeaderRef {
|
||
|
pub obj: CommitHeaderObject,
|
||
|
pub key: ObjectKey,
|
||
|
}
|
||
|
|
||
|
impl CommitHeaderRef {
|
||
|
pub fn from_id_key(id: BlockId, key: ObjectKey) -> Self {
|
||
|
CommitHeaderRef {
|
||
|
obj: CommitHeaderObject::Id(id),
|
||
|
key,
|
||
|
}
|
||
|
}
|
||
7 months ago
|
pub fn from_content_key(content: Vec<u8>, key: ObjectKey) -> Self {
|
||
|
CommitHeaderRef {
|
||
|
obj: CommitHeaderObject::EncryptedContent(content),
|
||
|
key,
|
||
|
}
|
||
|
}
|
||
|
pub fn encrypted_content_len(&self) -> usize {
|
||
|
match &self.obj {
|
||
|
CommitHeaderObject::EncryptedContent(ec) => ec.len(),
|
||
|
_ => 0,
|
||
|
}
|
||
|
}
|
||
7 months ago
|
}
|
||
|
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct BlockContentV0 {
|
||
7 months ago
|
/// Reference (actually, only its ID or an embedded block if the size is small enough)
|
||
|
/// to a CommitHeader of the root Block of a commit that contains references to other objects (e.g. Commit deps & acks)
|
||
7 months ago
|
/// Only set if the block is a commit (and it is the root block of the Object).
|
||
7 months ago
|
/// It is an easy way to know if the Block is a commit (but be careful because some root commits can be without a header).
|
||
|
pub commit_header: CommitHeaderObject,
|
||
7 months ago
|
|
||
7 months ago
|
/// Block IDs for child nodes in the Merkle tree,
|
||
|
/// is empty if ObjectContent fits in one block or this block is a leaf. in both cases, encrypted_content is then not empty
|
||
7 months ago
|
pub children: Vec<BlockId>,
|
||
|
|
||
7 months ago
|
/// contains encrypted ChunkContentV0 (entirety, when fitting, or chunks of ObjectContentV0, in DataChunk) used for leaves of the Merkle tree,
|
||
7 months ago
|
/// or to store the keys of children (in InternalNode)
|
||
7 months ago
|
///
|
||
|
/// Encrypted using convergent encryption with ChaCha20:
|
||
|
/// - convergence_key: BLAKE3 derive_key ("NextGraph Data BLAKE3 key",
|
||
7 months ago
|
/// StoreRepo + store's repo ReadCapSecret )
|
||
6 months ago
|
/// // basically similar to the InnerOverlayId but not hashed, so that brokers cannot do "confirmation of a file" attack
|
||
7 months ago
|
/// - 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),
|
||
2 years ago
|
}
|
||
|
|
||
7 months ago
|
impl BlockContent {
|
||
|
pub fn commit_header_obj(&self) -> &CommitHeaderObject {
|
||
|
match self {
|
||
|
Self::V0(v0) => &v0.commit_header,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
2 years ago
|
/// 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>,
|
||
|
|
||
7 months ago
|
/// Header
|
||
|
// #[serde(skip)]
|
||
7 months ago
|
// TODO
|
||
|
// pub header: Option<CommitHeader>,
|
||
2 years ago
|
|
||
7 months ago
|
/// 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>,
|
||
2 years ago
|
|
||
7 months ago
|
pub content: BlockContent,
|
||
2 years ago
|
}
|
||
|
|
||
7 months ago
|
/// Immutable block with encrypted content
|
||
2 years ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum Block {
|
||
|
V0(BlockV0),
|
||
|
}
|
||
|
|
||
7 months ago
|
/// REPO IMPLEMENTATION
|
||
|
|
||
2 years ago
|
/// Repository definition
|
||
|
///
|
||
7 months ago
|
/// First commit published in root branch, signed by repository key
|
||
|
/// For the Root repo of a store(overlay), the convergence_key should be derived from :
|
||
|
/// "NextGraph Store Root Repo BLAKE3 convergence key",
|
||
|
/// RepoId + RepoWriteCapSecret)
|
||
6 months ago
|
/// for a private store root repo, the repowritecapsecret can be omitted
|
||
2 years ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct RepositoryV0 {
|
||
|
/// Repo public key ID
|
||
2 years ago
|
pub id: RepoId,
|
||
2 years ago
|
|
||
7 months ago
|
/// Verification program (WASM)
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub verification_program: Vec<u8>,
|
||
2 years ago
|
|
||
7 months ago
|
/// User ID who created this repo
|
||
|
pub creator: Option<UserId>,
|
||
2 years ago
|
|
||
6 months ago
|
// TODO: for org store root repo, should have a sig by the org priv_key, over the repoid, and a sig by this repo_priv_key over the org_id (to establish the bidirectional linking between org and store)
|
||
|
|
||
7 months ago
|
// TODO: discrete doc type
|
||
7 months ago
|
// TODO: order (store, partial order, partial sign all commits,(conflict resolution strategy), total order, fsm, smart contract )
|
||
|
// TODO: immutable conditions (allow_change_owners, allow_change_quorum, min_quorum, allow_inherit_perms, signers_can_be_editors, all_editors_are_signers, etc...)
|
||
7 months ago
|
/// Immutable App-specific metadata
|
||
2 years ago
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
|
|
||
|
/// Repository definition
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum Repository {
|
||
|
V0(RepositoryV0),
|
||
|
}
|
||
|
|
||
7 months ago
|
impl fmt::Display for Repository {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
|
Self::V0(v0) => {
|
||
|
writeln!(f, "V0")?;
|
||
|
writeln!(f, "repo_id: {}", v0.id)?;
|
||
|
writeln!(
|
||
|
f,
|
||
|
"creator: {}",
|
||
|
v0.creator.map_or("None".to_string(), |c| format!("{}", c))
|
||
|
)?;
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
/// Root Branch definition V0
|
||
|
///
|
||
|
/// Second commit in the root branch, signed by repository key
|
||
7 months ago
|
/// is used also to update the root branch definition when users are removed, quorum(s) are changed, repo is moved to other store.
|
||
|
/// In this case, it is signed by its author, and requires an additional group signature by the total_order_quorum or by the owners_quorum.
|
||
|
/// DEPS: Reference to the previous root branch definition commit, if it is an update
|
||
1 year ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub struct RootBranchV0 {
|
||
|
/// Branch public key ID, equal to the repo_id
|
||
|
pub id: PubKey,
|
||
2 years ago
|
|
||
7 months ago
|
/// Reference to the repository commit, to get the verification_program and other immutable details
|
||
|
pub repo: ObjectRef,
|
||
|
|
||
7 months ago
|
/// Store ID the repo belongs to
|
||
6 months ago
|
/// the identity is checked by verifiers (check overlay is matching)
|
||
7 months ago
|
pub store: StoreOverlay,
|
||
|
|
||
6 months ago
|
/// signature of repoId with store's partial_order signature
|
||
7 months ago
|
/// in order to verify that the store recognizes this repo as part of itself.
|
||
|
/// only if not a store root repo itself
|
||
6 months ago
|
pub store_sig: Option<Signature>,
|
||
7 months ago
|
|
||
|
/// Pub/sub topic ID for publishing events about the root branch
|
||
|
pub topic: TopicId,
|
||
7 months ago
|
|
||
7 months ago
|
/// topic private key (a BranchWriteCapSecret), encrypted with a key derived as follow
|
||
|
/// BLAKE3 derive_key ("NextGraph Branch WriteCap Secret BLAKE3 key",
|
||
|
/// RepoWriteCapSecret, TopicId, BranchId )
|
||
|
/// so that only editors of the repo can decrypt the privkey
|
||
6 months ago
|
/// nonce = 0
|
||
|
/// not encrypted for individual store repo.
|
||
7 months ago
|
#[serde(with = "serde_bytes")]
|
||
|
pub topic_privkey: Vec<u8>,
|
||
|
|
||
7 months ago
|
/// if set, permissions are inherited from Store Repo.
|
||
|
/// Optional is a store_read_cap
|
||
|
/// (only set if this repo is not the store repo itself)
|
||
7 months ago
|
/// check that it matches the self.store
|
||
7 months ago
|
/// can only be committed by an owner
|
||
6 months ago
|
/// it generates a new certificate
|
||
7 months ago
|
/// owners are not inherited from store
|
||
6 months ago
|
// TODO: ReadCap or PermaCap. If it is a ReadCap, a new RootBranch commit should be published (RefreshReadCap) every time the store read cap changes.
|
||
|
/// empty for private repos, eventhough they are all implicitly inheriting perms from private store
|
||
7 months ago
|
pub inherit_perms_users_and_quorum_from_store: Option<ReadCap>,
|
||
|
|
||
|
/// Quorum definition ObjectRef
|
||
|
/// TODO: ObjectKey should be encrypted with SIGNER_KEY ?
|
||
|
pub quorum: Option<ObjectRef>,
|
||
7 months ago
|
|
||
|
/// BEC periodic reconciliation interval. zero deactivates it
|
||
|
pub reconciliation_interval: RelTime,
|
||
|
|
||
7 months ago
|
// list of owners. all of them are required to sign any RootBranch that modifies the list of owners or the inherit_perms_users_and_quorum_from_store field.
|
||
|
pub owners: Vec<UserId>,
|
||
7 months ago
|
|
||
|
/// Mutable App-specific metadata
|
||
6 months ago
|
/// when the list of owners is changed, a crypto_box containing the RepoWriteCapSecret should be included here for each owner.
|
||
|
/// this should also be done at creation time, with the UserId of the first owner, except for individual store repo, because it doesnt have a RepoWriteCapSecret
|
||
7 months ago
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
2 years ago
|
}
|
||
|
|
||
7 months ago
|
/// RootBranch definition
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum RootBranch {
|
||
|
V0(RootBranchV0),
|
||
2 years ago
|
}
|
||
|
|
||
7 months ago
|
impl RootBranch {
|
||
|
pub fn owners(&self) -> &Vec<UserId> {
|
||
|
match self {
|
||
|
Self::V0(v0) => &v0.owners,
|
||
|
}
|
||
7 months ago
|
}
|
||
|
}
|
||
|
|
||
|
impl fmt::Display for RootBranch {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
|
Self::V0(v0) => {
|
||
|
writeln!(f, "V0")?;
|
||
|
writeln!(f, "repo_id: {}", v0.id)?;
|
||
|
writeln!(f, "repo_ref: {}", v0.repo)?;
|
||
|
write!(f, "store: {}", v0.store)?;
|
||
|
writeln!(
|
||
|
f,
|
||
|
"store_sig: {}",
|
||
|
v0.store_sig
|
||
6 months ago
|
.as_ref()
|
||
7 months ago
|
.map_or("None".to_string(), |c| format!("{}", c))
|
||
|
)?;
|
||
|
writeln!(f, "topic: {}", v0.repo)?;
|
||
|
writeln!(
|
||
|
f,
|
||
|
"inherit_perms: {}",
|
||
|
v0.inherit_perms_users_and_quorum_from_store
|
||
|
.as_ref()
|
||
|
.map_or("None".to_string(), |c| format!("{}", c))
|
||
|
)?;
|
||
|
writeln!(
|
||
|
f,
|
||
|
"quorum: {}",
|
||
|
v0.quorum
|
||
|
.as_ref()
|
||
|
.map_or("None".to_string(), |c| format!("{}", c))
|
||
|
)?;
|
||
|
writeln!(f, "reconciliation_interval: {}", v0.reconciliation_interval)?;
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
7 months ago
|
}
|
||
|
}
|
||
|
|
||
|
/// Quorum definition V0
|
||
7 months ago
|
///
|
||
7 months ago
|
/// Changed when the signers need to be updated. Signers are not necessarily editors of the repo, and they do not need to be members either, as they will be notified of RefreshReadCaps anyway.
|
||
2 years ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub struct QuorumV0 {
|
||
7 months ago
|
/// Number of signatures required for a partial order commit to be valid (threshold+1)
|
||
7 months ago
|
pub partial_order_quorum: u32,
|
||
2 years ago
|
|
||
7 months ago
|
/// List of the users who can sign for partial order
|
||
|
pub partial_order_users: Vec<UserId>,
|
||
2 years ago
|
|
||
7 months ago
|
/// Number of signatures required for a total order commit to be valid (threshold+1)
|
||
7 months ago
|
pub total_order_quorum: u32,
|
||
|
|
||
|
/// List of the users who can sign for total order
|
||
|
pub total_order_users: Vec<UserId>,
|
||
|
|
||
6 months ago
|
// TODO:
|
||
|
// epoch: ObjectId pointing to rootbranch commit (read_cap_id)
|
||
7 months ago
|
/// cryptographic material for Threshold signature
|
||
2 years ago
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
|
|
||
7 months ago
|
/// Quorum definition, is part of the RootBranch commit
|
||
|
/// TODO: can it be sent in the root branch without being part of a RootBranch ?
|
||
2 years ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub enum Quorum {
|
||
|
V0(QuorumV0),
|
||
2 years ago
|
}
|
||
|
|
||
|
/// Branch definition
|
||
|
///
|
||
|
/// First commit in a branch, signed by branch key
|
||
7 months ago
|
/// 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
|
||
2 years ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct BranchV0 {
|
||
|
/// Branch public key ID
|
||
|
pub id: PubKey,
|
||
|
|
||
7 months ago
|
/// Reference to the repository commit
|
||
|
pub repo: ObjectRef,
|
||
|
|
||
7 months ago
|
/// object ID of the current root_branch commit (ReadCap), in order to keep in sync this branch with root_branch
|
||
|
/// The key is not provided as external readers should not be able to access the root branch definition.
|
||
|
/// it is only used by verifiers (who have the key already)
|
||
|
pub root_branch_readcap_id: ObjectId,
|
||
7 months ago
|
|
||
2 years ago
|
/// Pub/sub topic for publishing events
|
||
|
pub topic: PubKey,
|
||
|
|
||
7 months ago
|
/// topic private key (a BranchWriteCapSecret), encrypted with a key derived as follow
|
||
|
/// BLAKE3 derive_key ("NextGraph Branch WriteCap Secret BLAKE3 key",
|
||
|
/// RepoWriteCapSecret, TopicId, BranchId )
|
||
|
/// so that only editors of the repo can decrypt the privkey
|
||
6 months ago
|
/// not encrypted for individual store repo.
|
||
7 months ago
|
#[serde(with = "serde_bytes")]
|
||
|
pub topic_privkey: Vec<u8>,
|
||
2 years ago
|
|
||
7 months ago
|
/// App-specific metadata
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
2 years ago
|
|
||
6 months ago
|
impl fmt::Display for Branch {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
|
Self::V0(v0) => {
|
||
|
writeln!(f, "V0")?;
|
||
|
writeln!(f, "id: {}", v0.id)?;
|
||
|
writeln!(f, "repo: {}", v0.repo)?;
|
||
|
writeln!(f, "root_branch_readcap_id: {}", v0.root_branch_readcap_id)?;
|
||
|
writeln!(f, "topic: {}", v0.topic)?;
|
||
|
writeln!(f, "topic_privkey: {:?}", v0.topic_privkey)?;
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
/// Branch definition
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum Branch {
|
||
|
V0(BranchV0),
|
||
|
}
|
||
2 years ago
|
|
||
6 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum BranchType {
|
||
|
Main,
|
||
|
Chat,
|
||
|
Store,
|
||
|
Overlay,
|
||
|
User,
|
||
|
Transactional,
|
||
|
}
|
||
|
|
||
|
impl fmt::Display for BranchType {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
write!(
|
||
|
f,
|
||
|
"{}",
|
||
|
match self {
|
||
|
Self::Main => "Main",
|
||
|
Self::Chat => "Chat",
|
||
|
Self::Store => "Store",
|
||
|
Self::Overlay => "Overlay",
|
||
|
Self::User => "User",
|
||
|
Self::Transactional => "Transactional",
|
||
|
}
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
/// Add a branch to the repository
|
||
7 months ago
|
/// DEPS: if update branch: previous AddBranch commit of the same branchId
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct AddBranchV0 {
|
||
|
/// the new topic_id (will be needed immediately by future readers
|
||
7 months ago
|
/// in order to subscribe to the pub/sub). should be identical to the one in the Branch definition
|
||
6 months ago
|
pub topic_id: TopicId,
|
||
|
|
||
|
pub branch_type: BranchType,
|
||
7 months ago
|
|
||
|
// the new branch definition commit
|
||
|
// (we need the ObjectKey in order to open the pub/sub Event)
|
||
6 months ago
|
pub branch_read_cap: ReadCap,
|
||
|
}
|
||
|
|
||
|
impl fmt::Display for AddBranch {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
|
Self::V0(v0) => {
|
||
|
writeln!(f, "V0 {}", v0.branch_type)?;
|
||
|
writeln!(f, "topic_id: {}", v0.topic_id)?;
|
||
|
writeln!(f, "branch_read_cap: {}", v0.branch_read_cap)?;
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
7 months ago
|
}
|
||
|
|
||
|
/// Add a branch to the repository
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum AddBranch {
|
||
|
V0(AddBranchV0),
|
||
|
}
|
||
2 years ago
|
|
||
7 months ago
|
pub type RemoveBranchV0 = ();
|
||
|
|
||
|
/// Remove a branch from the repository
|
||
|
///
|
||
7 months ago
|
/// DEPS: should point to the previous AddBranch.
|
||
7 months ago
|
#[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)
|
||
2 years ago
|
#[serde(with = "serde_bytes")]
|
||
7 months ago
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum AddMember {
|
||
|
V0(AddMemberV0),
|
||
|
}
|
||
|
|
||
|
/// Remove member from a repo
|
||
7 months ago
|
/// An owner cannot be removed
|
||
|
/// The overlay should be refreshed if user was malicious, after the user is removed from last repo. See REFRESH_READ_CAP on store repo.
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct RemoveMemberV0 {
|
||
|
/// Member to remove
|
||
|
pub member: UserId,
|
||
|
|
||
|
/// should this user be banned and prevented from being invited again by anybody else
|
||
|
pub banned: bool,
|
||
2 years ago
|
|
||
7 months ago
|
/// Metadata
|
||
|
/// (reason, etc...)
|
||
2 years ago
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub enum RemoveMember {
|
||
|
V0(RemoveMemberV0),
|
||
2 years ago
|
}
|
||
|
|
||
6 months ago
|
/// when a signing capability is removed, a new SignerSecretKeys should be added to wallet, with the removed key set to None
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct SignerCap {
|
||
|
pub repo: RepoId,
|
||
|
|
||
|
/// latest RootBranch commit or Quorum commit that defines the signing epoch
|
||
|
pub epoch: ObjectId,
|
||
|
|
||
|
pub owner: Option<SerdeSecret<threshold_crypto::SecretKeyShare>>,
|
||
|
|
||
|
pub total_order: Option<SerdeSecret<threshold_crypto::SecretKeyShare>>,
|
||
|
|
||
|
pub partial_order: Option<SerdeSecret<threshold_crypto::SecretKeyShare>>,
|
||
|
}
|
||
|
|
||
7 months ago
|
/// Permissions
|
||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||
7 months ago
|
pub enum PermissionV0 {
|
||
6 months ago
|
Create, // Used internally by the creator at creation time. Not part of the permission set that can be added and removed
|
||
7 months ago
|
Owner, // used internally for owners
|
||
|
|
||
|
//
|
||
|
// permissions delegated by owners and admins (all admins inherit them)
|
||
|
//
|
||
6 months ago
|
AddReadMember, // adds a member to the repo (AddMember). without additional perm, the user is a reader
|
||
7 months ago
|
RemoveMember, // if user has any specific perm, RemoveWritePermission, RefreshWriteCap and/or Admin permission is needed. always behind SyncSignature
|
||
|
AddWritePermission, // can send AddPermission that add 3 perms to other user: WriteAsync, WriteSync, and RefreshWriteCap
|
||
|
WriteAsync, // can send AsyncTransaction, AddFile, RemoveFile, Snapshot, optionally with AsyncSignature
|
||
|
WriteSync, // can send SyncTransaction, AddFile, RemoveFile, always behind SyncSignature
|
||
|
Compact, // can send Compact, always behind SyncSignature
|
||
|
RemoveWritePermission, // can send RemovePermission that remove the WriteAsync, WriteSync or RefreshWriteCap permissions from user. RefreshWriteCap will probably be needed by the user who does the RemovePermission
|
||
|
|
||
6 months ago
|
AddBranch, // can send AddBranch and Branch commits
|
||
7 months ago
|
RemoveBranch, // can send removeBranch, always behind SyncSignature
|
||
|
ChangeName, // can send AddName and RemoveName
|
||
|
|
||
|
RefreshReadCap, // can send RefreshReadCap followed by UpdateRootBranch and/or UpdateBranch commits, with or without renewed topicIds. Always behind SyncSignature
|
||
|
RefreshWriteCap, // can send RefreshWriteCap followed by UpdateRootBranch and associated UpdateBranch commits on all branches, with renewed topicIds and RepoWriteCapSecret. Always behind SyncSignature
|
||
|
|
||
|
//
|
||
|
// permissions delegated by owners:
|
||
|
//
|
||
|
ChangeQuorum, // can add and remove Signers, change the quorum thresholds for total order and partial order. implies the RefreshReadCap perm (without changing topicids). Always behind SyncSignature
|
||
|
Admin, // can administer the repo: assigns perms to other user with AddPermission and RemovePermission. RemovePermission always behind SyncSignature
|
||
7 months ago
|
ChangeMainBranch,
|
||
7 months ago
|
|
||
|
// other permissions. TODO: specify them more in details
|
||
|
Chat, // can chat
|
||
|
Inbox, // can read inbox
|
||
6 months ago
|
PermaShare, // can create and answer to PermaCap (PermaLink)
|
||
|
UpdateStore, // only for store root repo (add repo, remove repo) to the store special branch
|
||
7 months ago
|
RefreshOverlay, // Equivalent to RefreshReadCap for the overlay special branch.
|
||
7 months ago
|
}
|
||
|
|
||
|
/// Add permission to a member in a repo
|
||
2 years ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub struct AddPermissionV0 {
|
||
|
/// Member receiving the permission
|
||
|
pub member: UserId,
|
||
2 years ago
|
|
||
7 months ago
|
/// Permission given to user
|
||
7 months ago
|
pub permission: PermissionV0,
|
||
2 years ago
|
|
||
7 months ago
|
/// Metadata
|
||
|
/// (role, app level permissions, cryptographic material, etc)
|
||
6 months ago
|
/// if the added permission is a write one, a crypto_box containing the RepoWriteCapSecret should be included here for the member that receives the perm.
|
||
|
///
|
||
7 months ago
|
/// 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
|
||
7 months ago
|
/// COMMON KEYS are: SHARE, INBOX,
|
||
7 months ago
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
2 years ago
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub enum AddPermission {
|
||
|
V0(AddPermissionV0),
|
||
2 years ago
|
}
|
||
|
|
||
7 months ago
|
impl AddPermission {
|
||
|
pub fn permission_v0(&self) -> &PermissionV0 {
|
||
|
match self {
|
||
|
Self::V0(v0) => &v0.permission,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
/// Remove permission from a user in a repo
|
||
2 years ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub struct RemovePermissionV0 {
|
||
|
/// Member to remove
|
||
|
pub member: UserId,
|
||
|
|
||
|
/// Permission removed from user
|
||
7 months ago
|
pub permission: PermissionV0,
|
||
7 months ago
|
|
||
|
/// 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),
|
||
|
}
|
||
|
|
||
7 months ago
|
impl RemovePermission {
|
||
|
pub fn permission_v0(&self) -> &PermissionV0 {
|
||
|
match self {
|
||
|
Self::V0(v0) => &v0.permission,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum RepoNamedItemV0 {
|
||
|
Branch(BranchId),
|
||
|
Commit(ObjectId),
|
||
|
File(ObjectId),
|
||
|
}
|
||
|
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum RepoNamedItem {
|
||
|
V0(RepoNamedItemV0),
|
||
|
}
|
||
|
|
||
7 months ago
|
/// 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.
|
||
|
pub name: String,
|
||
|
|
||
|
/// A branch, commit or file
|
||
7 months ago
|
pub item: RepoNamedItem,
|
||
7 months ago
|
|
||
|
/// Metadata
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
2 years ago
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub enum AddName {
|
||
|
V0(AddNameV0),
|
||
|
}
|
||
2 years ago
|
|
||
6 months ago
|
/// Remove a name from the repo, using ORset CRDT logic
|
||
|
/// DEPS: all the AddName commits seen for this name
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
6 months ago
|
pub struct RemoveNameV0 {
|
||
|
/// name to remove
|
||
|
pub name: String,
|
||
7 months ago
|
|
||
|
/// Metadata
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
2 years ago
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
6 months ago
|
pub enum RemoveName {
|
||
|
V0(RemoveNameV0),
|
||
7 months ago
|
}
|
||
|
|
||
6 months ago
|
//
|
||
|
// Commits on Store branch
|
||
|
//
|
||
|
|
||
|
/// Adds a repo into the store branch.
|
||
|
/// The repo's `store` field should match the store
|
||
|
/// DEPS to the previous AddRepo commit(s) if it is an update. in this case, repo_id of the referenced rootbranch definition(s) should match
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
6 months ago
|
pub struct AddRepoV0 {
|
||
|
pub read_cap: ReadCap,
|
||
7 months ago
|
|
||
|
/// Metadata
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
6 months ago
|
pub enum AddRepo {
|
||
|
V0(AddRepoV0),
|
||
|
}
|
||
|
|
||
|
/// Removes a repo from the store branch.
|
||
|
/// DEPS to the previous AddRepo commit(s) (ORset logic) with matching repo_id
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct RemoveRepoV0 {
|
||
|
pub id: RepoId,
|
||
|
|
||
|
/// Metadata
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum RemoveRepo {
|
||
|
V0(RemoveRepoV0),
|
||
|
}
|
||
|
|
||
|
// TODO: publish (for public site only)
|
||
|
|
||
|
//
|
||
|
// Commits on User branch
|
||
|
//
|
||
|
|
||
|
/// Adds a link into the user branch, so that a user can share with all its device a new Link they received.
|
||
|
/// The repo's `store` field should not match with any store of the user. Only external repos are accepted here.
|
||
|
/// DEPS to the previous AddLink commit(s) if it is an update. in this case, repo_id of the referenced rootbranch definition(s) should match
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct AddLinkV0 {
|
||
|
pub read_cap: ReadCap,
|
||
|
|
||
|
/// Metadata
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum AddLink {
|
||
|
V0(AddLinkV0),
|
||
|
}
|
||
|
|
||
|
/// Removes a link from the `user` branch.
|
||
|
/// DEPS to the previous AddLink commit(s) (ORset logic) with matching repo_id
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct RemoveLinkV0 {
|
||
|
pub id: RepoId,
|
||
|
|
||
|
/// Metadata
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum RemoveLink {
|
||
|
V0(RemoveLinkV0),
|
||
|
}
|
||
|
|
||
|
/// Adds a SignerCap into the user branch, so that a user can share with all its device a new signing capability that was just created.
|
||
|
/// The cap's `epoch` field should be dereferenced and the user must be part of the quorum/owners.
|
||
|
/// DEPS to the previous AddSignerCap commit(s) if it is an update. in this case, repo_ids have to match,
|
||
|
/// and the the referenced rootbranch definition(s) should have compatible causal past (the newer AddSignerCap must have a newer epoch compared to the one of the replaced cap )
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct AddSignerCapV0 {
|
||
|
pub cap: SignerCap,
|
||
|
|
||
|
/// Metadata
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum AddSignerCap {
|
||
|
V0(AddSignerCapV0),
|
||
|
}
|
||
|
|
||
|
/// Removes a SignerCap from the `user` branch.
|
||
|
/// DEPS to the previous AddSignerCap commit(s) (ORset logic) with matching repo_id
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct RemoveSignerCapV0 {
|
||
|
pub id: RepoId,
|
||
|
|
||
|
/// Metadata
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum RemoveSignerCap {
|
||
|
V0(RemoveSignerCapV0),
|
||
|
}
|
||
|
|
||
|
/// Adds a wallet operation so all the devices can sync their locally saved wallet on disk (at the next wallet opening)
|
||
|
/// DEPS are the last HEAD of wallet updates.
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct WalletUpdateV0 {
|
||
|
pub op: Vec<u8>,
|
||
|
|
||
|
/// Metadata
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum WalletUpdate {
|
||
|
V0(WalletUpdateV0),
|
||
2 years ago
|
}
|
||
|
|
||
6 months ago
|
/// Updates the ReadCap of the public and protected sites (and potentially also Group stores)
|
||
|
/// DEPS to the previous ones.
|
||
|
/// this is used to speedup joining the overlay of such stores, for new devices on new brokers
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct StoreUpdateV0 {
|
||
|
// id of the store.
|
||
|
pub id: PubKey,
|
||
|
|
||
|
pub store_read_cap: ReadCap,
|
||
|
|
||
|
pub inner_overlay_read_cap: ReadCap,
|
||
|
|
||
|
/// Metadata
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum StoreUpdate {
|
||
|
V0(StoreUpdateV0),
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Commits on transaction branches
|
||
|
//
|
||
|
|
||
2 years ago
|
/// Transaction with CRDT operations
|
||
7 months ago
|
// TODO: edeps: List<(repo_id,ObjectRef)>
|
||
|
// TODO: rcpts: List<repo_id>
|
||
|
pub type TransactionV0 = Vec<u8>;
|
||
|
|
||
2 years ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum Transaction {
|
||
|
#[serde(with = "serde_bytes")]
|
||
7 months ago
|
V0(TransactionV0),
|
||
|
}
|
||
|
|
||
|
/// Add a new binary file in a branch
|
||
7 months ago
|
/// FILES: the file ObjectRef
|
||
7 months ago
|
#[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)
|
||
7 months ago
|
/// NFILES: the file ObjectRef
|
||
7 months ago
|
/// 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),
|
||
2 years ago
|
}
|
||
|
|
||
|
/// Snapshot of a Branch
|
||
|
///
|
||
|
/// Contains a data structure
|
||
|
/// computed from the commits at the specified head.
|
||
7 months ago
|
/// ACKS contains the head the snapshot was made from
|
||
2 years ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct SnapshotV0 {
|
||
7 months ago
|
// Branch heads the snapshot was made from, can be useful when shared outside and the commit_header_key is set to None. otherwise it is redundant to ACKS
|
||
|
pub heads: Vec<ObjectId>,
|
||
2 years ago
|
|
||
|
/// 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),
|
||
|
}
|
||
|
|
||
7 months ago
|
/// Compact: Hard 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
|
||
|
///
|
||
|
/// hard snapshot will erase all the CommitBody of ancestors in the branch
|
||
|
/// the compact boolean should be set in the Header too.
|
||
|
/// after a hard snapshot, it is recommended to refresh the read capability (to empty the topics of they keys they still hold)
|
||
|
/// If a branch is based on a hard snapshot, it cannot be merged back into the branch where the hard snapshot was made.
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub struct CompactV0 {
|
||
|
// Branch heads the snapshot was made from, can be useful when shared outside and the commit_header_key is set to None. otherwise it is redundant to ACKS
|
||
|
pub heads: Vec<ObjectId>,
|
||
|
|
||
|
/// Snapshot data structure
|
||
7 months ago
|
#[serde(with = "serde_bytes")]
|
||
7 months ago
|
pub content: Vec<u8>,
|
||
2 years ago
|
}
|
||
|
|
||
7 months ago
|
/// Snapshot of a Branch
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
7 months ago
|
pub enum Compact {
|
||
|
V0(CompactV0),
|
||
|
}
|
||
|
|
||
|
/// Async Threshold Signature of a commit V0 based on the partial order quorum
|
||
|
/// Can sign Transaction, AddFile, and Snapshot, after they have been committed to the DAG.
|
||
|
/// DEPS: the signed commits
|
||
|
// #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
// pub struct AsyncSignatureV0 {
|
||
|
// /// An Object containing the Threshold signature
|
||
|
// pub signature: ObjectRef,
|
||
|
// }
|
||
|
|
||
|
/// Async Threshold Signature of a commit based on the partial order quorum
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum AsyncSignature {
|
||
|
V0(ObjectRef),
|
||
|
}
|
||
|
|
||
6 months ago
|
impl AsyncSignature {
|
||
|
pub fn verify(&self) -> bool {
|
||
|
// check that the signature object referenced here, is of type threshold_sig Partial
|
||
|
unimplemented!();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Sync Threshold Signature of one or a chain of commits . V0 . based on the total order quorum (or owners quorum)
|
||
|
/// mandatory for UpdateRootBranch, UpdateBranch, some AddBranch, RemoveBranch, RemoveMember, RemovePermission, Quorum, Compact, sync Transaction, RefreshReadCap, RefreshWriteCap
|
||
7 months ago
|
/// DEPS: the last signed commit in chain
|
||
|
/// ACKS: previous head before the chain of signed commit(s). should be identical to the HEADS (marked as DEPS) of first commit in chain
|
||
|
// #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
// pub struct SyncSignatureV0 {
|
||
|
// /// An Object containing the Threshold signature
|
||
|
// pub signature: ObjectRef,
|
||
|
// }
|
||
|
|
||
|
/// Threshold Signature of a commit
|
||
6 months ago
|
/// points to the new Signature Object
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum SyncSignature {
|
||
|
V0(ObjectRef),
|
||
|
}
|
||
|
|
||
6 months ago
|
impl SyncSignature {
|
||
|
pub fn verify(&self) -> bool {
|
||
|
// check that the signature object referenced here, is of type threshold_sig Total or Owner
|
||
|
unimplemented!();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl fmt::Display for SyncSignature {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
|
Self::V0(v0) => {
|
||
|
writeln!(f, "V0")?;
|
||
|
writeln!(f, "{}", v0)?;
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
/// RefreshReadCap. renew the ReadCap of a `transactional` branch, or the root_branch, or all transactional branches and the root_branch.
|
||
|
/// Each branch forms its separate chain for that purpose.
|
||
|
/// can refresh the topic ids, or not
|
||
|
/// DEPS: current HEADS in the branch at the moment of refresh.
|
||
|
/// followed in the chain by a Branch or RootBranch commit (linked with ACK). The key used in EventV0 for the commit in the future of the RefreshReadCap, is the refresh_secret.
|
||
|
/// the chain can be, by example: RefreshReadCap -> RootBranch -> AddBranch
|
||
|
/// or for a transactional branch: RefreshReadCap -> Branch
|
||
|
/// always eventually followed at the end of each chain by a SyncSignature (each branch its own)
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum RefreshReadCapV0 {
|
||
|
/// A randomly generated secret (SymKey) used for the refresh process, encrypted for each Member, Signer and Owner of the repo (except the one that is being excluded, if any)
|
||
|
/// format to be defined (see crypto_box)
|
||
|
RefreshSecret(),
|
||
|
|
||
|
// or a reference to a master RefreshReadCap commit when some transactional branches are refreshed together with the root_branch. the refresh_secret is taken from that referenced commit
|
||
|
MasterRefresh(ObjectRef),
|
||
|
}
|
||
|
|
||
|
/// RefreshReadCap
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum RefreshReadCap {
|
||
|
V0(RefreshReadCapV0),
|
||
|
}
|
||
|
|
||
|
/// RefreshWriteCap is always done on the root_branch, and always refreshes all the transaction branches WriteCaps, and TopicIDs.
|
||
|
/// DEPS: current HEADS in the branch at the moment of refresh.
|
||
6 months ago
|
/// the chain on the root_branch is : RemovePermission/RemoveMember -> RefreshWriteCap -> RootBranch -> optional AddPermission(s) -> AddBranch
|
||
7 months ago
|
/// and on each transactional branch: RefreshWriteCap -> Branch
|
||
|
/// always eventually followed at the end of each chain by a SyncSignature (each branch its own)
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct RefreshWriteCapV0 {
|
||
|
/// A RefreshReadCapV0::RefreshSecret when on the root_branch, otherwise on transactional branches, a RefreshReadCapV0::MasterRefresh pointing to this RefreshWriteCapV0
|
||
|
pub refresh_read_cap: RefreshReadCapV0,
|
||
|
|
||
|
/// the new RepoWriteCapSecret, encrypted for each Editor (any Member that also has at least one permission, plus all the Owners). See format of RefreshSecret
|
||
|
// TODO: format. should be encrypted
|
||
|
// None when used for a transaction branch, as we don't want to duplicate this encrypted secret in each branch.
|
||
|
pub write_secret: Option<RepoWriteCapSecret>,
|
||
|
}
|
||
|
|
||
|
/// RefreshWriteCap
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum RefreshWriteCap {
|
||
|
V0(RefreshWriteCapV0),
|
||
|
}
|
||
|
|
||
|
/// A Threshold Signature content
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct SignatureContentV0 {
|
||
|
/// list of all the "end of chain" commit for each branch when doing a SyncSignature, or a list of arbitrary commits to sign, for AsyncSignature.
|
||
6 months ago
|
pub commits: Vec<ObjectId>,
|
||
7 months ago
|
}
|
||
|
|
||
|
/// A Signature content
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum SignatureContent {
|
||
|
V0(SignatureContentV0),
|
||
|
}
|
||
|
|
||
6 months ago
|
impl fmt::Display for SignatureContent {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
|
Self::V0(v0) => {
|
||
|
writeln!(f, "V0 == Commits: {}", v0.commits.len())?;
|
||
|
let mut i = 0;
|
||
|
for block_id in &v0.commits {
|
||
|
writeln!(f, "========== {:03}: {}", i, block_id)?;
|
||
|
i += 1;
|
||
|
}
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
/// A Threshold Signature and the set used to generate it
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum ThresholdSignatureV0 {
|
||
|
PartialOrder(threshold_crypto::Signature),
|
||
|
TotalOrder(threshold_crypto::Signature),
|
||
|
Owners(threshold_crypto::Signature),
|
||
|
}
|
||
|
|
||
6 months ago
|
impl fmt::Display for ThresholdSignatureV0 {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
|
Self::PartialOrder(_) => {
|
||
|
writeln!(f, "PartialOrder")
|
||
|
}
|
||
|
Self::TotalOrder(_) => {
|
||
|
writeln!(f, "TotalOrder")
|
||
|
}
|
||
|
Self::Owners(_) => {
|
||
|
writeln!(f, "Owners")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
/// A Threshold Signature object (not a commit) containing all the information that the signers have prepared.
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct SignatureV0 {
|
||
|
/// the content that is signed
|
||
|
pub content: SignatureContent,
|
||
|
|
||
|
/// The threshold signature itself. can come from 3 different sets
|
||
|
pub threshold_sig: ThresholdSignatureV0,
|
||
|
|
||
|
/// A reference to the Certificate that should be used to verify this signature.
|
||
|
pub certificate_ref: ObjectRef,
|
||
|
}
|
||
|
|
||
6 months ago
|
impl fmt::Display for Signature {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
|
Self::V0(v0) => {
|
||
|
writeln!(f, "V0")?;
|
||
|
writeln!(f, "content: {}", v0.content)?;
|
||
|
writeln!(f, "threshold_sig: {}", v0.threshold_sig)?;
|
||
|
writeln!(f, "certificate_ref:{}", v0.certificate_ref)?;
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// A Signature object (it is not a commit), referenced in AsyncSignature or SyncSignature
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum Signature {
|
||
|
V0(SignatureV0),
|
||
|
}
|
||
|
|
||
|
/// Enum for "orders" PKsets. Can be inherited from the store, in this case, it is an ObjectRef pointing to the latest Certificate of the store.
|
||
6 months ago
|
/// Or can be 2 PublicKey defined specially for this repo,
|
||
|
/// .0 one for the total_order (first one). it is a PublicKeysSet so that verifier can see the threshold value, and can also verify Shares individually
|
||
|
/// .1 the other for the partial_order (second one. a PublicKey. is optional, as some repos are forcefully totally ordered and do not have this set).
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum OrdersPublicKeySetsV0 {
|
||
|
Store(ObjectRef),
|
||
|
Repo(
|
||
|
(
|
||
|
threshold_crypto::PublicKeySet,
|
||
6 months ago
|
Option<threshold_crypto::PublicKey>,
|
||
7 months ago
|
),
|
||
|
),
|
||
6 months ago
|
None, // the total_order quorum is not defined (yet, or anymore). there are no signers for the total_order, neither for the partial_order. The owners replace them.
|
||
7 months ago
|
}
|
||
|
|
||
|
/// A Certificate content, that will be signed by the previous certificate signers.
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct CertificateContentV0 {
|
||
|
/// the previous certificate in the chain of trust. Can be another Certificate or the Repository commit when we are at the root of the chain of trust.
|
||
|
pub previous: ObjectRef,
|
||
|
|
||
|
/// The Commit Id of the latest RootBranch definition (= the ReadCap ID) in order to keep in sync with the options for signing.
|
||
|
/// not used for verifying (this is why the secret is not present).
|
||
|
pub readcap_id: ObjectId,
|
||
|
|
||
6 months ago
|
/// PublicKey used by the Owners. verifier uses this PK if the signature was issued by the Owners.
|
||
|
pub owners_pk_set: threshold_crypto::PublicKey,
|
||
7 months ago
|
|
||
6 months ago
|
/// two "orders" PublicKeys (total_order and partial_order).
|
||
7 months ago
|
pub orders_pk_sets: OrdersPublicKeySetsV0,
|
||
7 months ago
|
}
|
||
|
|
||
6 months ago
|
/// A Signature of a Certificate, with an indication of which the threshold keyset or private key used to generate it
|
||
7 months ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum CertificateSignatureV0 {
|
||
6 months ago
|
/// the root CertificateContentV0 is signed with the PrivKey of the Repo
|
||
7 months ago
|
Repo(Sig),
|
||
|
/// Any other certificate in the chain of trust is signed by the total_order quorum of the previous certificate, hence establishing the chain of trust.
|
||
|
TotalOrder(threshold_crypto::Signature),
|
||
|
/// if the previous cert's total order PKset has a threshold value of 0 or 1 (1 or 2 signers in the quorum),
|
||
|
/// then it is allowed that the next certificate (this one) will be signed by the owners PKset instead.
|
||
|
/// This is for a simple reason: if a user is removed from the list of signers in the total_order quorum,
|
||
|
/// then in those 2 cases, the excluded signer will probably not cooperate to their exclusion, and will not sign the new certificate.
|
||
|
/// to avoid deadlocks, we allow the owners to step in and sign the new cert instead.
|
||
|
/// The Owners are also used when there is no quorum/signer defined (OrdersPublicKeySetsV0::None).
|
||
|
Owners(threshold_crypto::Signature),
|
||
|
/// in case the new certificate being signed is an update on the store certificate (OrdersPublicKeySetsV0::Store(ObjectRef) has changed from previous cert)
|
||
|
/// then the signature is in that new store certificate, and not here. nothing else should have changed in the CertificateContent, and the validity of the new store cert has to be checked
|
||
|
Store,
|
||
|
}
|
||
|
|
||
|
/// A Certificate object (not a commit) containing all the information needed to verify a signature.
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct CertificateV0 {
|
||
|
/// content of the certificate, which is signed here below by the previous certificate signers.
|
||
|
pub content: CertificateContentV0,
|
||
|
|
||
|
/// signature over the content.
|
||
|
pub sig: CertificateSignatureV0,
|
||
|
}
|
||
|
|
||
|
/// A certificate object
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum Certificate {
|
||
|
V0(CertificateV0),
|
||
7 months ago
|
}
|
||
|
|
||
|
/// Commit body V0
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum CommitBodyV0 {
|
||
|
//
|
||
|
// for root branch:
|
||
|
//
|
||
7 months ago
|
Repository(Repository), // singleton and should be first in root_branch
|
||
|
RootBranch(RootBranch), // singleton and should be second in root_branch
|
||
|
UpdateRootBranch(RootBranch), // total order enforced with total_order_quorum
|
||
|
AddMember(AddMember), // total order enforced with total_order_quorum
|
||
|
RemoveMember(RemoveMember), // total order enforced with total_order_quorum
|
||
|
AddPermission(AddPermission),
|
||
|
RemovePermission(RemovePermission),
|
||
|
AddBranch(AddBranch),
|
||
|
RemoveBranch(RemoveBranch),
|
||
|
AddName(AddName),
|
||
|
RemoveName(RemoveName),
|
||
6 months ago
|
Delete, // signed with owners key. Deleted the repo
|
||
|
|
||
7 months ago
|
// TODO? Quorum(Quorum), // changes the quorum without changing the RootBranch
|
||
7 months ago
|
|
||
|
//
|
||
7 months ago
|
// For transactional branches:
|
||
7 months ago
|
//
|
||
7 months ago
|
Branch(Branch), // singleton and should be first in branch
|
||
|
UpdateBranch(Branch), // total order enforced with total_order_quorum
|
||
|
Snapshot(Snapshot), // a soft snapshot
|
||
|
AsyncTransaction(Transaction), // partial_order
|
||
|
SyncTransaction(Transaction), // total_order
|
||
|
AddFile(AddFile),
|
||
|
RemoveFile(RemoveFile),
|
||
|
Compact(Compact), // a hard snapshot. total order enforced with total_order_quorum
|
||
|
//Merge(Merge),
|
||
|
//Revert(Revert), // only possible on partial order commit
|
||
|
AsyncSignature(AsyncSignature),
|
||
7 months ago
|
|
||
|
//
|
||
|
// For both
|
||
|
//
|
||
7 months ago
|
RefreshReadCap(RefreshReadCap),
|
||
|
RefreshWriteCap(RefreshWriteCap),
|
||
|
SyncSignature(SyncSignature),
|
||
6 months ago
|
|
||
|
//
|
||
|
// For store branch:
|
||
|
//
|
||
|
AddRepo(AddRepo),
|
||
|
RemoveRepo(RemoveRepo),
|
||
|
|
||
|
//
|
||
|
// For user branch:
|
||
|
//
|
||
|
AddLink(AddLink),
|
||
|
RemoveLink(RemoveLink),
|
||
|
AddSignerCap(AddSignerCap),
|
||
|
RemoveSignerCap(RemoveSignerCap),
|
||
|
WalletUpdate(WalletUpdate),
|
||
|
StoreUpdate(StoreUpdate),
|
||
7 months ago
|
}
|
||
|
|
||
|
/// Commit body
|
||
2 years ago
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum CommitBody {
|
||
7 months ago
|
V0(CommitBodyV0),
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum QuorumType {
|
||
|
NoSigning,
|
||
|
PartialOrder,
|
||
|
TotalOrder,
|
||
7 months ago
|
Owners,
|
||
6 months ago
|
IamTheSignature,
|
||
2 years ago
|
}
|
||
|
|
||
|
/// Content of a Commit
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct CommitContentV0 {
|
||
7 months ago
|
/// Commit author (a hash of UserId)
|
||
|
/// BLAKE3 keyed hash over UserId
|
||
|
/// - key: BLAKE3 derive_key ("NextGraph UserId Hash Overlay Id CommitContentV0 BLAKE3 key", overlayId)
|
||
6 months ago
|
/// hash will be different than for ForwardedPeerAdvertV0 so that core brokers dealing with public sites wont be able to correlate commits and editing peers (via common author's hash).
|
||
|
/// only the brokers of the authors that pin a repo for outeroverlay exposure, will be able to correlate.
|
||
|
/// it also is a different hash than the InboxId, and the OuterOverlayId, which is good to prevent correlation when the RepoId is used as author (for Repository, RootBranch and Branch commits)
|
||
7 months ago
|
pub author: Digest,
|
||
2 years ago
|
|
||
6 months ago
|
// Peer's sequence number
|
||
|
// pub seq: u64,
|
||
7 months ago
|
/// BranchId the commit belongs to (not a ref, as readers do not need to access the branch definition)
|
||
|
pub branch: BranchId,
|
||
2 years ago
|
|
||
7 months ago
|
/// optional list of dependencies on some commits in the root branch that contain the write permission needed for this commit
|
||
|
pub perms: Vec<ObjectId>,
|
||
|
|
||
7 months ago
|
/// Keys to be able to open all the references (deps, acks, files, etc...)
|
||
7 months ago
|
pub header_keys: Option<CommitHeaderKeys>,
|
||
2 years ago
|
|
||
7 months ago
|
/// This commit can only be accepted if signed by this quorum
|
||
|
pub quorum: QuorumType,
|
||
2 years ago
|
|
||
|
/// App-specific metadata (commit message, creation time, etc)
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
|
||
7 months ago
|
/// 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
|
||
2 years ago
|
pub body: ObjectRef,
|
||
|
}
|
||
|
|
||
7 months ago
|
/// Content of a Commit
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum CommitContent {
|
||
|
V0(CommitContentV0),
|
||
|
}
|
||
|
|
||
|
impl CommitContent {
|
||
|
pub fn header_keys(&self) -> &Option<CommitHeaderKeys> {
|
||
|
match self {
|
||
|
CommitContent::V0(v0) => &v0.header_keys,
|
||
|
}
|
||
|
}
|
||
|
pub fn author(&self) -> &Digest {
|
||
|
match self {
|
||
|
CommitContent::V0(v0) => &v0.author,
|
||
|
}
|
||
|
}
|
||
6 months ago
|
|
||
|
pub fn author_digest(author: &UserId, overlay: OverlayId) -> Digest {
|
||
|
let author_id = serde_bare::to_vec(author).unwrap();
|
||
|
let overlay_id = serde_bare::to_vec(&overlay).unwrap();
|
||
|
let mut key: [u8; 32] = blake3::derive_key(
|
||
|
"NextGraph UserId Hash Overlay Id CommitContentV0 BLAKE3 key",
|
||
|
overlay_id.as_slice(),
|
||
|
);
|
||
|
let key_hash = blake3::keyed_hash(&key, &author_id);
|
||
|
key.zeroize();
|
||
|
Digest::from_slice(*key_hash.as_bytes())
|
||
|
}
|
||
7 months ago
|
}
|
||
|
|
||
2 years ago
|
/// 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 {
|
||
7 months ago
|
/// ID of containing Object
|
||
2 years ago
|
#[serde(skip)]
|
||
|
pub id: Option<ObjectId>,
|
||
|
|
||
7 months ago
|
/// Key of containing Object
|
||
2 years ago
|
#[serde(skip)]
|
||
|
pub key: Option<SymKey>,
|
||
|
|
||
7 months ago
|
/// optional Commit Header
|
||
|
#[serde(skip)]
|
||
7 months ago
|
pub header: Option<CommitHeader>,
|
||
|
|
||
6 months ago
|
/// optional Commit Body
|
||
7 months ago
|
#[serde(skip)]
|
||
|
pub body: OnceCell<CommitBody>,
|
||
7 months ago
|
|
||
6 months ago
|
/// optional List of blocks, including the header and body ones. First one is the ObjectId of commit. Vec is ready to be sent in Event
|
||
|
#[serde(skip)]
|
||
|
pub blocks: Vec<BlockId>,
|
||
|
|
||
2 years ago
|
/// Commit content
|
||
7 months ago
|
pub content: CommitContent,
|
||
2 years ago
|
|
||
7 months ago
|
/// Signature over the content (a CommitContent) by the author. an editor (userId)
|
||
2 years ago
|
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)]
|
||
7 months ago
|
pub struct SmallFileV0 {
|
||
1 year ago
|
pub content_type: String,
|
||
2 years ago
|
|
||
|
#[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)]
|
||
7 months ago
|
pub enum SmallFile {
|
||
|
V0(SmallFileV0),
|
||
2 years ago
|
}
|
||
|
|
||
7 months ago
|
/// Random Access File Object
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub struct RandomAccessFileMetaV0 {
|
||
|
pub content_type: String,
|
||
|
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub metadata: Vec<u8>,
|
||
|
|
||
|
pub total_size: u64,
|
||
|
|
||
|
pub chunk_size: u32,
|
||
|
|
||
|
pub arity: u16,
|
||
|
|
||
|
pub depth: u8,
|
||
|
}
|
||
|
|
||
|
/// A Random Access file stored in an Object
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum RandomAccessFileMeta {
|
||
|
V0(RandomAccessFileMetaV0),
|
||
|
}
|
||
|
|
||
|
impl RandomAccessFileMeta {
|
||
|
pub fn arity(&self) -> u16 {
|
||
|
match self {
|
||
|
Self::V0(v0) => v0.arity,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn depth(&self) -> u8 {
|
||
|
match self {
|
||
|
Self::V0(v0) => v0.depth,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn set_depth(&mut self, depth: u8) {
|
||
|
match self {
|
||
|
Self::V0(v0) => {
|
||
|
v0.depth = depth;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn chunk_size(&self) -> u32 {
|
||
|
match self {
|
||
|
Self::V0(v0) => v0.chunk_size,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn total_size(&self) -> u64 {
|
||
|
match self {
|
||
|
Self::V0(v0) => v0.total_size,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn set_total_size(&mut self, size: u64) {
|
||
|
match self {
|
||
|
Self::V0(v0) => {
|
||
|
v0.total_size = size;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn metadata(&self) -> &Vec<u8> {
|
||
|
match self {
|
||
|
Self::V0(v0) => &v0.metadata,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn content_type(&self) -> &String {
|
||
|
match self {
|
||
|
Self::V0(v0) => &v0.content_type,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
7 months ago
|
/// Immutable data stored encrypted in a Merkle tree V0
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum ObjectContentV0 {
|
||
7 months ago
|
Commit(Commit),
|
||
|
CommitBody(CommitBody),
|
||
|
CommitHeader(CommitHeader),
|
||
|
Quorum(Quorum),
|
||
|
Signature(Signature),
|
||
|
Certificate(Certificate),
|
||
7 months ago
|
SmallFile(SmallFile),
|
||
7 months ago
|
RandomAccessFileMeta(RandomAccessFileMeta),
|
||
7 months ago
|
}
|
||
|
|
||
2 years ago
|
/// Immutable data stored encrypted in a Merkle tree
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
|
pub enum ObjectContent {
|
||
7 months ago
|
V0(ObjectContentV0),
|
||
2 years ago
|
}
|
||
6 months ago
|
|
||
|
//
|
||
|
// COMMON TYPES FOR MESSAGES
|
||
|
//
|
||
|
|
||
|
pub trait IObject {
|
||
|
fn block_ids(&self) -> Vec<BlockId>;
|
||
|
|
||
|
fn id(&self) -> Option<ObjectId>;
|
||
|
|
||
|
fn key(&self) -> Option<SymKey>;
|
||
|
}
|
||
|
|
||
|
pub type DirectPeerId = PubKey;
|
||
|
|
||
|
pub type ForwardedPeerId = PubKey;
|
||
|
|
||
|
/// Peer ID: public key of the node, or an encrypted version of it
|
||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
||
|
pub enum PeerId {
|
||
|
Direct(DirectPeerId),
|
||
|
Forwarded(ForwardedPeerId),
|
||
|
/// BLAKE3 keyed hash over ForwardedPeerId
|
||
|
/// - key: BLAKE3 derive_key ("NextGraph ForwardedPeerId Hash Overlay Id BLAKE3 key", overlayId)
|
||
|
ForwardedObfuscated(Digest),
|
||
|
}
|
||
|
|
||
|
impl fmt::Display for PeerId {
|
||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
match self {
|
||
|
Self::Direct(p) => {
|
||
|
write!(f, "Direct : {}", p)
|
||
|
}
|
||
|
Self::Forwarded(p) => {
|
||
|
write!(f, "Forwarded : {}", p)
|
||
|
}
|
||
|
Self::ForwardedObfuscated(p) => {
|
||
|
write!(f, "ForwardedObfuscated : {}", p)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Content of EventV0
|
||
|
/// Contains the objects of newly published Commit, its optional blocks, and optional FILES and their blocks.
|
||
|
/// If a block is not present in the Event, its ID should be present in block_ids and the block should be put on the emitting broker beforehand with BlocksPut.
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||
|
pub struct EventContentV0 {
|
||
|
/// Pub/sub topic
|
||
|
pub topic: TopicId,
|
||
|
|
||
|
// TODO: could be obfuscated (or not, if we want to be able to recall events)
|
||
|
// on public repos, should be obfuscated
|
||
|
pub publisher: PeerId,
|
||
|
|
||
|
/// Commit sequence number of publisher
|
||
|
pub seq: u64,
|
||
|
|
||
|
/// Blocks with encrypted content. First in the list is always the commit block followed by its children, then its optional header and body blocks (and eventual children),
|
||
|
/// blocks of the FILES are optional (only sent here if user specifically want to push them to the pub/sub).
|
||
|
/// the first in the list MUST contain a commit_header_key
|
||
|
/// When saved locally (the broker keeps the associated event, until the topic is refreshed(the last heads retain their events) ),
|
||
|
/// so, this `blocks` list is emptied (as the blocked are saved in the overlay storage anyway) and their IDs are kept on the side.
|
||
|
/// then when the event needs to be send in reply to a *TopicSyncReq, the blocks list is regenerated from the IDs,
|
||
|
/// so that a valid EventContent can be sent (and so that its signature can be verified successfully)
|
||
|
pub blocks: Vec<Block>,
|
||
|
|
||
|
/// Ids of additional Blocks (FILES) with encrypted content that are not to be pushed in the pub/sub
|
||
|
/// they will be retrieved later by interested users
|
||
|
pub file_ids: Vec<BlockId>,
|
||
|
|
||
|
/// can be :
|
||
|
/// * Encrypted key for the Commit object (the first Block in blocks vec)
|
||
|
/// The ObjectKey is encrypted using ChaCha20:
|
||
|
/// - key: BLAKE3 derive_key ("NextGraph Event Commit ObjectKey ChaCha20 key",
|
||
|
/// RepoId + BranchId + branch_secret(ReadCapSecret of the branch) + publisher)
|
||
|
/// - nonce: commit_seq
|
||
|
/// * If it is a CertificateRefresh, both the blocks and block_ids vectors are empty.
|
||
|
/// the key here contains an encrypted ObjectRef to the new Certificate.
|
||
|
/// The whole ObjectRef is encrypted (including the ID) to avoid correlation of topics who will have the same Certificate ID (belong to the same repo)
|
||
|
/// Encrypted using ChaCha20, with :
|
||
|
/// - key: BLAKE3 derive_key ("NextGraph Event Certificate ObjectRef ChaCha20 key",
|
||
|
/// RepoId + BranchId + branch_secret(ReadCapSecret of the branch) + publisher)
|
||
|
/// it is the same key as above, because the commit_seq will be different (incremented anyway)
|
||
|
/// - nonce: commit_seq
|
||
|
#[serde(with = "serde_bytes")]
|
||
|
pub key: Vec<u8>,
|
||
|
}
|
||
|
|
||
|
/// Pub/sub event published in a topic
|
||
|
///
|
||
|
/// Forwarded along event routing table entries
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||
|
pub struct EventV0 {
|
||
|
pub content: EventContentV0,
|
||
|
|
||
|
/// Signature over content by topic key
|
||
|
pub topic_sig: Sig,
|
||
|
|
||
|
/// Signature over content by publisher PeerID priv key
|
||
|
pub peer_sig: Sig,
|
||
|
}
|
||
|
|
||
|
/// Pub/sub event published in a topic
|
||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||
|
pub enum Event {
|
||
|
V0(EventV0),
|
||
|
}
|