// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers // All rights reserved. // This code is partly derived from work written by TG x Thoth from P2Pcollab. // Copyright 2022 TG x Thoth // Licensed under the Apache License, Version 2.0 // // or the MIT license , // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. //! P2P Repo types //! //! Corresponds to the BARE schema use crate::errors::NgError; use crate::utils::{ decode_key, dh_pubkey_array_from_ed_pubkey_slice, dh_pubkey_from_ed_pubkey_slice, ed_privkey_to_ed_pubkey, from_ed_privkey_to_dh_privkey, random_key, }; use core::fmt; use serde::{Deserialize, Serialize}; use serde_bare::to_vec; use std::collections::HashMap; use std::hash::Hash; use zeroize::{Zeroize, ZeroizeOnDrop}; // // COMMON TYPES // /// 32-byte Blake3 hash digest pub type Blake3Digest32 = [u8; 32]; /// Hash digest #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum Digest { Blake3Digest32(Blake3Digest32), } impl fmt::Display for Digest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Digest::Blake3Digest32(d) => write!(f, "{}", hex::encode(d)), } } } /// ChaCha20 symmetric key pub type ChaCha20Key = [u8; 32]; /// Symmetric cryptographic key #[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum SymKey { ChaCha20Key(ChaCha20Key), } impl SymKey { pub fn slice(&self) -> &[u8; 32] { match self { SymKey::ChaCha20Key(o) => o, } } pub fn random() -> Self { SymKey::ChaCha20Key(random_key()) } pub fn from_array(array: [u8; 32]) -> Self { SymKey::ChaCha20Key(array) } } impl fmt::Display for SymKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ChaCha20Key(k) => write!(f, "{}", base64_url::encode(k)), } } } impl TryFrom<&[u8]> for SymKey { type Error = NgError; fn try_from(buf: &[u8]) -> Result { let sym_key_array = *slice_as_array!(buf, [u8; 32]).ok_or(NgError::InvalidKey)?; let sym_key = SymKey::ChaCha20Key(sym_key_array); Ok(sym_key) } } /// Curve25519 public key Edwards form pub type Ed25519PubKey = [u8; 32]; /// Curve25519 public key Montgomery form pub type X25519PubKey = [u8; 32]; /// Curve25519 private key Edwards form pub type Ed25519PrivKey = [u8; 32]; /// Curve25519 private key Montgomery form pub type X25519PrivKey = [u8; 32]; /// Public key #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum PubKey { Ed25519PubKey(Ed25519PubKey), X25519PubKey(X25519PubKey), } impl PubKey { pub fn slice(&self) -> &[u8; 32] { match self { PubKey::Ed25519PubKey(o) | PubKey::X25519PubKey(o) => o, } } pub fn to_dh_from_ed(&self) -> PubKey { match self { PubKey::Ed25519PubKey(ed) => dh_pubkey_from_ed_pubkey_slice(ed), _ => panic!( "there is no need to convert a Montgomery key to Montgomery. it is already one. check your code" ), } } // pub fn dh_from_ed_slice(slice: &[u8]) -> PubKey { // dh_pubkey_from_ed_pubkey_slice(slice) // } pub fn to_dh_slice(&self) -> [u8; 32] { match self { PubKey::Ed25519PubKey(o) => dh_pubkey_array_from_ed_pubkey_slice(o), _ => panic!("can only convert an edward key to montgomery"), } } pub fn nil() -> Self { PubKey::Ed25519PubKey([0u8; 32]) } } impl fmt::Display for PubKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { PubKey::Ed25519PubKey(d) | PubKey::X25519PubKey(d) => { write!(f, "{}", base64_url::encode(d)) } } } } impl TryFrom<&str> for PubKey { type Error = NgError; fn try_from(str: &str) -> Result { let key = decode_key(str).map_err(|_| NgError::InvalidKey)?; Ok(PubKey::Ed25519PubKey(key)) } } /// Private key #[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum PrivKey { Ed25519PrivKey(Ed25519PrivKey), X25519PrivKey(X25519PrivKey), } impl PrivKey { pub fn slice(&self) -> &[u8; 32] { match self { PrivKey::Ed25519PrivKey(o) | PrivKey::X25519PrivKey(o) => o, } } pub fn to_pub(&self) -> PubKey { match self { PrivKey::Ed25519PrivKey(_) => ed_privkey_to_ed_pubkey(self), _ => panic!("X25519PrivKey to pub not implemented"), } } #[deprecated(note = "**Don't use dummy method**")] pub fn dummy() -> PrivKey { PrivKey::Ed25519PrivKey([0u8; 32]) } pub fn to_dh(&self) -> PrivKey { from_ed_privkey_to_dh_privkey(self) } pub fn random_ed() -> Self { PrivKey::Ed25519PrivKey(random_key()) } } impl From<[u8; 32]> for PrivKey { fn from(buf: [u8; 32]) -> Self { let priv_key = PrivKey::Ed25519PrivKey(buf); priv_key } } impl TryFrom<&[u8]> for PrivKey { type Error = NgError; fn try_from(buf: &[u8]) -> Result { let priv_key_array = *slice_as_array!(buf, [u8; 32]).ok_or(NgError::InvalidKey)?; let priv_key = PrivKey::Ed25519PrivKey(priv_key_array); Ok(priv_key) } } impl TryFrom<&str> for PrivKey { type Error = NgError; fn try_from(str: &str) -> Result { let key = decode_key(str).map_err(|_| NgError::InvalidKey)?; Ok(PrivKey::Ed25519PrivKey(key)) } } impl fmt::Display for PrivKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Ed25519PrivKey(ed) => { //let priv_key_ser = serde_bare::to_vec(ed).unwrap(); let prix_key_encoded = base64_url::encode(ed); write!(f, "{}", prix_key_encoded) } _ => { unimplemented!(); } } } } /// Ed25519 signature pub type Ed25519Sig = [[u8; 32]; 2]; /// Cryptographic signature #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum Sig { Ed25519Sig(Ed25519Sig), } /// Timestamp: absolute time in minutes since 2022-02-22 22:22 UTC pub type Timestamp = u32; pub const EPOCH_AS_UNIX_TIMESTAMP: u64 = 1645568520; /// Relative time (e.g. delay from current time) #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum RelTime { Seconds(u8), Minutes(u8), Hours(u8), Days(u8), } /// Bloom filter (variable size) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct BloomFilter { /// Number of hash functions pub k: u32, /// Filter #[serde(with = "serde_bytes")] pub f: Vec, } /// Bloom filter (128 B) /// /// (m=1024; k=7; p=0.01; n=107) pub type BloomFilter128 = [[u8; 32]; 4]; /// Bloom filter (1 KiB) /// /// (m=8192; k=7; p=0.01; n=855) pub type BloomFilter1K = [[u8; 32]; 32]; // // REPOSITORY TYPES // /// List of Permissions pub enum PermissionType { ADD_BRANCH, REMOVE_BRANCH, CHANGE_NAME, ADD_MEMBER, REMOVE_MEMBER, CHANGE_PERMISSION, TRANSACTION, SNAPSHOT, SHARING, CHANGE_ACK_CONFIG, } /// RepoHash: /// BLAKE3 hash of the RepoId pub type RepoHash = Digest; // impl From for String { // fn from(id: RepoHash) -> Self { // hex::encode(to_vec(&id).unwrap()) // } // } /// RepoId is a PubKey pub type RepoId = PubKey; /// Block ID: /// BLAKE3 hash over the serialized Object with encrypted content pub type BlockId = Digest; /// Block reference #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct BlockRef { /// Object ID pub id: BlockId, /// Key for decrypting the Object pub key: SymKey, } impl BlockRef { #[deprecated(note = "**Don't use dummy method**")] pub fn dummy() -> Self { BlockRef { id: Digest::Blake3Digest32([0u8; 32]), key: SymKey::ChaCha20Key([0u8; 32]), } } } /// Object ID pub type ObjectId = BlockId; /// Object reference pub type ObjectRef = BlockRef; /// Internal node of a Merkle tree pub type InternalNode = Vec; /// Content of BlockV0: a Merkle tree node #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum BlockContentV0 { /// Internal node with references to children InternalNode(InternalNode), #[serde(with = "serde_bytes")] DataChunk(Vec), } /// List of ObjectId dependencies as encrypted Object content #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum DepList { V0(Vec), } /// Dependencies of an Object #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum ObjectDeps { /// List of Object IDs (max. 8), ObjectIdList(Vec), /// Reference to an Object that contains a DepList DepListRef(ObjectRef), } /// Immutable block with encrypted content /// /// `ObjectContent` is chunked and stored as `Block`s in a Merkle tree. /// A Block is a Merkle tree node. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct BlockV0 { /// Block ID #[serde(skip)] pub id: Option, /// Block Key #[serde(skip)] pub key: Option, /// Block IDs for child nodes in the Merkle tree pub children: Vec, /// Other objects this object depends on (e.g. Commit deps & acks) /// Only set for the root block pub deps: ObjectDeps, /// Expiry time of this object and all of its children /// when the object should be deleted by all replicas /// Only set for the root block pub expiry: Option, /// Encrypted ObjectContentV0 /// /// Encrypted using convergent encryption with ChaCha20: /// - convergence_key: BLAKE3 derive_key ("NextGraph Data BLAKE3 key", /// repo_pubkey + repo_secret) /// - key: BLAKE3 keyed hash (convergence_key, plain_object_content) /// - nonce: 0 #[serde(with = "serde_bytes")] pub content: Vec, } /// Immutable object with encrypted content #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Block { V0(BlockV0), } /// Repository definition /// /// Published in root branch, where: /// - branch_pubkey: repo_pubkey /// - branch_secret: BLAKE3 derive_key ("NextGraph Root Branch secret", /// repo_pubkey + repo_secret) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct RepositoryV0 { /// Repo public key ID pub id: RepoId, /// List of branches pub branches: Vec, /// Whether or not to allow external requests pub allow_ext_requests: bool, /// App-specific metadata #[serde(with = "serde_bytes")] pub metadata: Vec, } /// Repository definition #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Repository { V0(RepositoryV0), } /// Add a branch to the repository #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum AddBranch { V0(ObjectRef), } /// Remove a branch from the repository #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum RemoveBranch { V0(ObjectRef), } /// Commit object types #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum CommitType { Repository, AddBranch, RemoveBranch, Branch, AddMembers, EndOfBranch, Transaction, Snapshot, Ack, } /// Member of a Branch #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct MemberV0 { /// Member public key ID pub id: PubKey, /// Commit types the member is allowed to publish in the branch pub commit_types: Vec, /// App-specific metadata /// (role, permissions, cryptographic material, etc) #[serde(with = "serde_bytes")] pub metadata: Vec, } /// Member of a branch #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Member { V0(MemberV0), } /// Branch definition /// /// First commit in a branch, signed by branch key /// In case of a fork, the commit deps indicat /// the previous branch heads. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct BranchV0 { /// Branch public key ID pub id: PubKey, /// Pub/sub topic for publishing events pub topic: PubKey, /// Branch secret key pub secret: SymKey, /// Members with permissions pub members: Vec, /// Number of acks required for a commit to be valid pub quorum: HashMap, /// Delay to send explicit acks, /// if not enough implicit acks arrived by then pub ack_delay: RelTime, /// Tags for organizing branches within the repository #[serde(with = "serde_bytes")] pub tags: Vec, /// App-specific metadata (validation rules, etc) #[serde(with = "serde_bytes")] pub metadata: Vec, } /// Branch definition #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Branch { V0(BranchV0), } /// Add members to an existing branch #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct AddMembersV0 { /// Members to add, with permissions pub members: Vec, /// New quorum pub quorum: Option>, /// New ackDelay pub ack_delay: Option, } /// Add members to an existing branch /// /// If a member already exists, it overwrites the previous definition, /// in that case this can only be used for adding new permissions, /// not to remove existing ones. /// The quorum and ackDelay can be changed as well #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum AddMembers { V0(AddMembersV0), } /// ObjectRef for EndOfBranch #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum PlainOrEncryptedObjectRef { Plain(ObjectRef), Encrypted(Vec), } /// End of branch /// /// No more commits accepted afterwards, only acks of this commit /// May reference a fork where the branch continues /// with possibly different members, permissions, validation rules. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct EndOfBranchV0 { /// (Encrypted) reference to forked branch (optional) pub fork: Option, /// Expiry time when all commits in the branch should be deleted pub expiry: Timestamp, } /// End of branch #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum EndOfBranch { V0(EndOfBranchV0), } /// Transaction with CRDT operations #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Transaction { #[serde(with = "serde_bytes")] V0(Vec), } /// Snapshot of a Branch /// /// Contains a data structure /// computed from the commits at the specified head. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct SnapshotV0 { /// Branch heads the snapshot was made from pub heads: Vec, /// Snapshot data structure #[serde(with = "serde_bytes")] pub content: Vec, } /// Snapshot of a Branch #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Snapshot { V0(SnapshotV0), } /// Acknowledgement of another Commit #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Ack { V0(), } /// Commit body, corresponds to CommitType #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum CommitBody { Repository(Repository), AddBranch(AddBranch), RemoveBranch(RemoveBranch), Branch(Branch), AddMembers(AddMembers), EndOfBranch(EndOfBranch), Transaction(Transaction), Snapshot(Snapshot), Ack(Ack), } /// Content of a Commit #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct CommitContentV0 { /// Commit author pub author: PubKey, /// Author's commit sequence number in this branch pub seq: u32, /// Branch the commit belongs to pub branch: ObjectRef, /// Direct dependencies of this commit pub deps: Vec, /// Not directly dependent heads to acknowledge pub acks: Vec, /// Files the commit references pub refs: Vec, /// App-specific metadata (commit message, creation time, etc) #[serde(with = "serde_bytes")] pub metadata: Vec, /// Object with a CommitBody inside pub body: ObjectRef, /// Expiry time of the body object pub expiry: Option, } /// Commit object /// Signed by branch key, or a member key authorized to publish this commit type #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct CommitV0 { /// ID of parent Object #[serde(skip)] pub id: Option, /// Key of parent Object #[serde(skip)] pub key: Option, /// Commit content pub content: CommitContentV0, /// Signature over the content by the author pub sig: Sig, } /// Commit Object #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Commit { V0(CommitV0), } /// File Object #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct FileV0 { pub content_type: String, #[serde(with = "serde_bytes")] pub metadata: Vec, #[serde(with = "serde_bytes")] pub content: Vec, } /// A file stored in an Object #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum File { V0(FileV0), } /// Immutable data stored encrypted in a Merkle tree #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum ObjectContent { Commit(Commit), CommitBody(CommitBody), File(File), DepList(DepList), }