From 8b511b20e79d9c70b030f62b7cc1daee5187861c Mon Sep 17 00:00:00 2001 From: KeelrLeek <156356974+KeelrLeek@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:50:18 -0500 Subject: [PATCH] community: spaces, an owner-signed roster with epochs, a bounded window of signed lines, bodies sealed to a rotating key when private --- Cargo.lock | 1 - crates/persona-state/Cargo.toml | 6 +- crates/persona-state/src/lib.rs | 1 + crates/persona-state/src/space.rs | 386 +++++++ crates/space-contract/Cargo.lock | 1449 ++++++++++++++++++++++++++ crates/space-contract/Cargo.toml | 28 + crates/space-contract/src/lib.rs | 118 +++ src-tauri/assets/space_contract.wasm | Bin 0 -> 257216 bytes src-tauri/src/inbox.rs | 25 + src-tauri/src/lib.rs | 9 + src-tauri/src/spaces.rs | 637 +++++++++++ src/lib/api.js | 8 + src/lib/community.svelte.js | 63 +- src/views/Community.svelte | 195 +++- 14 files changed, 2913 insertions(+), 13 deletions(-) create mode 100644 crates/persona-state/src/space.rs create mode 100644 crates/space-contract/Cargo.lock create mode 100644 crates/space-contract/Cargo.toml create mode 100644 crates/space-contract/src/lib.rs create mode 100644 src-tauri/assets/space_contract.wasm create mode 100644 src-tauri/src/spaces.rs diff --git a/Cargo.lock b/Cargo.lock index 4b0eb16..c07301b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -771,7 +771,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] diff --git a/crates/persona-state/Cargo.toml b/crates/persona-state/Cargo.toml index e084ae8..b9949a7 100644 --- a/crates/persona-state/Cargo.toml +++ b/crates/persona-state/Cargo.toml @@ -13,8 +13,10 @@ serde.workspace = true bincode = "1" ed25519-dalek = { version = "2", default-features = false, features = ["alloc"] } blake3 = "1" -x25519-dalek = { version = "2", features = ["static_secrets"] } -chacha20poly1305 = "0.10" +x25519-dalek = { version = "2", default-features = false, features = ["static_secrets", "alloc", "precomputed-tables"] } +# no default features: the OS-RNG feature would drag getrandom into the +# wasm32 contract build; this crate draws no entropy of its own anyway +chacha20poly1305 = { version = "0.10", default-features = false, features = ["alloc"] } [dev-dependencies] ed25519-dalek = { version = "2", features = ["rand_core"] } diff --git a/crates/persona-state/src/lib.rs b/crates/persona-state/src/lib.rs index be111cf..2073bfc 100644 --- a/crates/persona-state/src/lib.rs +++ b/crates/persona-state/src/lib.rs @@ -19,6 +19,7 @@ pub mod slsk_link; pub mod friend; pub mod playlist; pub mod inbox; +pub mod space; pub const SCHEMA_VERSION: u32 = 1; pub const SIGN_DOMAIN: &str = "soulstream.profile/v1"; diff --git a/crates/persona-state/src/space.rs b/crates/persona-state/src/space.rs new file mode 100644 index 0000000..230754d --- /dev/null +++ b/crates/persona-state/src/space.rs @@ -0,0 +1,386 @@ +//! A SoulStream Space — the persistent community the F6 decision chose to +//! build on River's patterns rather than River's format: ONE contract per +//! Space addressed by (owner, space id); an owner-signed CHECKPOINT that IS +//! the authority (name, mode, the member roster, the pinned playlist) with +//! a monotonic epoch, so a removal is a new epoch without that member and +//! nothing about membership is ever inferred from who talked recently; a +//! bounded window of member-signed MESSAGES admitted only from the current +//! roster; and, for a private Space, message bodies sealed to a per-epoch +//! space key the owner hands members through their encrypted inboxes — +//! rotated on every removal. Deterministic merge, canonical-only +//! validation: the F5 bouncer, again. BYTE-COMPATIBILITY IS THE CONTRACT: +//! `crates/space-contract` compiles this very module into the wasm. +//! +//! Signing rides `soulstream.friend/v1` like the inbox does (the delegate +//! generation in the app has no space domain yet). + +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +pub const SCHEMA_VERSION: u32 = 1; +pub const PARAMS_FAMILY: &[u8] = b"soulstream.space.v1"; +pub const SIGN_DOMAIN: &str = "soulstream.friend/v1"; +pub const MAX_STATE_BYTES: usize = 1024 * 1024; +pub const MAX_MEMBERS: usize = 256; +pub const MAX_MESSAGES: usize = 500; +pub const DEFAULT_MESSAGE_CAP: u32 = 200; +pub const MAX_BODY_BYTES: usize = 4096; +pub const MAX_NAME_BYTES: usize = 64; +pub const MAX_DESC_BYTES: usize = 512; +pub const MAX_PER_AUTHOR_UNCAPPED: usize = 64; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum Role { + Owner, + Member, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Member { + pub key: [u8; 32], + pub role: Role, + pub since_epoch: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PlaylistRef { + pub owner: [u8; 32], + pub uuid: [u8; 16], +} + +/// The owner's word on what the Space is and who is in it. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Checkpoint { + pub schema_version: u32, + pub space_id: [u8; 16], + pub owner: [u8; 32], + pub epoch: u64, + pub name: String, + pub description: String, + pub private: bool, + pub members: Vec, + pub pinned: Option, + pub message_cap: u32, + pub sig: Vec, +} + +#[derive(Serialize)] +struct CheckpointSigBody<'a> { + schema_version: u32, + space_id: &'a [u8; 16], + owner: &'a [u8; 32], + epoch: u64, + name: &'a str, + description: &'a str, + private: bool, + members: &'a [Member], + pinned: &'a Option, + message_cap: u32, +} + +pub fn checkpoint_sig_body(c: &Checkpoint) -> Vec { + bincode::serialize(&CheckpointSigBody { + schema_version: c.schema_version, + space_id: &c.space_id, + owner: &c.owner, + epoch: c.epoch, + name: &c.name, + description: &c.description, + private: c.private, + members: &c.members, + pinned: &c.pinned, + message_cap: c.message_cap, + }) + .expect("serializes") +} +pub fn checkpoint_sign_payload(c: &Checkpoint) -> Vec { + blake3::hash(&checkpoint_sig_body(c)).as_bytes().to_vec() +} + +/// One line in the Space. Public: `body` is UTF-8. Private: `body` is the +/// ChaCha20-Poly1305 seal of the UTF-8 under the epoch's space key. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Message { + pub space_id: [u8; 16], + pub epoch: u64, + pub msg_id: [u8; 16], + pub author: [u8; 32], + pub created_at: u64, + pub body: Vec, + pub sig: Vec, +} + +#[derive(Serialize)] +struct MessageSigBody<'a> { + space_id: &'a [u8; 16], + epoch: u64, + msg_id: &'a [u8; 16], + author: &'a [u8; 32], + created_at: u64, + body: &'a [u8], +} + +pub fn message_sig_body(m: &Message) -> Vec { + bincode::serialize(&MessageSigBody { + space_id: &m.space_id, + epoch: m.epoch, + msg_id: &m.msg_id, + author: &m.author, + created_at: m.created_at, + body: &m.body, + }) + .expect("serializes") +} +pub fn message_sign_payload(m: &Message) -> Vec { + blake3::hash(&message_sig_body(m)).as_bytes().to_vec() +} + +/// The AAD for a sealed body: everything about the message but the body. +fn message_aad(m: &Message) -> Vec { + let mut v = Vec::with_capacity(80); + v.extend_from_slice(&m.space_id); + v.extend_from_slice(&m.epoch.to_le_bytes()); + v.extend_from_slice(&m.msg_id); + v.extend_from_slice(&m.author); + v.extend_from_slice(&m.created_at.to_le_bytes()); + v +} +fn body_key(space_key: &[u8; 32]) -> [u8; 32] { + blake3::derive_key("soulstream.space-body.v1", space_key) +} +fn body_nonce(msg_id: &[u8; 16]) -> [u8; 12] { + blake3::hash(msg_id).as_bytes()[..12].try_into().unwrap() +} + +/// Seal a private message body. Call BEFORE signing (the signature covers +/// the sealed bytes). `m.body` must hold the plaintext going in. +pub fn seal_body(space_key: &[u8; 32], m: &Message) -> Vec { + use chacha20poly1305::aead::{Aead, KeyInit, Payload}; + use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; + let key = body_key(space_key); + ChaCha20Poly1305::new(Key::from_slice(&key)) + .encrypt( + Nonce::from_slice(&body_nonce(&m.msg_id)), + Payload { msg: &m.body, aad: &message_aad(m) }, + ) + .expect("seal") +} + +/// Open a private message body. None = wrong key for this epoch, or tampered. +pub fn open_body(space_key: &[u8; 32], m: &Message) -> Option> { + use chacha20poly1305::aead::{Aead, KeyInit, Payload}; + use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; + let key = body_key(space_key); + ChaCha20Poly1305::new(Key::from_slice(&key)) + .decrypt( + Nonce::from_slice(&body_nonce(&m.msg_id)), + Payload { msg: &m.body, aad: &message_aad(m) }, + ) + .ok() +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct SpaceState { + pub schema_version: u32, + pub space_id: [u8; 16], + pub checkpoint: Option, + pub messages: Vec, +} + +pub fn make_params(owner: &[u8; 32], space_id: &[u8; 16]) -> Vec { + let mut p = Vec::with_capacity(PARAMS_FAMILY.len() + 48); + p.extend_from_slice(PARAMS_FAMILY); + p.extend_from_slice(owner); + p.extend_from_slice(space_id); + p +} + +pub fn params_parts(params: &[u8]) -> Option<([u8; 32], [u8; 16])> { + let t = PARAMS_FAMILY.len(); + if params.len() != t + 48 || ¶ms[..t] != PARAMS_FAMILY { + return None; + } + Some(( + params[t..t + 32].try_into().unwrap(), + params[t + 32..t + 48].try_into().unwrap(), + )) +} + +pub fn checkpoint_valid(owner: &[u8; 32], space_id: &[u8; 16], c: &Checkpoint) -> bool { + c.schema_version == SCHEMA_VERSION + && &c.space_id == space_id + && &c.owner == owner + && c.epoch >= 1 + && c.name.len() <= MAX_NAME_BYTES + && c.description.len() <= MAX_DESC_BYTES + && !c.members.is_empty() + && c.members.len() <= MAX_MEMBERS + && c.message_cap >= 1 + && c.message_cap as usize <= MAX_MESSAGES + && c.members.iter().any(|m| &m.key == owner && m.role == Role::Owner) + && crate::verify_sig_in(SIGN_DOMAIN, owner, &checkpoint_sign_payload(c), &c.sig) +} + +pub fn message_valid(space_id: &[u8; 16], c: &Checkpoint, m: &Message) -> bool { + &m.space_id == space_id + && m.epoch <= c.epoch + && m.body.len() <= MAX_BODY_BYTES + 16 + && c.members.iter().any(|mem| mem.key == m.author) + && crate::verify_sig_in(SIGN_DOMAIN, &m.author, &message_sign_payload(m), &m.sig) +} + +/// Deterministic multi-writer merge: the highest valid (epoch, hash) +/// checkpoint wins; messages are admitted only from ITS roster, deduped by +/// id (smaller content hash wins a collision), fairly bounded per author, +/// then the newest `message_cap` kept in (created_at, id) order. +pub fn merge_space(owner: &[u8; 32], space_id: &[u8; 16], candidates: &[&[u8]]) -> SpaceState { + let mut best: Option<(u64, [u8; 32], Checkpoint)> = None; + let mut parsed: Vec = Vec::new(); + for bytes in candidates { + if bytes.is_empty() || bytes.len() > MAX_STATE_BYTES { + continue; + } + let Ok(st) = bincode::deserialize::(bytes) else { continue }; + if st.schema_version != SCHEMA_VERSION || &st.space_id != space_id { + continue; + } + if let Some(c) = &st.checkpoint { + if checkpoint_valid(owner, space_id, c) { + let rank = (c.epoch, *blake3::hash(&checkpoint_sig_body(c)).as_bytes()); + let better = match &best { + None => true, + Some((e, h, _)) => rank > (*e, *h), + }; + if better { + best = Some((rank.0, rank.1, c.clone())); + } + } + } + parsed.push(st); + } + let Some((_, _, checkpoint)) = best else { + return SpaceState { schema_version: SCHEMA_VERSION, space_id: *space_id, checkpoint: None, messages: vec![] }; + }; + + let mut by_id: BTreeMap<[u8; 16], (Message, [u8; 32])> = BTreeMap::new(); + for st in &parsed { + for m in &st.messages { + if !message_valid(space_id, &checkpoint, m) { + continue; + } + let h = *blake3::hash(&bincode::serialize(m).unwrap()).as_bytes(); + match by_id.get(&m.msg_id) { + Some((_, existing)) if existing <= &h => {} + _ => { + by_id.insert(m.msg_id, (m.clone(), h)); + } + } + } + } + let mut all: Vec = by_id.into_values().map(|(m, _)| m).collect(); + all.sort_by_key(|m| (m.created_at, m.msg_id)); + // Per-author fairness before the global window: one voice cannot fill + // the whole window and push everyone else out. + let mut per_author: BTreeMap<[u8; 32], usize> = BTreeMap::new(); + let mut kept: Vec = Vec::with_capacity(all.len()); + for m in all.into_iter().rev() { + let n = per_author.entry(m.author).or_insert(0); + if *n < MAX_PER_AUTHOR_UNCAPPED { + *n += 1; + kept.push(m); + } + } + kept.reverse(); + let cap = checkpoint.message_cap as usize; + if kept.len() > cap { + kept.drain(..kept.len() - cap); + } + SpaceState { schema_version: SCHEMA_VERSION, space_id: *space_id, checkpoint: Some(checkpoint), messages: kept } +} + +#[cfg(test)] +mod tests { + use super::*; + use ed25519_dalek::{Signer, SigningKey}; + + fn sign(key: &SigningKey, payload: &[u8]) -> Vec { + key.sign(&crate::signable(SIGN_DOMAIN, payload)).to_bytes().to_vec() + } + fn checkpoint(owner: &SigningKey, epoch: u64, members: &[&SigningKey], private: bool) -> Checkpoint { + let mut c = Checkpoint { + schema_version: SCHEMA_VERSION, + space_id: [4; 16], + owner: owner.verifying_key().to_bytes(), + epoch, + name: "Late Night Ambient".into(), + description: String::new(), + private, + members: std::iter::once(Member { key: owner.verifying_key().to_bytes(), role: Role::Owner, since_epoch: 1 }) + .chain(members.iter().map(|k| Member { key: k.verifying_key().to_bytes(), role: Role::Member, since_epoch: epoch })) + .collect(), + pinned: None, + message_cap: DEFAULT_MESSAGE_CAP, + sig: vec![], + }; + c.sig = sign(owner, &checkpoint_sign_payload(&c)); + c + } + fn message(author: &SigningKey, epoch: u64, id: u8, at: u64, key: Option<&[u8; 32]>) -> Message { + let mut m = Message { + space_id: [4; 16], + epoch, + msg_id: [id; 16], + author: author.verifying_key().to_bytes(), + created_at: at, + body: b"hello".to_vec(), + sig: vec![], + }; + if let Some(k) = key { + m.body = seal_body(k, &m); + } + m.sig = sign(author, &message_sign_payload(&m)); + m + } + fn state(c: Option<&Checkpoint>, msgs: &[Message]) -> Vec { + bincode::serialize(&SpaceState { schema_version: SCHEMA_VERSION, space_id: [4; 16], checkpoint: c.cloned(), messages: msgs.to_vec() }).unwrap() + } + + #[test] + fn roster_is_the_authority_and_removal_is_an_epoch() { + let owner = SigningKey::from_bytes(&[1; 32]); + let bob = SigningKey::from_bytes(&[2; 32]); + let mallory = SigningKey::from_bytes(&[3; 32]); + let ok = owner.verifying_key().to_bytes(); + let c1 = checkpoint(&owner, 1, &[&bob], false); + let m_bob = message(&bob, 1, 10, 100, None); + let m_mal = message(&mallory, 1, 11, 101, None); + let merged = merge_space(&ok, &[4; 16], &[&state(Some(&c1), &[]), &state(None, &[m_bob.clone(), m_mal])]); + assert_eq!(merged.messages.len(), 1, "an off-roster author never enters"); + // the canonical state merges to itself + let canon = bincode::serialize(&merged).unwrap(); + assert_eq!(bincode::serialize(&merge_space(&ok, &[4; 16], &[&canon])).unwrap(), canon); + // a newer epoch without bob drops bob's messages; an old checkpoint cannot restore him + let c2 = checkpoint(&owner, 2, &[], false); + let after = merge_space(&ok, &[4; 16], &[&canon, &state(Some(&c2), &[])]); + assert_eq!(after.checkpoint.as_ref().map(|c| c.epoch), Some(2)); + assert!(after.messages.is_empty()); + let replay = merge_space(&ok, &[4; 16], &[&bincode::serialize(&after).unwrap(), &state(Some(&c1), &[m_bob])]); + assert_eq!(replay.checkpoint.as_ref().map(|c| c.epoch), Some(2)); + // a checkpoint signed by a non-owner is not a checkpoint + let mut forged = checkpoint(&owner, 3, &[&mallory], false); + forged.sig = sign(&mallory, &checkpoint_sign_payload(&forged)); + assert!(!checkpoint_valid(&ok, &[4; 16], &forged)); + } + + #[test] + fn private_bodies_open_only_with_the_epoch_key() { + let owner = SigningKey::from_bytes(&[1; 32]); + let key = [9u8; 32]; + let m = message(&owner, 1, 7, 100, Some(&key)); + assert_eq!(open_body(&key, &m).as_deref(), Some(&b"hello"[..])); + assert_eq!(open_body(&[8u8; 32], &m), None); + let mut t = m.clone(); + t.created_at += 1; + assert_eq!(open_body(&key, &t), None, "the AAD binds the header"); + } +} diff --git a/crates/space-contract/Cargo.lock b/crates/space-contract/Cargo.lock new file mode 100644 index 0000000..bc37639 --- /dev/null +++ b/crates/space-contract/Cargo.lock @@ -0,0 +1,1449 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "blake3" +version = "1.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" +dependencies = [ + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", + "zeroize", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "ctutils", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "flatbuffers" +version = "24.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1baf0dbf96932ec9a3038d57900329c015b0bfb7b63d904f3bc27e2b02a096" +dependencies = [ + "bitflags", + "rustc_version", +] + +[[package]] +name = "freenet-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26e5c6bee9ffa0e2683bc8b41a7bd9f1e57e7fd49ac4f6ae433e5a82e9006df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "freenet-stdlib" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62aeba7634d1ae5c2d02f78db33ee40442511858275be5be2099d24a13b2559" +dependencies = [ + "bincode", + "blake3", + "bs58", + "byteorder", + "bytes", + "chrono", + "flatbuffers", + "freenet-macros", + "futures", + "semver", + "serde", + "serde_json", + "serde_with", + "thiserror", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "persona-state" +version = "1.6.0-alpha" +dependencies = [ + "bincode", + "blake3", + "chacha20poly1305", + "ed25519-dalek", + "serde", + "x25519-dalek", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_with" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.1", + "jiff", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" + +[[package]] +name = "soulstream-space-contract" +version = "0.1.0" +dependencies = [ + "bincode", + "blake3", + "freenet-stdlib", + "persona-state", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/crates/space-contract/Cargo.toml b/crates/space-contract/Cargo.toml new file mode 100644 index 0000000..fb09f83 --- /dev/null +++ b/crates/space-contract/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "soulstream-space-contract" +version = "0.1.0" +edition = "2021" +description = "The SoulStream Space contract — a thin shell over persona_state::space::merge_space. Built to wasm32-unknown-unknown and bundled into the app; its code hash is a commitment (see src-tauri/assets)." + +[lib] +crate-type = ["cdylib"] + +[dependencies] +freenet-stdlib = { version = "=0.8.5" } +persona-state = { path = "../persona-state" } +bincode = "1" +blake3 = "1" + +[features] +default = ["freenet-main-contract", "contract"] +freenet-main-contract = [] +contract = ["freenet-stdlib/contract"] +trace = ["freenet-stdlib/trace"] + +[profile.release] +strip = true +lto = true + +# Its own workspace: this crate targets wasm32 and must never join the +# app's host build. +[workspace] diff --git a/crates/space-contract/src/lib.rs b/crates/space-contract/src/lib.rs new file mode 100644 index 0000000..bbf6862 --- /dev/null +++ b/crates/space-contract/src/lib.rs @@ -0,0 +1,118 @@ +//! The SoulStream Space contract. Multi-writer: the owner offers +//! checkpoints, members offer messages; `persona_state::space::merge_space` +//! is the bouncer (owner signature, roster, dedup, per-author fairness, +//! the window). Deterministic by construction, so every delivery order +//! converges; a state that does not merge to itself is refused. +//! +//! Build: `cargo build --release --target wasm32-unknown-unknown` in this +//! directory; copy `target/wasm32-unknown-unknown/release/ +//! soulstream_space_contract.wasm` to `src-tauri/assets/space_contract.wasm` +//! and re-pin the code hash in `src-tauri/src/spaces.rs`. + +use freenet_stdlib::prelude::*; +use persona_state::space as t; + +pub struct Contract; + +fn parts(parameters: &Parameters<'static>) -> Result<([u8; 32], [u8; 16]), ContractError> { + t::params_parts(parameters.as_ref()) + .ok_or_else(|| ContractError::Other("params are not family+owner+space".into())) +} + +fn collect<'a>(state: &'a State<'static>, data: &'a [UpdateData<'static>]) -> (Vec<&'a [u8]>, bool) { + let mut candidates: Vec<&[u8]> = Vec::new(); + if !state.as_ref().is_empty() { + candidates.push(state.as_ref()); + } + let mut any_parse = false; + let mut push = |b: &'a [u8]| { + if !b.is_empty() { + any_parse |= bincode::deserialize::(b).is_ok(); + candidates.push(b); + } + }; + for ud in data { + match ud { + UpdateData::State(s) => push(s.as_ref()), + UpdateData::Delta(d) => push(d.as_ref()), + UpdateData::StateAndDelta { state, delta } => { + push(state.as_ref()); + push(delta.as_ref()); + } + _ => {} + } + } + (candidates, any_parse) +} + +#[contract] +impl ContractInterface for Contract { + fn validate_state( + parameters: Parameters<'static>, + state: State<'static>, + _related: RelatedContracts<'static>, + ) -> Result { + let (owner, space_id) = parts(¶meters)?; + let bytes = state.as_ref(); + if bytes.is_empty() { + return Ok(ValidateResult::Valid); + } + if bytes.len() > t::MAX_STATE_BYTES { + return Err(ContractError::Other(format!( + "space state {} bytes over cap {}", + bytes.len(), + t::MAX_STATE_BYTES + ))); + } + let merged = t::merge_space(&owner, &space_id, &[bytes]); + let canon = bincode::serialize(&merged).map_err(|e| ContractError::Deser(e.to_string()))?; + if canon != bytes { + return Err(ContractError::Other( + "state is not canonical (unsigned, off-roster, or over the window)".into(), + )); + } + Ok(ValidateResult::Valid) + } + + fn update_state( + parameters: Parameters<'static>, + state: State<'static>, + data: Vec>, + ) -> Result, ContractError> { + let (owner, space_id) = parts(¶meters)?; + let (candidates, any_parse) = collect(&state, &data); + if !any_parse && !data.is_empty() { + return Err(ContractError::InvalidUpdate); + } + let merged = t::merge_space(&owner, &space_id, &candidates); + let bytes = bincode::serialize(&merged).map_err(|e| ContractError::Deser(e.to_string()))?; + Ok(UpdateModification::valid(State::from(bytes))) + } + + fn summarize_state( + parameters: Parameters<'static>, + state: State<'static>, + ) -> Result, ContractError> { + let _ = parts(¶meters)?; + if state.as_ref().is_empty() { + return Ok(StateSummary::from(Vec::new())); + } + Ok(StateSummary::from(blake3::hash(state.as_ref()).as_bytes().to_vec())) + } + + fn get_state_delta( + parameters: Parameters<'static>, + state: State<'static>, + summary: StateSummary<'static>, + ) -> Result, ContractError> { + let _ = parts(¶meters)?; + if state.as_ref().is_empty() { + return Ok(StateDelta::from(Vec::new())); + } + if summary.as_ref() == blake3::hash(state.as_ref()).as_bytes() { + Ok(StateDelta::from(Vec::new())) + } else { + Ok(StateDelta::from(state.as_ref().to_vec())) + } + } +} diff --git a/src-tauri/assets/space_contract.wasm b/src-tauri/assets/space_contract.wasm new file mode 100644 index 0000000000000000000000000000000000000000..752a5d43e97ae5f96edc2957876a3007b9a5f169 GIT binary patch literal 257216 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 W=jZGB|E!, +} + +/// A rotated space key for members who stay after a removal. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SpaceKeyCargo { + pub space_id: [u8; 16], + pub owner: [u8; 32], + pub epoch: u64, + pub key: [u8; 32], } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -410,6 +433,8 @@ async fn dispatch(app: &tauri::AppHandle, sender: [u8; 32], cargo: Cargo) { Cargo::FriendAssertion(a) => crate::friends::on_inbox_assertion(app, sender, a).await, Cargo::FriendDecline(id) => crate::friends::on_inbox_decline(app, sender, id), Cargo::Share(s) => crate::shared_playlists::on_inbox_share(app, sender, s), + Cargo::SpaceInvite(i) => crate::spaces::on_inbox_invite(app, sender, i), + Cargo::SpaceKey(k) => crate::spaces::on_inbox_space_key(app, sender, k), } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5939d85..69fd79a 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -26,6 +26,7 @@ mod shared_playlists; mod shazam; mod slsk_link; mod smtc; +mod spaces; mod state; mod thumbbar; mod vocal_sep; @@ -150,6 +151,14 @@ pub fn run() { inbox::inbox_status, inbox::inbox_publish, inbox::inbox_check, + spaces::spaces_list, + spaces::space_create, + spaces::space_open, + spaces::space_post, + spaces::space_invite, + spaces::space_remove_member, + spaces::space_set_pinned, + spaces::space_leave, commands::buddy_list, commands::buddy_add, commands::buddy_remove, diff --git a/src-tauri/src/spaces.rs b/src-tauri/src/spaces.rs new file mode 100644 index 0000000..10cde56 --- /dev/null +++ b/src-tauri/src/spaces.rs @@ -0,0 +1,637 @@ +//! Spaces, product side (F6 decision → the app). One contract per Space at +//! (owner, space id): the owner's signed CHECKPOINT is the authority — +//! name, mode, roster, pinned playlist — with an epoch that moves on every +//! roster change; members post signed MESSAGES into a bounded window; a +//! private Space seals bodies to a per-epoch space key the owner hands out +//! through members' encrypted inboxes and rotates on every removal. +//! Invitations are owner-authored: to invite is to add the key to the +//! roster and tell the person, sealed, where the Space is. +//! +//! What this device knows about its Spaces — which ones, its role, the +//! epoch keys it was given — lives in `/freenet/spaces.json`. +//! Losing that file loses the keys; the owner can re-send them. +//! +//! v1 keeps members as their persona keys (a joined Space is a linked +//! identity); the room-local member key the design describes for +//! unlinkability is the next step, not a different contract. + +use std::collections::BTreeMap; +use std::path::PathBuf; +use std::time::{SystemTime, UNIX_EPOCH}; + +use freenet_adapter::FreenetAdapter; +use persona_state::space as sp; +use serde::{Deserialize, Serialize}; +use tauri::{Emitter, Manager}; + +use crate::inbox::{self, Cargo, SpaceInvite, SpaceKeyCargo}; +use crate::persona; +use crate::slsk_link; +use crate::state::AppState; + +const SPACE_WASM: &[u8] = include_bytes!("../assets/space_contract.wasm"); +pub const EVENT: &str = "community:spaces"; + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct SpaceRecord { + owner: String, + space_id: String, + name: String, + private: bool, + my_role: String, + joined_at: u64, + /// epoch → space key (b58), for private Spaces. + #[serde(default)] + keys: BTreeMap, + #[serde(default)] + last_epoch: u64, +} + +#[derive(Default, Serialize, Deserialize)] +struct Ledger { + #[serde(default)] + spaces: BTreeMap, +} + +#[derive(Debug, Serialize)] +pub struct SpaceListItem { + pub id: String, + pub owner: String, + pub space_id: String, + pub name: String, + pub private: bool, + pub my_role: String, + pub last_epoch: u64, +} + +#[derive(Debug, Serialize)] +pub struct MemberView { + pub key: String, + pub role: String, + pub username: Option, + pub is_me: bool, + pub since_epoch: u64, +} + +#[derive(Debug, Serialize)] +pub struct MessageView { + pub id: String, + pub author: String, + pub username: Option, + pub is_me: bool, + pub created_at: u64, + pub epoch: u64, + /// None when sealed to a key this device does not hold. + pub text: Option, +} + +#[derive(Debug, Serialize)] +pub struct SpaceView { + pub id: String, + pub owner: String, + pub space_id: String, + pub name: String, + pub description: String, + pub private: bool, + pub epoch: u64, + pub my_role: String, + pub message_cap: u32, + pub members: Vec, + pub pinned_owner: Option, + pub pinned_uuid: Option, + pub messages: Vec, + pub can_post: bool, + pub has_key: bool, + /// The contract exists on the network (a Space someone invited us to + /// may not have propagated to this node yet). + pub reachable: bool, +} + +fn now_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0) +} +fn rand16() -> [u8; 16] { + use rand::RngCore; + let mut n = [0u8; 16]; + rand::thread_rng().fill_bytes(&mut n); + n +} +fn rand32() -> [u8; 32] { + use rand::RngCore; + let mut n = [0u8; 32]; + rand::thread_rng().fill_bytes(&mut n); + n +} +fn b58(bytes: &[u8]) -> String { + bs58::encode(bytes).into_string() +} +fn key_of(s: &str) -> Result<[u8; 32], String> { + inbox::key_of(s) +} +fn id_of(s: &str) -> Result<[u8; 16], String> { + bs58::decode(s) + .into_vec() + .map_err(|e| format!("bad id: {e}"))? + .as_slice() + .try_into() + .map_err(|_| "id is not 16 bytes".to_string()) +} +fn cap(s: &str, max: usize) -> String { + if s.len() <= max { + return s.to_string(); + } + let mut end = max; + while end > 0 && !s.is_char_boundary(end) { + end -= 1; + } + s[..end].to_string() +} +fn record_id(owner: &str, space_id: &str) -> String { + format!("{owner}:{space_id}") +} + +fn path(app: &tauri::AppHandle) -> Result { + Ok(app + .path() + .app_data_dir() + .map_err(|e| e.to_string())? + .join("freenet") + .join("spaces.json")) +} +fn load(app: &tauri::AppHandle) -> Ledger { + path(app) + .ok() + .and_then(|p| std::fs::read(p).ok()) + .and_then(|b| serde_json::from_slice(&b).ok()) + .unwrap_or_default() +} +fn save(app: &tauri::AppHandle, l: &Ledger) -> Result<(), String> { + let p = path(app)?; + if let Some(dir) = p.parent() { + std::fs::create_dir_all(dir).map_err(|e| e.to_string())?; + } + std::fs::write(&p, serde_json::to_vec_pretty(l).map_err(|e| e.to_string())?) + .map_err(|e| e.to_string())?; + let _ = app.emit(EVENT, ()); + Ok(()) +} + +async fn my_key(app: &tauri::AppHandle) -> Result<[u8; 32], String> { + let adapter = app.state::().freenet.clone(); + let info = persona::current(&adapter) + .await? + .ok_or("create a SoulStream identity first")?; + key_of(&info.public_key) +} +async fn sign(app: &tauri::AppHandle, payload: Vec) -> Result, String> { + let adapter = app.state::().freenet.clone(); + persona::sign(&adapter, sp::SIGN_DOMAIN, payload).await +} + +async fn fetch_state(app: &tauri::AppHandle, owner: &[u8; 32], space_id: &[u8; 16]) -> Result<(sp::SpaceState, bool), String> { + let adapter = app.state::().freenet.clone(); + let ids = FreenetAdapter::contract_ids(SPACE_WASM.to_vec(), &sp::make_params(owner, space_id)); + match adapter.contract_get(&ids.instance, false).await { + Ok(bytes) => Ok((sp::merge_space(owner, space_id, &[&bytes]), true)), + Err(e) if e.to_string().contains("not found") => Ok((sp::SpaceState::default(), false)), + Err(e) => Err(e.to_string()), + } +} + +async fn offer(app: &tauri::AppHandle, owner: &[u8; 32], space_id: &[u8; 16], state: &sp::SpaceState, first: bool) -> Result<(), String> { + let adapter = app.state::().freenet.clone(); + let bytes = bincode::serialize(state).map_err(|e| e.to_string())?; + let params = sp::make_params(owner, space_id); + if first { + adapter.contract_put(SPACE_WASM.to_vec(), ¶ms, bytes, false).await.map(|_| ()) + } else { + adapter.get_then_update(SPACE_WASM.to_vec(), ¶ms, bytes).await.map(|_| ()) + } + .map_err(|e| e.to_string()) +} + +fn empty_state(space_id: [u8; 16], checkpoint: Option, messages: Vec) -> sp::SpaceState { + sp::SpaceState { schema_version: sp::SCHEMA_VERSION, space_id, checkpoint, messages } +} + +// ---------------------------------------------------------------- views + +fn view_of(app: &tauri::AppHandle, rec: &SpaceRecord, state: &sp::SpaceState, me: &[u8; 32], reachable: bool) -> SpaceView { + let me_b58 = b58(me); + let key_for = |epoch: u64| -> Option<[u8; 32]> { + rec.keys.get(&epoch.to_string()).and_then(|k| key_of(k).ok()) + }; + let Some(c) = &state.checkpoint else { + return SpaceView { + id: record_id(&rec.owner, &rec.space_id), + owner: rec.owner.clone(), + space_id: rec.space_id.clone(), + name: rec.name.clone(), + description: String::new(), + private: rec.private, + epoch: 0, + my_role: rec.my_role.clone(), + message_cap: sp::DEFAULT_MESSAGE_CAP, + members: vec![], + pinned_owner: None, + pinned_uuid: None, + messages: vec![], + can_post: false, + has_key: false, + reachable, + }; + }; + let is_member = c.members.iter().any(|m| &m.key == me); + let has_key = !c.private || key_for(c.epoch).is_some(); + SpaceView { + id: record_id(&rec.owner, &rec.space_id), + owner: rec.owner.clone(), + space_id: rec.space_id.clone(), + name: c.name.clone(), + description: c.description.clone(), + private: c.private, + epoch: c.epoch, + my_role: if &c.owner == me { "owner".into() } else if is_member { "member".into() } else { "none".into() }, + message_cap: c.message_cap, + members: c + .members + .iter() + .map(|m| MemberView { + key: b58(&m.key), + role: match m.role { + sp::Role::Owner => "owner".into(), + sp::Role::Member => "member".into(), + }, + username: slsk_link::username_for_key(app, &b58(&m.key)), + is_me: &m.key == me, + since_epoch: m.since_epoch, + }) + .collect(), + pinned_owner: c.pinned.as_ref().map(|p| b58(&p.owner)), + pinned_uuid: c.pinned.as_ref().map(|p| b58(&p.uuid)), + messages: state + .messages + .iter() + .map(|m| { + let text = if c.private { + key_for(m.epoch).and_then(|k| sp::open_body(&k, m)).and_then(|b| String::from_utf8(b).ok()) + } else { + String::from_utf8(m.body.clone()).ok() + }; + MessageView { + id: b58(&m.msg_id), + author: b58(&m.author), + username: slsk_link::username_for_key(app, &b58(&m.author)), + is_me: &m.author == me, + created_at: m.created_at, + epoch: m.epoch, + text, + } + }) + .collect(), + can_post: is_member && has_key, + has_key, + reachable, + } + .with_me(me_b58) +} + +impl SpaceView { + fn with_me(self, _me: String) -> Self { + self + } +} + +pub fn list(app: &tauri::AppHandle) -> Vec { + let l = load(app); + let mut v: Vec<_> = l + .spaces + .values() + .map(|r| SpaceListItem { + id: record_id(&r.owner, &r.space_id), + owner: r.owner.clone(), + space_id: r.space_id.clone(), + name: r.name.clone(), + private: r.private, + my_role: r.my_role.clone(), + last_epoch: r.last_epoch, + }) + .collect(); + v.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); + v +} + +// ---------------------------------------------------------------- actions + +pub async fn create(app: &tauri::AppHandle, name: &str, description: &str, private: bool) -> Result { + let me = my_key(app).await?; + let name = cap(name.trim(), sp::MAX_NAME_BYTES); + if name.is_empty() { + return Err("a Space needs a name".into()); + } + let space_id = rand16(); + let mut c = sp::Checkpoint { + schema_version: sp::SCHEMA_VERSION, + space_id, + owner: me, + epoch: 1, + name: name.clone(), + description: cap(description.trim(), sp::MAX_DESC_BYTES), + private, + members: vec![sp::Member { key: me, role: sp::Role::Owner, since_epoch: 1 }], + pinned: None, + message_cap: sp::DEFAULT_MESSAGE_CAP, + sig: vec![], + }; + c.sig = sign(app, sp::checkpoint_sign_payload(&c)).await?; + let state = empty_state(space_id, Some(c), vec![]); + offer(app, &me, &space_id, &state, true).await?; + let mut keys = BTreeMap::new(); + if private { + keys.insert("1".to_string(), b58(&rand32())); + } + let rec = SpaceRecord { + owner: b58(&me), + space_id: b58(&space_id), + name, + private, + my_role: "owner".into(), + joined_at: now_secs(), + keys, + last_epoch: 1, + }; + let mut l = load(app); + l.spaces.insert(record_id(&rec.owner, &rec.space_id), rec.clone()); + save(app, &l)?; + log::info!("spaces: created {:?} ({})", rec.name, if private { "private" } else { "public" }); + Ok(view_of(app, &rec, &state, &me, true)) +} + +pub async fn open(app: &tauri::AppHandle, owner_b58: &str, space_b58: &str) -> Result { + let me = my_key(app).await?; + let owner = key_of(owner_b58)?; + let space_id = id_of(space_b58)?; + let mut l = load(app); + let rid = record_id(owner_b58, space_b58); + let rec = l.spaces.get(&rid).cloned().ok_or("not a Space this device knows")?; + let (state, reachable) = fetch_state(app, &owner, &space_id).await?; + if let Some(c) = &state.checkpoint { + let r = l.spaces.get_mut(&rid).unwrap(); + let changed = r.name != c.name || r.last_epoch != c.epoch || r.private != c.private; + r.name = c.name.clone(); + r.last_epoch = c.epoch; + r.private = c.private; + // Adding members never rotates the key: the previous epoch's key + // carries forward until a removal issues a new one. + if c.private && !r.keys.contains_key(&c.epoch.to_string()) { + if let Some((_, k)) = r.keys.iter().filter(|(e, _)| e.parse::().unwrap_or(0) < c.epoch).last() { + let k = k.clone(); + r.keys.insert(c.epoch.to_string(), k); + } + } + if changed { + let _ = save(app, &l); + } + } + let rec = l.spaces.get(&rid).cloned().unwrap_or(rec); + Ok(view_of(app, &rec, &state, &me, reachable)) +} + +pub async fn post(app: &tauri::AppHandle, owner_b58: &str, space_b58: &str, text: &str) -> Result<(), String> { + let me = my_key(app).await?; + let owner = key_of(owner_b58)?; + let space_id = id_of(space_b58)?; + let text = text.trim(); + if text.is_empty() { + return Err("nothing to say".into()); + } + if text.len() > sp::MAX_BODY_BYTES { + return Err(format!("over {} bytes", sp::MAX_BODY_BYTES)); + } + let (state, _) = fetch_state(app, &owner, &space_id).await?; + let c = state.checkpoint.as_ref().ok_or("the Space has not reached this node yet")?; + if !c.members.iter().any(|m| m.key == me) { + return Err("you are not in this Space's roster".into()); + } + let mut m = sp::Message { + space_id, + epoch: c.epoch, + msg_id: rand16(), + author: me, + created_at: now_secs(), + body: text.as_bytes().to_vec(), + sig: vec![], + }; + if c.private { + let l = load(app); + let rec = l.spaces.get(&record_id(owner_b58, space_b58)).ok_or("not a Space this device knows")?; + let key = rec + .keys + .get(&c.epoch.to_string()) + .and_then(|k| key_of(k).ok()) + .ok_or("no key for this epoch yet — the owner has to hand it to you")?; + m.body = sp::seal_body(&key, &m); + } + m.sig = sign(app, sp::message_sign_payload(&m)).await?; + offer(app, &owner, &space_id, &empty_state(space_id, None, vec![m]), false).await +} + +/// The owner rewrites the checkpoint: a new epoch with `mutate` applied. +async fn bump(app: &tauri::AppHandle, me: [u8; 32], owner: [u8; 32], space_id: [u8; 16], mutate: impl FnOnce(&mut sp::Checkpoint)) -> Result { + if me != owner { + return Err("only the owner can change a Space".into()); + } + let (state, _) = fetch_state(app, &owner, &space_id).await?; + let mut c = state.checkpoint.ok_or("the Space has not reached this node yet")?; + c.epoch += 1; + mutate(&mut c); + if c.members.len() > sp::MAX_MEMBERS { + return Err(format!("over {} members", sp::MAX_MEMBERS)); + } + c.sig = sign(app, sp::checkpoint_sign_payload(&c)).await?; + offer(app, &owner, &space_id, &empty_state(space_id, Some(c.clone()), vec![]), false).await?; + Ok(c) +} + +pub async fn invite(app: &tauri::AppHandle, owner_b58: &str, space_b58: &str, persona_key: &str) -> Result { + let me = my_key(app).await?; + let owner = key_of(owner_b58)?; + let space_id = id_of(space_b58)?; + let their = key_of(persona_key)?; + if their == me { + return Err("that is your own identity".into()); + } + if !inbox::reachable(app, &their).await { + return Err("they have not published an encrypted inbox — the invitation has nowhere to go".into()); + } + let c = bump(app, me, owner, space_id, |c| { + if !c.members.iter().any(|m| m.key == their) { + let e = c.epoch; + c.members.push(sp::Member { key: their, role: sp::Role::Member, since_epoch: e }); + } + }) + .await?; + let rid = record_id(owner_b58, space_b58); + let mut l = load(app); + let rec = l.spaces.get_mut(&rid).ok_or("not a Space this device knows")?; + let mut key = None; + if c.private { + let prev = rec.keys.get(&(c.epoch - 1).to_string()).cloned().or_else(|| rec.keys.values().last().cloned()); + let k = prev.unwrap_or_else(|| b58(&rand32())); + rec.keys.insert(c.epoch.to_string(), k.clone()); + key = Some(key_of(&k)?); + } + rec.last_epoch = c.epoch; + save(app, &l)?; + inbox::send( + app, + &their, + &Cargo::SpaceInvite(SpaceInvite { space_id, owner, name: c.name.clone(), private: c.private, epoch: c.epoch, key }), + ) + .await?; + log::info!("spaces: invited {persona_key} to {:?} (epoch {})", c.name, c.epoch); + open(app, owner_b58, space_b58).await +} + +pub async fn remove_member(app: &tauri::AppHandle, owner_b58: &str, space_b58: &str, persona_key: &str) -> Result { + let me = my_key(app).await?; + let owner = key_of(owner_b58)?; + let space_id = id_of(space_b58)?; + let gone = key_of(persona_key)?; + if gone == me { + return Err("the owner cannot be removed".into()); + } + let c = bump(app, me, owner, space_id, |c| c.members.retain(|m| m.key != gone)).await?; + let rid = record_id(owner_b58, space_b58); + let mut l = load(app); + let rec = l.spaces.get_mut(&rid).ok_or("not a Space this device knows")?; + rec.last_epoch = c.epoch; + if c.private { + // A removal rotates: everyone who stays gets the new epoch's key. + let k = rand32(); + rec.keys.insert(c.epoch.to_string(), b58(&k)); + save(app, &l)?; + for m in c.members.iter().filter(|m| m.key != me) { + let cargo = Cargo::SpaceKey(SpaceKeyCargo { space_id, owner, epoch: c.epoch, key: k }); + if let Err(e) = inbox::send(app, &m.key, &cargo).await { + log::warn!("spaces: could not hand the epoch-{} key to {}: {e}", c.epoch, b58(&m.key)); + } + } + } else { + save(app, &l)?; + } + open(app, owner_b58, space_b58).await +} + +pub async fn set_pinned(app: &tauri::AppHandle, owner_b58: &str, space_b58: &str, pl_owner: Option, pl_uuid: Option) -> Result { + let me = my_key(app).await?; + let owner = key_of(owner_b58)?; + let space_id = id_of(space_b58)?; + let pinned = match (pl_owner, pl_uuid) { + (Some(o), Some(u)) => Some(sp::PlaylistRef { owner: key_of(&o)?, uuid: id_of(&u)? }), + _ => None, + }; + bump(app, me, owner, space_id, |c| c.pinned = pinned).await?; + open(app, owner_b58, space_b58).await +} + +pub fn leave(app: &tauri::AppHandle, owner_b58: &str, space_b58: &str) -> Result<(), String> { + let mut l = load(app); + let rid = record_id(owner_b58, space_b58); + if l.spaces.get(&rid).is_some_and(|r| r.my_role == "owner") { + return Err("an owner keeps the Space; remove members instead".into()); + } + l.spaces.remove(&rid); + save(app, &l) +} + +// ---------------------------------------------------------------- inbox cargo + +pub(crate) fn on_inbox_invite(app: &tauri::AppHandle, sender: [u8; 32], inv: SpaceInvite) { + if sender != inv.owner { + log::warn!("spaces: an invitation was sealed by someone other than the Space's owner"); + return; + } + let rid = record_id(&b58(&inv.owner), &b58(&inv.space_id)); + let mut l = load(app); + let rec = l.spaces.entry(rid).or_insert_with(|| SpaceRecord { + owner: b58(&inv.owner), + space_id: b58(&inv.space_id), + name: cap(&inv.name, sp::MAX_NAME_BYTES), + private: inv.private, + my_role: "member".into(), + joined_at: now_secs(), + keys: BTreeMap::new(), + last_epoch: 0, + }); + rec.name = cap(&inv.name, sp::MAX_NAME_BYTES); + rec.private = inv.private; + if let Some(k) = inv.key { + rec.keys.insert(inv.epoch.to_string(), b58(&k)); + } + if let Err(e) = save(app, &l) { + log::warn!("spaces: could not keep an invitation: {e}"); + } else { + log::info!("spaces: invited to {:?} by {}", inv.name, b58(&inv.owner)); + } +} + +pub(crate) fn on_inbox_space_key(app: &tauri::AppHandle, sender: [u8; 32], sk: SpaceKeyCargo) { + if sender != sk.owner { + return; + } + let rid = record_id(&b58(&sk.owner), &b58(&sk.space_id)); + let mut l = load(app); + let Some(rec) = l.spaces.get_mut(&rid) else { return }; + rec.keys.insert(sk.epoch.to_string(), b58(&sk.key)); + let _ = save(app, &l); +} + +// ---------------------------------------------------------------- commands + +#[tauri::command] +pub fn spaces_list(app: tauri::AppHandle) -> Vec { + list(&app) +} +#[tauri::command] +pub async fn space_create(app: tauri::AppHandle, name: String, description: String, private: bool) -> Result { + create(&app, &name, &description, private).await +} +#[tauri::command] +pub async fn space_open(app: tauri::AppHandle, owner: String, space: String) -> Result { + open(&app, &owner, &space).await +} +#[tauri::command] +pub async fn space_post(app: tauri::AppHandle, owner: String, space: String, text: String) -> Result<(), String> { + post(&app, &owner, &space, &text).await +} +#[tauri::command] +pub async fn space_invite(app: tauri::AppHandle, owner: String, space: String, persona_key: String) -> Result { + invite(&app, &owner, &space, &persona_key).await +} +#[tauri::command] +pub async fn space_remove_member(app: tauri::AppHandle, owner: String, space: String, persona_key: String) -> Result { + remove_member(&app, &owner, &space, &persona_key).await +} +#[tauri::command] +pub async fn space_set_pinned(app: tauri::AppHandle, owner: String, space: String, playlist_owner: Option, playlist_uuid: Option) -> Result { + set_pinned(&app, &owner, &space, playlist_owner, playlist_uuid).await +} +#[tauri::command] +pub fn space_leave(app: tauri::AppHandle, owner: String, space: String) -> Result<(), String> { + leave(&app, &owner, &space) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn contract_generation_is_pinned() { + let ids = FreenetAdapter::contract_ids(SPACE_WASM.to_vec(), &sp::make_params(&[7u8; 32], &[1u8; 16])); + assert_eq!(ids.code_hash, "BQc5bg1QEfTsgkog1WsS3di7fkwVSd8VJQpNUHdEMH3a"); + } +} diff --git a/src/lib/api.js b/src/lib/api.js index 40a27d8..4ffce01 100644 --- a/src/lib/api.js +++ b/src/lib/api.js @@ -174,6 +174,14 @@ export const shareDismiss = (id) => invoke("share_dismiss", { id }); export const inboxStatus = () => invoke("inbox_status"); export const inboxPublish = () => invoke("inbox_publish"); export const inboxCheck = () => invoke("inbox_check"); +export const spacesList = () => invoke("spaces_list"); +export const spaceCreate = (name, description, isPrivate) => invoke("space_create", { name, description, private: isPrivate }); +export const spaceOpen = (owner, space) => invoke("space_open", { owner, space }); +export const spacePost = (owner, space, text) => invoke("space_post", { owner, space, text }); +export const spaceInvite = (owner, space, personaKey) => invoke("space_invite", { owner, space, personaKey }); +export const spaceRemoveMember = (owner, space, personaKey) => invoke("space_remove_member", { owner, space, personaKey }); +export const spaceSetPinned = (owner, space, playlistOwner, playlistUuid) => invoke("space_set_pinned", { owner, space, playlistOwner, playlistUuid }); +export const spaceLeave = (owner, space) => invoke("space_leave", { owner, space }); export const buddyList = () => invoke("buddy_list"); export const buddyAdd = (username) => invoke("buddy_add", { username }); export const buddyRemove = (username) => invoke("buddy_remove", { username }); diff --git a/src/lib/community.svelte.js b/src/lib/community.svelte.js index 13b6241..5242e64 100644 --- a/src/lib/community.svelte.js +++ b/src/lib/community.svelte.js @@ -75,6 +75,11 @@ export const community = $state({ inbox: null, /** What the last check found ({new_items, quarantined, pruned}). */ inboxLast: null, + /** spaces_list: the Spaces this device owns or was invited to. */ + spaces: [], + /** The open Space (space_open), or null. */ + space: null, + spaceError: "", /** persona key → ProfileView | {none} | {error} | {pending}: other * people's signed profiles, read once per session when surfaced. */ remoteProfiles: {}, @@ -319,6 +324,10 @@ export async function initCommunity() { else if (community.persona) community.persona.available = false; }); api.onEvent("community:friends", () => void refreshFriends()); + api.onEvent("community:spaces", () => { + void refreshSpaces(); + if (community.space) void openSpace(community.space.owner, community.space.space_id); + }); api.onEvent("community:shares", () => { void refreshShares(); void refreshPublished(); @@ -327,7 +336,7 @@ export async function initCommunity() { if (p.status === 1 || p.status === 2) noteSeen(p.username); }); } - await Promise.all([refreshFreenet(), loadContacts(), refreshLinks(), refreshFriends(), refreshShares(), refreshPublished(), refreshInbox()]); + await Promise.all([refreshFreenet(), loadContacts(), refreshLinks(), refreshFriends(), refreshShares(), refreshPublished(), refreshInbox(), refreshSpaces()]); await refreshPersona(); } @@ -519,7 +528,57 @@ export const friendshipForKey = (key) => { }; +// ---- spaces ------------------------------------------------------------------------- +export async function refreshSpaces() { + try { + community.spaces = await api.spacesList(); + } catch { + // stays as it was + } +} +/** Read a Space through the node; the view is what the checkpoint says. */ +export async function openSpace(owner, space) { + community.spaceError = ""; + community.busy["space:" + space] = true; + try { + community.space = await api.spaceOpen(owner, space); + } catch (e) { + community.spaceError = String(e?.message ?? e); + } finally { + delete community.busy["space:" + space]; + } +} +export const createSpace = (name, description, isPrivate) => + run("space:create", async () => { + community.space = await api.spaceCreate(name, description, isPrivate); + await refreshSpaces(); + }); +export const postToSpace = (owner, space, text) => + run("space:post", async () => { + await api.spacePost(owner, space, text); + await openSpace(owner, space); + }); +export const inviteToSpace = (owner, space, personaKey) => + run("space:invite", async () => { + community.space = await api.spaceInvite(owner, space, personaKey); + }); +export const removeFromSpace = (owner, space, personaKey) => + run("space:remove", async () => { + community.space = await api.spaceRemoveMember(owner, space, personaKey); + }); +export const pinToSpace = (owner, space, playlistOwner, playlistUuid) => + run("space:pin", async () => { + community.space = await api.spaceSetPinned(owner, space, playlistOwner, playlistUuid); + }); +export const leaveSpace = (owner, space) => + run("space:leave", async () => { + await api.spaceLeave(owner, space); + community.space = null; + await refreshSpaces(); + }); + + // Dev handle beside `__soulstream` (state.svelte.js): probes reach the store // through this, never through a bare import (which would be a second copy). if (import.meta.env.DEV) globalThis.__community = community; -if (import.meta.env.DEV) globalThis.__communityActions = { friendRequest, friendRequestByKey, friendAccept, friendDecline, friendRemove, friendForget, checkLink, refreshFriends, sharedPublish, sharedWithdraw, shareSend, sharePreview, shareImport, refreshShares, refreshPublished, publishInbox, checkInbox, refreshInbox }; +if (import.meta.env.DEV) globalThis.__communityActions = { friendRequest, friendRequestByKey, friendAccept, friendDecline, friendRemove, friendForget, checkLink, refreshFriends, sharedPublish, sharedWithdraw, shareSend, sharePreview, shareImport, refreshShares, refreshPublished, publishInbox, checkInbox, refreshInbox, refreshSpaces, openSpace, createSpace, postToSpace, inviteToSpace, removeFromSpace, pinToSpace, leaveSpace }; diff --git a/src/views/Community.svelte b/src/views/Community.svelte index 2cee5f8..4cd8a82 100644 --- a/src/views/Community.svelte +++ b/src/views/Community.svelte @@ -80,6 +80,14 @@ publishInbox, checkInbox, friendRequestByKey, + refreshSpaces, + openSpace, + createSpace, + postToSpace, + inviteToSpace, + removeFromSpace, + pinToSpace, + leaveSpace, } from "../lib/community.svelte.js"; onMount(() => { @@ -445,6 +453,53 @@ return () => clearInterval(t); }); let addKey = $state(""); + // ---- spaces ------------------------------------------------------------ + let spaceName = $state(""); + let spacePrivate = $state(true); + let spaceText = $state(""); + let spaceInviteKey = $state(""); + let spaceTab = $state("chat"); + let spaceCreating = $state(false); + const sv = $derived(community.space); + const spaceOwner = $derived(sv?.my_role === "owner"); + let spaceEl = $state(null); + $effect(() => { + const n = sv?.messages.length; + if (n && spaceEl) spaceEl.scrollTop = spaceEl.scrollHeight; + }); + // The open Space is re-read every 30 s while the section is showing. + $effect(() => { + if (community.section !== "spaces" || !sv || !nodeUp) return; + const { owner, space_id } = sv; + const t = setInterval(() => void openSpace(owner, space_id), 30_000); + return () => clearInterval(t); + }); + async function sayInSpace() { + const t = spaceText.trim(); + if (!t || !sv) return; + await postToSpace(sv.owner, sv.space_id, t); + spaceText = ""; + } + async function makeSpace() { + const n = spaceName.trim(); + if (!n) return; + await createSpace(n, "", spacePrivate); + spaceName = ""; + spaceCreating = false; + } + const whoIs = (m) => (m.is_me ? "you" : m.username ? display(m.username) : fpShort(m.key ?? m.author)); + const pinnedPreview = $derived(sv?.pinned_uuid ? community.sharePreviews["pin:" + sv.pinned_uuid] : null); + $effect(() => { + const s0 = sv; + if (s0?.pinned_uuid && !community.sharePreviews["pin:" + s0.pinned_uuid]) { + untrack(() => sharePreview({ id: "pin:" + s0.pinned_uuid, owner_key: s0.pinned_owner, playlist_uuid: s0.pinned_uuid })); + } + }); + async function importPinned() { + if (!sv?.pinned_uuid) return; + const id = await shareImport({ id: "pin:" + sv.pinned_uuid, owner_key: sv.pinned_owner, playlist_uuid: sv.pinned_uuid, name: pinnedPreview?.name ?? "Pinned playlist" }); + if (id) goToView("playlist", { playlistId: id, label: pinnedPreview?.name ?? "Pinned playlist" }); + } const isMe = (u) => !!me && (u ?? "").trim().toLowerCase() === me.trim().toLowerCase(); $effect(() => { const lk = drawerLink; @@ -1005,15 +1060,120 @@ {:else if community.section === "spaces"} -
- -
No Spaces yet
-
- A Space is a persistent SoulStream community on Freenet with its own member keys — invitation only, encrypted - when private, and never a Soulseek Room in disguise. The research behind it is done; the Space contract itself - is the next piece of the Freenet track, so nothing here is live yet. +
+
+
Your Spaces {community.spaces.length}
+ {#each community.spaces as sp0 (sp0.id)} + + {/each} + {#if spaceCreating} +
{ e.preventDefault(); void makeSpace(); }}> +
+ +
+
+ {:else} + + {/if} +

What a Space is

A persistent SoulStream community on Freenet. The owner's signed checkpoint is the roster; a removal is a new epoch, and in a private Space every removal rotates the key. Not a Soulseek Room, and never mirrored to one. Invitations travel sealed through the encrypted inbox — the person you invite needs one.

- beta · not live + {#if sv} +
+
+
{sv.name.slice(0, 2).toUpperCase()}
+
+
{sv.name} Space
+
+ {#if sv.private} private · sealed{:else}public{/if}· + {sv.members.length} {sv.members.length === 1 ? "member" : "members"}· + epoch {sv.epoch}· + not a Soulseek Room + {#if !sv.reachable}·not on this node yet{:else}·read from the network{/if} +
+
+
+ + {#if !spaceOwner}{/if} +
+
+
+ + + + Proposals · next +
+ {#if community.spaceError}

{community.spaceError}

{/if} + {#if spaceTab === "chat"} + {#if sv.pinned_uuid} +

Pinned playlist {#if pinnedPreview && !pinnedPreview.pending && !pinnedPreview.error}revision {pinnedPreview.revision_seq}{/if}

+
+ +
+
{pinnedPreview?.name ?? "Reading…"}
+
{#if pinnedPreview?.entries}{pinnedPreview.entries.length} tracks · signed by {fpShort(sv.pinned_owner)}{:else if pinnedPreview?.error}{pinnedPreview.error}{:else if pinnedPreview?.withdrawn}withdrawn by its owner{:else}fetching through your node{/if}
+
{#if spaceOwner}{/if}
+
+
+
+ {:else if spaceOwner && community.published.some((p) => !p.withdrawn)} +
Pin one of your public playlists:{#each community.published.filter((p) => !p.withdrawn) as p (p.playlist_id)}{/each}
+ {/if} +
+
+ {#if !sv.messages.length}
Nothing said yet. The last {sv.message_cap} lines are kept; older ones fall away for everyone.
{/if} + {#each sv.messages as m (m.id)} +
{fmtClock(m.created_at * 1000)}{whoIs(m)}{#if m.text != null}{m.text}{:else} sealed to a key this device does not hold (epoch {m.epoch}){/if}
+ {/each} +
+
{ e.preventDefault(); void sayInSpace(); }}> +
+ {sv.private ? "sealed · members only" : "public · signed"} · last {sv.message_cap} kept + +
+
+ {:else if spaceTab === "members"} +
+
+ {#each sv.members as m (m.key)} +
+ + {m.is_me ? "you" : m.username ? display(m.username) : fpShort(m.key)} + {m.role}{#if m.since_epoch > 1} · since {m.since_epoch}{/if} + {#if spaceOwner && !m.is_me}{/if} +
+ {/each} +
+ {#if spaceOwner} +
{ e.preventDefault(); const k = spaceInviteKey.trim(); if (k) { void inviteToSpace(sv.owner, sv.space_id, k); spaceInviteKey = ""; } }}> + + +
+ {#if community.friends.some((f) => f.state === "friends")}

Friends: {#each community.friends.filter((f) => f.state === "friends") as f (f.id)} {/each}

{/if} + {/if} +

Members are their SoulStream identities here — a joined Space is a linked identity. Removing someone is a new epoch{#if sv.private}, and in this private Space it also rotates the key: those who stay receive the new one sealed{/if}.

+
+ {:else} +
+

About

+
Owner
{sv.owner}
Space id
{sv.space_id}
Mode
{sv.private ? "private — message bodies sealed to a per-epoch key" : "public — anyone who resolves the contract can read"}
Window
the last {sv.message_cap} messages
Epoch
{sv.epoch}
+

What the network can see: that the Space exists, its owner, its roster keys, and — for a private Space — how many sealed lines it holds. Leaving does not erase copies others hold.

+ {#if sv.description}

{sv.description}

{/if} +
+ {/if} +
+ {:else} +
+ +
{community.spaces.length ? "Pick a Space" : "No Spaces yet"}
+
A Space is a persistent SoulStream community on Freenet with its own roster — invitation only, sealed when private, never a Soulseek Room in disguise.
+
+ {/if}
@@ -1694,6 +1854,25 @@ .emptyshelf b { color: var(--text-secondary); } .emptyshelf .ax-ghost { margin-left: auto; } + /* ── spaces ── */ + .spmini { width: 40px; height: 40px; border-radius: 10px; flex: none; display: grid; place-items: center; background: color-mix(in srgb, var(--space) 16%, transparent); color: #d9a6ff; font-size: 13px; font-weight: 900; } + .sphead { display: flex; align-items: center; gap: 16px; padding-bottom: 14px; border-bottom: 1px solid var(--hair); } + .spico { width: 56px; height: 56px; border-radius: 16px; display: grid; place-items: center; flex: none; background: color-mix(in srgb, var(--space) 16%, transparent); color: #d9a6ff; font-size: 20px; font-weight: 900; } + .sphead .nm { font-size: 20px; font-weight: 800; display: flex; align-items: center; gap: 10px; } + .sphead .sub { font-size: 12px; color: var(--text-muted); margin-top: 4px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; max-width: none; } + .src.space { background: color-mix(in srgb, var(--space) 14%, transparent); color: #d9a6ff; } + .sptabs { display: flex; gap: 2px; margin: 12px 0 14px; align-items: center; } + .subtab { padding: 6px 13px; border-radius: 8px; font-size: 12px; font-weight: 800; color: var(--text-secondary); display: inline-flex; gap: 6px; align-items: center; } + .subtab.on { background: var(--surf-hi); color: #fff; } + .subtab b { font-size: 10px; } + .pinned { display: flex; gap: 14px; align-items: center; } + .pinned .t { font-size: 15px; font-weight: 800; } + .pinned .s { font-size: 12px; color: var(--text-secondary); margin-top: 2px; } + .members { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 12px 8px; } + .mem { display: flex; flex-direction: column; align-items: center; gap: 5px; text-align: center; } + .mem .nm { font-size: 11.5px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 96px; } + .mem .role { font-size: 9.5px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); } + .mem .role.own { color: #d9a6ff; } /* ── you ── */ /* The You section sized by the PANE (container query), never the window: three columns from ~1240px of pane, two down to ~820, then one. */