pub mod attachments;
pub mod cache;
pub mod cipher;
pub mod derive;
pub mod envelope;
pub mod inbound;
pub mod invite;
pub mod invite_list;
pub mod list;
pub mod metadata;
pub mod edition;
pub mod owner;
pub mod rekey;
pub mod roster;
pub mod version;
pub mod public_invite;
pub mod realtime;
pub mod roles;
pub mod send;
pub mod service;
pub mod transport;
pub mod v2;
use nostr_sdk::prelude::PublicKey;
use rand::RngCore;
use serde::{Deserialize, Serialize};
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConcordProtocol {
V1,
V2,
}
impl ConcordProtocol {
pub fn as_i64(self) -> i64 {
match self {
ConcordProtocol::V1 => 1,
ConcordProtocol::V2 => 2,
}
}
pub fn from_i64(n: i64) -> ConcordProtocol {
match n {
2 => ConcordProtocol::V2,
_ => ConcordProtocol::V1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CommunityId(pub [u8; 32]);
impl CommunityId {
pub fn to_hex(&self) -> String {
crate::simd::hex::bytes_to_hex_32(&self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChannelId(pub [u8; 32]);
impl ChannelId {
pub fn to_hex(&self) -> String {
crate::simd::hex::bytes_to_hex_32(&self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Epoch(pub u64);
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct ChannelKey(pub [u8; 32]);
impl ChannelKey {
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
impl core::fmt::Debug for ChannelKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("ChannelKey(<redacted>)")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Pseudonym(pub [u8; 32]);
impl Pseudonym {
pub fn to_hex(&self) -> String {
crate::simd::hex::bytes_to_hex_32(&self.0)
}
}
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct ServerRootKey(pub [u8; 32]);
impl ServerRootKey {
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
impl core::fmt::Debug for ServerRootKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("ServerRootKey(<redacted>)")
}
}
pub const SERVER_ROOT_SCOPE_HEX: &str =
"0000000000000000000000000000000000000000000000000000000000000000";
pub(crate) fn random_32() -> [u8; 32] {
let mut b = [0u8; 32];
rand::rngs::OsRng.fill_bytes(&mut b);
b
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CommunityImage {
pub url: String,
pub key: String,
pub nonce: String,
pub hash: String,
#[serde(default)]
pub ext: String,
}
#[derive(Debug, Clone)]
pub struct Channel {
pub id: ChannelId,
pub key: ChannelKey,
pub epoch: Epoch,
pub name: String,
pub banned: Vec<PublicKey>,
pub protected: Vec<PublicKey>,
pub roster: roles::CommunityRoles,
pub epoch_keys: Vec<(Epoch, ChannelKey)>,
pub dissolved: bool,
}
impl Channel {
pub fn read_epoch_keys(&self) -> Vec<(Epoch, ChannelKey)> {
let mut keys = if self.epoch_keys.is_empty() {
vec![(self.epoch, self.key.clone())]
} else {
self.epoch_keys.clone()
};
keys.sort_by(|a, b| b.0.0.cmp(&a.0.0));
keys
}
}
#[derive(Debug, Clone)]
pub struct Community {
pub id: CommunityId,
pub server_root_key: ServerRootKey,
pub server_root_epoch: Epoch,
pub name: String,
pub description: Option<String>,
pub icon: Option<CommunityImage>,
pub banner: Option<CommunityImage>,
pub relays: Vec<String>,
pub channels: Vec<Channel>,
pub owner_attestation: Option<String>,
pub dissolved: bool,
}
pub const MAX_COMMUNITY_RELAYS: usize = 5;
pub fn cap_relays(relays: Vec<String>) -> Vec<String> {
let mut seen = std::collections::HashSet::new();
let mut out = Vec::with_capacity(relays.len().min(MAX_COMMUNITY_RELAYS));
for r in relays {
if out.len() >= MAX_COMMUNITY_RELAYS {
break;
}
if seen.insert(r.clone()) {
out.push(r);
}
}
out
}
impl Community {
pub fn create(
name: impl Into<String>,
default_channel_name: impl Into<String>,
relays: Vec<String>,
) -> Self {
let channel = Channel {
id: ChannelId(random_32()),
key: ChannelKey(random_32()),
epoch: Epoch(0),
name: default_channel_name.into(),
banned: Vec::new(),
protected: Vec::new(), roster: Default::default(),
epoch_keys: Vec::new(),
dissolved: false,
};
Community {
id: CommunityId(random_32()),
server_root_key: ServerRootKey(random_32()),
server_root_epoch: Epoch(0),
name: name.into(),
description: None,
icon: None,
banner: None,
relays: cap_relays(relays),
channels: vec![channel],
owner_attestation: None,
dissolved: false,
}
}
}