From 617160739433b506862000c9eb96f52ea582ccd4 Mon Sep 17 00:00:00 2001 From: Ocnrb Date: Tue, 25 Aug 2026 22:42:31 +0100 Subject: [PATCH] Content structure refactor --- docs/concepts/architecture.md | 90 ---- ...and-paid-channels.md => channel-access.md} | 61 ++- docs/concepts/channel-anatomy.md | 59 ++ docs/concepts/channels-and-ownership.md | 53 -- docs/concepts/client.md | 40 ++ docs/concepts/encryption.md | 6 +- docs/{guides => concepts}/notifications.md | 10 +- docs/concepts/ownership-and-moderation.md | 33 ++ docs/concepts/privacy-model.md | 11 +- ...and-persistence.md => storage-and-sync.md} | 30 +- docs/getting-started/first-steps.md | 6 +- docs/getting-started/identity.md | 11 +- docs/guides/backup-and-recovery.md | 8 +- docs/guides/direct-messages.md | 39 ++ docs/guides/file-sharing.md | 2 +- docs/guides/managing-channels.md | 6 +- docs/help/faq.md | 12 +- docs/help/troubleshooting.md | 2 +- docs/operators/run-a-relay.md | 2 +- docs/security/privacy-at-a-glance.md | 29 + docs/security/threat-model.md | 5 +- docs/welcome.md | 6 +- docusaurus.config.js | 17 +- package-lock.json | 25 + package.json | 1 + sidebars.js | 46 +- src/css/custom.css | 7 + src/pages/legal/privacy-policy.md | 508 +++++++----------- 28 files changed, 552 insertions(+), 573 deletions(-) delete mode 100644 docs/concepts/architecture.md rename docs/concepts/{gated-and-paid-channels.md => channel-access.md} (63%) create mode 100644 docs/concepts/channel-anatomy.md delete mode 100644 docs/concepts/channels-and-ownership.md create mode 100644 docs/concepts/client.md rename docs/{guides => concepts}/notifications.md (63%) create mode 100644 docs/concepts/ownership-and-moderation.md rename docs/concepts/{storage-and-persistence.md => storage-and-sync.md} (66%) create mode 100644 docs/guides/direct-messages.md create mode 100644 docs/security/privacy-at-a-glance.md diff --git a/docs/concepts/architecture.md b/docs/concepts/architecture.md deleted file mode 100644 index 7f0e1a8..0000000 --- a/docs/concepts/architecture.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -id: architecture -title: Architecture -description: How Pombo works without a backend — Streamr transport, Polygon permissions, storage nodes, client-side crypto. ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Architecture - -Pombo has no message backend: no server receives, routes or authorizes your conversations. The client sits on top of three open layers — transport, persistence, and ownership — and handles all cryptography itself: - -``` -┌─────────────────────────────────────────────┐ -│ Pombo client (browser PWA / Android app) │ -│ identity · encryption · UI │ -├─────────────────────────────────────────────┤ -│ Streamr Network → message transport │ -│ (P2P pub/sub) & delivery │ -├─────────────────────────────────────────────┤ -│ Storage nodes → message history │ -│ (Streamr + Cassandra) & offline delivery │ -├─────────────────────────────────────────────┤ -│ Polygon PoS → ownership & │ -│ (Streamr registries) permissions │ -└─────────────────────────────────────────────┘ -``` - -- **Transport — Streamr Network.** Messages are published to *streams* (topics) and propagate peer-to-peer between subscribers. No relay server sits in the middle of your conversations. -- **Ownership — Polygon PoS.** Streams are registered on-chain in the Streamr registry contracts. The record of who owns a channel and who may publish or subscribe to it is public blockchain state — not a row in someone's database. On-chain writes (creating a channel, granting membership) cost a small fee in POL; everything else is free. -- **Persistence — storage nodes.** Streamr nodes running the storage plugin retain stream history, so messages reach people who were offline. Channel owners choose the storage node and the retention period. See [Storage and persistence](storage-and-persistence.md). -- **Cryptography — your device.** Keys are generated and used locally. DM encryption, protected-channel encryption and message signing all happen client-side before anything is published. Two deliberate choices are worth knowing: the Streamr SDK's own encryption layer is **disabled** — all confidentiality is applied at the app layer — and channel messages are published under a per-channel throwaway key carrying a signed proof of the real account inside the payload (in contract-backed channels, the publisher is the membership contract instead; see [Privacy model](privacy-model.md)). - -Beyond these layers, the client talks to a small set of auxiliary services — public RPC endpoints, The Graph, the push relay, the Explore curation manifest — none of which handle message content. The full list and what each one sees is in the [threat model](../security/threat-model.md). - -## Anatomy of a channel - -Every channel is a set of Streamr streams under the creator's address — three for open and protected channels, four when membership is contract-backed: - -| Stream | Stored? | Partitions | Purpose | -|---|---|---|---| -| `…-1` Message stream | Yes | 11 | Chat content (P0), edit/delete control messages (P1), file chunks (P2–P10) | -| `…-2` Ephemeral stream | No | 3 | Presence and typing (P0), live P2P media signals (P1) and data (P2) | -| `…-3` Admin stream | Yes | 3 | Moderation state (P0), channel image (P1), password challenge (P2) — writable only by the owner | -| `…-4` Keys stream | Yes | 1 | Distribution of the channel's encryption keys — closed, gated and paid channels only | - -The stream ID itself has the form `{ownerAddress}/{id}`, which is what makes ownership self-evident: the creator's address is part of the channel's name. - -The keys stream is stored on purpose: it is what makes joining a private channel asynchronous. A newcomer's key request waits there until some member comes online to answer it, instead of both having to be connected at the same moment. See [Encryption](encryption.md#closed-gated-and-paid-channels). - -The full picture per channel type — on-chain metadata, permissions, partitions, and where encryption applies (click to zoom): - - - - -![Open channel — multiple stream architecture: three streams with their on-chain metadata, permissions and partition layout](../assets/diagrams/open-channel.webp) - -*Content flows unencrypted; the four metadata variants cover visible/hidden and read-only combinations.* - - - - -![Protected channel — multiple stream architecture: identical stream layout, with every partition encrypted by a key derived from the shared password](../assets/diagrams/protected-channel.webp) - -*Same layout as an open channel, but every partition's content passes through AES-256-GCM with a PBKDF2-derived key (green path); the admin stream gains the password-challenge partition.* - - - - -Diagram coming soon — contract-backed channels add the keys stream to the layout above. Every stream's permission is held by one grantee, the channel's membership contract, and content is encrypted under a channel key distributed over `…-4`. See [Gated and paid channels](gated-and-paid-channels.md). - - - - -## Direct messages: the mailbox model - -Each user has a personal **DM inbox** — a deterministic pair of streams derived from their address: a stored message stream (13 partitions: messages, sync, notifications, file chunks) and an ephemeral one for presence and typing. Its permissions are the inverse of a normal channel: **anyone can publish** into it, but **only the owner can subscribe** (read). The inbox metadata also carries the owner's encryption public key, which is what lets anyone seal a message to them. - -Sending a DM means encrypting a message for the recipient and dropping it into their inbox. Because the inbox is backed by a storage node, the recipient doesn't need to be online — they pull their inbox history when they next open the app. This is what makes Pombo's DMs asynchronous, like a phone's SMS inbox, rather than requiring both parties online. - -Your own inbox also doubles as your **cross-device sync channel**: the app writes self-encrypted state snapshots to it, so a second device logged into the same account converges to the same contacts, channels and settings. - -![DM inbox — dual stream architecture: a stored inbox stream and an ephemeral stream, with ECDH-derived AES-256-GCM encryption on every partition](../assets/diagrams/dm-inbox.webp) - -*The inbox publishes the owner's encryption public key as on-chain metadata (green); every partition — messages, sync, notifications, file chunks — is sealed with a key derived via ECDH + HKDF before it touches the network.* - -## The interface is replaceable - -Pombo (the app at app.pombo.cc) is *an* interface to this protocol, not *the* system. All of the state — identities, channels, permissions, history — lives on public networks. Anyone can build another client against the same streams, and if the Pombo interface vanished, the protocol and your data would remain. diff --git a/docs/concepts/gated-and-paid-channels.md b/docs/concepts/channel-access.md similarity index 63% rename from docs/concepts/gated-and-paid-channels.md rename to docs/concepts/channel-access.md index d99ee11..62489e8 100644 --- a/docs/concepts/gated-and-paid-channels.md +++ b/docs/concepts/channel-access.md @@ -1,22 +1,39 @@ --- -id: gated-and-paid-channels -title: Gated and paid channels -description: Membership contracts on Polygon — closed allowlists, token and NFT gates, and subscriptions. +id: channel-access +title: Channel access +description: The five access models, and the membership contracts behind closed, gated and paid channels. --- -# Gated and paid channels +# Channel access -Three of Pombo's five channel types put membership in a **contract on Polygon**: Closed, Gated and Paid. They share one mechanism and differ only in the rule the contract enforces. +Every channel has one of five access models, chosen at creation: -| Type | Who gets in | -|---|---| -| **Closed** | Addresses the owner adds to an allowlist | -| **Gated** | Anyone holding a chosen ERC-20 balance, or any token of a chosen ERC-721 | -| **Paid** | Anyone with an active subscription — a price in a chosen ERC-20, per period | +
-## One contract per channel +| | Open | Protected | Closed | Gated | Paid | +|---|---|---|---|---|---| +| Read access | Everyone | Password holders | Allowlisted addresses | Token / NFT holders | Active subscribers | +| Write access | Everyone | Password holders | Allowlisted addresses | Token / NFT holders | Active subscribers | +| Access enforced by | — (open by design) | Client-side encryption | On-chain gate contract | On-chain gate contract | On-chain gate contract | +| Content on the wire | Signed plaintext (Pombo format) | AES-256-GCM ciphertext | AES-256-GCM under a channel key | Same | Same | +| Cost to join | Free | Free | Free (owner pays gas to add you) | Holding the asset | The subscription price | -Creating any of the three deploys a **PomboGate** — a minimal membership contract, one [EIP-1167 clone](https://eips.ethereum.org/EIPS/eip-1167) per channel, from a shared factory. The clone costs a single transaction at creation and then answers two questions for the rest of the channel's life: +
+ +- **Open channels** are public rooms. Anyone can read and write. +- **Protected channels** look public to the network, but every message is encrypted client-side with AES-256-GCM using a key derived from the shared password (PBKDF2, 310,000 iterations). Without the password, the network carries only ciphertext. One caveat: each protected channel publishes a verification challenge that anyone can fetch and test guesses against offline — so the channel is exactly as secret as the password is strong. +- **Closed, Gated and Paid channels** enforce membership *on-chain*, through a small membership contract deployed per channel. This is the only family where access control is enforceable at the protocol level — the network itself refuses messages from authors the contract does not vouch for — and content is encrypted under a channel key that only members obtain. They differ only in the rule the contract enforces: an owner-managed allowlist, holding a token or NFT, or an active subscription. The rest of this page covers the three in detail. +- A channel can also be created **read-only** (announcement style): everyone can read, only the owner posts. + +:::note[Terminology] +Pombo's source code calls the pre-contract generation of closed channels *native channels*. Those still work, but can no longer be created: closed, gated and paid channels are all backed by a gate contract. +::: + +Who a channel belongs to, and what its owner can do about behaviour inside it, is the subject of [Ownership and moderation](ownership-and-moderation.md). + +## The membership contract + +Creating a closed, gated or paid channel deploys a **PomboGate** — a minimal membership contract, one [EIP-1167 clone](https://eips.ethereum.org/EIPS/eip-1167) per channel, from a shared factory. The clone costs a single transaction at creation and then answers two questions for the rest of the channel's life: - **`isValidSignature`** ([ERC-1271]) — *was this author ever a member?* This is what the Streamr Network checks on every message, which is why the clone, not you, is the channel's publisher on the wire. - **`checkAccess`** — *does this address have access right now?* This drives encryption-key distribution and what the app shows you; it never invalidates messages already published. @@ -27,13 +44,13 @@ The consequence worth internalizing: **membership is sticky.** Being removed fro Because there is one grantee for every stream — the clone — adding or removing a member is a transaction on the gate, never a change to the channel's on-chain stream permissions. A Closed channel created with ten initial members costs one batched transaction, not ten. -## Closed channels +### Closed channels The owner (or a moderator) adds and removes Ethereum addresses. Members read and write for free; the owner pays the gas for each membership change. Closed channels are always **unlisted** — they never appear in Explore, and their name and description stay off-chain. This is the type to use for a private group. -## Token and NFT gates +### Token and NFT gates The gate reads the member's balance at the moment access is checked: @@ -46,7 +63,7 @@ Selling has a second effect that is easy to miss. The same live check is what vo Registering your membership on-chain is the one-transaction opt-in that prevents this: it records you permanently, so the contract keeps vouching for you as an author whatever you later do with the asset. It does not extend your *access* — that ends when you sell either way. Only the standing of what you already wrote. -## Paid channels +### Paid channels The owner sets a price in an ERC-20 and a period in days. A subscriber pays, and the contract records an expiry timestamp. @@ -54,24 +71,12 @@ The owner sets a price in an ERC-20 and a period in days. A subscriber pays, and - **Renewing early loses nothing** — a renewal extends from the current expiry, not from the moment you pay. - **There is no grace period.** Access ends at the recorded timestamp. The app warns you in the three days before, and shows an expired state with a renew action after. - **Payment is first-person.** The contract deliberately refuses third-party payment: paying for someone else would link payer and beneficiary on-chain forever. -- For tokens that support [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) permits (such as USDC on Polygon), paying is a single transaction; otherwise it is the usual approve-then-pay pair. Gates priced in POL are settled in wrapped POL, and the app wraps the shortfall for you. +- For tokens that support [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) permits (such as USDC on Polygon), paying is a single transaction; otherwise it is the usual approve-then-pay pair. Prices set in POL are settled in wrapped POL, and the app wraps the shortfall for you. :::caution[A subscription buys the future, not the past] New subscribers receive only the channel's **current** encryption key, so history published before they joined does not open for them — including files. This is deliberate: without it, one month's subscription would buy everything the channel ever said. See [Encryption](encryption.md#closed-gated-and-paid-channels). ::: -## Moderation - -Bans here are the enforceable kind: the contract stops vouching for the address, and the network stops accepting its messages. This is the difference the whole family exists for — elsewhere a ban is [advisory](channels-and-ownership.md#moderation). - -Owners can also appoint **moderators**, who add, remove and ban members but cannot erase history, act on the owner or other moderators, or appoint anyone themselves. - -## Discovery - -Gated and paid channels are the only private types that can be **listed** in Explore. They show their name, description, image and access marker — the condition or the price — so someone can see what they would be joining before acquiring anything. The conversation itself stays encrypted: a listing is a shop window, not a preview. - -Closed channels are never listed, whatever the owner picks. The general rules for discovery and curation are in [Channels and ownership](channels-and-ownership.md#discovery-and-curation). - ## Honest limits - **Revocation is not instant.** Encryption keys rotate on a weekly cadence, so someone who sells the gate asset or lets a subscription lapse can still read new messages until the next rotation — and rotations happen only while the channel's admin is online. The contract cuts them off immediately for everything the *contract* controls; the key layer catches up afterwards. diff --git a/docs/concepts/channel-anatomy.md b/docs/concepts/channel-anatomy.md new file mode 100644 index 0000000..48b8dae --- /dev/null +++ b/docs/concepts/channel-anatomy.md @@ -0,0 +1,59 @@ +--- +id: channel-anatomy +title: Anatomy of channels +description: The streams behind every channel and DM inbox — what each carries, what is stored, and where encryption applies. +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Anatomy of channels + +Every channel is a set of Streamr streams under the creator's address — three for open and protected channels, four when membership is contract-backed: + +| Stream | Stored? | Partitions | Purpose | +|---|---|---|---| +| `…-1` Message stream | Yes | 11 | Chat content (P0), edit/delete control messages (P1), file chunks (P2–P10) | +| `…-2` Ephemeral stream | No | 3 | Presence and typing (P0), live P2P media signals (P1) and data (P2) | +| `…-3` Admin stream | Yes | 3 | Moderation state (P0), channel image (P1), password challenge (P2) — writable only by the owner | +| `…-4` Keys stream | Yes | 1 | Distribution of the channel's encryption keys — closed, gated and paid channels only | + +The stream ID itself has the form `{ownerAddress}/{id}`, which is what makes ownership self-evident: the creator's address is part of the channel's name. What that ownership means in practice is the subject of [Channel access](channel-access.md) and [Ownership and moderation](ownership-and-moderation.md). + +The keys stream is stored on purpose: it is what makes joining a private channel asynchronous. A newcomer's key request waits there until some member comes online to answer it, instead of both having to be connected at the same moment. See [Encryption](encryption.md#closed-gated-and-paid-channels). + +The full picture per channel type — on-chain metadata, permissions, partitions, and where encryption applies (click to zoom): + + + + +![Open channel — multiple stream architecture: three streams with their on-chain metadata, permissions and partition layout](../assets/diagrams/open-channel.webp) + +*Content flows unencrypted; the four metadata variants cover visible/hidden and read-only combinations.* + + + + +![Protected channel — multiple stream architecture: identical stream layout, with every partition encrypted by a key derived from the shared password](../assets/diagrams/protected-channel.webp) + +*Same layout as an open channel, but every partition's content passes through AES-256-GCM with a PBKDF2-derived key (green path); the admin stream gains the password-challenge partition.* + + + + +Diagram coming soon — contract-backed channels add the keys stream to the layout above. Every stream's permission is held by one grantee, the channel's membership contract, and content is encrypted under a channel key distributed over `…-4`. See [The membership contract](channel-access.md#the-membership-contract). + + + + +## Direct messages: the mailbox model + +Each user has a personal **DM inbox** — a deterministic pair of streams derived from their address: a stored message stream (13 partitions: messages, sync, notifications, file chunks) and an ephemeral one for presence and typing. Its permissions are the inverse of a normal channel: **anyone can publish** into it, but **only the owner can subscribe** (read). The inbox metadata also carries the owner's encryption public key, which is what lets anyone seal a message to them. + +Sending a DM means encrypting a message for the recipient and dropping it into their inbox. Because the inbox is backed by a storage node, the recipient doesn't need to be online — they pull their inbox history when they next open the app. This is what makes Pombo's DMs asynchronous, like a phone's SMS inbox, rather than requiring both parties online. + +The inbox also doubles as your personal [cross-device sync](storage-and-sync.md#cross-device-sync) channel. + +![DM inbox — dual stream architecture: a stored inbox stream and an ephemeral stream, with ECDH-derived AES-256-GCM encryption on every partition](../assets/diagrams/dm-inbox.webp) + +*The inbox publishes the owner's encryption public key as on-chain metadata (green); every partition — messages, sync, notifications, file chunks — is sealed with a key derived via ECDH + HKDF before it touches the network.* diff --git a/docs/concepts/channels-and-ownership.md b/docs/concepts/channels-and-ownership.md deleted file mode 100644 index c206e81..0000000 --- a/docs/concepts/channels-and-ownership.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -id: channels-and-ownership -title: Channels and ownership -description: Channel types, on-chain ownership, retention, moderation and discovery. ---- - -# Channels and ownership - -## Channel types - -| | Open | Protected | Closed | Gated | Paid | -|---|---|---|---|---|---| -| Read access | Everyone | Password holders | Allowlisted addresses | Token / NFT holders | Active subscribers | -| Write access | Everyone | Password holders | Allowlisted addresses | Token / NFT holders | Active subscribers | -| Access enforced by | — (open by design) | Client-side encryption | On-chain gate contract | On-chain gate contract | On-chain gate contract | -| Content on the wire | Signed plaintext (Pombo format) | AES-256-GCM ciphertext | AES-256-GCM under a channel key | Same | Same | -| Cost to join | Free | Free | Free (owner pays gas to add you) | Holding the asset | The subscription price | - -- **Open channels** are public rooms. Anyone can read and write. -- **Protected channels** look public to the network, but every message is encrypted client-side with AES-256-GCM using a key derived from the shared password (PBKDF2, 310,000 iterations). Without the password, the network carries only ciphertext. One caveat: each protected channel publishes a verification challenge that anyone can fetch and test guesses against offline — so the channel is exactly as secret as the password is strong. -- **Closed, Gated and Paid channels** enforce membership *on-chain*, through a small membership contract deployed per channel. This is the only family where access control is enforceable at the protocol level — the network itself refuses messages from authors the contract does not vouch for — and content is encrypted under a channel key that only members obtain. They differ only in the rule the contract enforces: an owner-managed allowlist, holding a token or NFT, or an active subscription. Full detail in [Gated and paid channels](gated-and-paid-channels.md). -- A channel can also be created **read-only** (announcement style): everyone can read, only the owner posts. - -:::note[Terminology] -Pombo's source code calls the pre-contract generation of closed channels *native channels*. Those still work, but can no longer be created: closed, gated and paid channels are all backed by a gate contract. -::: - -## Ownership - -The channel's stream ID literally contains the creator's address: `{ownerAddress}/{id}`. Ownership and permissions are entries in the Streamr registry contracts on Polygon PoS — public blockchain state that no third party, including Pombo, can alter or confiscate. - -Two consequences worth understanding: - -- **Your channels survive Pombo.** Any client that speaks the protocol can read them. -- **Creating a channel is public.** The creator's address is permanently visible in the channel ID. If you don't want a channel linked to your main identity, create it from a separate account. - -## Retention - -Channel owners choose how long storage nodes keep message history (1–365 days, default **180**; changeable later in channel settings). Older messages age out of storage; the live network never held them anyway. - -## Moderation - -Owners moderate through the channel's admin stream: **ban members, hide messages, pin messages**. Clients apply this state when rendering. In gate-backed channels the owner can also appoint **moderators**, who manage membership and bans but cannot erase history or appoint further moderators. - -:::caution[Honest limits] -In **open and protected channels**, moderation is *cosmetic*: compliant clients hide banned users' messages, but since accounts are free and instant, a banned user can return with a new address in one click — and the messages still exist on the network for non-compliant clients. Bans are only truly enforceable in **gate-backed channels** (Closed, Gated, Paid), where the contract itself stops refusing to vouch for the banned address. - -Two caveats even there: in open channels the ban list is world-readable, and a ban cuts *future* access without unsigning what the member already published. Removing their history is a separate, explicit owner action, available only in Closed channels. -::: - -## Discovery and curation - -The **Explore** view lists channels that opted into discovery: listing is a choice at creation time, channels are **unlisted by default**, and closed channels are never listed. Gated and paid channels *can* be listed, as [storefronts](gated-and-paid-channels.md#discovery). An unlisted channel publishes no name or description on-chain — only the owner address and creation time are visible. On top of that, the Pombo interface applies a curation manifest — a list of pinned channels and channels hidden *from discovery*. Hiding is interface-level only: a hidden channel still exists on the network and remains reachable by direct link. This reflects Pombo's design split: the *protocol* is neutral and uncensorable; the *interface* curates what it presents. diff --git a/docs/concepts/client.md b/docs/concepts/client.md new file mode 100644 index 0000000..2ed5606 --- /dev/null +++ b/docs/concepts/client.md @@ -0,0 +1,40 @@ +--- +id: client +title: Client +description: The system in one narrative — a client on your device, Streamr for transport, storage nodes for history, Polygon for ownership. +--- + +# Client + +Pombo has no message backend: no server receives, routes or authorizes your conversations. Everything you'd expect a backend to do is done either by the client on your device or by open infrastructure that anyone can run. The client sits on top of three layers: + +``` +┌─────────────────────────────────────────────┐ +│ Pombo client (browser PWA / Android app) │ +│ identity · encryption · UI │ +├─────────────────────────────────────────────┤ +│ Streamr Network → message transport │ +│ (P2P pub/sub) & delivery │ +├─────────────────────────────────────────────┤ +│ Storage nodes → message history │ +│ (Streamr + Cassandra) & offline delivery │ +├─────────────────────────────────────────────┤ +│ Polygon PoS → ownership & │ +│ (Streamr registries) permissions │ +└─────────────────────────────────────────────┘ +``` + +Follow one message through the system and the whole architecture appears. You type in a channel; the client encrypts the message if the channel calls for it, signs it, and publishes it to one of the channel's *streams* on the **Streamr Network** — a peer-to-peer pub/sub network, so it propagates directly between subscribers with no relay server in the middle. A **storage node** archiving that stream keeps a copy, which is how members who were offline read it later. And the reason the network accepted the message at all — who owns the channel, who may publish to it — is a record on **Polygon PoS**, public blockchain state rather than a row in someone's database. + +Each layer in one line: + +- **Transport — Streamr Network.** Messages are published to streams (topics) and propagate peer-to-peer between subscribers. +- **Ownership — Polygon PoS.** Streams are registered on-chain in the Streamr registry contracts. On-chain writes (creating a channel, granting membership) cost a small fee in POL; everything else is free. +- **Persistence — storage nodes.** Streamr nodes running the storage plugin retain stream history, so messages reach people who were offline. See [Storage & Sync](storage-and-sync.md). +- **Cryptography — your device.** Keys are generated and used locally. All encryption and signing happens client-side before anything is published. Two deliberate choices are worth knowing: the Streamr SDK's own encryption layer is **disabled** — all confidentiality is applied at the app layer, as [Encryption](encryption.md) describes — and channel messages are published under a per-channel throwaway key carrying a signed proof of the real account inside the payload (in contract-backed channels, the publisher is the membership contract instead; see [Privacy model](privacy-model.md)). + +Beyond these layers, the client talks to a small set of auxiliary services — public RPC endpoints, The Graph, the push relay ([Push notifications](notifications.md) explains that whole subsystem), the Explore curation manifest — none of which handle message content. The full list and what each one sees is in the [threat model](../security/threat-model.md). + +## The interface is replaceable + +Pombo (the app at app.pombo.cc) is *an* interface to this protocol, not *the* system. All of the state — identities, channels, permissions, history — lives on public networks. Anyone can build another client against the same streams, and if the Pombo interface vanished, the protocol and your data would remain. diff --git a/docs/concepts/encryption.md b/docs/concepts/encryption.md index 3de7ee0..b387f75 100644 --- a/docs/concepts/encryption.md +++ b/docs/concepts/encryption.md @@ -1,13 +1,15 @@ --- id: encryption title: Encryption -description: How Pombo encrypts DMs (ECDH + sealed sender), protected channels, epoch-keyed private channels, and media. +description: How Pombo encrypts DMs (ECDH + sealed sender), protected channels, epoch-keyed private channels, and local state. --- # Encryption All cryptography in Pombo runs **on your device**. The web app uses the browser's built-in WebCrypto for symmetric operations and audited Ethereum libraries for elliptic-curve operations; the Android app uses the platform's native crypto for symmetric operations and runs the same Ethereum library code for elliptic-curve operations, guaranteeing byte-for-byte compatibility between the two. Private keys never leave your device; the only key material ever published is your *public* key, attached to your DM inbox so others can encrypt to you. +This page covers what encryption protects: the *content*. The separate question — who can see that it was **you** — is the subject of the [privacy model](privacy-model.md). + ## Direct messages: end-to-end, sealed sender DMs use the strongest protection in Pombo: @@ -42,7 +44,7 @@ Channels differ: their epoch keys do rotate, see [Rotation and history](#rotatio ## Protected channels -Password-channel messages are encrypted with **AES-256-GCM** under a key derived from the shared password via **PBKDF2 (310,000 iterations, SHA-256)**. The network and storage nodes carry only ciphertext. Anyone who has the password can derive the key — the secrecy of the channel is exactly the secrecy of its password. +Protected-channel messages are encrypted with **AES-256-GCM** under a key derived from the shared password via **PBKDF2 (310,000 iterations, SHA-256)**. The network and storage nodes carry only ciphertext. Anyone who has the password can derive the key — the secrecy of the channel is exactly the secrecy of its password. ## Closed, gated and paid channels diff --git a/docs/guides/notifications.md b/docs/concepts/notifications.md similarity index 63% rename from docs/guides/notifications.md rename to docs/concepts/notifications.md index 4dc722b..70ee11a 100644 --- a/docs/guides/notifications.md +++ b/docs/concepts/notifications.md @@ -1,12 +1,12 @@ --- id: notifications title: Notifications -description: How Pombo delivers push notifications without learning who you are — wake signals, k-anonymity, and a blind relay. +description: How Pombo delivers push notifications without learning who you are — wake signals, k-anonymity, a blind relay, and the per-platform quirks. --- # Notifications -Push notifications are the hardest feature to do privately: the platforms (Google FCM, Apple APNs) sit in the delivery path by construction. Pombo's design goal is that **no party in the push path — relay, Google, or Apple — learns who is messaging whom, or what was said.** +Pombo can wake your device when a channel you follow or your DM inbox gets a message. Push is the hardest feature to do privately: the platforms (Google FCM, Apple APNs) sit in the delivery path by construction. Pombo's design goal is that **no party in the push path — relay, Google, or Apple — learns who is messaging whom, or what was said.** ## How it works @@ -14,7 +14,7 @@ Push notifications are the hardest feature to do privately: the platforms (Googl 2. A **relay** (an open-source, community-runnable server) listens for wake signals, checks the proof-of-work, and fires a Web Push to *every* device registered under that tag. 3. Your device wakes and checks whether there's actually a message for you — the service worker queries the channel's storage node over HTTPS. Only then does a notification render; message content never travels through the push system. -Push is **opt-in per channel**; your own DM inbox is registered automatically, registrations refresh every 6 hours, and the proof-of-work is bound to a 10-second epoch so wake signals can't be replayed. The registration itself — the one message that carries your push endpoint — is **encrypted to the relay's public key** (published in the push stream's on-chain metadata and verified against the relay's known address before use), and is sent under a throwaway key: nothing on the stream links it to an account, and only the relay can read the endpoint inside. +Push is opt-in per channel; your own DM inbox is registered automatically. Registrations refresh every 6 hours, and the proof-of-work is bound to a 10-second epoch so wake signals can't be replayed. The registration itself — the one message that carries your push endpoint — is **encrypted to the relay's public key** (published in the push stream's on-chain metadata and verified against the relay's known address before use), and is sent under a throwaway key: nothing on the stream links it to an account, and only the relay can read the endpoint inside. The full flow, with the k-anonymity collision made visible — Alice and Charlie share tag `0x75` for different channels, so both devices wake and each verifies locally; Bob's tag doesn't match, so his device never wakes (click to zoom): @@ -32,7 +32,7 @@ The 1-byte tag is the key trick: with only 256 possible tags, many users share e | Google / Apple | That your device runs Pombo | Content, sender, which channels/contacts you have | | Network observer | Wake-signal timing (see caveat below) | Content, sender, recipient beyond the tag bucket | -The honest residuals: platform push tokens inherently tell Google/Apple *that* you use Pombo. The relay stores your push token keyed to your tags, so it can link the notifications one device receives over time (though not who that device belongs to). The wake-verification step means the storage node sees which stream your device polls, and when. Registrations are encrypted to the relay's key, but wake signals travel unencrypted by design (they must stay cheap to send and carry nothing sensitive) — the push stream's subscribe permission is a network-level restriction, so a determined observer running a modified node could still watch wake-signal timing. The design assumes this: even a full view of the stream yields only tag buckets and timestamps. Small user bases also mean smaller anonymity sets. +The honest residuals: platform push tokens inherently tell Google/Apple *that* you use Pombo. The relay stores your push token keyed to your tags, so it can link the notifications one device receives over time (though not who that device belongs to) — what it holds and for how long is stated in the [privacy policy](/legal/privacy-policy). The wake-verification step means the storage node sees which stream your device polls, and when. Registrations are encrypted to the relay's key, but wake signals travel unencrypted by design (they must stay cheap to send and carry nothing sensitive) — the push stream's subscribe permission is a network-level restriction, so a determined observer running a modified node could still watch wake-signal timing. The design assumes this: even a full view of the stream yields only tag buckets and timestamps. Small user bases also mean smaller anonymity sets. ## Platform notes @@ -40,6 +40,8 @@ The honest residuals: platform push tokens inherently tell Google/Apple *that* y - **Android app**: uses Firebase Cloud Messaging. Battery optimizations (Doze, Adaptive Battery) can delay delivery by minutes — exempting Pombo from battery optimization helps. - **Relay availability**: today one community relay serves the network; if it's down, push pauses (messages are unaffected — they're waiting on the network). The relay is open source and anyone can run one — see [Run a push relay](../operators/run-a-relay.md). +Even when everything is set up right, push is best-effort: delivery can lag from seconds to minutes depending on the platform. [Troubleshooting](../help/troubleshooting.md#im-not-receiving-push-notifications) has the checklist. + ## In-app notifications and invites Notifications *inside* Pombo (such as channel invites) don't use the push system at all — they're delivered through your DM inbox, end-to-end encrypted like any DM. diff --git a/docs/concepts/ownership-and-moderation.md b/docs/concepts/ownership-and-moderation.md new file mode 100644 index 0000000..6342bd8 --- /dev/null +++ b/docs/concepts/ownership-and-moderation.md @@ -0,0 +1,33 @@ +--- +id: ownership-and-moderation +title: Ownership and moderation +description: On-chain ownership, what a ban is worth in each access model, and how discovery and curation work. +--- + +# Ownership and moderation + +## Ownership + +The channel's stream ID literally contains the creator's address: `{ownerAddress}/{id}`. Ownership and permissions are entries in the Streamr registry contracts on Polygon PoS — public blockchain state that no third party, including Pombo, can alter or confiscate. + +Two consequences worth understanding: + +- **Your channels survive Pombo.** Any client that speaks the protocol can read them. +- **Creating a channel is public.** The creator's address is permanently visible in the channel ID. If you don't want a channel linked to your main identity, create it from a separate account. + +## Moderation + +Owners moderate through the channel's admin stream, which only they can write to: **ban members, hide messages, pin messages**. Clients apply this state when rendering. + +How much a ban is worth depends entirely on the channel's [access model](channel-access.md): + +- In **open and protected channels**, moderation is *advisory*: compliant clients hide banned users' messages, but since accounts are free and instant, a banned user can return with a new address in one click — and the messages still exist on the network for non-compliant clients. In open channels the ban list itself is world-readable. +- In **contract-backed channels** (Closed, Gated, Paid), bans are the enforceable kind: the contract stops vouching for the address, and the network stops accepting its messages. This is the difference [that family](channel-access.md#the-membership-contract) exists for. + +Even an enforceable ban cuts *future* access without unsigning what the member already published. Removing their history is a separate, explicit owner action, available only in Closed channels. And their ability to *read* ends at the channel's next key rotation, not the instant of the ban — see the [honest limits](channel-access.md#honest-limits). + +In contract-backed channels the owner can also appoint **moderators**, who add, remove and ban members but cannot erase history, act on the owner or other moderators, or appoint anyone themselves. + +## Discovery and curation + +The **Explore** view lists channels that opted into discovery: listing is a choice at creation time, channels are **unlisted by default**, and closed channels are never listed. Gated and paid channels *can* be listed, as storefronts: the listing shows the channel's name, description, image and access marker — the condition or the price — so someone can see what they would be joining before acquiring anything, while the conversation itself stays encrypted; a shop window, not a preview. An unlisted channel publishes no name or description on-chain — only the owner address and creation time are visible. On top of that, the Pombo interface applies a curation manifest — a list of pinned channels and channels hidden *from discovery*. Hiding is interface-level only: a hidden channel still exists on the network and remains reachable by direct link. This reflects Pombo's design split: the *protocol* is neutral and uncensorable; the *interface* curates what it presents. diff --git a/docs/concepts/privacy-model.md b/docs/concepts/privacy-model.md index 9e1ae8c..bc3a2c2 100644 --- a/docs/concepts/privacy-model.md +++ b/docs/concepts/privacy-model.md @@ -1,14 +1,15 @@ --- id: privacy-model title: Privacy model +sidebar_label: Privacy description: Ephemeral publisher identities, sealed sender, and what an observer of the network can actually see. --- # Privacy model -Pombo's privacy design has one organizing idea: **your real account should not be visible on the wire unless the context deliberately makes it so.** +Pombo's privacy design has one organizing idea: **your real account should not be visible on the wire unless the context deliberately makes it so.** [Encryption](encryption.md) protects *what you said*; this page is about who can see *that it was you*, and what an observer of the network actually learns. -This page explains how the protections work. For the separate question of what data leaves your device and who receives it, see the [privacy policy](/legal/privacy-policy). +For the separate question of what data leaves your device and who receives it, see the [privacy policy](/legal/privacy-policy); for what an attacker can achieve, the [threat model](../security/threat-model.md). ## Ephemeral publisher identities @@ -20,11 +21,11 @@ Your real identity travels as a **publisher proof** — a signature by your acco |---|---|---|---| | Open channel | Ephemeral, rotates per join | Plaintext in the message | Anyone who parses the Pombo format | | Protected channel | Ephemeral | Inside the AES envelope | Channel members only | -| Direct message | Ephemeral (sealed sender) | Inside the ECDH envelope | The recipient only | +| Direct message | Ephemeral ([sealed sender](encryption.md#sealed-sender)) | Inside the ECDH envelope | The recipient only | | Closed / gated / paid channel | The channel's membership contract | Your account's signature on the message itself | Anyone | | Read-only channel (owner posts) | Your real account | — (not needed) | Anyone | -So in a **protected channel**, an outside observer sees only ciphertext published by random throwaway addresses. In a **DM**, even your recipient's inbox reveals nothing about you to observers. In a **open channel**, your identity is readable — deliberately, because public rooms are public — but only at the application layer, not as raw wallet signatures on the transport. +So in a **protected channel**, an outside observer sees only ciphertext published by random throwaway addresses. In a **DM**, even your recipient's inbox reveals nothing about you to observers. In an **open channel**, your identity is readable — deliberately, because public rooms are public — but only at the application layer, not as raw wallet signatures on the transport. ## Contract-backed channels @@ -47,7 +48,7 @@ Pombo intentionally has **no per-channel "anonymous mode" toggle**. If you want ## Metadata protections that are on by default - **ENS lookups are decoyed** in the direction that runs constantly: resolving the name behind an address you see mixes the real lookup with decoy addresses, in shuffled order, so RPC operators can't tell which one you cared about. Resolving a name *you typed* — starting a DM, sending an invite — is not covered, and the provider sees exactly the name you asked for. -- **Push notifications carry no content** and use k-anonymity tags so the relay can't tell who a notification is really for — and both wake signals and registrations are published under a fresh throwaway key. See [Notifications](../guides/notifications.md). +- **Push notifications carry no content** and use k-anonymity tags so the relay can't tell who a notification is really for — see [Push notifications](notifications.md). - **Cross-device sync is sealed to yourself**: state snapshots published to your own inbox are encrypted so only your key can read them, and any payload not authored by your own wallet is rejected. - **Network node IDs are not derived from your wallet.** diff --git a/docs/concepts/storage-and-persistence.md b/docs/concepts/storage-and-sync.md similarity index 66% rename from docs/concepts/storage-and-persistence.md rename to docs/concepts/storage-and-sync.md index f003326..500e4d9 100644 --- a/docs/concepts/storage-and-persistence.md +++ b/docs/concepts/storage-and-sync.md @@ -1,16 +1,16 @@ --- -id: storage-and-persistence -title: Storage and persistence -description: How message history persists — storage nodes, retention, local encrypted storage, and cross-device sync. +id: storage-and-sync +title: Storage & Sync +description: How history persists and devices converge — storage nodes, retention, local state, and self-encrypted sync. --- -# Storage and persistence +# Storage & Sync The Streamr Network delivers messages live; it does not remember them. Persistence comes from **storage nodes** — Streamr nodes that archive stream history and serve it back on request. ## Storage nodes -When a channel (or DM inbox) is created, its stored streams are assigned to one or more storage nodes (changeable later in settings), which archive messages for the configured retention period (default **180 days**, chosen by the owner). When you open a channel, Pombo fetches recent history from the storage node, then follows the live stream. +When a channel (or DM inbox) is created, its stored streams are assigned to one or more storage nodes (changeable later in settings). When you open a channel, Pombo fetches recent history from the storage node, then follows the live stream. - **Default provider:** Pombo operates a storage cluster (two servers with a replicated Cassandra database behind a single node identity) that channels use out of the box. - **Custom storage nodes:** channel owners can point a channel (or their own DM inbox) at any Streamr storage node instead. The node must be web-reachable: HTTPS with a real hostname, since browsers cannot fetch plain-HTTP or raw-IP endpoints. See [Run a storage node](../operators/run-a-storage-node.md). @@ -19,28 +19,22 @@ What storage nodes hold is exactly what the network carried: ciphertext for prot Reading is a direct HTTPS request from your device to the node, so whoever runs it sees which channels you open and when. Choosing a node is choosing who gets that. -Closed, gated and paid channels store one thing more: their keys stream, which is what lets someone join and receive the channel's encryption key without another member being online at that exact moment. +## Retention + +Channel owners choose how long storage nodes keep message history (1–365 days, default **180**; changeable later in channel settings). Older messages age out of storage; the live network never held them anyway. A DM inbox has the same setting, chosen by its owner — the [threat model](../security/threat-model.md#shrinking-what-a-seizure-would-find) covers retention as a privacy lever. ## What lives on your device Everything personal starts local, and is encrypted at rest. With a DM inbox it also travels, sealed to your own key, so your other devices can read it — see [Cross-device sync](#cross-device-sync) below. -- Your key and app state (contacts, channel list, settings, image ledger) are encrypted at rest and isolated per account — in the web app as a scrypt keystore plus AES-256-GCM-encrypted IndexedDB; on Android in the platform's encrypted storage under the Android Keystore. +- Your key and app state (contacts, channel list, settings, image ledger) are encrypted at rest and isolated per account — in the web app as a scrypt keystore plus AES-256-GCM-encrypted IndexedDB; on Android in the platform's encrypted storage under the Android Keystore. Detail in [Encryption](encryption.md#what-is-protected-locally). - Channel messages are deliberately **not** cached locally — they are re-fetched from storage nodes each time you open a channel. - Sent DMs are kept locally — a quirk of the mailbox model: your sent messages live in the *recipient's* inbox, so your own copy is your record of them. ## Cross-device sync -Your DM inbox doubles as a personal sync channel: the app periodically writes **self-encrypted snapshots** of your state to it. Log in on another device with the same account and it pulls those snapshots, merges them, and converges — contacts, channels, settings, sent messages (read state stays per-device). Only your key can decrypt them. - -## Two file-sharing transports - -Pombo moves files two different ways, with different persistence: +Your DM inbox doubles as a personal sync channel: the app periodically writes **self-encrypted snapshots** of your state to it. Log in on another device with the same account and it pulls those snapshots, merges them, and converges — contacts, channels, settings, sent messages (read state stays per-device). Only your key can decrypt them, and any payload not authored by your own wallet is rejected. -| | Mesh (live P2P) | Persistent (storage-based) | -|---|---|---| -| How | Swarm transfer between online peers | File chunked and archived on the storage node | -| Sender must be online? | Yes (someone must seed) | Only during upload | -| Lifetime | While seeded (a seeder's local copy is kept up to 7 days, in a 700 MB cache) | Channel's retention period | +## Files travel two ways -See [File sharing](../guides/file-sharing.md) for details and limits. +Pombo moves files either through the storage node (chunked, archived, downloadable for the retention period) or over a live P2P mesh between online peers (nothing archived, someone must seed). You choose per file when sending — [File sharing](../guides/file-sharing.md) has the comparison and limits. diff --git a/docs/getting-started/first-steps.md b/docs/getting-started/first-steps.md index 5dbf465..b57819d 100644 --- a/docs/getting-started/first-steps.md +++ b/docs/getting-started/first-steps.md @@ -14,7 +14,7 @@ Channels are Pombo's group spaces. You can reach them three ways: - **A direct link**: any channel can be shared as a URL. Opening it takes you straight there. Try the official Pombo channel: [app.pombo.cc/#/channel/…](https://app.pombo.cc/#/channel/0xae340e799e8151f6a4999d245e466197aa217667/9862eb7bd898f338-1) - **The channel ID**: paste it into **Join Channel**. Every channel has one, in the form `0x…/name`, and it works whether or not the channel is listed. -Channels are [unlisted by default](../concepts/channels-and-ownership.md#discovery-and-curation), and an unlisted channel never appears in Explore. For those, a link or the channel ID is the only way in. +Channels are [unlisted by default](../concepts/ownership-and-moderation.md#discovery-and-curation), and an unlisted channel never appears in Explore. For those, a link or the channel ID is the only way in. There are five kinds of channels; the app shows which is which: @@ -26,7 +26,7 @@ There are five kinds of channels; the app shows which is which: | **Gated** | Anyone holding the required token or NFT | The same | | **Paid** | Anyone with an active subscription | The same | -More detail in [Channels and ownership](../concepts/channels-and-ownership.md), and in [Gated and paid channels](../concepts/gated-and-paid-channels.md) for the last three. +More detail in [Channel access](../concepts/channel-access.md). ## Send a message @@ -38,7 +38,7 @@ Start by creating your **DM inbox**. It is what lets you receive messages and re With that done, you can write to anyone who has an inbox of their own. Enter their address or ENS name and send. They don't need to be online, and there is no friend request to accept first. -DMs are end-to-end encrypted with sealed sender: only the recipient can read the message, and only the recipient learns who sent it. See [Encryption](../concepts/encryption.md). +DMs are end-to-end encrypted with sealed sender: only the recipient can read the message, and only the recipient learns who sent it. The full picture — the inbox, blocking, where messages live — is in [Direct messages](../guides/direct-messages.md). ## Create your own channel diff --git a/docs/getting-started/identity.md b/docs/getting-started/identity.md index d3f4626..52eda5c 100644 --- a/docs/getting-started/identity.md +++ b/docs/getting-started/identity.md @@ -1,7 +1,7 @@ --- id: identity title: Your identity and keys -description: How Pombo accounts work — locally generated Ethereum keypairs, no sign-up, no recovery service. +description: How Pombo accounts work — locally generated keypairs, no sign-up, no recovery service. --- # Your identity and keys @@ -12,9 +12,10 @@ Your address (`0x…`) is your identity on the network: it's how people DM you, ## How your key is protected -- Your private key is stored **encrypted at rest** using the standard Ethereum Keystore V3 format (scrypt key derivation + AES), unlocked with the password you choose. -- On Android there is no password: the key is stored in encrypted preferences under a device-bound key from the Android Keystore. The honest trade-off is that anything running *as the Pombo app* on an unlocked device can use the key (this is what lets push notifications decrypt in the background); sensitive screens are gated by your device lock. -- All other local app data (contacts, channel list, settings) is additionally encrypted with AES-256-GCM using a key derived from a deterministic wallet signature — one account cannot read another account's local data. +- On the **web**, your private key is stored encrypted, and unlocked with the password you choose. +- On **Android** there is no password: the key is protected by the device itself, through its hardware keystore. The honest trade-off is that anything running *as the Pombo app* on an unlocked device can use the key (this is what lets push notifications decrypt in the background); sensitive screens are gated by your device lock. + +Everything else the app stores — contacts, channels, settings — is also encrypted, separately for each account. The mechanisms are in [What is protected locally](../concepts/encryption.md#what-is-protected-locally). ## There is no "Forgot password" @@ -29,7 +30,7 @@ Export a backup file as soon as you create your account: see [Backup and recover - You can create and switch between **multiple named accounts** on the same device, each with its own isolated data. - **Guest mode** gives you a throwaway account with no persistence — useful for having a look around without creating anything. -Because accounts are free and instant, switching accounts is also Pombo's answer to pseudonymity: rather than a per-channel "anonymous mode", you simply use a different account for contexts you want to keep separate. See [Privacy model](../concepts/privacy-model.md) for why this matters. +Because accounts are free and instant, switching accounts is also Pombo's answer to pseudonymity: rather than a per-channel "anonymous mode", you simply use a different account for contexts you want to keep separate. See the [privacy model](../concepts/privacy-model.md#anonymity--another-account) for why this matters. ## Using an existing wallet diff --git a/docs/guides/backup-and-recovery.md b/docs/guides/backup-and-recovery.md index cc37321..fe6bcd7 100644 --- a/docs/guides/backup-and-recovery.md +++ b/docs/guides/backup-and-recovery.md @@ -14,16 +14,16 @@ Nobody holds your key and there is no custodian, so there is **no "forgot passwo In settings, export your account backup. You get a single portable file containing: -- your **encrypted key** (Keystore V3), +- your **encrypted key**, - your **encrypted app state** (contacts, channels, settings, your sent messages), and - the **encryption keys of the private channels you belong to**. -The file is encrypted with scrypt under your **account password** (verified before export) — without it, the file is useless to whoever finds it. Treat it with the same care as the key itself. +The file is encrypted under your **account password** (verified before export) — without it, the file is useless to whoever finds it. Treat it with the same care as the key itself. The backup deliberately carries only what cannot be recovered from elsewhere: anything the network still holds is left out and re-downloaded on restore. Media you sent in DMs is the one judgement call — it lives only in the recipient's inbox, so it is included by default, behind a toggle you can turn off to keep the file small. :::info[Why channel keys are in there] -Paid channels never re-distribute past keys, and the announcements that anchor older ones expire with the channel's retention. For history in those channels, your backup is the only recovery path — see [Encryption](../concepts/encryption.md#closed-gated-and-paid-channels). +Paid channels never re-distribute past keys, and the announcements that anchor older ones expire with the channel's retention. For history in those channels, your backup is the only recovery path — see [Encryption](../concepts/encryption.md#rotation-and-history). ::: ## Restoring @@ -37,7 +37,7 @@ On any device — new phone, fresh browser, after a reinstall — import the bac ## Multi-device sync -You don't need to shuttle backup files between devices you use actively. Import your account once on each device; from then on, Pombo syncs state automatically through your own DM inbox — including your private channels' encryption keys, so a second device doesn't have to re-request them — self-encrypted so only your key can read it (see [Storage and persistence](../concepts/storage-and-persistence.md#cross-device-sync)). +You don't need to shuttle backup files between devices you use actively. Import your account once on each device; from then on, Pombo syncs state automatically through your own DM inbox — including your private channels' encryption keys, so a second device doesn't have to re-request them — self-encrypted so only your key can read it (see [Cross-device sync](../concepts/storage-and-sync.md#cross-device-sync)). ## Practical advice diff --git a/docs/guides/direct-messages.md b/docs/guides/direct-messages.md new file mode 100644 index 0000000..7540ebc --- /dev/null +++ b/docs/guides/direct-messages.md @@ -0,0 +1,39 @@ +--- +id: direct-messages +title: Direct messages +description: Set up your DM inbox, message anyone, and know what a public mailbox does and doesn't mean. +--- + +# Direct messages + +DMs in Pombo travel live, peer-to-peer, when you're both online — and behind every conversation sits a mailbox: each message also lands in the recipient's inbox, where it waits if they're away. Neither of you ever needs to be online at the same time. + +## Set up your inbox + +To *receive* DMs you need a **DM inbox** of your own. Creating it registers streams on-chain, which costs a small POL fee — once, and never again. The inbox is more than a mailbox: it is also where channel invites arrive, and it is what [keeps your devices in sync](../concepts/storage-and-sync.md#cross-device-sync). + +Two things to know before creating it: + +- **Sync starts with it.** Once the inbox exists, Pombo automatically publishes self-encrypted snapshots of your state to it — contacts, channels, settings — so a second device can converge. Only your key can read them, and you can set sync to manual in Settings. +- **Guest mode has no inbox**, so guests can't receive DMs or sync devices. + +## Send a message + +Enter the recipient's address or ENS name and write. They don't need to be online, and there is no friend request to accept first — anyone with an inbox can be written to. Images and files ride along, sealed the same way as the text. + +What protects a DM: end-to-end encryption with **sealed sender** — only the recipient can read the message, and only the recipient learns who sent it; the network sees ciphertext from a throwaway address. The mechanics, and the one trade-off worth knowing (there is no forward secrecy in DMs), are in [Encryption](../concepts/encryption.md#direct-messages-end-to-end-sealed-sender). + +## Living with a public mailbox + +Your inbox is **public-write by design** — that is what lets a stranger answer your channel post or accept your invite without a handshake. The honest consequence is that anyone can write to you, including people you'd rather not hear from, and there is no server-side filter to change that. + +What you *can* do is **block a peer**: your clients stop showing you anything from them, and the block list syncs across your devices. Blocking is client-side — it hides, it cannot stop a deposit from arriving at a public mailbox. The wider spam question is an [open problem](../security/threat-model.md#known-open-problems) the threat model states plainly. + +## Where your messages live + +The mailbox model has one asymmetry worth understanding: + +- **What you receive** sits in *your* inbox, under the storage node and [retention period](../concepts/storage-and-sync.md#retention) you set. +- **What you send** sits in the *recipient's* inbox, under their node and their retention — which is why your own device keeps a local copy of your sent messages: it is your only record of them. + +History you receive re-downloads from storage on any device; if privacy matters more than convenience, a short retention on your inbox shrinks what it holds — the [threat model](../security/threat-model.md#shrinking-what-a-seizure-would-find) covers that trade. diff --git a/docs/guides/file-sharing.md b/docs/guides/file-sharing.md index 4cb839e..96a5419 100644 --- a/docs/guides/file-sharing.md +++ b/docs/guides/file-sharing.md @@ -28,7 +28,7 @@ For live transfer between online peers: a pull-based swarm over the channel's ep In closed, gated and paid channels, both transports are sealed with the channel's encryption key — the same one that protects messages — so storage nodes and the network carry ciphertext either way. -One consequence to expect: because those keys rotate, **a member who joins after a rotation cannot open files shared before it**, exactly as they cannot read the messages from that period. In a paid channel that is the rule rather than an edge case — a subscription buys the future. See [Encryption](../concepts/encryption.md#closed-gated-and-paid-channels). +One consequence to expect: because those keys rotate, **a member who joins after a rotation cannot open files shared before it**, exactly as they cannot read the messages from that period. In a paid channel that is the rule rather than an edge case — a subscription buys the future. See [Encryption](../concepts/encryption.md#rotation-and-history). ## Managing transfers diff --git a/docs/guides/managing-channels.md b/docs/guides/managing-channels.md index f137e9b..89f4373 100644 --- a/docs/guides/managing-channels.md +++ b/docs/guides/managing-channels.md @@ -10,7 +10,7 @@ description: Create a channel, choose its type and retention, manage members and Creating a channel registers its streams on Polygon PoS, which costs a network fee (a few cents' worth of POL — the app estimates the cost and checks your balance before starting). You'll choose: -- **Type**: open, protected, closed, gated or paid. Pick based on who should get in; the trade-offs are explained in [Channels and ownership](../concepts/channels-and-ownership.md). Any type can also be made **read-only** (only you post). The last three deploy a membership contract as part of creation, which adds one transaction to the cost — see [Gated and paid channels](../concepts/gated-and-paid-channels.md). +- **Type**: open, protected, closed, gated or paid. Pick based on who should get in; the trade-offs are explained in [Channel access](../concepts/channel-access.md). Any type can also be made **read-only** (only you post). The last three deploy a membership contract as part of creation, which adds one transaction to the cost — see [The membership contract](../concepts/channel-access.md#the-membership-contract). - **Visibility** — whether the channel is listed in Explore. Channels are unlisted by default; listed channels also carry a description, language and category. - **Retention** — how long storage keeps history (1–365 days, default 180). - **Storage node** — the default Pombo cluster, or a custom storage node identified by its Ethereum address (the app verifies on-chain that the node publishes an HTTPS endpoint browsers can reach). @@ -23,7 +23,7 @@ The channel is owned by the account that creates it. Ownership is on-chain and c In a closed channel, you control access by **adding and removing Ethereum addresses** in the channel's membership contract. Each change is a Polygon transaction paid by you (the owner); adding several people at once is a single transaction. Members join and chat for free. -This is the channel type to use when a ban has to actually stick: once you remove someone, the network itself refuses their messages. Two things to know before relying on it — what they already published stays published unless you also erase it, and their ability to *read* ends at the channel's next key rotation, not the instant you remove them. Both are explained in [Gated and paid channels](../concepts/gated-and-paid-channels.md). +This is the channel type to use when a ban has to actually stick: once you remove someone, the network itself refuses their messages. Two things to know before relying on it — what they already published stays published unless you also erase it, and their ability to *read* ends at the channel's next key rotation, not the instant you remove them. Both are explained in [Channel access](../concepts/channel-access.md#the-membership-contract). In gated and paid channels there is no member list to manage: the contract's rule — holding the asset, or an active subscription — decides. What you manage there is bans and, if you want the help, moderators. @@ -39,7 +39,7 @@ As owner, you can: Moderation state is published on the channel's admin stream, which only you can write to. :::caution -In open and protected channels, remember that bans are advisory: a determined user can rejoin with a fresh account, and the underlying messages remain on the network. For enforceable access control, use a contract-backed channel. See [the honest limits](../concepts/channels-and-ownership.md#moderation). +In open and protected channels, remember that bans are advisory: a determined user can rejoin with a fresh account, and the underlying messages remain on the network. For enforceable access control, use a contract-backed channel. See [Moderation](../concepts/ownership-and-moderation.md#moderation). ::: ## Inviting people diff --git a/docs/help/faq.md b/docs/help/faq.md index ffa8266..cd63639 100644 --- a/docs/help/faq.md +++ b/docs/help/faq.md @@ -6,16 +6,14 @@ description: Frequently asked questions about Pombo. # FAQ +Privacy questions — can Pombo read my messages, is it anonymous, who sees my IP — have their own page: [Privacy at a glance](../security/privacy-at-a-glance.md). + ### Is Pombo free? Pombo itself has no ads, no premium tier and no cut of anything. Chatting, DMs and file sharing are free; the costs are small Polygon network fees (paid in POL, typically cents) for **on-chain actions**: creating a channel, the one-time creation of your personal DM inbox (needed to *receive* DMs and to sync devices), managing members in a closed channel, and changing an existing channel's retention or storage nodes. Two things a channel's *creator* can charge for, if they choose: a **gated** channel asks you to hold a token or NFT, and a **paid** channel asks for a subscription. That money goes to the channel owner — the contract takes no fee and Pombo is not a party to it. Everything else stays free, and if you only join open channels and chat, you pay nothing. -### Can Pombo (the developers) read my messages? - -No — and not as a promise, but structurally. There is no server where messages pass in the clear: DMs and protected channels are encrypted on your device, and the infrastructure (Streamr nodes, storage nodes) only ever handles ciphertext. Open channels are readable by everyone *including* us, because they're public. - ### What happens if pombo.cc disappears? Your channels, messages, and identity live on the Streamr Network and Polygon — not on Pombo's website. The app is open source; any client implementing the protocol can access the same data. The interface is replaceable; your data outlives it. @@ -26,16 +24,12 @@ No. Pombo generates its own keypair for you — no MetaMask, no seed phrase cere ### Are there group chats? -Group conversation in Pombo is a **channel**. There is no separate "group DM" primitive — a private group is a password or closed channel. +Group conversation in Pombo is a **channel**. There is no separate "group DM" primitive — a private group is a protected or closed channel. ### Someone I banned came back with a new account. Why? Because accounts are free and unlinked, a ban in an open or protected channel can't stop a determined user — this is a structural property of permissionless systems, and Pombo is honest about it rather than pretending otherwise. If you need enforceable access control, use a **closed, gated or paid channel**, where membership is contract state you control and the network refuses messages from anyone the contract won't vouch for. -### Is Pombo anonymous? - -Pombo is **pseudonymous with strong wire privacy**, not an anonymity network. Your real account is hidden from network observers, but open channel participation is publicly attributable to your account, and your IP is visible to peers like in any P2P app. For separation of contexts, use multiple accounts; for IP privacy, use a VPN or Tor. Full picture: [Threat model](../security/threat-model.md). - ### How is this different from Signal? From Matrix? From Farcaster? Briefly: **Signal** is the gold standard for content encryption, but it requires a phone number — a real-world identity anchor — and runs on central servers you have to trust to stay up and neutral; Pombo asks for no identifier at all and has no backend to trust. **Matrix** federates servers; Pombo has no backend to federate — transport is P2P and state is on-chain. **Farcaster**-style social protocols are public-first; Pombo is messaging-first with E2EE DMs and encrypted channels. Pombo's particular corner is: no sign-up, no servers, creator-owned channels, sealed-sender DMs. diff --git a/docs/help/troubleshooting.md b/docs/help/troubleshooting.md index 418b8fa..9c2ae32 100644 --- a/docs/help/troubleshooting.md +++ b/docs/help/troubleshooting.md @@ -33,7 +33,7 @@ On-chain actions need a little POL for gas on Polygon PoS: ## I forgot my password / lost my device -If you have a **backup file**, import it and enter its passphrase — everything comes back. If you don't, the account cannot be recovered by anyone, including us; that's the flip side of no one else holding your keys. Create a new account and [back it up this time](../guides/backup-and-recovery.md). +If you have a **backup file**, import it and enter your account password — everything comes back. If you don't, the account cannot be recovered by anyone, including us; that's the flip side of no one else holding your keys. Create a new account and [back it up this time](../guides/backup-and-recovery.md). ## The app seems out of sync between my devices diff --git a/docs/operators/run-a-relay.md b/docs/operators/run-a-relay.md index b02cf24..fc1cf8d 100644 --- a/docs/operators/run-a-relay.md +++ b/docs/operators/run-a-relay.md @@ -6,7 +6,7 @@ description: Run a Pombo push relay — the easiest way to contribute infrastruc # Run a push relay -The push relay bridges Streamr wake signals to Web Push (FCM/APNs). It's deliberately **blind** — it never sees message content or user identities (see [Notifications](../guides/notifications.md)) — and deliberately **cheap to run**. This is the easiest piece of Pombo infrastructure to operate. +The push relay bridges Streamr wake signals to Web Push (FCM/APNs). It's deliberately **blind** — it never sees message content or user identities (see [Push notifications](../concepts/notifications.md)) — and deliberately **cheap to run**. This is the easiest piece of Pombo infrastructure to operate. ## Requirements diff --git a/docs/security/privacy-at-a-glance.md b/docs/security/privacy-at-a-glance.md new file mode 100644 index 0000000..cf254db --- /dev/null +++ b/docs/security/privacy-at-a-glance.md @@ -0,0 +1,29 @@ +--- +id: privacy-at-a-glance +title: Privacy at a glance +description: The ten answers most people came for, each with a link to the page that owns the full explanation. +--- + +# Privacy at a glance + +One line per answer; the link owns the explanation. Underneath this page sit the documents with the full story: [Encryption](../concepts/encryption.md) explains how content is protected, the [privacy model](../concepts/privacy-model.md) explains what an observer can see, the [threat model](threat-model.md) says what an attacker can achieve, and the [privacy policy](/legal/privacy-policy) is the legal statement of what leaves your device and who receives it. + +**Can Pombo read my messages?** No — structurally: there is no server where messages pass in the clear, and open channels are public rooms by design. → [Encryption](../concepts/encryption.md) + +**Can anyone see who I DM?** No — what's visible is your inbox's timing and volume, never the correspondents. → [Sealed sender](../concepts/encryption.md#sealed-sender), [threat model](threat-model.md#visible-metadata) + +**Is Pombo anonymous?** No — pseudonymous with strong wire privacy; separation of contexts means separate accounts. → [Anonymity = another account](../concepts/privacy-model.md#anonymity--another-account) + +**Who sees my IP address?** Network peers, the storage node you read history from, RPC providers, The Graph, and the host behind any ENS avatar you view (that last one has an off switch). → [Threat model](threat-model.md#visible-metadata) + +**Can storage nodes read what they store?** No — ciphertext, except open channels, which are plaintext for everyone. → [Storage nodes](../concepts/storage-and-sync.md#storage-nodes) + +**What ends up on the blockchain, permanently?** Channel creation, membership and bans in contract-backed channels, and subscription payments — never content. → [Honest limits](../concepts/channel-access.md#honest-limits) + +**What does enabling notifications reveal?** Google learns your device runs Pombo; the relay stores a push token it cannot link to your identity or channels. → [Push notifications](../concepts/notifications.md) + +**What does device sync upload?** A snapshot of your state, encrypted to your own key, in your own inbox. → [Cross-device sync](../concepts/storage-and-sync.md#cross-device-sync) + +**What happens if my key leaks?** The account is the attacker's too — there is no revocation, and no forward secrecy in DMs. → [The forward-secrecy trade](../concepts/encryption.md#what-that-costs-no-forward-secrecy-in-dms) + +**What does the Pombo project itself hold?** Three things: encrypted history on its default storage cluster, your push token if notifications are on, and mail you send to privacy@pombo.cc. → [Privacy policy](/legal/privacy-policy) diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md index 9703612..ae1a789 100644 --- a/docs/security/threat-model.md +++ b/docs/security/threat-model.md @@ -11,7 +11,7 @@ Privacy tools earn trust by being precise about their limits. This page is the h ## What Pombo protects - **DM content and sender identity.** DMs are end-to-end encrypted (ECDH + AES-256-GCM) with sealed sender: no party except the recipient — not the network, not storage nodes, not relays — learns the content *or who sent it*. -- **Password-channel content.** Encrypted client-side; the network carries ciphertext published by throwaway keys. +- **Protected-channel content.** Encrypted client-side; the network carries ciphertext published by throwaway keys. - **Your account on the wire.** In open and protected channels and DMs, message traffic is published under an ephemeral session key, not your wallet. Contract-backed channels are the exception: the membership contract is the publisher, but your account's signature travels with each message and identifies you — as does publishing a read-only channel or a moderation action. See visible metadata below. - **Private-channel content.** Closed, gated and paid channels are encrypted under a channel key that only members obtain, rotated over time; the network and storage nodes carry ciphertext. - **Your data at rest.** Keys and app state are encrypted on-device and isolated per account, on both web and Android (in the web app, a few low-sensitivity preferences remain in plain browser storage). @@ -43,7 +43,7 @@ The accepted risk follows: if attacker code ever does run in the app's origin Things an observer can see, some inherent to the design: -- **Open channels are public.** Anyone implementing the message format can recover the real account behind each message and correlate a person's activity **across open channels**. The countermeasure is using separate accounts, not a setting. +- **Open channels are public.** Anyone implementing the message format can recover the real account behind each message and correlate a person's activity **across open channels**; even your display name travels in cleartext, in the channel's presence and typing signals. The countermeasure is using separate accounts, not a setting. - **Channel creators are permanent public record** — the creator's address is embedded in the channel ID. - **Membership of contract-backed channels is on-chain** and queryable by anyone: allowlists, bans, and — in paid channels — who subscribed and until when. Paying for a channel is a public act. - **Moderation is visible.** In open channels the moderation state (ban lists, pins) is world-readable; in protected channels it is encrypted for members; in contract-backed channels it is member-only. In every type, though, moderation actions are published by the **owner's real wallet**, exposing the owner and the timing of each action. @@ -55,7 +55,6 @@ Things an observer can see, some inherent to the design: For a threat model that includes traffic analysis, this is the residual to weigh: a watcher who checks an inbox periodically learns your messaging rhythm without ever learning a single contact. - **Reading history reveals you to the node you read from.** Opening a channel fetches its retained messages over HTTPS from the storage node it uses, so that operator sees your address and which channels you open, whether the node is ours or a third party's. The content stays sealed; the pattern does not. - **A DM you send is retained under the recipient's settings, not yours.** It lives in their inbox, on the node they chose, for as long as they chose. This is the one exposure you cannot fix from your side. -- **Display names travel in cleartext** in open-channel presence and typing signals. - **IP addresses are visible to network peers**, as in any P2P system, and timing correlation is possible for a well-positioned observer. Today, pair Pombo with a VPN or Tor if your threat model includes network observers; a proxy-node layer built on Streamr Sponsorships is in development to address this at the protocol level. ## Shrinking what a seizure would find diff --git a/docs/welcome.md b/docs/welcome.md index 95208c4..f436852 100644 --- a/docs/welcome.md +++ b/docs/welcome.md @@ -46,9 +46,9 @@ Everything else is free. Joining an open channel, chatting, DMs and file sharing | | | |---|---| | **Try it** | [Install Pombo](getting-started/install.md), then [first steps](getting-started/first-steps.md) | -| **Run a channel** | [Managing channels](guides/managing-channels.md) · [Gated and paid channels](concepts/gated-and-paid-channels.md) | -| **Understand it** | [Architecture](concepts/architecture.md) · [Encryption](concepts/encryption.md) · [Privacy model](concepts/privacy-model.md) | -| **Check our claims** | [Threat model](security/threat-model.md) | +| **Run a channel** | [Managing channels](guides/managing-channels.md) · [Channel access](concepts/channel-access.md) | +| **Understand it** | [Client](concepts/client.md) · [Encryption](concepts/encryption.md) · [Privacy model](concepts/privacy-model.md) | +| **Check our claims** | [Privacy at a glance](security/privacy-at-a-glance.md) · [Threat model](security/threat-model.md) | | **Run infrastructure** | [Push relay](operators/run-a-relay.md) · [Storage node](operators/run-a-storage-node.md) | ## Platforms diff --git a/docusaurus.config.js b/docusaurus.config.js index 4048bae..af2e913 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -46,7 +46,22 @@ const config = { ], ], - plugins: ['docusaurus-plugin-image-zoom'], + plugins: [ + 'docusaurus-plugin-image-zoom', + [ + '@docusaurus/plugin-client-redirects', + { + // Concept pages were reorganized; keep the published URLs alive. + redirects: [ + {from: '/concepts/architecture', to: '/concepts/client'}, + {from: '/concepts/channels-and-ownership', to: '/concepts/channel-access'}, + {from: '/concepts/gated-and-paid-channels', to: '/concepts/channel-access'}, + {from: '/concepts/storage-and-persistence', to: '/concepts/storage-and-sync'}, + {from: '/guides/notifications', to: '/concepts/notifications'}, + ], + }, + ], + ], themes: [ [ diff --git a/package-lock.json b/package-lock.json index 99c0620..c0b47e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@docusaurus/core": "3.10.2", "@docusaurus/faster": "3.10.2", + "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "3.10.2", "@easyops-cn/docusaurus-search-local": "^0.55.3", "@mdx-js/react": "^3.0.0", @@ -3665,6 +3666,30 @@ "react-dom": "*" } }, + "node_modules/@docusaurus/plugin-client-redirects": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.10.2.tgz", + "integrity": "sha512-z5I5ttCXw+8y2gHVZvqAMmUw4Rb0ZzKA5eCPk87SfM/jCKOTvE24yDpPAwwipvug96ij7mOwEHXWw8G4LlWdGA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "eta": "^2.2.0", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, "node_modules/@docusaurus/plugin-content-blog": { "version": "3.10.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.2.tgz", diff --git a/package.json b/package.json index d6d5372..4bab97c 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dependencies": { "@docusaurus/core": "3.10.2", "@docusaurus/faster": "3.10.2", + "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "3.10.2", "@easyops-cn/docusaurus-search-local": "^0.55.3", "@mdx-js/react": "^3.0.0", diff --git a/sidebars.js b/sidebars.js index 27295c3..3608ecc 100644 --- a/sidebars.js +++ b/sidebars.js @@ -16,37 +16,37 @@ const sidebars = { 'getting-started/first-steps', ], }, - { - type: 'category', - label: 'Core Concepts', - collapsed: false, - items: [ - 'concepts/architecture', - 'concepts/channels-and-ownership', - 'concepts/gated-and-paid-channels', - 'concepts/encryption', - 'concepts/privacy-model', - 'concepts/storage-and-persistence', - ], - }, { type: 'category', label: 'Guides', collapsed: true, items: [ + 'guides/direct-messages', 'guides/managing-channels', 'guides/file-sharing', - 'guides/notifications', 'guides/backup-and-recovery', ], }, { type: 'category', - label: 'Node Operators', - collapsed: true, + label: 'How Pombo Works', + collapsed: false, items: [ - 'operators/run-a-relay', - 'operators/run-a-storage-node', + { + type: 'category', + label: 'Architecture', + collapsed: false, + items: [ + 'concepts/client', + 'concepts/channel-anatomy', + 'concepts/storage-and-sync', + 'concepts/notifications', + ], + }, + 'concepts/channel-access', + 'concepts/ownership-and-moderation', + 'concepts/encryption', + 'concepts/privacy-model', ], }, { @@ -54,10 +54,20 @@ const sidebars = { label: 'Security & Privacy', collapsed: true, items: [ + 'security/privacy-at-a-glance', 'security/threat-model', {type: 'link', label: 'Privacy policy', href: '/legal/privacy-policy'}, ], }, + { + type: 'category', + label: 'Node Operators', + collapsed: true, + items: [ + 'operators/run-a-relay', + 'operators/run-a-storage-node', + ], + }, { type: 'category', label: 'Help', diff --git a/src/css/custom.css b/src/css/custom.css index 1a86ba5..b1e6ace 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -68,3 +68,10 @@ [data-theme='dark'] .table-of-contents__link--active { color: var(--ifm-color-primary); } + +/* Matrix tables: the category column reads like the header row + (thead's background is --ifm-table-stripe-background in Infima). */ +.matrix-table table td:first-child { + background-color: var(--ifm-table-stripe-background); + font-weight: var(--ifm-table-head-font-weight); +} diff --git a/src/pages/legal/privacy-policy.md b/src/pages/legal/privacy-policy.md index f03348f..7df3a20 100644 --- a/src/pages/legal/privacy-policy.md +++ b/src/pages/legal/privacy-policy.md @@ -9,251 +9,70 @@ description: What data leaves your device when you use Pombo, and who receives i **Last updated:** 25 August 2026 Pombo is built so that we cannot see your messages, your contacts, or who you -talk to. This document explains what that means in practice, and it is careful -to describe the places where the design has limits. A privacy policy is worth -what it admits, so the limits are stated as plainly as the protections. - -This is the legal statement of what data leaves your device and who receives it. -It is written to be read on its own. If you want to know *how* the protections -work rather than what they cover, the [privacy model](/concepts/privacy-model) -explains the mechanisms and the [threat model](/security/threat-model) sets out -what an attacker can achieve. Where those documents and this one describe the -same thing, this one governs. - -## 1. What this policy covers - -This policy covers the Pombo web application at `app.pombo.cc`, the Pombo -Android application, and the websites `pombo.cc` and `docs.pombo.cc`. - -It covers what we run, not what the code can be used for. Pombo is MIT-licensed -and yours to take: if you build it yourself, host it somewhere else, or run your -own storage node or relay, that is your service and not ours. Whoever operates -something is responsible for it, and for anything it does with other people's -data. This policy speaks only for the instances named above. +talk to. This document is the legal statement of what data leaves your device +and who receives it, and it states the limits as plainly as the protections. +If you want to know *how* the protections work rather than what they cover, +the [privacy model](/concepts/privacy-model) explains the mechanisms +and the [threat model](/security/threat-model) sets out what an attacker can +achieve. Where those documents and this one describe the same thing, this one +governs. + +## 1. Who we are, and what this covers + +"Pombo", "we" and "us" mean Vasco Branco, who publishes the Pombo web +application at `app.pombo.cc`, the Pombo Android application, and the websites +`pombo.cc` and `docs.pombo.cc`, and operates the services in section 4. Pombo +is an open source project rather than a company. This policy covers the +applications, websites and services named here. + +It covers what we run, not what the code can be used for. Pombo is MIT-licensed: +if you build it yourself, host it somewhere else, or run your own storage node +or relay, that is your service and not ours, and whoever operates it is +responsible for it. Pombo is a client for open infrastructure that it does not own. The Streamr Network, the Polygon blockchain, storage nodes and RPC providers are run by whoever chooses to run them, under their own terms, and this policy does not -cover them. Where using Pombo causes data to reach them, it says so. - -The project does run some of that infrastructure itself, as a service rather -than as part of the application: a storage cluster and a push relay. Section 9 -lists them, and they are covered by this policy like anything else we operate. - -"Pombo", "we" and "us" in this document mean Vasco Branco, who publishes the -applications and websites listed above and operates the services named in -section 9. Pombo is an open source project rather than a company. Questions -about this policy, and any request relating to your data, reach us at the -address in section 11. - -## 2. There is no account - -Pombo does not have accounts. Your identity is a cryptographic key pair -generated on your device. We do not ask for an email address, a phone number, -a name, or any other identifier, and we do not assign one to you. - -This means we have no user database, and no way to look you up. It also means -that if you lose your key and your backup, nobody can restore your identity for -you, including us. - -Nothing about you is required in order to use Pombo. There is no field you have -to fill and no consequence for leaving one blank, because there are none to -begin with. - -## 3. What is stored on your device - -The following is held locally, and none of it is readable by us: - -- Your private key, in an encrypted keystore protected by the password you set. -- Messages you have sent and received, and the channels you have joined. -- Your contacts and any local names you gave them. -- Channel encryption keys. -- Cached ENS names and avatars. -- Backups you export, until you move or delete them. - -Uninstalling the application, or clearing site data in your browser, erases all -of it. We cannot recover it. - -If you have a DM inbox, a copy of most of that state travels to it as well, so -your other devices can pick it up. Section 4.10 explains what that means. - -## 4. What leaves your device, and who can see it - -### 4.1 The peer-to-peer network can see your IP address - -Pombo delivers messages over the Streamr Network, a peer-to-peer network. Your -device connects **directly to other participants**. Message content is -encrypted end to end and those participants cannot read it, but they can observe -the IP address you connect from, as is inherent to any direct connection. - -If your IP address must not be visible to other participants, use Pombo through -a VPN or a network that does not identify you. There is no proxy layer between -you and the network that would hide it for you. - -### 4.2 Storage nodes hold encrypted history - -Pombo is a client for open infrastructure. Message history is kept by Streamr -storage nodes, which anyone can run: they are not part of the application, and -the application has no privileged relationship with any of them. - -**We operate one of those nodes, and it is the one a channel uses unless its -owner picks another.** It is a two-server cluster, offered because a network -with no reachable storage is not much use to anyone, and it is a service we run -rather than a component of the app. Channel creation shows the choice, names the -node, and explains how to point a channel at your own cluster or a third -party's instead. - -Whichever node a channel uses, what is stored is encrypted and its operator -cannot read it. What that operator does see is stream identifiers, message -sizes, and timing. Loading history is a direct request from your device to that -node, so it also sees the address you connect from. - -On our own cluster that is all we keep. It joins the network like any other -node, so it sees the addresses of its neighbours rather than of whoever -published a message, and it does not log the addresses that connect to it. - -How long history is kept is set by the channel's owner, between one and 365 -days, and it is enforced by the node rather than by us. - -### 4.3 The blockchain is public and permanent - -Creating a channel, joining a gated channel, and paying for access are -transactions on the Polygon blockchain. They are public, permanent, and cannot -be deleted or amended, by us or by anyone. - -Blockchain addresses are pseudonymous, not anonymous. Anyone who links an -address to a person can then see everything that address has done. Payments for -paid channels go directly to the channel owner; Pombo takes no fee. - -### 4.4 Channel discovery - -Browsing channels queries an indexing service, The Graph, over the internet. -That service receives your IP address and the queries you make, which reveals -which channels you look at. You can supply your own API key in Settings, which -changes who your queries are attributed to but not that they are made. - -### 4.5 Blockchain access and ENS names - -Reading gate conditions, estimating fees, and sending transactions use public -RPC endpoints, which receive your IP address and the addresses you query. The -endpoints are listed in the application and can be changed in Settings. - -Looking up the `.eth` name of someone you see in a channel is sent with cover -traffic, so the provider learns that you resolved some address but not which -one. The [privacy model](/concepts/privacy-model) describes how. - -Looking up a name you typed yourself, to start a direct message or send an -invitation, is **not** covered. The provider sees the exact name you asked for, -which reveals who you intend to contact. This is inherent to resolving ENS names -through someone else's node, and the only complete answer is to run your own; -we mention it because most applications leave it unsaid. - -### 4.6 Push notifications - -Push notifications are optional and off until you enable them. - -When enabled, wake signals reach your device through the platform push service -your device already uses: Google's Firebase Cloud Messaging on Android, and your -browser's push service on the web. That service handles delivery and can see -that a message was delivered to your device, but not its content, its sender, or -which channel it came from. - -The Pombo relay that triggers those signals cannot determine the recipient, the -sender, or the channel. What reaches it is an anonymous tag, one of a small -number shared by many devices, and the [privacy model](/concepts/privacy-model) -describes how that works. - -To deliver anything at all, the relay does have to store your device's push -token, alongside that tag and the times the registration was created and last -refreshed. A push token identifies a device, not a person: it is issued by -Google or by your browser, it carries no name, address or account, and the relay -holds nothing that links it to your Pombo identity or to any channel. It is -still a persistent identifier, so we name it here rather than leave you to -assume the relay stores nothing. Turning notifications off removes it. - -On Android, the Firebase Messaging component sends an installation identifier to -Google in order to receive messages at all. This is a property of using the -platform's push service and applies whenever notifications are enabled. - -### 4.7 Profile pictures set by other users - -If someone you see in a channel has set an ENS profile picture, your device -loads that image from wherever their ENS record points, which is a server of -their choosing. That server receives your IP address and browser information. -Images stored on IPFS are fetched through a public gateway, which receives the -same. - -This is worth stating plainly: a person can set their profile picture to an -address on a server they control and thereby learn the IP address of everyone -who views their messages. Pombo requires such images to be served over HTTPS, -which protects the request in transit but does not hide who made it. - -You can turn this off. Settings has an **ENS Avatars** control, under Content, -that stops remote profile pictures from loading and shows the locally generated -identicon instead. Turning it off closes the request entirely, everywhere an -avatar is drawn, including the account screens shown before you unlock and the -pictures attached to notifications. It is on by default, because most people -want to see the pictures. - -### 4.8 Embedded video - -If you play a video embedded in a message, it is loaded from YouTube in -privacy-enhanced mode. Nothing is loaded from YouTube until you press play. - -### 4.9 Website hosting - -`app.pombo.cc`, `pombo.cc` and `docs.pombo.cc` are served by GitHub Pages. -GitHub keeps ordinary web server access logs, which include IP addresses, under -its own privacy statement. - -### 4.10 Syncing between your devices - -If you create a DM inbox, Pombo also uses it to keep your devices in step. From -then on it publishes a snapshot of your own state there: your channel list, your -contacts and the local names you gave them, blocked peers, cached ENS names, -your display name, the messages you sent and the images that went with them. -This starts automatically once the inbox exists, and you can set it to manual in -Settings. - -The snapshot is encrypted to your own key before it leaves the device. Nobody -else can read it, including us, and including whoever runs the storage node your -inbox uses. What that operator holds is an encrypted blob, its size, and when it -arrived, and by default that operator is us, because a DM inbox uses the cluster -described in section 4.2 unless you chose otherwise. - -This is the one case where state that would otherwise never leave your device is -also somewhere else. It is there so Pombo works on more than one device, and it -is sealed the whole way. - -## 5. What Pombo does not do - -- No analytics, no telemetry, no crash reporting, no usage measurement. -- No advertising, no advertising identifiers, no tracking across sites or apps. -- No profiling, and no inference about you from your behaviour. -- No automated decision-making. Nothing about you is decided by a machine, - because nothing about you is held to decide with. -- No selling or sharing of personal data. The little we hold is listed in - section 9, and none of it is passed on to anyone. -- No server that holds your messages, your contact list, or your keys. - -There is no third-party analytics or tracking code in the Pombo applications. - -## 6. Third parties your use of Pombo reaches - -Most of these are outside the European Economic Area, so using Pombo sends your -IP address to countries whose data protection law differs from the EU's. GitHub -and Google are United States companies. For the rest we cannot tell you where -your data goes, and saying otherwise would be an invention: an RPC or indexing -gateway answers from whichever edge location its network chooses, and the -peer-to-peer network connects you to whoever happens to be carrying the same -channel, anywhere in the world. Each of these parties operates under its own -terms and its own transfer safeguards; we are not a party to that relationship -and cannot vouch for it. +cover them. Where using Pombo causes data to reach them, section 3 says so. + +## 2. What we collect + +Nothing, by construction. Pombo has no accounts: your identity is a +cryptographic key pair generated on your device. We do not ask for an email +address, a phone number, a name, or any other identifier, and we do not assign +one to you. We have no user database and no way to look you up — which also +means that if you lose your key and your backup, nobody can restore your +identity, including us. Nothing about you is required in order to use Pombo; +there is no field you have to fill. + +There is no analytics, telemetry, crash reporting or usage measurement; no +advertising, advertising identifiers, or tracking across sites or apps; no +profiling; no automated decision-making; and no selling or sharing of personal +data. The little we hold is listed in section 4, and none of it is passed on +to anyone. + +Your private key (in an encrypted keystore), your messages, the channels you +have joined, your contacts and the local names you gave them, channel +encryption keys, cached ENS data and exported backups live on your device. Uninstalling the application, or clearing site data in your browser, +erases all of it, and we cannot recover it. If you have a DM inbox, an +encrypted copy of most of that state travels to it so your other devices can +pick it up — see "Syncing between your devices" in section 3. + +## 3. What leaves your device, and who receives it + +Most of these parties are outside the European Economic Area, so using Pombo +sends your IP address to countries whose data protection law differs from the +EU's. GitHub and Google are United States companies. For the rest we cannot +tell you where your data goes: an RPC or indexing gateway answers from +whichever edge location its network chooses, and the peer-to-peer network +connects you to whoever happens to be carrying the same channel, anywhere in +the world. Each party operates under its own terms and its own transfer +safeguards; we are not a party to that relationship. | Party | What it receives | When | | --- | --- | --- | | Other network participants | Your IP address | Whenever the app is connected | -| The storage node a channel uses, ours by default | Encrypted content, stream ids, sizes, timing | For channels with retention | +| The storage node a channel uses, ours by default | Encrypted content, stream ids, sizes, timing, the address you read from | For channels with retention | | The Polygon blockchain | Your address and transactions, permanently and publicly | On-chain actions | | The Graph | Your IP address and channel queries | Browsing and opening channels | | RPC providers | Your IP address and the addresses you query | Gate checks, ENS, transactions | @@ -262,97 +81,144 @@ and cannot vouch for it. | YouTube | Standard embedded player data | Only if you play a video | | Hosting providers | Web server access logs | Loading the sites | -## 7. Retention, and what we cannot delete - -There is no central store of your messages, your contacts or your identity. What -the project does hold is listed in section 9, and it is small. - -For the rest we want to be exact rather than reassuring: - -- Channel history stays on the storage node the channel uses until its retention - window expires, then the node purges it. It is encrypted throughout. On our - own cluster we can see that a channel's data is there and when it will go, and - we do not remove it early, because doing so would break the guarantee the - channel owner chose. On any other node we have no access at all. -- Blockchain transactions are permanent. They cannot be deleted by anyone. -- Messages already delivered to other people's devices are theirs. We have no - mechanism to reach into them. - -Deleting a message in the application publishes an instruction to hide it, which -compliant clients honour. It is not a guarantee that every copy disappears, and -we do not present it as one. - -## 8. Age +What the table cannot carry: + +**The network.** Your device connects directly to other participants, who can +observe your IP address, as is inherent to any direct connection. There is no +proxy layer that would hide it for you; if your IP address must not be visible, +use Pombo through a VPN or a network that does not identify you. + +**Storage nodes.** Anyone can run one; a channel uses ours unless its owner +picks another, and the choice is shown at creation. Whichever node it is, what +it stores is encrypted and its operator cannot read it; what the operator sees +is stream identifiers, sizes, timing, and — because loading history is a direct +request from your device — the address you connect from. Our own cluster does +not log connecting addresses. History is kept for 1 to 365 days, set by the +channel's owner and enforced by the node. + +**The blockchain.** Creating a channel, joining a gated channel and paying for +access are Polygon transactions: public, permanent, and deletable by no one. +Addresses are pseudonymous, not anonymous — anyone who links an address to a +person sees everything that address has done. Payments for paid channels go +directly to the channel owner; Pombo takes no fee. + +**Channel discovery and blockchain access.** Browsing channels queries The +Graph, which receives your IP address and the queries you make; supplying your +own API key in Settings changes who your queries are attributed to, not that +they are made. Reading gate conditions and sending transactions use public RPC +endpoints, listed in the application and changeable in Settings. Looking up the +`.eth` name of someone you see is sent with cover traffic, so the provider +learns that you resolved some address but not which one. Looking up a name you +typed yourself — to start a DM or send an invitation — is **not** covered: the +provider sees the exact name, which reveals who you intend to contact. This is +inherent to resolving ENS through someone else's node; we say it because most +applications leave it unsaid. + +**Push notifications** are off until you enable them. The platform push service +(Google's Firebase Cloud Messaging on Android, your browser's push service on +the web) can see that something was delivered to your device, but not its +content, sender or channel. The Pombo relay that triggers deliveries cannot +determine any of those either: what reaches it is an anonymous tag shared by +many devices. To deliver at all, the relay does store your device's push token, +with that tag and the registration times. A push token identifies a device, not +a person — it carries no name or account, and the relay holds nothing linking +it to your Pombo identity or channels — but it is a persistent identifier, so +we name it. Turning notifications off removes it. On Android, the Firebase +Messaging component also sends an installation identifier to Google; that is a +property of the platform's push service. + +**Profile pictures set by other users.** If someone has an ENS profile picture, +your device loads it from wherever their record points — a server of their +choosing, which receives your IP address and browser information (IPFS images +go through a public gateway, which receives the same). A person can point +their picture at a server they control precisely to learn who views their +messages; HTTPS is required, but that protects the request in transit, not who +made it. **Settings → Content → ENS Avatars** turns the fetch off entirely, +everywhere an avatar is drawn, and shows a locally generated image instead. It +is on by default. + +**Embedded video** is loaded from YouTube in privacy-enhanced mode, and nothing +is loaded until you press play. + +**The websites** are served by GitHub Pages, which keeps ordinary access logs, +including IP addresses, under its own privacy statement. + +**Syncing between your devices.** If you create a DM inbox, Pombo publishes a +snapshot of your own state to it: your channel list, contacts and the local +names you gave them, blocked peers, cached ENS names, your display name, the +messages you sent and the images that went with them. This starts automatically +once the inbox exists and can be set to manual in Settings. The snapshot is +encrypted to your own key before it leaves the device; nobody else can read it, +including us and including the storage node your inbox uses (ours, unless you +chose otherwise), which holds an encrypted blob, its size, and when it arrived. + +## 4. What we operate + +Three places where the project holds data, and they are the only ones: + +- **The storage cluster** that channels and inboxes use by default: encrypted + channel history and encrypted device-sync snapshots, for as long as their + retention windows say. We keep it so the history a channel's owner asked for + is there when its members come back, on our legitimate interest in running a + usable service. +- **The push relay**: the device push tokens described in section 3, for as + long as notifications stay enabled, kept only to deliver the notifications + you asked for, on the basis of your consent. Turning notifications off + withdraws it and deletes the token. +- **This mailbox**: writing to the address in section 9 means we hold your + message and the address you sent it from, for as long as it takes to answer + and keep a record of having done so, on our legitimate interest in answering + people who write to us. + +Web server logs are not on the list because we do not have them: GitHub keeps +the sites' logs for its own purposes, and we can neither read nor delete them. +None of the three is a component of the application; each can be replaced by +someone else's, or by your own. + +## 5. Retention, and what we cannot delete + +Channel history stays on the storage node the channel uses until its retention +window expires, then the node purges it; it is encrypted throughout. On our +cluster we can see that a channel's data exists and when it will go, and we do +not remove it early, because that would break the guarantee the channel's owner +chose; on any other node we have no access at all. Blockchain transactions are +permanent and cannot be deleted by anyone. Messages already delivered to other +people's devices are theirs; we have no mechanism to reach into them. + +Deleting a message in the application publishes an instruction to hide it, +which compliant clients honour. It is not a guarantee that every copy +disappears, and we do not present it as one. + +## 6. Your rights +If you are in the European Economic Area or the United Kingdom, data protection +law gives you rights of access, correction, deletion, restriction, objection +and portability. These rights are exercised against whoever holds your data, +and in Pombo's case almost everything is held by you, on your own device, to +export, inspect and destroy without asking us. -Pombo is not directed at children. You must be at least 16 years old to use it. +For what we do not hold, we cannot act on your behalf: we cannot delete +blockchain transactions, reach into a storage node run by someone else, or +retrieve messages from other people's devices. For the three services in +section 4 we take responsibility — if you want something removed, ask (for the +push token, turning notifications off in the app is faster). If you think we +have handled your data badly, you can complain to the data protection authority +of the country where you live; the European Data Protection Board +[lists them all](https://www.edpb.europa.eu/about-edpb/about-edpb/members_en). -We cannot verify your age, and we want to be honest about why: verifying it -would require collecting identifying information about you, which is exactly -what this application is designed not to do. The age requirement is a condition -of use, not something we enforce technically. +## 7. Age -## 9. Your rights +Pombo is not directed at children, and you must be at least 16 years old to use +it. We cannot verify your age: verifying it would require collecting exactly +the identifying information this application is designed not to collect. The +requirement is a condition of use, not something we enforce technically. -If you are in the European Economic Area or the United Kingdom, data protection -law gives you rights of access, correction, deletion, restriction, objection and -portability. +## 8. Changes to this policy -These rights are exercised against whoever holds your data. In Pombo's case, -almost everything is held by you: your messages and keys are on your own device, -and you can export, inspect and destroy them at any time without asking us. +If this policy changes we will update the effective date above. The full +history of every change is public in the repository that publishes this page. -For the parts we do not hold, we cannot act on your behalf. We cannot delete -blockchain transactions, reach into a storage node run by someone else, or -retrieve messages from other people's devices. Saying otherwise would be a -promise we could not keep. - -Where the project itself operates infrastructure, we take responsibility for it. -There are three places, and they are the only ones: - -- **The storage cluster** described in section 4.2, holding the encrypted - history of every channel that uses it, and the encrypted device-sync snapshots - of every inbox that uses it, for as long as their retention windows say. We - keep it so the history a channel's owner asked for is there when its members - come back, and so your own devices can find each other, and we rely on our - legitimate interest in running a usable service to do so. This is the largest - of the three by far. -- **The push relay**, which stores the device push tokens described in section - 4.6, for as long as notifications stay enabled. We keep them only to deliver - the notifications you asked for, on the basis of your consent, and you can - withdraw it at any time by turning notifications off, which deletes the token. -- **This mailbox.** Writing to the address in section 11 means we hold your - message and whatever address you sent it from, for as long as it takes to - answer you and keep a record of having done so, on the basis of our legitimate - interest in answering people who write to us. - -Web server logs are not on that list, because we do not have them. The sites are -served by GitHub Pages, and GitHub keeps those logs for its own purposes under -its own privacy statement. We cannot read them and we cannot delete them. - -None of the three is a component of the application. They are services the -project runs on open infrastructure, and each of them can be replaced by someone -else's or by your own. - -If you want any of that removed, ask. For the push token, turning notifications -off in the app is faster than writing to us. - -If you think we have handled your data badly, you can complain to the data -protection authority of the country where you live. The European Data Protection -Board [lists them all](https://www.edpb.europa.eu/about-edpb/about-edpb/members_en). - -We do not claim responsibility for the Streamr Network, the Polygon blockchain, -storage nodes, RPC providers or indexing services. They are independent, they -are not ours to direct, and pretending otherwise would mislead you about who can -actually act on a request. - -## 10. Changes to this policy - -If this policy changes we will update the effective date above. The full history -of every change is public in the repository that publishes this page, so you can -see exactly what changed and when. - -## 11. Contact +## 9. Contact Questions about this policy, and any request relating to your data, go to **privacy@pombo.cc**.