New crates/core/src/fof.rs module owns the author-side FoF Layer 2 publish path: - build_fof_comment_gating(storage, author_persona_id): gathers the author's keyring (own current V_me + every distinct received V_x), generates a fresh CEK + slot_binder_nonce, generates a fresh per-V_x (priv_x, pub_x) Ed25519 keypair per real slot, seals each slot, pads with random-bytes dummies to the next bucket (min 8, power-of-2 to 256, +128 above per Layer 3), shuffles real + dummy together, and returns the FoFCommentGating wire block + author-local CEK + the slot_binder_nonce. - Dedup at V_x byte level: a key held under multiple owners produces exactly one slot. - next_vouch_batch_bucket promoted to pub(crate) in profile.rs so the Layer 2 fof module can reuse the bucket rule from Layer 3. Three unit tests cover: - Real-count + padding + roundtrip (Alice's own V_me unlocks her slot; Bob's V_x unlocks his slot; both yield same CEK). - No V_me → returns None (graceful). - Duplicate V_x bytes across owners are deduped (single slot). 134 → 138 tests pass (no regressions). Subsequent slices wire this into the post-create path, add the reader/commenter side, the CDN four-check verification, and the revocation/access-grant diff handlers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
73 lines
2.4 KiB
Rust
73 lines
2.4 KiB
Rust
pub mod activity;
|
|
pub mod blob;
|
|
pub mod connection;
|
|
pub mod content;
|
|
pub mod control;
|
|
pub mod crypto;
|
|
pub mod group_key_distribution;
|
|
pub mod http;
|
|
pub mod export;
|
|
pub mod fof;
|
|
pub mod identity;
|
|
pub mod import;
|
|
pub mod announcement;
|
|
pub mod network;
|
|
pub mod node;
|
|
pub mod profile;
|
|
pub mod protocol;
|
|
pub mod storage;
|
|
pub mod stun;
|
|
pub mod types;
|
|
pub mod upnp;
|
|
pub mod web;
|
|
|
|
// Re-export iroh types needed by consumers
|
|
pub use iroh::{EndpointAddr, EndpointId};
|
|
|
|
use types::NodeId;
|
|
|
|
/// Posting-identity public key of the bootstrap anchor. Only announcements
|
|
/// authored by this key are accepted by `control::receive_post` for the
|
|
/// `VisibilityIntent::Announcement` intent. Hardcoded so clients cannot be
|
|
/// tricked into accepting a forged network-wide announcement.
|
|
pub const DEFAULT_ANCHOR_POSTING_ID: NodeId = [
|
|
0x17, 0xaf, 0x14, 0x19, 0x56, 0xae, 0x0b, 0x50,
|
|
0xdc, 0x1c, 0xb9, 0x24, 0x8c, 0xad, 0xf5, 0xfc,
|
|
0xa3, 0x71, 0xea, 0x2d, 0x85, 0x31, 0xac, 0x9a,
|
|
0xdd, 0x3c, 0x03, 0xca, 0xff, 0xc6, 0x14, 0x41,
|
|
];
|
|
|
|
/// Parse a connect string "nodeid_hex@ip:port" or "nodeid_hex@host:port" or bare "nodeid_hex"
|
|
/// into (NodeId, EndpointAddr). Supports DNS hostnames via `ToSocketAddrs`.
|
|
/// Shared utility used by CLI, Tauri, and bootstrap.
|
|
pub fn parse_connect_string(s: &str) -> anyhow::Result<(NodeId, EndpointAddr)> {
|
|
use std::net::ToSocketAddrs;
|
|
if let Some((id_hex, addr_str)) = s.split_once('@') {
|
|
let nid = parse_node_id_hex(id_hex)?;
|
|
let endpoint_id = EndpointId::from_bytes(&nid)?;
|
|
let all_addrs: Vec<std::net::SocketAddr> = addr_str
|
|
.to_socket_addrs()?
|
|
.collect();
|
|
if all_addrs.is_empty() {
|
|
anyhow::bail!("could not resolve address: {}", addr_str);
|
|
}
|
|
let mut addr = EndpointAddr::from(endpoint_id);
|
|
for sock_addr in all_addrs {
|
|
addr = addr.with_ip_addr(sock_addr);
|
|
}
|
|
Ok((nid, addr))
|
|
} else {
|
|
let nid = parse_node_id_hex(s)?;
|
|
let endpoint_id = EndpointId::from_bytes(&nid)?;
|
|
Ok((nid, EndpointAddr::from(endpoint_id)))
|
|
}
|
|
}
|
|
|
|
/// Parse a hex-encoded node ID string into NodeId bytes.
|
|
pub fn parse_node_id_hex(hex_str: &str) -> anyhow::Result<NodeId> {
|
|
let bytes = hex::decode(hex_str)?;
|
|
let id: NodeId = bytes
|
|
.try_into()
|
|
.map_err(|v: Vec<u8>| anyhow::anyhow!("expected 32 bytes, got {}", v.len()))?;
|
|
Ok(id)
|
|
}
|