p2panda_net/
lib.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Data-type-agnostic p2p networking, discovery, gossip and local-first sync.
4//!
5//! ## Features
6//!
7//! `p2panda-net` is a collection of Rust modules providing solutions for a whole set of
8//! peer-to-peer and [local-first] application requirements. Collectively these modules solve the
9//! problem of event delivery.
10//!
11//! - [Publish & Subscribe] for ephemeral messages (gossip protocol)
12//! - Publish & Subscribe for messages with [Eventual Consistency] guarantee (sync protocol)
13//! - Confidentially discover nodes who are interested in the same topic ([Private Set Intersection])
14//! - Establish and manage direct connections to any device over the Internet (using [iroh])
15//! - Monitor system with supervisors and restart modules on critical failure (Erlang-inspired
16//!   [Supervision Trees])
17//! - Modular API allowing users to choose or replace the layers they want to use
18//!
19//! ## Important note
20//!
21//! `p2panda-net` depends on a fixed version
22//! ([`#117`](https://github.com/n0-computer/iroh-gossip/pull/117)) of `iroh-gossip` which has not
23//! been published yet.
24//!
25//! Please patch your build with the fixed crate for now until this is handled upstream by adding
26//! the following lines to your root `Cargo.toml`:
27//!
28//! ```toml
29//! [patch.crates-io]
30//! iroh-gossip = { git = "https://github.com/p2panda/iroh-gossip", rev = "533c34a2758518ece19c1de9f21bc40d61f9b5a5" }
31//! ```
32//!
33//! ## Getting Started
34//!
35//! Install the Rust crate using `cargo add p2panda-net`.
36//!
37//! ```rust
38//! # use std::error::Error;
39//! #
40//! # #[tokio::main]
41//! # async fn main() -> Result<(), Box<dyn Error>> {
42//! use futures_util::StreamExt;
43//! use p2panda_core::Hash;
44//! use p2panda_net::iroh_mdns::MdnsDiscoveryMode;
45//! use p2panda_net::{AddressBook, Discovery, Endpoint, MdnsDiscovery, Gossip};
46//!
47//! // Topics are used to discover other nodes and establish connections around them.
48//! let topic = Hash::new(b"shirokuma-cafe").into();
49//!
50//! // Maintain an address book of newly discovered or manually added nodes.
51//! let address_book = AddressBook::builder().spawn().await?;
52//!
53//! // Establish direct connections to any device with the help of iroh.
54//! let endpoint = Endpoint::builder(address_book.clone())
55//!     .spawn()
56//!     .await?;
57//!
58//! // Discover nodes on your local-area network.
59//! let mdns = MdnsDiscovery::builder(address_book.clone(), endpoint.clone())
60//!     .mode(MdnsDiscoveryMode::Active)
61//!     .spawn()
62//!     .await?;
63//!
64//! // Confidentially discover nodes interested in the same topic.
65//! let discovery = Discovery::builder(address_book.clone(), endpoint.clone())
66//!     .spawn()
67//!     .await?;
68//!
69//! // Disseminate messages among nodes.
70//! let gossip = Gossip::builder(address_book.clone(), endpoint.clone())
71//!     .spawn()
72//!     .await?;
73//!
74//! // Join topic to publish and subscribe to stream of (ephemeral) messages.
75//! let cafe = gossip.stream(topic).await?;
76//!
77//! // This message will be seen by other nodes if they're online. If you want messages to arrive
78//! // eventually, even when they've been offline, you need to use p2panda's "sync" module.
79//! cafe.publish(b"Hello, Panda!").await?;
80//!
81//! let mut rx = cafe.subscribe();
82//! tokio::spawn(async move {
83//!     while let Some(Ok(bytes)) = rx.next().await {
84//!         println!("{}", String::from_utf8(bytes).expect("valid UTF-8 string"));
85//!     }
86//! });
87//! #
88//! # Ok(())
89//! # }
90//! ```
91//!
92//! For a complete command-line application using `p2panda-net` with a sync protocol, see our
93//! [`chat.rs`] example.
94//!
95//! ## Local-first Event Delivery
96//!
97//! `p2panda-net` is concerned with the **event delivery** layer of peer-to-peer and local-first
98//! application stacks.
99//!
100//! This layer ensures that your application's data eventually arrives on all devices in a
101//! peer-to-peer fashion, no matter where they are and if they've been offline.
102//!
103//! ## Decentralised and offline-first
104//!
105//! `p2panda-net` is designed for ad-hoc network topologies with **no central registry** and where
106//! the size of the network might be unknown. Nodes can go on- or offline at any point in time.
107//!
108//! ## Broadcast topology
109//!
110//! The [Publish & Subscribe] methods in `p2panda-net` suggest a broadcast topology, where one node
111//! can communicate to a whole group by sending a single message.
112//!
113//! Reducing the API surface of direct connections helps with building a wide range of peer-to-peer
114//! applications which do not require knowledge of stateful connections but rather look for **state
115//! convergence**. This aligns well with the **local-first** paradigm.
116//!
117//! This approach is a prerequisite to make applications compatible with genuine broadcast-based
118//! communication systems, like **LoRa, Bluetooth Low Energy (BLE) or packet radio**.
119//!
120//! `p2panda-net` can be understood as a broadcast abstraction independent of the underlying
121//! transport, including the Internet Protocol or other stateful connection protocols where
122//! underneath we're still maintaining connections.
123//!
124//! ## Bring your own Data-Type
125//!
126//! `p2panda-net` is agnostic over the actual data of your application. It can be encoded in any
127//! way (JSON, CBOR, etc.) and hold any data you need (CRDTs, messages, etc.).
128//!
129//! Your choice of sync protocol will determine a concrete **Base Convergent Data Type** (Base
130//! CDT). The data type must be known in order to design efficient sync protocols. However, these
131//! data types are simply carriers for your own application data, regardless of what form it takes.
132//!
133//! If you're interested in bringing your own Base CDT (for example Merkle-Trees or Sets) we have
134//! lower-level APIs and traits in [`p2panda-sync`] which allow you to implement your own sync
135//! protocol next to the rest of `p2panda-net`.
136//!
137//! ## Modules
138//!
139//! All modules can be enabled by feature flags, most of them are enabled by default.
140//!
141//! ### Direct Internet connections with iroh [`Endpoint`]
142//!
143//! Most of the lower-level Internet Protocol networking of `p2panda-net` is made possible by the
144//! work of [iroh] utilising well-established and known standards, like QUIC for transport,
145//! (self-certified) TLS 1.3 for transport encryption, QUIC Address Discovery (QAD) for STUN and
146//! TURN servers for relayed fallbacks.
147//!
148//! ### Node and Confidental Topic [`Discovery`]
149//!
150//! Our random-walk discovery algorithm finds other nodes in the network without any centralised
151//! registry. Any node can serve as a **bootstrap** into the network.
152//!
153//! `p2panda-net` is designed around topics of shared interest and we need an additional topic
154//! discovery strategy to find nodes sharing the same topics.
155//!
156//! Topics usually represent identifiers or namespaces for data and documents associated with a
157//! specific group of people (for example a text document, chat group or image folder). For this
158//! reason, a topic should never be leaked to people outside of the intended group, whether
159//! accidentally or purposefully.
160//!
161//! Read more about how we've implemented confidential topic discovery (and thus sync) in
162//! [`p2panda-discovery`].
163//!
164//! ### Ephemeral Messaging via [`Gossip`] Protocol
165//!
166//! Not all messages in peer-to-peer applications need to be persisted, for example cursor
167//! positions or "awareness & presence" status.
168//!
169//! For these use-cases `p2panda-net` offers a gossip protocol to broadcast ephemeral messages to
170//! all online nodes interested in the same topic.
171//!
172//! ### Eventual Consistent local-first [`LogSync`]
173//!
174//! In local-first applications we want to converge towards the same state eventually, which
175//! requires nodes to catch up on missed messages - independent of if they've been offline or
176//! not.
177//!
178//! `p2panda-net` comes with a default `LogSync` protocol implementation which uses p2panda's
179//! **append-only log** Base Convergent Data Type (CDT).
180//!
181//! After initial sync finished, nodes switch to **live-mode** to directly push new messages to the
182//! network using a gossip protocol.
183//!
184//! ### Local [`MdnsDiscovery`] finding nearby devices
185//!
186//! Some devices might be already reachable on your local-area network where no Internet will be
187//! required to connect to them. mDNS discovery helps with finding these nodes.
188//!
189//! ### Manage nodes and associated topics in [`AddressBook`]
190//!
191//! To keep track of all nodes and their topic interests we're managing a local and persisted
192//! address book.
193//!
194//! The address book is an important tool to watch for transport information changes, keep track of
195//! stale nodes and **identify network partitions** which can be automatically "healed".
196//!
197//! Use the address book to manually add nodes to bootstrap a network from.
198//!
199//! ### Robust, failure-resistant modules with [`Supervisor`]
200//!
201//! All modules in `p2panda-net` are internally implemented with the [Actor Model]. Inspired by
202//! Erlang's [Supervision Trees] these actors can be monitored and automatically restarted on
203//! critical failure (caused by bugs in our code or third-party dependencies).
204//!
205//! Use the `supervisor` flag to enable this feature.
206//!
207//! ## Security & Privacy
208//!
209//! Every connection attempt to any node in a network can reveal sensitive meta-data, for example
210//! IP addresses or knowledge of the network or data itself.
211//!
212//! With `p2panda-net` we work towards a best-effort in containing accidental leakage of such
213//! information by:
214//!
215//! - Use **Network identifiers** to actively partition the network. The identifier serves as a
216//!   shared secret; nodes will not be able to establish connections if their identifiers differ.
217//! - Use **Confidential Discovery and Sync** to only reveal information about ourselves and
218//!   exchange application data with nodes who have knowledge of the same topic.
219//! - **Disable mDNS** discovery by default to avoid unknowingly leaking information in local-area
220//!   networks.
221//! - Give full control over which **boostrap nodes** and **STUN / TURN / relay servers** to
222//!   choose. They aid with establishing connections and overlays and can acquire more knowledge over
223//!   networking behaviour than other participants.
224//! - Allow **connecting to nodes** without any intermediaries, which unfortunately is only
225//!   possible if the address is directly reachable.
226//!
227//! In the future we're planning additional features to improve privacy:
228//!
229//! - Support **"onion" routing protocols** (Tor, I2P, Veilid) and mix-networks (Katzenpost) to
230//!   allow multi-hop routing without revealing the origin of the sender. [`#934`]
231//! - Introduce **Allow- and Deny-lists** for nodes to give fine-grained access with whom we can
232//!   ever form a connection with. This can be nicely paired with an access control system. [`#925`]
233//!
234//! [local-first]: https://www.inkandswitch.com/local-first-software/
235//! [Publish & Subscribe]: https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern
236//! [Eventual Consistency]: https://en.wikipedia.org/wiki/Eventual_consistency
237//! [Actor Model]: https://en.wikipedia.org/wiki/Actor_model
238//! [Private Set Intersection]: https://en.wikipedia.org/wiki/Private_set_intersection
239//! [Supervision Trees]: https://adoptingerlang.org/docs/development/supervision_trees/
240//! [iroh]: https://www.iroh.computer/
241//! [`chat.rs`]: https://github.com/p2panda/p2panda/blob/main/p2panda-net/examples/chat.rs
242//! [`#925`]: https://github.com/p2panda/p2panda/issues/925
243//! [`#934`]: https://github.com/p2panda/p2panda/issues/934
244//! [`p2panda-discovery`]: https://docs.rs/p2panda-discovery/latest/p2panda_discovery/
245//! [`p2panda-sync`]: https://docs.rs/p2panda-discovery/latest/p2panda_sync/
246#[cfg(feature = "address_book")]
247pub mod address_book;
248pub mod addrs;
249pub mod cbor;
250#[cfg(feature = "discovery")]
251pub mod discovery;
252#[cfg(feature = "gossip")]
253pub mod gossip;
254#[cfg(feature = "iroh_endpoint")]
255pub mod iroh_endpoint;
256#[cfg(feature = "iroh_mdns")]
257pub mod iroh_mdns;
258#[cfg(feature = "supervisor")]
259pub mod supervisor;
260#[cfg(feature = "sync")]
261pub mod sync;
262#[cfg(any(test, feature = "test_utils"))]
263#[doc(hidden)]
264pub mod test_utils;
265pub mod timestamp;
266pub mod utils;
267pub mod watchers;
268
269#[cfg(feature = "address_book")]
270#[doc(inline)]
271pub use address_book::AddressBook;
272#[cfg(feature = "discovery")]
273#[doc(inline)]
274pub use discovery::Discovery;
275#[cfg(feature = "gossip")]
276#[doc(inline)]
277pub use gossip::Gossip;
278#[cfg(feature = "iroh_endpoint")]
279#[doc(inline)]
280pub use iroh_endpoint::Endpoint;
281#[cfg(feature = "iroh_mdns")]
282#[doc(inline)]
283pub use iroh_mdns::MdnsDiscovery;
284#[cfg(feature = "supervisor")]
285#[doc(inline)]
286pub use supervisor::Supervisor;
287#[cfg(feature = "sync")]
288#[doc(inline)]
289pub use sync::LogSync;
290
291/// Identifer for a node in the network.
292///
293/// Each node in p2panda has a unique identifier created as a cryptographic key to globally
294/// identify it and encrypt network traffic for this node only.
295pub type NodeId = p2panda_core::PublicKey;
296
297/// Identifier for an gossip- or sync topic.
298///
299/// A topic identifier is required when subscribing or publishing to a stream.
300///
301/// Topics usually describe concrete data which nodes want to exchange over, for example a document
302/// id or chat group id and so forth. Applications usually want to share topics via a secure side
303/// channel.
304///
305/// **WARNING:** Sensitive topics have to be treated like secret values and generated using a
306/// cryptographically secure pseudorandom number generator (CSPRNG). Otherwise they can be easily
307/// guessed by third parties or leaked during discovery.
308pub type TopicId = [u8; 32];
309
310/// Identifier for a network.
311///
312/// The network identifier is used to achieve separation and prevent interoperability between
313/// distinct networks. This is the most global identifier to group nodes into networks. Different
314/// applications may choose to share the same underlying network infrastructure by using the same
315/// network identifier.
316///
317/// A BLAKE3 hash function is performed against each protocol identifier which is registered with
318/// `p2panda-net`, using the network identifier as an additional input. Even if two instances of
319/// `p2panda-net` are created with the same network protocols, any communication attempts will fail
320/// if they are not using the same network identifier.
321///
322/// **WARNING:** Sensitive network identifiers have to be treated like secret values and generated
323/// using a cryptographically secure pseudorandom number generator (CSPRNG). Otherwise they can be
324/// easily guessed by third parties.
325pub type NetworkId = [u8; 32];
326
327/// Identifier for a protocol.
328///
329/// A BLAKE3 hash function is performed against each protocol identifier which is registered with
330/// `p2panda-net`, using the network identifier as an additional input. Even if two instances of
331/// `p2panda-net` are created with the same network protocols, any communication attempts will fail
332/// if they are not using the same network identifier.
333pub type ProtocolId = Vec<u8>;
334
335/// Default network identifier used by `p2panda-net` for all transports.
336///
337/// See [NetworkId] for configuring your custom identifier to opt-out of the default network.
338pub const DEFAULT_NETWORK_ID: NetworkId = [
339    247, 69, 248, 242, 132, 120, 159, 230, 98, 100, 214, 200, 78, 40, 79, 94, 174, 8, 12, 27, 84,
340    195, 246, 159, 132, 240, 79, 208, 1, 43, 132, 118,
341];
342
343/// Hash the concatenation of the given protocol- and network identifiers.
344fn hash_protocol_id_with_network_id(
345    protocol_id: impl AsRef<[u8]>,
346    network_id: NetworkId,
347) -> Vec<u8> {
348    p2panda_core::Hash::new([protocol_id.as_ref(), &network_id].concat())
349        .as_bytes()
350        .to_vec()
351}