refactor concord
This commit is contained in:
@@ -300,9 +300,6 @@ async fn subscribe(
|
||||
}
|
||||
}
|
||||
|
||||
// Concord wraps share kind 1059 with NIP-59 gift wraps, so an automatic
|
||||
// target sends gossip after the plane authors as if they were DM peers.
|
||||
// The community's own relays are the routing relays, so target them.
|
||||
let target = if relays.is_empty() {
|
||||
ReqTarget::auto(vec![filter])
|
||||
} else {
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::fmt;
|
||||
use data_encoding::BASE64;
|
||||
use nostr::nips::nip44::v2::{ConversationKey, decrypt_to_bytes, encrypt_to_bytes_with_nonce};
|
||||
use nostr_sdk::prelude::{
|
||||
Event, EventBuilder, EventId, FinalizeEvent, Keys, Kind, PublicKey, Tag, Timestamp,
|
||||
UnsignedEvent,
|
||||
AsyncGetPublicKey, AsyncNip44, AsyncSignEvent, Event, EventBuilder, EventId, FinalizeEvent,
|
||||
FinalizeEventAsync, Keys, Kind, PublicKey, Tag, Timestamp, UnsignedEvent,
|
||||
};
|
||||
|
||||
use crate::derive::GroupKey;
|
||||
@@ -198,32 +198,52 @@ pub fn open_bytes(conversation: &ConversationKey, content: &str) -> Result<Vec<u
|
||||
}
|
||||
|
||||
/// A member's own document (the Community List, the Invite List): NIP-44 to self.
|
||||
pub fn seal_to_self(keys: &Keys, plaintext: &[u8]) -> Result<String, StreamError> {
|
||||
seal_bytes(
|
||||
&ConversationKey::derive(keys.secret_key(), &keys.public_key())
|
||||
.map_err(|error| StreamError::Encrypt(error.to_string()))?,
|
||||
plaintext,
|
||||
)
|
||||
pub async fn seal_to_self<S>(signer: &S, plaintext: &str) -> Result<String, StreamError>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncNip44 + ?Sized,
|
||||
{
|
||||
check_plaintext_cap(plaintext.len())?;
|
||||
|
||||
let address = signer
|
||||
.get_public_key_async()
|
||||
.await
|
||||
.map_err(|error| StreamError::Encrypt(error.to_string()))?;
|
||||
|
||||
signer
|
||||
.nip44_encrypt_async(&address, plaintext)
|
||||
.await
|
||||
.map_err(|error| StreamError::Encrypt(error.to_string()))
|
||||
}
|
||||
|
||||
pub fn open_to_self(keys: &Keys, content: &str) -> Result<Vec<u8>, StreamError> {
|
||||
open_bytes(
|
||||
&ConversationKey::derive(keys.secret_key(), &keys.public_key())
|
||||
.map_err(|error| StreamError::Decrypt(error.to_string()))?,
|
||||
content,
|
||||
)
|
||||
pub async fn open_to_self<S>(signer: &S, content: &str) -> Result<String, StreamError>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncNip44 + ?Sized,
|
||||
{
|
||||
let address = signer
|
||||
.get_public_key_async()
|
||||
.await
|
||||
.map_err(|error| StreamError::Decrypt(error.to_string()))?;
|
||||
|
||||
signer
|
||||
.nip44_decrypt_async(&address, content)
|
||||
.await
|
||||
.map_err(|error| StreamError::Decrypt(error.to_string()))
|
||||
}
|
||||
|
||||
pub fn build_seal(
|
||||
pub async fn build_seal<S>(
|
||||
rumor: &UnsignedEvent,
|
||||
form: SealForm,
|
||||
group: &GroupKey,
|
||||
author: &Keys,
|
||||
) -> Result<Event, StreamError> {
|
||||
author: &S,
|
||||
) -> Result<Event, StreamError>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
let content = seal_content(rumor, form, group)?;
|
||||
EventBuilder::new(Kind::Custom(form.kind()), content)
|
||||
.custom_created_at(rumor.created_at)
|
||||
.finalize(author)
|
||||
.finalize_async(author)
|
||||
.await
|
||||
.map_err(|error| StreamError::Sign(error.to_string()))
|
||||
}
|
||||
|
||||
@@ -450,7 +470,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn sealed(rumor: &UnsignedEvent, form: SealForm, author: &Keys) -> Event {
|
||||
build_seal(rumor, form, &group(0), author).expect("seals")
|
||||
smol::block_on(build_seal(rumor, form, &group(0), author)).expect("seals")
|
||||
}
|
||||
|
||||
fn wrapped(seal: &Event, kind: u16, at_secs: u64) -> Event {
|
||||
|
||||
@@ -183,18 +183,21 @@ pub fn build_snapshot_chunks(
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn seal_rumor(
|
||||
pub async fn seal_rumor<S>(
|
||||
rumor: &UnsignedEvent,
|
||||
group: &GroupKey,
|
||||
author: &Keys,
|
||||
) -> Result<(Event, Keys), GuestbookError> {
|
||||
author: &S,
|
||||
) -> Result<(Event, Keys), GuestbookError>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
let kind = rumor.kind.as_u16();
|
||||
|
||||
if !is_guestbook_kind(kind) {
|
||||
return Err(GuestbookError::UnknownKind(kind));
|
||||
}
|
||||
|
||||
let seal = build_seal(rumor, SealForm::Encrypted, group, author)?;
|
||||
let seal = build_seal(rumor, SealForm::Encrypted, group, author).await?;
|
||||
|
||||
Ok(wrap_seal(&seal, group, KIND_WRAP, rumor.created_at, &[])?)
|
||||
}
|
||||
@@ -556,7 +559,9 @@ mod tests {
|
||||
}
|
||||
|
||||
fn publish(rumor: &UnsignedEvent, author: &Keys) -> GuestbookRumor {
|
||||
let wrap = seal_rumor(rumor, &group(), author).expect("seals").0;
|
||||
let wrap = smol::block_on(seal_rumor(rumor, &group(), author))
|
||||
.expect("seals")
|
||||
.0;
|
||||
|
||||
open(&wrap, &group()).expect("opens").1
|
||||
}
|
||||
@@ -826,7 +831,9 @@ mod tests {
|
||||
);
|
||||
assert!(matches!(
|
||||
open(
|
||||
&seal_rumor(&bad_ms, &group(), &member).expect("seals").0,
|
||||
&smol::block_on(seal_rumor(&bad_ms, &group(), &member))
|
||||
.expect("seals")
|
||||
.0,
|
||||
&group()
|
||||
),
|
||||
Err(GuestbookError::Stream(StreamError::BadMs))
|
||||
@@ -835,7 +842,9 @@ mod tests {
|
||||
let bad_verb = build_rumor_ms(KIND_JOIN_LEAVE, member.public_key(), "maybe", vec![], AT);
|
||||
assert!(matches!(
|
||||
open(
|
||||
&seal_rumor(&bad_verb, &group(), &member).expect("seals").0,
|
||||
&smol::block_on(seal_rumor(&bad_verb, &group(), &member))
|
||||
.expect("seals")
|
||||
.0,
|
||||
&group()
|
||||
),
|
||||
Err(GuestbookError::BadTag(TAG_CONTENT))
|
||||
@@ -854,7 +863,7 @@ mod tests {
|
||||
);
|
||||
assert!(matches!(
|
||||
open(
|
||||
&seal_rumor(&ambiguous, &group(), &moderator)
|
||||
&smol::block_on(seal_rumor(&ambiguous, &group(), &moderator))
|
||||
.expect("seals")
|
||||
.0,
|
||||
&group()
|
||||
@@ -876,7 +885,9 @@ mod tests {
|
||||
);
|
||||
assert!(matches!(
|
||||
open(
|
||||
&seal_rumor(&rumor, &group(), &moderator).expect("seals").0,
|
||||
&smol::block_on(seal_rumor(&rumor, &group(), &moderator))
|
||||
.expect("seals")
|
||||
.0,
|
||||
&group()
|
||||
),
|
||||
Err(GuestbookError::BadTag(TAG_SNAP))
|
||||
|
||||
@@ -183,25 +183,32 @@ pub fn merge(held: CommunityList, incoming: CommunityList) -> CommunityList {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_list_event(keys: &Keys, list: &CommunityList) -> Result<Event, ListError> {
|
||||
pub async fn build_list_event<S>(keys: &S, list: &CommunityList) -> Result<Event, ListError>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + AsyncNip44 + ?Sized,
|
||||
{
|
||||
list.fits()?;
|
||||
|
||||
let json = serde_json::to_string(list).map_err(json_error)?;
|
||||
let content = cord01::seal_to_self(keys, json.as_bytes())?;
|
||||
let content = cord01::seal_to_self(keys, &json).await?;
|
||||
|
||||
EventBuilder::new(Kind::Custom(KIND_COMMUNITY_LIST), content)
|
||||
.finalize(keys)
|
||||
.finalize_async(keys)
|
||||
.await
|
||||
.map_err(crypto_error)
|
||||
}
|
||||
|
||||
pub fn parse_list_event(keys: &Keys, event: &Event) -> Result<CommunityList, ListError> {
|
||||
pub async fn parse_list_event<S>(keys: &S, event: &Event) -> Result<CommunityList, ListError>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncNip44 + ?Sized,
|
||||
{
|
||||
if event.kind.as_u16() != KIND_COMMUNITY_LIST {
|
||||
return Err(ListError::Kind(event.kind.as_u16()));
|
||||
}
|
||||
|
||||
let json = cord01::open_to_self(keys, &event.content)?;
|
||||
let json = cord01::open_to_self(keys, &event.content).await?;
|
||||
|
||||
serde_json::from_slice(&json).map_err(json_error)
|
||||
serde_json::from_str(&json).map_err(json_error)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
@@ -419,18 +426,21 @@ mod tests {
|
||||
extra: Extra::default(),
|
||||
};
|
||||
|
||||
let event = build_list_event(&me, &mine).expect("builds");
|
||||
let event = smol::block_on(build_list_event(&me, &mine)).expect("builds");
|
||||
assert_eq!(event.kind, Kind::Custom(KIND_COMMUNITY_LIST));
|
||||
assert_eq!(parse_list_event(&me, &event).expect("parses"), mine);
|
||||
assert_eq!(
|
||||
smol::block_on(parse_list_event(&me, &event)).expect("parses"),
|
||||
mine
|
||||
);
|
||||
assert!(
|
||||
!parse_list_event(&me, &event)
|
||||
!smol::block_on(parse_list_event(&me, &event))
|
||||
.expect("parses")
|
||||
.is_live(&id(0x33))
|
||||
);
|
||||
|
||||
// Only the member's own keys open it, and an unreadable list is "no news".
|
||||
let stranger = Keys::generate();
|
||||
assert!(parse_list_event(&stranger, &event).is_err());
|
||||
assert!(smol::block_on(parse_list_event(&stranger, &event)).is_err());
|
||||
|
||||
// Unknown fields survive the round trip, so a republish cannot wipe them.
|
||||
let mut held = mine.clone();
|
||||
@@ -440,8 +450,11 @@ mod tests {
|
||||
.current
|
||||
.extra
|
||||
.insert("held_roots".to_owned(), serde_json::json!([{"epoch": 1}]));
|
||||
let rebuilt =
|
||||
parse_list_event(&me, &build_list_event(&me, &held).expect("builds")).expect("parses");
|
||||
let rebuilt = smol::block_on(parse_list_event(
|
||||
&me,
|
||||
&smol::block_on(build_list_event(&me, &held)).expect("builds"),
|
||||
))
|
||||
.expect("parses");
|
||||
assert_eq!(rebuilt, held);
|
||||
|
||||
// The write gate refuses an over-cap or oversized List before publishing.
|
||||
@@ -459,7 +472,7 @@ mod tests {
|
||||
.collect(),
|
||||
);
|
||||
assert!(matches!(
|
||||
build_list_event(&me, &crowded),
|
||||
smol::block_on(build_list_event(&me, &crowded)),
|
||||
Err(ListError::TooManyMemberships(n)) if n == MAX_MEMBERSHIPS + 1
|
||||
));
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ pub mod list;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use nostr_sdk::prelude::{Event, Keys, PublicKey, Timestamp, UnsignedEvent};
|
||||
use nostr_sdk::prelude::{
|
||||
AsyncGetPublicKey, AsyncSignEvent, Event, PublicKey, Timestamp, UnsignedEvent,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::cord01::{KIND_WRAP, SealForm, build_seal, open_wrap_at, wrap_seal_with};
|
||||
@@ -114,17 +116,24 @@ pub struct CommunityGenesis {
|
||||
pub wraps: Vec<Event>,
|
||||
}
|
||||
|
||||
pub fn genesis(
|
||||
owner: &Keys,
|
||||
pub async fn genesis<S>(
|
||||
owner: &S,
|
||||
metadata: &CommunityMetadata,
|
||||
at_secs: u64,
|
||||
) -> Result<CommunityGenesis> {
|
||||
) -> Result<CommunityGenesis>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
let metadata_content = encode_metadata(metadata)?;
|
||||
let owner_salt = random_32()?;
|
||||
let owner_key = owner
|
||||
.get_public_key_async()
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("signer: {error}"))?;
|
||||
|
||||
let identity = CommunityIdentity {
|
||||
community_id: community_id_of(&owner.public_key().to_bytes(), &owner_salt),
|
||||
owner: owner.public_key(),
|
||||
community_id: community_id_of(&owner_key.to_bytes(), &owner_salt),
|
||||
owner: owner_key,
|
||||
owner_salt,
|
||||
};
|
||||
|
||||
@@ -167,7 +176,7 @@ pub fn genesis(
|
||||
let mut wraps = Vec::with_capacity(editions.len());
|
||||
|
||||
for edition in &editions {
|
||||
wraps.push(seal_edition(edition, owner, &read, &signer, at_secs)?);
|
||||
wraps.push(seal_edition(edition, owner, &read, &signer, at_secs).await?);
|
||||
}
|
||||
|
||||
Ok(CommunityGenesis {
|
||||
@@ -211,12 +220,15 @@ pub struct Edition<'a> {
|
||||
}
|
||||
|
||||
impl ControlWriter {
|
||||
pub fn publish(
|
||||
pub async fn publish<S>(
|
||||
&self,
|
||||
keys: &Keys,
|
||||
keys: &S,
|
||||
edition: Edition<'_>,
|
||||
at_secs: u64,
|
||||
) -> Result<(Event, EntityHead)> {
|
||||
) -> Result<(Event, EntityHead)>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
let rumor = build_edition(EditionFields {
|
||||
author: self.author,
|
||||
subkind: edition.subkind,
|
||||
@@ -231,20 +243,23 @@ impl ControlWriter {
|
||||
});
|
||||
|
||||
let parsed = parse_edition(&rumor)?;
|
||||
let wrap = seal_edition(&rumor, keys, &self.read, &self.signer, at_secs)?;
|
||||
let wrap = seal_edition(&rumor, keys, &self.read, &self.signer, at_secs).await?;
|
||||
|
||||
Ok((wrap, EntityHead::from(&parsed)))
|
||||
}
|
||||
|
||||
pub fn set_community_metadata(
|
||||
pub async fn set_community_metadata<S>(
|
||||
&self,
|
||||
keys: &Keys,
|
||||
keys: &S,
|
||||
community_id: &CommunityId,
|
||||
metadata: &CommunityMetadata,
|
||||
head: Option<&EntityHead>,
|
||||
citation: Option<AuthorityCitation>,
|
||||
at_secs: u64,
|
||||
) -> Result<(Event, EntityHead)> {
|
||||
) -> Result<(Event, EntityHead)>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
let content = encode_metadata(metadata)?;
|
||||
|
||||
self.publish(
|
||||
@@ -258,17 +273,21 @@ impl ControlWriter {
|
||||
},
|
||||
at_secs,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn set_channel_metadata(
|
||||
pub async fn set_channel_metadata<S>(
|
||||
&self,
|
||||
keys: &Keys,
|
||||
keys: &S,
|
||||
channel: &ChannelId,
|
||||
metadata: &ChannelMetadata,
|
||||
head: Option<&EntityHead>,
|
||||
citation: Option<AuthorityCitation>,
|
||||
at_secs: u64,
|
||||
) -> Result<(Event, EntityHead)> {
|
||||
) -> Result<(Event, EntityHead)>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
if metadata.name.len() > MAX_NAME_BYTES {
|
||||
bail!("channel name exceeds {MAX_NAME_BYTES} bytes");
|
||||
}
|
||||
@@ -286,16 +305,20 @@ impl ControlWriter {
|
||||
},
|
||||
at_secs,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn set_role(
|
||||
pub async fn set_role<S>(
|
||||
&self,
|
||||
keys: &Keys,
|
||||
keys: &S,
|
||||
role: &Role,
|
||||
head: Option<&EntityHead>,
|
||||
citation: Option<AuthorityCitation>,
|
||||
at_secs: u64,
|
||||
) -> Result<(Event, EntityHead)> {
|
||||
) -> Result<(Event, EntityHead)>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
if role.name.len() > MAX_NAME_BYTES {
|
||||
bail!("role name exceeds {MAX_NAME_BYTES} bytes");
|
||||
}
|
||||
@@ -313,17 +336,21 @@ impl ControlWriter {
|
||||
},
|
||||
at_secs,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn set_grant(
|
||||
pub async fn set_grant<S>(
|
||||
&self,
|
||||
keys: &Keys,
|
||||
keys: &S,
|
||||
community_id: &CommunityId,
|
||||
grant: &Grant,
|
||||
head: Option<&EntityHead>,
|
||||
citation: Option<AuthorityCitation>,
|
||||
at_secs: u64,
|
||||
) -> Result<(Event, EntityHead)> {
|
||||
) -> Result<(Event, EntityHead)>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
let content = grant.to_content()?;
|
||||
|
||||
self.publish(
|
||||
@@ -337,17 +364,21 @@ impl ControlWriter {
|
||||
},
|
||||
at_secs,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn set_banlist(
|
||||
pub async fn set_banlist<S>(
|
||||
&self,
|
||||
keys: &Keys,
|
||||
keys: &S,
|
||||
community_id: &CommunityId,
|
||||
banned: &BTreeSet<PublicKey>,
|
||||
head: Option<&EntityHead>,
|
||||
citation: Option<AuthorityCitation>,
|
||||
at_secs: u64,
|
||||
) -> Result<(Event, EntityHead)> {
|
||||
) -> Result<(Event, EntityHead)>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
if banned.len() > MAX_BANLIST {
|
||||
bail!("banlist exceeds {MAX_BANLIST} entries");
|
||||
}
|
||||
@@ -366,19 +397,23 @@ impl ControlWriter {
|
||||
},
|
||||
at_secs,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn set_registry(
|
||||
pub async fn set_registry<S>(
|
||||
&self,
|
||||
keys: &Keys,
|
||||
keys: &S,
|
||||
community_id: &CommunityId,
|
||||
creator: &PublicKey,
|
||||
links: &[PublicKey],
|
||||
head: Option<&EntityHead>,
|
||||
citation: Option<AuthorityCitation>,
|
||||
at_secs: u64,
|
||||
) -> Result<(Event, EntityHead)> {
|
||||
) -> Result<(Event, EntityHead)>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
let entries: Vec<String> = links
|
||||
.iter()
|
||||
.take(MAX_REGISTRY_LINKS)
|
||||
@@ -397,20 +432,24 @@ impl ControlWriter {
|
||||
},
|
||||
at_secs,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// The whole Pin List, in whichever of CORD-04 §7's two forms the Channel calls for.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn set_pin_list(
|
||||
pub async fn set_pin_list<S>(
|
||||
&self,
|
||||
keys: &Keys,
|
||||
keys: &S,
|
||||
community_id: &CommunityId,
|
||||
channel: &ChannelId,
|
||||
content: &str,
|
||||
head: Option<&EntityHead>,
|
||||
citation: Option<AuthorityCitation>,
|
||||
at_secs: u64,
|
||||
) -> Result<(Event, EntityHead)> {
|
||||
) -> Result<(Event, EntityHead)>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
self.publish(
|
||||
keys,
|
||||
Edition {
|
||||
@@ -422,6 +461,7 @@ impl ControlWriter {
|
||||
},
|
||||
at_secs,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -708,14 +748,17 @@ fn authorized_head<'a>(
|
||||
selection.head.map(|index| authorized[index])
|
||||
}
|
||||
|
||||
fn seal_edition(
|
||||
async fn seal_edition<S>(
|
||||
edition: &UnsignedEvent,
|
||||
owner: &Keys,
|
||||
owner: &S,
|
||||
read: &GroupKey,
|
||||
signer: &GroupKey,
|
||||
at_secs: u64,
|
||||
) -> Result<Event> {
|
||||
let seal = build_seal(edition, SealForm::Plaintext, read, owner)?;
|
||||
) -> Result<Event>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
let seal = build_seal(edition, SealForm::Plaintext, read, owner).await?;
|
||||
|
||||
let (wrap, _) = wrap_seal_with(
|
||||
&seal,
|
||||
@@ -731,6 +774,8 @@ fn seal_edition(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nostr_sdk::prelude::Keys;
|
||||
|
||||
use super::*;
|
||||
use crate::cord03::{self, build_message, seal_rumor};
|
||||
use crate::cord04::pins;
|
||||
@@ -768,7 +813,7 @@ mod tests {
|
||||
#[test]
|
||||
fn metadata_and_channel_edits_reach_a_second_client() {
|
||||
let owner = Keys::generate();
|
||||
let minted = genesis(&owner, &metadata("coop"), AT).expect("mints");
|
||||
let minted = smol::block_on(genesis(&owner, &metadata("coop"), AT)).expect("mints");
|
||||
let community_id = minted.identity.community_id;
|
||||
let owner_pk = owner.public_key();
|
||||
let (read, signer) = holder(&minted);
|
||||
@@ -797,33 +842,31 @@ mod tests {
|
||||
.get(minted.channel_id.as_bytes())
|
||||
.expect("head");
|
||||
|
||||
let (community_wrap, _) = writer
|
||||
.set_community_metadata(
|
||||
&owner,
|
||||
&community_id,
|
||||
&CommunityMetadata {
|
||||
relays: vec!["wss://relay.example".to_owned()],
|
||||
..metadata("coop two")
|
||||
},
|
||||
Some(community_head),
|
||||
None,
|
||||
AT + 1,
|
||||
)
|
||||
.expect("publishes");
|
||||
let (channel_wrap, _) = writer
|
||||
.set_channel_metadata(
|
||||
&owner,
|
||||
&minted.channel_id,
|
||||
&ChannelMetadata {
|
||||
name: "lobby".to_owned(),
|
||||
private: false,
|
||||
..ChannelMetadata::default()
|
||||
},
|
||||
Some(channel_head),
|
||||
None,
|
||||
AT + 2,
|
||||
)
|
||||
.expect("publishes");
|
||||
let (community_wrap, _) = smol::block_on(writer.set_community_metadata(
|
||||
&owner,
|
||||
&community_id,
|
||||
&CommunityMetadata {
|
||||
relays: vec!["wss://relay.example".to_owned()],
|
||||
..metadata("coop two")
|
||||
},
|
||||
Some(community_head),
|
||||
None,
|
||||
AT + 1,
|
||||
))
|
||||
.expect("publishes");
|
||||
let (channel_wrap, _) = smol::block_on(writer.set_channel_metadata(
|
||||
&owner,
|
||||
&minted.channel_id,
|
||||
&ChannelMetadata {
|
||||
name: "lobby".to_owned(),
|
||||
private: false,
|
||||
..ChannelMetadata::default()
|
||||
},
|
||||
Some(channel_head),
|
||||
None,
|
||||
AT + 2,
|
||||
))
|
||||
.expect("publishes");
|
||||
|
||||
let mut edited = genesis_editions.clone();
|
||||
edited.extend(open_all(
|
||||
@@ -875,7 +918,7 @@ mod tests {
|
||||
fn a_delegated_member_edits_metadata_only_under_its_own_grant() {
|
||||
let owner = Keys::generate();
|
||||
let member = Keys::generate();
|
||||
let minted = genesis(&owner, &metadata("coop"), AT).expect("mints");
|
||||
let minted = smol::block_on(genesis(&owner, &metadata("coop"), AT)).expect("mints");
|
||||
let community_id = minted.identity.community_id;
|
||||
let owner_pk = owner.public_key();
|
||||
let (read, signer) = holder(&minted);
|
||||
@@ -896,21 +939,20 @@ mod tests {
|
||||
extra: Extra::default(),
|
||||
};
|
||||
|
||||
let (role_wrap, _) = writer
|
||||
.publish(
|
||||
&owner,
|
||||
Edition {
|
||||
subkind: vsk::ROLE,
|
||||
entity: *role_id.as_bytes(),
|
||||
content: &role.to_content().expect("serializes"),
|
||||
head: None,
|
||||
citation: None,
|
||||
},
|
||||
AT + 1,
|
||||
)
|
||||
.expect("publishes");
|
||||
let (grant_wrap, _) = writer
|
||||
.publish(
|
||||
let (role_wrap, _) = smol::block_on(writer.publish(
|
||||
&owner,
|
||||
Edition {
|
||||
subkind: vsk::ROLE,
|
||||
entity: *role_id.as_bytes(),
|
||||
content: &role.to_content().expect("serializes"),
|
||||
head: None,
|
||||
citation: None,
|
||||
},
|
||||
AT + 1,
|
||||
))
|
||||
.expect("publishes");
|
||||
let (grant_wrap, _) = smol::block_on(
|
||||
writer.publish(
|
||||
&owner,
|
||||
Edition {
|
||||
subkind: vsk::GRANT,
|
||||
@@ -927,8 +969,9 @@ mod tests {
|
||||
citation: None,
|
||||
},
|
||||
AT + 2,
|
||||
)
|
||||
.expect("publishes");
|
||||
),
|
||||
)
|
||||
.expect("publishes");
|
||||
|
||||
let mut base = open_all(&minted.wraps, &read, &signer.pk());
|
||||
base.extend(open_all(&[role_wrap, grant_wrap], &read, &signer.pk()));
|
||||
@@ -959,36 +1002,34 @@ mod tests {
|
||||
};
|
||||
let content = serde_json::to_string(&metadata("coop by mod")).expect("serializes");
|
||||
|
||||
let (uncited, _) = member_writer
|
||||
.publish(
|
||||
&member,
|
||||
Edition {
|
||||
subkind: vsk::COMMUNITY_METADATA,
|
||||
entity: *community_id.as_bytes(),
|
||||
content: &content,
|
||||
head: Some(head),
|
||||
citation: None,
|
||||
},
|
||||
AT + 3,
|
||||
)
|
||||
.expect("publishes");
|
||||
let (cited, _) = member_writer
|
||||
.publish(
|
||||
&member,
|
||||
Edition {
|
||||
subkind: vsk::COMMUNITY_METADATA,
|
||||
entity: *community_id.as_bytes(),
|
||||
content: &content,
|
||||
head: Some(head),
|
||||
citation: Some(AuthorityCitation {
|
||||
entity: grant.entity,
|
||||
version: grant.version,
|
||||
hash: grant.self_hash,
|
||||
}),
|
||||
},
|
||||
AT + 4,
|
||||
)
|
||||
.expect("publishes");
|
||||
let (uncited, _) = smol::block_on(member_writer.publish(
|
||||
&member,
|
||||
Edition {
|
||||
subkind: vsk::COMMUNITY_METADATA,
|
||||
entity: *community_id.as_bytes(),
|
||||
content: &content,
|
||||
head: Some(head),
|
||||
citation: None,
|
||||
},
|
||||
AT + 3,
|
||||
))
|
||||
.expect("publishes");
|
||||
let (cited, _) = smol::block_on(member_writer.publish(
|
||||
&member,
|
||||
Edition {
|
||||
subkind: vsk::COMMUNITY_METADATA,
|
||||
entity: *community_id.as_bytes(),
|
||||
content: &content,
|
||||
head: Some(head),
|
||||
citation: Some(AuthorityCitation {
|
||||
entity: grant.entity,
|
||||
version: grant.version,
|
||||
hash: grant.self_hash,
|
||||
}),
|
||||
},
|
||||
AT + 4,
|
||||
))
|
||||
.expect("publishes");
|
||||
|
||||
// Uncited, the edit claims an authority the member never showed.
|
||||
let mut forged = base.clone();
|
||||
@@ -1023,7 +1064,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_pin_list_folds_under_its_coordinate_for_a_second_client() {
|
||||
let owner = Keys::generate();
|
||||
let minted = genesis(&owner, &metadata("coop"), AT).expect("mints");
|
||||
let minted = smol::block_on(genesis(&owner, &metadata("coop"), AT)).expect("mints");
|
||||
let community_id = minted.identity.community_id;
|
||||
let owner_pk = owner.public_key();
|
||||
let (read, signer) = holder(&minted);
|
||||
@@ -1042,7 +1083,7 @@ mod tests {
|
||||
AT * 1_000,
|
||||
None,
|
||||
);
|
||||
let (wrap, _) = seal_rumor(&rumor, &group, &author, false).expect("seals");
|
||||
let (wrap, _) = smol::block_on(seal_rumor(&rumor, &group, &author, false)).expect("seals");
|
||||
let opened = cord03::open(&wrap, &group, &channel, ROOT_EPOCH)
|
||||
.expect("opens")
|
||||
.0;
|
||||
@@ -1064,17 +1105,16 @@ mod tests {
|
||||
read: read.clone(),
|
||||
signer: signer.clone(),
|
||||
};
|
||||
let (pin_wrap, _) = writer
|
||||
.set_pin_list(
|
||||
&owner,
|
||||
&community_id,
|
||||
&channel,
|
||||
&content,
|
||||
None,
|
||||
None,
|
||||
AT + 1,
|
||||
)
|
||||
.expect("publishes");
|
||||
let (pin_wrap, _) = smol::block_on(writer.set_pin_list(
|
||||
&owner,
|
||||
&community_id,
|
||||
&channel,
|
||||
&content,
|
||||
None,
|
||||
None,
|
||||
AT + 1,
|
||||
))
|
||||
.expect("publishes");
|
||||
|
||||
let mut editions = open_all(&minted.wraps, &read, &signer.pk());
|
||||
editions.extend(open_all(&[pin_wrap], &read, &signer.pk()));
|
||||
@@ -1109,7 +1149,7 @@ mod tests {
|
||||
#[test]
|
||||
fn the_timer_is_never_guessed_and_the_write_caps_hold() {
|
||||
let owner = Keys::generate();
|
||||
let minted = genesis(&owner, &metadata("coop"), AT).expect("mints");
|
||||
let minted = smol::block_on(genesis(&owner, &metadata("coop"), AT)).expect("mints");
|
||||
let community_id = minted.identity.community_id;
|
||||
let owner_pk = owner.public_key();
|
||||
let (read, signer) = holder(&minted);
|
||||
@@ -1124,10 +1164,16 @@ mod tests {
|
||||
&owner_pk,
|
||||
&community_id,
|
||||
&open_all(
|
||||
&[writer
|
||||
.set_community_metadata(&owner, &community_id, metadata, None, None, AT + 1)
|
||||
.expect("publishes")
|
||||
.0],
|
||||
&[smol::block_on(writer.set_community_metadata(
|
||||
&owner,
|
||||
&community_id,
|
||||
metadata,
|
||||
None,
|
||||
None,
|
||||
AT + 1,
|
||||
))
|
||||
.expect("publishes")
|
||||
.0],
|
||||
&writer.read,
|
||||
&writer.signer.pk(),
|
||||
),
|
||||
@@ -1163,8 +1209,7 @@ mod tests {
|
||||
.map(|_| Keys::generate().public_key())
|
||||
.collect();
|
||||
assert!(
|
||||
writer
|
||||
.set_banlist(&owner, &community_id, &banned, None, None, AT + 2)
|
||||
smol::block_on(writer.set_banlist(&owner, &community_id, &banned, None, None, AT + 2))
|
||||
.is_err()
|
||||
);
|
||||
|
||||
@@ -1179,20 +1224,19 @@ mod tests {
|
||||
assert!(grant.to_content().is_err());
|
||||
|
||||
assert!(
|
||||
writer
|
||||
.set_channel_metadata(
|
||||
&owner,
|
||||
&minted.channel_id,
|
||||
&ChannelMetadata {
|
||||
name: "x".repeat(MAX_NAME_BYTES + 1),
|
||||
private: false,
|
||||
..ChannelMetadata::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
AT + 3,
|
||||
)
|
||||
.is_err()
|
||||
smol::block_on(writer.set_channel_metadata(
|
||||
&owner,
|
||||
&minted.channel_id,
|
||||
&ChannelMetadata {
|
||||
name: "x".repeat(MAX_NAME_BYTES + 1),
|
||||
private: false,
|
||||
..ChannelMetadata::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
AT + 3,
|
||||
))
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,19 +292,22 @@ pub fn build_typing(
|
||||
}
|
||||
|
||||
/// `ephemeral` picks the 21059 wrap, which relays must not store.
|
||||
pub fn seal_rumor(
|
||||
pub async fn seal_rumor<S>(
|
||||
rumor: &UnsignedEvent,
|
||||
group: &GroupKey,
|
||||
author: &Keys,
|
||||
author: &S,
|
||||
ephemeral: bool,
|
||||
) -> Result<(Event, Keys), ChatError> {
|
||||
) -> Result<(Event, Keys), ChatError>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
let kind = rumor.kind.as_u16();
|
||||
|
||||
if !is_chat_kind(kind) {
|
||||
return Err(ChatError::UnknownKind(kind));
|
||||
}
|
||||
|
||||
let seal = build_seal(rumor, SealForm::Encrypted, group, author)?;
|
||||
let seal = build_seal(rumor, SealForm::Encrypted, group, author).await?;
|
||||
let wrap_kind = if ephemeral {
|
||||
KIND_WRAP_EPHEMERAL
|
||||
} else {
|
||||
@@ -674,7 +677,9 @@ mod tests {
|
||||
}
|
||||
|
||||
fn sealed(rumor: &UnsignedEvent, group: &GroupKey, author: &Keys) -> Event {
|
||||
seal_rumor(rumor, group, author, false).expect("seals").0
|
||||
smol::block_on(seal_rumor(rumor, group, author, false))
|
||||
.expect("seals")
|
||||
.0
|
||||
}
|
||||
|
||||
fn read(rumor: &UnsignedEvent, group: &GroupKey, author: &Keys, epoch: Epoch) -> ChatRumor {
|
||||
@@ -949,7 +954,8 @@ mod tests {
|
||||
|
||||
// Chat is encrypted-seal only (CORD-02 §5), and a retired kind is not a
|
||||
// chat rumor however well-formed it looks.
|
||||
let seal = build_seal(&plain, SealForm::Plaintext, &group, &alice).expect("seals");
|
||||
let seal =
|
||||
smol::block_on(build_seal(&plain, SealForm::Plaintext, &group, &alice)).expect("seals");
|
||||
let (wrap, _) = wrap_seal(
|
||||
&seal,
|
||||
&group,
|
||||
@@ -971,7 +977,7 @@ mod tests {
|
||||
AT,
|
||||
);
|
||||
assert!(matches!(
|
||||
seal_rumor(&ghost, &group, &alice, false),
|
||||
smol::block_on(seal_rumor(&ghost, &group, &alice, false)),
|
||||
Err(ChatError::UnknownKind(3300))
|
||||
));
|
||||
|
||||
|
||||
@@ -583,7 +583,7 @@ mod tests {
|
||||
at_ms,
|
||||
None,
|
||||
);
|
||||
let (wrap, _) = seal_rumor(&rumor, &group(), author, false).expect("seals");
|
||||
let (wrap, _) = smol::block_on(seal_rumor(&rumor, &group(), author, false)).expect("seals");
|
||||
|
||||
open(&wrap, &group(), &channel(), Epoch(0)).expect("opens")
|
||||
}
|
||||
@@ -699,7 +699,7 @@ mod tests {
|
||||
AT_MS + 5_000,
|
||||
None,
|
||||
);
|
||||
let (wrap, _) = seal_rumor(&edit, &group(), &author, false).expect("seals");
|
||||
let (wrap, _) = smol::block_on(seal_rumor(&edit, &group(), &author, false)).expect("seals");
|
||||
let (edit_opened, _) = open(&wrap, &group(), &channel(), Epoch(0)).expect("opens");
|
||||
|
||||
let refreshed = with_proven_edit(&entry, &edit_opened, &group(), &channel());
|
||||
@@ -718,7 +718,8 @@ mod tests {
|
||||
AT_MS + 6_000,
|
||||
None,
|
||||
);
|
||||
let (wrap, _) = seal_rumor(&hijack, &group(), &stranger, false).expect("seals");
|
||||
let (wrap, _) =
|
||||
smol::block_on(seal_rumor(&hijack, &group(), &stranger, false)).expect("seals");
|
||||
let (hijack_opened, _) = open(&wrap, &group(), &channel(), Epoch(0)).expect("opens");
|
||||
|
||||
let unchanged = with_proven_edit(&entry, &hijack_opened, &group(), &channel());
|
||||
@@ -805,7 +806,8 @@ mod tests {
|
||||
None,
|
||||
AT_MS + 1_000,
|
||||
);
|
||||
let (wrap, _) = seal_rumor(&delete, &group(), author_keys, false).expect("seals");
|
||||
let (wrap, _) =
|
||||
smol::block_on(seal_rumor(&delete, &group(), author_keys, false)).expect("seals");
|
||||
let (_, rumor) = open(&wrap, &group(), &channel(), Epoch(0)).expect("opens");
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -590,25 +590,32 @@ pub fn merge_invite_lists(held: InviteList, incoming: InviteList) -> InviteList
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_invite_list(keys: &Keys, list: &InviteList) -> Result<Event, InviteError> {
|
||||
pub async fn build_invite_list<S>(keys: &S, list: &InviteList) -> Result<Event, InviteError>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + AsyncNip44 + ?Sized,
|
||||
{
|
||||
list.fits()?;
|
||||
|
||||
let json = serde_json::to_string(list).map_err(json_error)?;
|
||||
let content = cord01::seal_to_self(keys, json.as_bytes())?;
|
||||
let content = cord01::seal_to_self(keys, &json).await?;
|
||||
|
||||
EventBuilder::new(Kind::Custom(KIND_INVITE_LIST), content)
|
||||
.finalize(keys)
|
||||
.finalize_async(keys)
|
||||
.await
|
||||
.map_err(crypto_error)
|
||||
}
|
||||
|
||||
pub fn parse_invite_list(keys: &Keys, event: &Event) -> Result<InviteList, InviteError> {
|
||||
pub async fn parse_invite_list<S>(keys: &S, event: &Event) -> Result<InviteList, InviteError>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncNip44 + ?Sized,
|
||||
{
|
||||
if event.kind.as_u16() != KIND_INVITE_LIST {
|
||||
return Err(InviteError::Kind(event.kind.as_u16()));
|
||||
}
|
||||
|
||||
let json = cord01::open_to_self(keys, &event.content)?;
|
||||
let json = cord01::open_to_self(keys, &event.content).await?;
|
||||
|
||||
serde_json::from_slice(&json).map_err(json_error)
|
||||
serde_json::from_str(&json).map_err(json_error)
|
||||
}
|
||||
|
||||
/// An entry is immutable once minted, so two copies should agree.
|
||||
|
||||
@@ -4,7 +4,10 @@ use std::fmt;
|
||||
use anyhow::Result;
|
||||
use data_encoding::HEXLOWER;
|
||||
use nostr::nips::nip44::v2::ConversationKey;
|
||||
use nostr_sdk::prelude::{Event, Keys, PublicKey, SecretKey, Tag, Timestamp, UnsignedEvent};
|
||||
use nostr_sdk::prelude::{
|
||||
AsyncGetPublicKey, AsyncSignEvent, Event, Keys, PublicKey, SecretKey, Tag, Timestamp,
|
||||
UnsignedEvent,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::cord01::{self, KIND_SEAL_PLAINTEXT, OpenedStream, SealForm, StreamError};
|
||||
@@ -598,8 +601,8 @@ pub fn build_rekey_rumor(
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_rekey_chunks(
|
||||
rotator: &Keys,
|
||||
pub async fn build_rekey_chunks<S>(
|
||||
rotator: &S,
|
||||
group: &GroupKey,
|
||||
scope: RekeyScope,
|
||||
new_epoch: Epoch,
|
||||
@@ -609,7 +612,11 @@ pub fn build_rekey_chunks(
|
||||
citation: Option<&AuthorityCitation>,
|
||||
severed: bool,
|
||||
at_secs: u64,
|
||||
) -> Result<Vec<Event>, RekeyError> {
|
||||
) -> Result<Vec<Event>, RekeyError>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
let rotator_key = rotator.get_public_key_async().await.map_err(crypto_error)?;
|
||||
let mut groups: Vec<&[RekeyBlob]> = blobs.chunks(MAX_REKEY_BLOBS_PER_EVENT).collect();
|
||||
|
||||
if groups.is_empty() {
|
||||
@@ -621,7 +628,7 @@ pub fn build_rekey_chunks(
|
||||
|
||||
for (index, group_blobs) in groups.into_iter().enumerate() {
|
||||
let rumor = build_rekey_rumor(
|
||||
rotator.public_key(),
|
||||
rotator_key,
|
||||
scope,
|
||||
new_epoch,
|
||||
prev_epoch,
|
||||
@@ -633,7 +640,7 @@ pub fn build_rekey_chunks(
|
||||
at_secs,
|
||||
)?;
|
||||
|
||||
let seal = cord01::build_seal(&rumor, SealForm::Encrypted, group, rotator)?;
|
||||
let seal = cord01::build_seal(&rumor, SealForm::Encrypted, group, rotator).await?;
|
||||
let (wrap, _) = cord01::wrap_seal(
|
||||
&seal,
|
||||
group,
|
||||
@@ -731,14 +738,17 @@ pub fn dissolved_tombstone_rumor(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn seal_dissolved(
|
||||
pub async fn seal_dissolved<S>(
|
||||
rumor: &UnsignedEvent,
|
||||
community_id: &CommunityId,
|
||||
owner: &Keys,
|
||||
owner: &S,
|
||||
at_secs: u64,
|
||||
) -> Result<Event, RekeyError> {
|
||||
) -> Result<Event, RekeyError>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
{
|
||||
let group = dissolved_group_key(community_id).map_err(crypto_error)?;
|
||||
let seal = cord01::build_seal(rumor, SealForm::Plaintext, &group, owner)?;
|
||||
let seal = cord01::build_seal(rumor, SealForm::Plaintext, &group, owner).await?;
|
||||
let (wrap, _) = cord01::wrap_seal(
|
||||
&seal,
|
||||
&group,
|
||||
@@ -1059,7 +1069,7 @@ mod tests {
|
||||
|
||||
let group = rekey_group(scope, &ROOT, &community_id, epoch).expect("derives");
|
||||
let prior_commit = epoch_key_commitment(Epoch(0), &PRIOR_KEY);
|
||||
let chunks = build_rekey_chunks(
|
||||
let chunks = smol::block_on(build_rekey_chunks(
|
||||
&rotator,
|
||||
&group,
|
||||
scope,
|
||||
@@ -1070,7 +1080,7 @@ mod tests {
|
||||
None,
|
||||
false,
|
||||
AT,
|
||||
)
|
||||
))
|
||||
.expect("builds");
|
||||
assert_eq!(chunks.len(), 1);
|
||||
|
||||
@@ -1269,26 +1279,26 @@ mod tests {
|
||||
content: String,
|
||||
at_secs: u64,
|
||||
) -> Event {
|
||||
writer
|
||||
.publish(
|
||||
owner,
|
||||
Edition {
|
||||
subkind,
|
||||
entity,
|
||||
content: &content,
|
||||
head: None,
|
||||
citation: None,
|
||||
},
|
||||
at_secs,
|
||||
)
|
||||
.expect("publishes")
|
||||
.0
|
||||
smol::block_on(writer.publish(
|
||||
owner,
|
||||
Edition {
|
||||
subkind,
|
||||
entity,
|
||||
content: &content,
|
||||
head: None,
|
||||
citation: None,
|
||||
},
|
||||
at_secs,
|
||||
))
|
||||
.expect("publishes")
|
||||
.0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_rotation_needs_the_permission_and_must_strictly_outrank_every_target() {
|
||||
let owner = Keys::generate();
|
||||
let minted = genesis(&owner, &CommunityMetadata::default(), AT).expect("mints");
|
||||
let minted =
|
||||
smol::block_on(genesis(&owner, &CommunityMetadata::default(), AT)).expect("mints");
|
||||
let community_id = minted.identity.community_id;
|
||||
let read =
|
||||
control_group_key(&minted.community_root, &community_id, ROOT_EPOCH).expect("derives");
|
||||
@@ -1426,7 +1436,7 @@ mod tests {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let chunks = build_rekey_chunks(
|
||||
let chunks = smol::block_on(build_rekey_chunks(
|
||||
&rotator,
|
||||
&group,
|
||||
scope,
|
||||
@@ -1437,7 +1447,7 @@ mod tests {
|
||||
None,
|
||||
false,
|
||||
AT,
|
||||
)
|
||||
))
|
||||
.expect("builds");
|
||||
|
||||
assert_eq!(chunks.len(), 1, "a full send chunk is one event");
|
||||
@@ -1452,7 +1462,7 @@ mod tests {
|
||||
wrapped: "x".to_owned(),
|
||||
});
|
||||
|
||||
let chunks = build_rekey_chunks(
|
||||
let chunks = smol::block_on(build_rekey_chunks(
|
||||
&rotator,
|
||||
&group,
|
||||
scope,
|
||||
@@ -1463,7 +1473,7 @@ mod tests {
|
||||
None,
|
||||
false,
|
||||
AT,
|
||||
)
|
||||
))
|
||||
.expect("builds");
|
||||
|
||||
assert_eq!(chunks.len(), 2, "one over the cap splits across two events");
|
||||
@@ -1485,8 +1495,13 @@ mod tests {
|
||||
content: "{}",
|
||||
at_secs: AT,
|
||||
});
|
||||
let seal =
|
||||
cord01::build_seal(&rumor, SealForm::Plaintext, &prior_read, &owner).expect("seals");
|
||||
let seal = smol::block_on(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");
|
||||
@@ -1507,8 +1522,13 @@ mod tests {
|
||||
assert_eq!(reopened.author, owner.public_key());
|
||||
|
||||
// Only a plaintext seal can be carried forward.
|
||||
let encrypted =
|
||||
cord01::build_seal(&rumor, SealForm::Encrypted, &prior_read, &owner).expect("seals");
|
||||
let encrypted = smol::block_on(cord01::build_seal(
|
||||
&rumor,
|
||||
SealForm::Encrypted,
|
||||
&prior_read,
|
||||
&owner,
|
||||
))
|
||||
.expect("seals");
|
||||
assert!(matches!(
|
||||
compact(&[encrypted], &read, &signer, AT + 1),
|
||||
Err(RekeyError::Stream(StreamError::NotRewrappable))
|
||||
@@ -1527,7 +1547,8 @@ mod tests {
|
||||
};
|
||||
|
||||
let rumor = dissolved_tombstone_rumor(owner.public_key(), &community_id, AT);
|
||||
let wrap = seal_dissolved(&rumor, &community_id, &owner, AT).expect("seals");
|
||||
let wrap =
|
||||
smol::block_on(seal_dissolved(&rumor, &community_id, &owner, AT)).expect("seals");
|
||||
|
||||
assert!(verify_dissolved(&wrap, &identity));
|
||||
assert_eq!(
|
||||
@@ -1538,18 +1559,18 @@ mod tests {
|
||||
// Anyone holding the community id finds the address, but only the committed
|
||||
// owner's signature counts.
|
||||
let impostor = Keys::generate();
|
||||
let forged = seal_dissolved(
|
||||
let forged = smol::block_on(seal_dissolved(
|
||||
&dissolved_tombstone_rumor(impostor.public_key(), &community_id, AT),
|
||||
&community_id,
|
||||
&impostor,
|
||||
AT,
|
||||
)
|
||||
))
|
||||
.expect("seals");
|
||||
assert!(!verify_dissolved(&forged, &identity));
|
||||
|
||||
// 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(
|
||||
let zeroed = smol::block_on(seal_dissolved(
|
||||
&cord01::build_rumor_secs(
|
||||
KIND_CONTROL,
|
||||
owner.public_key(),
|
||||
@@ -1563,7 +1584,7 @@ mod tests {
|
||||
&community_id,
|
||||
&owner,
|
||||
AT,
|
||||
)
|
||||
))
|
||||
.expect("seals");
|
||||
assert!(matches!(
|
||||
open_dissolved(&zeroed, &community_id),
|
||||
|
||||
@@ -461,7 +461,11 @@ mod tests {
|
||||
at_ms,
|
||||
None,
|
||||
);
|
||||
relay.insert(seal_rumor(&rumor, &group, &author, false).expect("seals").0);
|
||||
relay.insert(
|
||||
smol::block_on(seal_rumor(&rumor, &group, &author, false))
|
||||
.expect("seals")
|
||||
.0,
|
||||
);
|
||||
}
|
||||
|
||||
let mut seen = BTreeSet::new();
|
||||
|
||||
Reference in New Issue
Block a user