chore: restructure concord crate (#50)
Reviewed-on: #50
This commit was merged in pull request #50.
This commit is contained in:
@@ -6,13 +6,13 @@ use anyhow::Result;
|
||||
use data_encoding::HEXLOWER;
|
||||
use nostr_sdk::prelude::*;
|
||||
|
||||
use crate::edition::{
|
||||
AuthorityCitation, TAG_CITATION, canonical_decimal, citation_from, citation_tag,
|
||||
};
|
||||
use crate::stream::{
|
||||
use crate::cord01::{
|
||||
KIND_WRAP, OpenedStream, SealForm, StreamError, build_rumor_ms, build_seal, open_wrap,
|
||||
wrap_seal,
|
||||
};
|
||||
use crate::cord04::{
|
||||
AuthorityCitation, TAG_CITATION, canonical_decimal, citation_from, citation_tag,
|
||||
};
|
||||
use crate::{GroupKey, decode_hex_32};
|
||||
|
||||
pub const KIND_JOIN_LEAVE: u16 = 3306;
|
||||
@@ -532,8 +532,8 @@ fn pubkey(hex: &str, name: &'static str) -> Result<PublicKey, GuestbookError> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cord01::build_rumor_secs;
|
||||
use crate::derive::guestbook_group_key;
|
||||
use crate::stream::build_rumor_secs;
|
||||
use crate::{CommunityId, Epoch};
|
||||
|
||||
const ROOT: [u8; 32] = [0x5au8; 32];
|
||||
@@ -5,8 +5,8 @@ use std::fmt;
|
||||
use nostr_sdk::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::invite::{ChannelGrant, CommunityInvite};
|
||||
use crate::stream::{self, NIP44_MAX_PLAINTEXT};
|
||||
use crate::cord01::{self, NIP44_MAX_PLAINTEXT};
|
||||
use crate::cord05::{ChannelGrant, CommunityInvite};
|
||||
use crate::{CommunityId, Epoch, Extra};
|
||||
|
||||
pub const KIND_COMMUNITY_LIST: u16 = 13302;
|
||||
@@ -42,8 +42,8 @@ impl fmt::Display for ListError {
|
||||
|
||||
impl std::error::Error for ListError {}
|
||||
|
||||
impl From<stream::StreamError> for ListError {
|
||||
fn from(error: stream::StreamError) -> Self {
|
||||
impl From<cord01::StreamError> for ListError {
|
||||
fn from(error: cord01::StreamError) -> Self {
|
||||
ListError::Crypto(error.to_string())
|
||||
}
|
||||
}
|
||||
@@ -187,7 +187,7 @@ pub fn build_list_event(keys: &Keys, list: &CommunityList) -> Result<Event, List
|
||||
list.fits()?;
|
||||
|
||||
let json = serde_json::to_string(list).map_err(json_error)?;
|
||||
let content = stream::seal_to_self(keys, json.as_bytes())?;
|
||||
let content = cord01::seal_to_self(keys, json.as_bytes())?;
|
||||
|
||||
EventBuilder::new(Kind::Custom(KIND_COMMUNITY_LIST), content)
|
||||
.finalize(keys)
|
||||
@@ -199,7 +199,7 @@ pub fn parse_list_event(keys: &Keys, event: &Event) -> Result<CommunityList, Lis
|
||||
return Err(ListError::Kind(event.kind.as_u16()));
|
||||
}
|
||||
|
||||
let json = stream::open_to_self(keys, &event.content)?;
|
||||
let json = cord01::open_to_self(keys, &event.content)?;
|
||||
|
||||
serde_json::from_slice(&json).map_err(json_error)
|
||||
}
|
||||
@@ -1,22 +1,25 @@
|
||||
pub mod guestbook;
|
||||
pub mod list;
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use nostr_sdk::prelude::{Event, Keys, PublicKey, Timestamp, UnsignedEvent};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::cord01::{KIND_WRAP, SealForm, build_seal, open_wrap_at, wrap_seal_with};
|
||||
use crate::cord04::roles::{
|
||||
AuthorityEdition, CommunityRoles, Grant, MAX_BANLIST, Permissions, Role, Roster, citation_ok,
|
||||
fold_roster,
|
||||
};
|
||||
use crate::cord04::{
|
||||
AuthorityCitation, EditionFields, EditionMeta, EntityHead, Floors, ParsedEdition,
|
||||
build_edition, fold_head, parse_edition, vsk,
|
||||
};
|
||||
use crate::derive::{
|
||||
banlist_locator, community_id_of, control_group_key, control_signer_group_key, grant_locator,
|
||||
invite_links_locator, pins_locator, verify_community_id,
|
||||
};
|
||||
use crate::edition::{
|
||||
AuthorityCitation, EditionFields, EditionMeta, EntityHead, Floors, ParsedEdition,
|
||||
build_edition, fold_head, parse_edition, vsk,
|
||||
};
|
||||
use crate::roles::{
|
||||
AuthorityEdition, CommunityRoles, Grant, MAX_BANLIST, Permissions, Role, Roster, citation_ok,
|
||||
fold_roster,
|
||||
};
|
||||
use crate::stream::{KIND_WRAP, SealForm, build_seal, open_wrap_at, wrap_seal_with};
|
||||
use crate::{ChannelId, CommunityId, Epoch, Extra, GroupKey, random_32};
|
||||
|
||||
pub const MAX_NAME_BYTES: usize = 64;
|
||||
@@ -731,12 +734,13 @@ mod tests {
|
||||
use nostr_memory::MemoryDatabase;
|
||||
|
||||
use super::*;
|
||||
use crate::chat::{self, build_message, seal_rumor};
|
||||
use crate::cord03::{self, build_message, seal_rumor};
|
||||
use crate::cord04::fold;
|
||||
use crate::cord04::pins;
|
||||
use crate::cord04::roles::{Grant, MAX_BANLIST, MAX_ROLES_PER_MEMBER, Role, RoleScope};
|
||||
use crate::derive::{channel_group_key, grant_locator};
|
||||
use crate::edition::fold;
|
||||
use crate::roles::{Grant, MAX_BANLIST, MAX_ROLES_PER_MEMBER, Role, RoleScope};
|
||||
use crate::store::{CommunityState, load_state, save_state};
|
||||
use crate::{Extra, RoleId, pins};
|
||||
use crate::{Extra, RoleId};
|
||||
|
||||
const AT: u64 = 1_700_000_000;
|
||||
|
||||
@@ -1102,7 +1106,7 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
let (wrap, _) = seal_rumor(&rumor, &group, &author, false).expect("seals");
|
||||
let opened = chat::open(&wrap, &group, &channel, ROOT_EPOCH)
|
||||
let opened = cord03::open(&wrap, &group, &channel, ROOT_EPOCH)
|
||||
.expect("opens")
|
||||
.0;
|
||||
let entry = pins::build_entry(&opened, &group, &channel).expect("pins");
|
||||
@@ -5,15 +5,15 @@ use std::fmt;
|
||||
use anyhow::Result;
|
||||
use nostr_sdk::prelude::*;
|
||||
|
||||
use crate::derive::channel_group_key;
|
||||
use crate::edition::{
|
||||
AuthorityCitation, TAG_CITATION, canonical_decimal, citation_from, citation_tag,
|
||||
};
|
||||
use crate::stream::{
|
||||
use crate::cord01::{
|
||||
KIND_WRAP, KIND_WRAP_EPHEMERAL, OpenedStream, SealForm, StreamError, build_rumor_ms,
|
||||
build_seal, channel_binding_tags, check_channel_binding, open_wrap, resolve_ms_strict,
|
||||
wrap_seal,
|
||||
};
|
||||
use crate::cord04::{
|
||||
AuthorityCitation, TAG_CITATION, canonical_decimal, citation_from, citation_tag,
|
||||
};
|
||||
use crate::derive::channel_group_key;
|
||||
use crate::{ChannelId, Epoch, GroupKey, decode_hex_32};
|
||||
|
||||
pub const KIND_MESSAGE: u16 = 9;
|
||||
@@ -1,3 +1,6 @@
|
||||
pub mod pins;
|
||||
pub mod roles;
|
||||
|
||||
use std::cmp::Reverse;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
@@ -7,8 +10,8 @@ use nostr_sdk::prelude::{EventId, PublicKey, Tag, UnsignedEvent};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::cord01::build_rumor_secs;
|
||||
use crate::decode_hex_32;
|
||||
use crate::stream::build_rumor_secs;
|
||||
|
||||
pub const KIND_CONTROL: u16 = 3308;
|
||||
|
||||
@@ -9,9 +9,9 @@ use nostr_sdk::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::chat::{ChatAction, ChatRumor, KIND_COMMENT, KIND_EDIT, KIND_MESSAGE};
|
||||
use crate::edition::canonical_decimal;
|
||||
use crate::stream::{self, OpenedStream, SealForm, resolve_ms_strict};
|
||||
use crate::cord01::{self, OpenedStream, SealForm, resolve_ms_strict};
|
||||
use crate::cord03::{ChatAction, ChatRumor, KIND_COMMENT, KIND_EDIT, KIND_MESSAGE};
|
||||
use crate::cord04::canonical_decimal;
|
||||
use crate::{ChannelId, Epoch, Extra, GroupKey, decode_hex_lower};
|
||||
|
||||
pub const PIN_MAX_ENTRIES: usize = 25;
|
||||
@@ -315,7 +315,7 @@ fn disclosed_keys(opened: &OpenedStream, group: &GroupKey) -> Result<MessageKeys
|
||||
pub fn verify_entry(entry: &PinEntry, channel: &ChannelId) -> Option<VerifiedPin> {
|
||||
let seal = &entry.seal;
|
||||
|
||||
if seal.kind.as_u16() != stream::KIND_SEAL_ENCRYPTED || seal.verify().is_err() {
|
||||
if seal.kind.as_u16() != cord01::KIND_SEAL_ENCRYPTED || seal.verify().is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ fn verify_edit_bundle(
|
||||
let seal = &bundle.seal;
|
||||
|
||||
// Checkable before any crypto: nobody else may revise another member's words.
|
||||
if seal.kind.as_u16() != stream::KIND_SEAL_ENCRYPTED || seal.pubkey != *original_author {
|
||||
if seal.kind.as_u16() != cord01::KIND_SEAL_ENCRYPTED || seal.pubkey != *original_author {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -456,7 +456,7 @@ fn serialize_sealed(
|
||||
}
|
||||
|
||||
let inner = encode_form(entries)?;
|
||||
let sealed = stream::seal_bytes(group.conversation(), inner.as_bytes())
|
||||
let sealed = cord01::seal_bytes(group.conversation(), inner.as_bytes())
|
||||
.map_err(|error| PinError::Seal(error.to_string()))?;
|
||||
let content = serde_json::json!({ "epoch": epoch.to_string(), "sealed": sealed }).to_string();
|
||||
|
||||
@@ -526,7 +526,7 @@ pub fn read_list(content: &str, unseal: impl Fn(Epoch) -> Option<GroupKey>) -> R
|
||||
};
|
||||
};
|
||||
|
||||
let Ok(inner) = stream::open_bytes(group.conversation(), sealed) else {
|
||||
let Ok(inner) = cord01::open_bytes(group.conversation(), sealed) else {
|
||||
return EMPTY;
|
||||
};
|
||||
|
||||
@@ -554,7 +554,7 @@ mod tests {
|
||||
use nostr::nips::nip44::v2::{self, ConversationKey};
|
||||
|
||||
use super::*;
|
||||
use crate::chat::{ChatRumor, build_delete, build_edit, build_message, open, seal_rumor};
|
||||
use crate::cord03::{ChatRumor, build_delete, build_edit, build_message, open, seal_rumor};
|
||||
use crate::derive::channel_group_key;
|
||||
|
||||
const AT_MS: u64 = 1_700_000_000_000;
|
||||
@@ -657,7 +657,7 @@ mod tests {
|
||||
assert!(verify_entry(&forged, &channel()).is_none());
|
||||
|
||||
// A rumor carrying a claimed id that is not its own is refused.
|
||||
let plaintext = stream::open_bytes(&conversation(), &opened.seal.content).expect("opens");
|
||||
let plaintext = cord01::open_bytes(&conversation(), &opened.seal.content).expect("opens");
|
||||
let mut value: serde_json::Value = serde_json::from_slice(&plaintext).expect("json");
|
||||
value["id"] = serde_json::Value::String("00".repeat(32));
|
||||
|
||||
@@ -668,7 +668,7 @@ mod tests {
|
||||
)
|
||||
.expect("encrypts");
|
||||
let content = BASE64.encode(&raw);
|
||||
let seal = EventBuilder::new(Kind::Custom(stream::KIND_SEAL_ENCRYPTED), &content)
|
||||
let seal = EventBuilder::new(Kind::Custom(cord01::KIND_SEAL_ENCRYPTED), &content)
|
||||
.custom_created_at(opened.seal.created_at)
|
||||
.finalize(&author)
|
||||
.expect("signs");
|
||||
@@ -4,10 +4,10 @@ use anyhow::{Result, bail};
|
||||
use nostr_sdk::prelude::PublicKey;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::derive::{banlist_locator, grant_locator};
|
||||
use crate::edition::{
|
||||
use crate::cord04::{
|
||||
AuthorityCitation, EditionMeta, EntityHead, Floors, ParsedEdition, fold_head, vsk,
|
||||
};
|
||||
use crate::derive::{banlist_locator, grant_locator};
|
||||
use crate::{ChannelId, CommunityId, Extra, RoleId, decode_hex_32};
|
||||
|
||||
pub const MAX_ROLES_PER_COMMUNITY: usize = 100;
|
||||
@@ -759,7 +759,7 @@ mod tests {
|
||||
use nostr_sdk::prelude::Keys;
|
||||
|
||||
use super::*;
|
||||
use crate::edition::{EditionFields, build_edition, parse_edition};
|
||||
use crate::cord04::{EditionFields, build_edition, parse_edition};
|
||||
|
||||
const COMMUNITY: [u8; 32] = [0xc0; 32];
|
||||
const AT: u64 = 1_700_000_000;
|
||||
@@ -10,11 +10,11 @@ use nostr::nips::nip59::{GiftWrapBuilder, UnwrappedGift};
|
||||
use nostr_sdk::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::control::{ImageRef, MAX_RELAYS};
|
||||
use crate::cord01::{self, NIP44_MAX_PLAINTEXT, StreamError};
|
||||
use crate::cord02::list::{canonical, union};
|
||||
use crate::cord02::{ImageRef, MAX_RELAYS};
|
||||
use crate::cord04::{TAG_SUBKIND, vsk};
|
||||
use crate::derive::{TOKEN_LEN, verify_community_id};
|
||||
use crate::edition::{TAG_SUBKIND, vsk};
|
||||
use crate::list::{canonical, union};
|
||||
use crate::stream::{self, NIP44_MAX_PLAINTEXT, StreamError};
|
||||
use crate::{ChannelId, CommunityId, Epoch, Extra, decode_hex_32};
|
||||
|
||||
pub const KIND_BUNDLE: u16 = 33301;
|
||||
@@ -594,7 +594,7 @@ pub fn build_invite_list(keys: &Keys, list: &InviteList) -> Result<Event, Invite
|
||||
list.fits()?;
|
||||
|
||||
let json = serde_json::to_string(list).map_err(json_error)?;
|
||||
let content = stream::seal_to_self(keys, json.as_bytes())?;
|
||||
let content = cord01::seal_to_self(keys, json.as_bytes())?;
|
||||
|
||||
EventBuilder::new(Kind::Custom(KIND_INVITE_LIST), content)
|
||||
.finalize(keys)
|
||||
@@ -606,7 +606,7 @@ pub fn parse_invite_list(keys: &Keys, event: &Event) -> Result<InviteList, Invit
|
||||
return Err(InviteError::Kind(event.kind.as_u16()));
|
||||
}
|
||||
|
||||
let json = stream::open_to_self(keys, &event.content)?;
|
||||
let json = cord01::open_to_self(keys, &event.content)?;
|
||||
|
||||
serde_json::from_slice(&json).map_err(json_error)
|
||||
}
|
||||
@@ -626,14 +626,14 @@ fn merge_entry(held: &InviteEntry, incoming: &InviteEntry) -> InviteEntry {
|
||||
}
|
||||
|
||||
fn seal_bundle(bundle_key: &[u8; 32], json: &str) -> Result<String, InviteError> {
|
||||
Ok(stream::seal_bytes(
|
||||
Ok(cord01::seal_bytes(
|
||||
&ConversationKey::new(*bundle_key),
|
||||
json.as_bytes(),
|
||||
)?)
|
||||
}
|
||||
|
||||
fn open_bundle(bundle_key: &[u8; 32], content: &str) -> Result<String, InviteError> {
|
||||
let plaintext = stream::open_bytes(&ConversationKey::new(*bundle_key), content)?;
|
||||
let plaintext = cord01::open_bytes(&ConversationKey::new(*bundle_key), content)?;
|
||||
|
||||
String::from_utf8(plaintext).map_err(|_| InviteError::BadFragment("bundle is not utf8"))
|
||||
}
|
||||
@@ -941,7 +941,7 @@ mod tests {
|
||||
assert!(unwrap_direct_invite(&wrap, &stranger).is_err());
|
||||
|
||||
// ...and a wrap that opens to some other kind is not an invite.
|
||||
let rumor = EventBuilder::new(Kind::Custom(crate::chat::KIND_MESSAGE), "hello")
|
||||
let rumor = EventBuilder::new(Kind::Custom(crate::cord03::KIND_MESSAGE), "hello")
|
||||
.finalize_unsigned(recipient.public_key());
|
||||
let wrap = GiftWrapBuilder::new(recipient.public_key(), rumor)
|
||||
.finalize(&recipient)
|
||||
@@ -7,17 +7,17 @@ use nostr::nips::nip44::v2::ConversationKey;
|
||||
use nostr_sdk::prelude::{Event, Keys, PublicKey, SecretKey, Tag, Timestamp, UnsignedEvent};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::control::CommunityIdentity;
|
||||
use crate::cord01::{self, KIND_SEAL_PLAINTEXT, OpenedStream, SealForm, StreamError};
|
||||
use crate::cord02::CommunityIdentity;
|
||||
use crate::cord04::roles::CommunityRoles;
|
||||
use crate::cord04::{
|
||||
AuthorityCitation, KIND_CONTROL, TAG_SUBKIND, canonical_decimal, citation_from, citation_tag,
|
||||
vsk,
|
||||
};
|
||||
use crate::derive::{
|
||||
base_rekey_group_key, channel_rekey_group_key, control_group_key, control_signer_group_key,
|
||||
dissolved_group_key, epoch_key_commitment, recipient_locator,
|
||||
};
|
||||
use crate::edition::{
|
||||
AuthorityCitation, KIND_CONTROL, TAG_SUBKIND, canonical_decimal, citation_from, citation_tag,
|
||||
vsk,
|
||||
};
|
||||
use crate::roles::CommunityRoles;
|
||||
use crate::stream::{self, KIND_SEAL_PLAINTEXT, OpenedStream, SealForm, StreamError};
|
||||
use crate::{ChannelId, CommunityId, Epoch, GroupKey, random_32};
|
||||
|
||||
pub const KIND_REKEY: u16 = 3303;
|
||||
@@ -325,7 +325,7 @@ pub fn open_blob(
|
||||
) -> Result<KeyDelivery, RekeyError> {
|
||||
let conversation =
|
||||
ConversationKey::derive(recipient.secret_key(), rotator).map_err(crypto_error)?;
|
||||
let plaintext = stream::open_bytes(&conversation, &blob.wrapped)?;
|
||||
let plaintext = cord01::open_bytes(&conversation, &blob.wrapped)?;
|
||||
|
||||
parse_blob_plaintext(&plaintext, scope, epoch, community_id)
|
||||
}
|
||||
@@ -347,7 +347,7 @@ fn seal_to(
|
||||
plaintext: &[u8],
|
||||
) -> Result<String, RekeyError> {
|
||||
let conversation = ConversationKey::derive(secret, recipient).map_err(crypto_error)?;
|
||||
Ok(stream::seal_bytes(&conversation, plaintext)?)
|
||||
Ok(cord01::seal_bytes(&conversation, plaintext)?)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -539,7 +539,7 @@ pub fn compact(
|
||||
let mut wraps = Vec::with_capacity(seals.len());
|
||||
|
||||
for seal in seals {
|
||||
wraps.push(stream::rewrap_seal(seal, read, signer, at)?.0);
|
||||
wraps.push(cord01::rewrap_seal(seal, read, signer, at)?.0);
|
||||
}
|
||||
|
||||
Ok(wraps)
|
||||
@@ -592,7 +592,7 @@ pub fn build_rekey_rumor(
|
||||
tags.push(Tag::custom(TAG_SEVER, ["1"]));
|
||||
}
|
||||
|
||||
Ok(stream::build_rumor_secs(
|
||||
Ok(cord01::build_rumor_secs(
|
||||
KIND_REKEY, rotator, &content, tags, at_secs,
|
||||
))
|
||||
}
|
||||
@@ -633,11 +633,11 @@ pub fn build_rekey_chunks(
|
||||
at_secs,
|
||||
)?;
|
||||
|
||||
let seal = stream::build_seal(&rumor, SealForm::Encrypted, group, rotator)?;
|
||||
let (wrap, _) = stream::wrap_seal(
|
||||
let seal = cord01::build_seal(&rumor, SealForm::Encrypted, group, rotator)?;
|
||||
let (wrap, _) = cord01::wrap_seal(
|
||||
&seal,
|
||||
group,
|
||||
stream::KIND_WRAP,
|
||||
cord01::KIND_WRAP,
|
||||
Timestamp::from_secs(at_secs),
|
||||
&[],
|
||||
)?;
|
||||
@@ -704,7 +704,7 @@ pub fn parse_rekey_chunk(opened: &OpenedStream) -> Result<RekeyChunk, RekeyError
|
||||
prev_commit,
|
||||
chunk: parse_chunk(rumor)?,
|
||||
blobs,
|
||||
citation: tag(rumor, crate::edition::TAG_CITATION)?.and_then(citation_from),
|
||||
citation: tag(rumor, crate::cord04::TAG_CITATION)?.and_then(citation_from),
|
||||
severed,
|
||||
})
|
||||
}
|
||||
@@ -719,7 +719,7 @@ pub fn dissolved_tombstone_rumor(
|
||||
community_id: &CommunityId,
|
||||
at_secs: u64,
|
||||
) -> UnsignedEvent {
|
||||
stream::build_rumor_secs(
|
||||
cord01::build_rumor_secs(
|
||||
KIND_CONTROL,
|
||||
owner,
|
||||
"",
|
||||
@@ -738,11 +738,11 @@ pub fn seal_dissolved(
|
||||
at_secs: u64,
|
||||
) -> Result<Event, RekeyError> {
|
||||
let group = dissolved_group_key(community_id).map_err(crypto_error)?;
|
||||
let seal = stream::build_seal(rumor, SealForm::Plaintext, &group, owner)?;
|
||||
let (wrap, _) = stream::wrap_seal(
|
||||
let seal = cord01::build_seal(rumor, SealForm::Plaintext, &group, owner)?;
|
||||
let (wrap, _) = cord01::wrap_seal(
|
||||
&seal,
|
||||
&group,
|
||||
stream::KIND_WRAP,
|
||||
cord01::KIND_WRAP,
|
||||
Timestamp::from_secs(at_secs),
|
||||
&[],
|
||||
)?;
|
||||
@@ -756,7 +756,7 @@ pub fn open_dissolved(
|
||||
community_id: &CommunityId,
|
||||
) -> Result<DissolvedTombstone, RekeyError> {
|
||||
let group = dissolved_group_key(community_id).map_err(crypto_error)?;
|
||||
let opened = stream::open_wrap(wrap, &group)?;
|
||||
let opened = cord01::open_wrap(wrap, &group)?;
|
||||
|
||||
if !is_tombstone(&opened.rumor, community_id) {
|
||||
return Err(RekeyError::NotADissolution);
|
||||
@@ -859,13 +859,13 @@ mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use super::*;
|
||||
use crate::control::{
|
||||
use crate::cord01::KIND_WRAP;
|
||||
use crate::cord02::{
|
||||
CommunityMetadata, ControlWriter, Edition, ROOT_EPOCH, fold_control, genesis, open_edition,
|
||||
};
|
||||
use crate::cord04::roles::{Grant, Permissions, Role, RoleScope};
|
||||
use crate::cord04::{EditionFields, Floors, build_edition};
|
||||
use crate::derive::{community_id_of, grant_locator};
|
||||
use crate::edition::{EditionFields, Floors, build_edition};
|
||||
use crate::roles::{Grant, Permissions, Role, RoleScope};
|
||||
use crate::stream::KIND_WRAP;
|
||||
use crate::{Extra, RoleId};
|
||||
|
||||
const AT: u64 = 1_700_000_000;
|
||||
@@ -1074,7 +1074,7 @@ mod tests {
|
||||
.expect("builds");
|
||||
assert_eq!(chunks.len(), 1);
|
||||
|
||||
let opened = stream::open_wrap(&chunks[0], &group).expect("opens");
|
||||
let opened = cord01::open_wrap(&chunks[0], &group).expect("opens");
|
||||
let chunk = parse_rekey_chunk(&opened).expect("parses");
|
||||
assert_eq!(
|
||||
chunk.rotator,
|
||||
@@ -1486,7 +1486,7 @@ mod tests {
|
||||
at_secs: AT,
|
||||
});
|
||||
let seal =
|
||||
stream::build_seal(&rumor, SealForm::Plaintext, &prior_read, &owner).expect("seals");
|
||||
cord01::build_seal(&rumor, SealForm::Plaintext, &prior_read, &owner).expect("seals");
|
||||
|
||||
let refounding = plan_refounding(Epoch(1)).expect("plans");
|
||||
let read = refounding.read(&community_id).expect("derives");
|
||||
@@ -1497,7 +1497,7 @@ mod tests {
|
||||
compact(std::slice::from_ref(&seal), &read, &signer, AT + 1).expect("compacts");
|
||||
assert_eq!(compacted.len(), 1);
|
||||
|
||||
let reopened = stream::open_wrap_at(&compacted[0], &signer.pk(), read.conversation(), true)
|
||||
let reopened = cord01::open_wrap_at(&compacted[0], &signer.pk(), read.conversation(), true)
|
||||
.expect("opens");
|
||||
assert_eq!(
|
||||
reopened.seal.sig, seal.sig,
|
||||
@@ -1508,7 +1508,7 @@ mod tests {
|
||||
|
||||
// Only a plaintext seal can be carried forward.
|
||||
let encrypted =
|
||||
stream::build_seal(&rumor, SealForm::Encrypted, &prior_read, &owner).expect("seals");
|
||||
cord01::build_seal(&rumor, SealForm::Encrypted, &prior_read, &owner).expect("seals");
|
||||
assert!(matches!(
|
||||
compact(&[encrypted], &read, &signer, AT + 1),
|
||||
Err(RekeyError::Stream(StreamError::NotRewrappable))
|
||||
@@ -1550,7 +1550,7 @@ mod tests {
|
||||
// The spec's all-zero `eid` is refused: it would let one owner's genuine
|
||||
// tombstone be re-wrapped at another of their communities and kill it.
|
||||
let zeroed = seal_dissolved(
|
||||
&stream::build_rumor_secs(
|
||||
&cord01::build_rumor_secs(
|
||||
KIND_CONTROL,
|
||||
owner.public_key(),
|
||||
"",
|
||||
@@ -1580,10 +1580,10 @@ mod tests {
|
||||
owner: owner.public_key(),
|
||||
owner_salt: other_salt,
|
||||
};
|
||||
let seal = stream::open_wrap(&wrap, &dissolved_group_key(&community_id).expect("derives"))
|
||||
let seal = cord01::open_wrap(&wrap, &dissolved_group_key(&community_id).expect("derives"))
|
||||
.expect("opens")
|
||||
.seal;
|
||||
let replayed = stream::wrap_seal(
|
||||
let replayed = cord01::wrap_seal(
|
||||
&seal,
|
||||
&dissolved_group_key(&other_id).expect("derives"),
|
||||
KIND_WRAP,
|
||||
@@ -0,0 +1,9 @@
|
||||
//! One module per CORD document. CORD-07 (audio/video) is unimplemented, and
|
||||
//! CORD-08's timer rides the Chat and Control planes it edits rather than owning a file.
|
||||
|
||||
pub mod cord01;
|
||||
pub mod cord02;
|
||||
pub mod cord03;
|
||||
pub mod cord04;
|
||||
pub mod cord05;
|
||||
pub mod cord06;
|
||||
+9
-143
@@ -1,146 +1,12 @@
|
||||
pub mod chat;
|
||||
pub mod control;
|
||||
pub mod derive;
|
||||
pub mod edition;
|
||||
pub mod guestbook;
|
||||
pub mod invite;
|
||||
pub mod list;
|
||||
pub mod pins;
|
||||
pub mod rekey;
|
||||
pub mod roles;
|
||||
mod cords;
|
||||
mod types;
|
||||
mod utils;
|
||||
|
||||
pub mod store;
|
||||
pub mod stream;
|
||||
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
pub use cords::{cord01, cord02, cord03, cord04, cord05, cord06};
|
||||
pub use types::{ChannelId, CommunityId, Epoch, RoleId};
|
||||
pub use utils::derive::{self, GroupKey};
|
||||
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use data_encoding::HEXLOWER;
|
||||
pub use derive::GroupKey;
|
||||
use rand::TryRng as _;
|
||||
use rand::rngs::SysRng;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// Unknown fields a content struct does not model, so a republish cannot wipe them.
|
||||
pub(crate) type Extra = serde_json::Map<String, serde_json::Value>;
|
||||
|
||||
macro_rules! hex_id {
|
||||
($(#[$meta:meta])* $name:ident) => {
|
||||
$(#[$meta])*
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct $name([u8; 32]);
|
||||
|
||||
impl $name {
|
||||
pub fn from_bytes(bytes: [u8; 32]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn to_hex(&self) -> String {
|
||||
HEXLOWER.encode(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for $name {
|
||||
fn from(bytes: [u8; 32]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for $name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}({})", stringify!($name), self.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for $name {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self> {
|
||||
Ok(Self(decode_hex_32(value)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for $name {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&self.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for $name {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let value = String::deserialize(deserializer)?;
|
||||
value.parse().map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
hex_id! {
|
||||
/// A self-certifying commitment to the owner's key, never on the wire.
|
||||
CommunityId
|
||||
}
|
||||
|
||||
hex_id! {
|
||||
ChannelId
|
||||
}
|
||||
|
||||
hex_id! {
|
||||
/// Both a Role's entity coordinate and the field it repeats in its own content.
|
||||
RoleId
|
||||
}
|
||||
|
||||
/// A key-rotation counter; it bumps only on a Rekey that removes somebody.
|
||||
#[derive(
|
||||
Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct Epoch(pub u64);
|
||||
|
||||
impl fmt::Display for Epoch {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Uppercase and other non-canonical spellings are rejected.
|
||||
pub(crate) fn decode_hex_32(value: &str) -> Result<[u8; 32]> {
|
||||
decode_hex_lower::<32>(value)
|
||||
}
|
||||
|
||||
pub(crate) fn decode_hex_lower<const N: usize>(value: &str) -> Result<[u8; N]> {
|
||||
let bytes = HEXLOWER
|
||||
.decode(value.as_bytes())
|
||||
.map_err(|error| anyhow!("invalid hex: {error}"))?;
|
||||
|
||||
let decoded: [u8; N] = bytes
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.map_err(|_| anyhow!("expected {N} bytes, got {}", bytes.len()))?;
|
||||
|
||||
if HEXLOWER.encode(&decoded) != value {
|
||||
bail!("hex must be lowercase and canonical");
|
||||
}
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
pub(crate) fn fill_random(bytes: &mut [u8]) -> Result<()> {
|
||||
SysRng
|
||||
.try_fill_bytes(bytes)
|
||||
.map_err(|error| anyhow!("os rng: {error}"))
|
||||
}
|
||||
|
||||
pub(crate) fn random_32() -> Result<[u8; 32]> {
|
||||
let mut bytes = [0u8; 32];
|
||||
fill_random(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
pub(crate) use types::Extra;
|
||||
pub(crate) use utils::{decode_hex_32, decode_hex_lower, fill_random, random_32};
|
||||
|
||||
+11
-10
@@ -6,13 +6,13 @@ use anyhow::{Result, anyhow};
|
||||
use nostr_sdk::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::chat::{self, ChatRumor, plane_keys};
|
||||
use crate::control::{
|
||||
use crate::cord01::{KIND_WRAP_EPHEMERAL, OpenedStream};
|
||||
use crate::cord02::{
|
||||
ChannelMetadata, CommunityGenesis, CommunityMetadata, ControlFold, ROOT_EPOCH,
|
||||
};
|
||||
use crate::cord03::{self, ChatRumor, plane_keys};
|
||||
use crate::cord04::{EntityHead, Floors, ParsedEdition, vsk};
|
||||
use crate::derive::control_signer_group_key;
|
||||
use crate::edition::{EntityHead, Floors, ParsedEdition, vsk};
|
||||
use crate::stream::{KIND_WRAP_EPHEMERAL, OpenedStream};
|
||||
use crate::{ChannelId, CommunityId, Epoch, GroupKey};
|
||||
|
||||
static LOCAL_KEYS: LazyLock<Keys> = LazyLock::new(Keys::generate);
|
||||
@@ -31,7 +31,8 @@ pub async fn cache_rumor(
|
||||
channel: &ChannelId,
|
||||
opened: &OpenedStream,
|
||||
) -> Result<bool> {
|
||||
if chat::expiration_of(&opened.rumor)?.is_some_and(|expiration| expiration <= Timestamp::now())
|
||||
if cord03::expiration_of(&opened.rumor)?
|
||||
.is_some_and(|expiration| expiration <= Timestamp::now())
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -73,7 +74,7 @@ pub async fn purge_expired(
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(Some(expiration)) = chat::expiration_of(&rumor) else {
|
||||
let Ok(Some(expiration)) = cord03::expiration_of(&rumor) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -374,7 +375,7 @@ fn advance(
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok((opened, rumor)) = chat::open(wrap, group, channel, *epoch) else {
|
||||
let Ok((opened, rumor)) = cord03::open(wrap, group, channel, *epoch) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -419,11 +420,11 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::Epoch;
|
||||
use crate::chat::{build_message, seal_rumor};
|
||||
use crate::derive::channel_group_key;
|
||||
use crate::stream::{
|
||||
use crate::cord01::{
|
||||
KIND_WRAP, SealForm, build_rumor_ms, build_seal, channel_binding_tags, open_wrap, wrap_seal,
|
||||
};
|
||||
use crate::cord03::{build_message, seal_rumor};
|
||||
use crate::derive::channel_group_key;
|
||||
|
||||
const SECRET: [u8; 32] = [0x07u8; 32];
|
||||
const NEXT_SECRET: [u8; 32] = [0x11u8; 32];
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use data_encoding::HEXLOWER;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::decode_hex_32;
|
||||
|
||||
/// Unknown fields a content struct does not model, so a republish cannot wipe them.
|
||||
pub(crate) type Extra = serde_json::Map<String, serde_json::Value>;
|
||||
|
||||
macro_rules! hex_id {
|
||||
($(#[$meta:meta])* $name:ident) => {
|
||||
$(#[$meta])*
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct $name([u8; 32]);
|
||||
|
||||
impl $name {
|
||||
pub fn from_bytes(bytes: [u8; 32]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn to_hex(&self) -> String {
|
||||
HEXLOWER.encode(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for $name {
|
||||
fn from(bytes: [u8; 32]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for $name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}({})", stringify!($name), self.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for $name {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self> {
|
||||
Ok(Self(decode_hex_32(value)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for $name {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&self.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for $name {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let value = String::deserialize(deserializer)?;
|
||||
value.parse().map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
hex_id! {
|
||||
/// A self-certifying commitment to the owner's key, never on the wire.
|
||||
CommunityId
|
||||
}
|
||||
|
||||
hex_id! {
|
||||
ChannelId
|
||||
}
|
||||
|
||||
hex_id! {
|
||||
/// Both a Role's entity coordinate and the field it repeats in its own content.
|
||||
RoleId
|
||||
}
|
||||
|
||||
/// A key-rotation counter; it bumps only on a Rekey that removes somebody.
|
||||
#[derive(
|
||||
Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct Epoch(pub u64);
|
||||
|
||||
impl fmt::Display for Epoch {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
pub mod derive;
|
||||
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use data_encoding::HEXLOWER;
|
||||
use rand::TryRng as _;
|
||||
use rand::rngs::SysRng;
|
||||
|
||||
/// Uppercase and other non-canonical spellings are rejected.
|
||||
pub(crate) fn decode_hex_32(value: &str) -> Result<[u8; 32]> {
|
||||
decode_hex_lower::<32>(value)
|
||||
}
|
||||
|
||||
pub(crate) fn decode_hex_lower<const N: usize>(value: &str) -> Result<[u8; N]> {
|
||||
let bytes = HEXLOWER
|
||||
.decode(value.as_bytes())
|
||||
.map_err(|error| anyhow!("invalid hex: {error}"))?;
|
||||
|
||||
let decoded: [u8; N] = bytes
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.map_err(|_| anyhow!("expected {N} bytes, got {}", bytes.len()))?;
|
||||
|
||||
if HEXLOWER.encode(&decoded) != value {
|
||||
bail!("hex must be lowercase and canonical");
|
||||
}
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
pub(crate) fn fill_random(bytes: &mut [u8]) -> Result<()> {
|
||||
SysRng
|
||||
.try_fill_bytes(bytes)
|
||||
.map_err(|error| anyhow!("os rng: {error}"))
|
||||
}
|
||||
|
||||
pub(crate) fn random_32() -> Result<[u8; 32]> {
|
||||
let mut bytes = [0u8; 32];
|
||||
fill_random(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
Reference in New Issue
Block a user