Milestone 6: DiscoveryProvider seam, signed announcements, redoal sketch
The discovery module defines the seam: DiscoveryProvider (subscribe/ announce over 32-byte TopicKeys) and a signed Announcement carrying root hash, ed25519 author, metadata and provider addresses. Signatures cover a deterministic postcard encoding including the topic (no cross- channel replay) and the provider identities (addresses stay refreshable hints). LanDiscovery conforms to the trait — mdns sightings become locally-authored announcements, one per pinned root — and the daemon's auto-sync now runs entirely through it: verify, index unconditionally, fetch only for trusted authors from allowlisted providers on already- pinned incomplete roots. The milestone-4 real-mdns sync test passes unchanged through the new path. Verification unit tests cover round trip, tampered root, wrong topic, forged author, mismatched signing key, and serde survival. docs/redoal-integration.md sketches the gesture-topic gossip provider against this contract. Also: fix a flaky hang in the socket-activation test (dup2(3,3) leaves CLOEXEC set when the listener already sits on fd 3; parent's listener copy masked daemon death), and give the test client a read-timeout hang guard. Add a top-level README. Dependencies: ed25519-dalek (Signature type; same implementation iroh keys use), postcard (deterministic signed encoding, iroh's canonical compact codec). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# Milestone 6 — Discovery provider seam
|
||||
|
||||
## What landed
|
||||
|
||||
- `varde_daemon::discovery` with the spec's `DiscoveryProvider` trait,
|
||||
`TopicKey`, and the signed `Announcement` type. Signatures are ed25519
|
||||
(the same key type as iroh NodeIds) over a deterministic postcard
|
||||
encoding that includes the topic, so announcements can't be replayed
|
||||
onto other channels. Verification tests cover round trip, tampered
|
||||
root, wrong topic, forged author, mismatched signing key, and
|
||||
serde-then-verify.
|
||||
- `LanDiscovery` implements the trait: mDNS sightings become
|
||||
locally-authored, signed announcements ("peer N may hold pinned root
|
||||
R"), one per pinned root. `announce()` is a no-op on the LAN — mDNS
|
||||
already advertises presence, and varde never broadcasts content lists.
|
||||
- The daemon's auto-sync was refactored *onto* the seam: sightings feed
|
||||
presence tracking and the LanDiscovery channel; a single
|
||||
`on_announcement` path verifies, indexes, and applies the consent
|
||||
tiers (trusted author → pinned + incomplete → trusted providers →
|
||||
fetch). The milestone-4 mdns integration test passes unchanged through
|
||||
the new path, which is the proof the seam carries real traffic.
|
||||
- Announcements surface on the event stream (`announcement` events) and
|
||||
are indexed in memory per root.
|
||||
- `docs/redoal-integration.md` sketches the gesture-topic gossip
|
||||
provider against this contract, including the one extension it will
|
||||
need (topic-scoped trust) and why nothing else changes.
|
||||
|
||||
## Deviations from the spec sketch
|
||||
|
||||
- The trait deals in `SignedAnnouncement` rather than bare
|
||||
`Announcement`: the spec says announcements are signed, so the wire
|
||||
type carries its signature and the subscriber can't forget to check.
|
||||
- `Announcement.author` is an iroh `PublicKey` (ed25519, as specified);
|
||||
provider addresses are excluded from the signed payload (they're
|
||||
refreshable hints) while provider identities are covered.
|
||||
- Signature bytes travel as a length-checked `Vec<u8>` because serde
|
||||
cannot derive for `[u8; 64]`.
|
||||
@@ -0,0 +1,93 @@
|
||||
# redoal integration sketch
|
||||
|
||||
How a gesture-derived discovery provider plugs into varde without
|
||||
touching the daemon. Nothing here is implemented; this documents the
|
||||
contract the v1 seam already enforces.
|
||||
|
||||
## The seam
|
||||
|
||||
`varde_daemon::discovery` defines:
|
||||
|
||||
```rust
|
||||
pub trait DiscoveryProvider: Send + Sync {
|
||||
fn subscribe(&self, topic: TopicKey) -> BoxStream<SignedAnnouncement>;
|
||||
fn announce(&self, topic: TopicKey, ann: SignedAnnouncement) -> Result<()>;
|
||||
}
|
||||
```
|
||||
|
||||
- `TopicKey` is an opaque 32-byte channel id.
|
||||
- `Announcement { root, author, meta, providers }` is signed (ed25519,
|
||||
the same key type as iroh NodeIds) over a deterministic postcard
|
||||
encoding that *includes the topic*, so announcements can't be replayed
|
||||
across channels. Provider socket addresses are excluded from the
|
||||
signature (relays may refresh them); provider *identities* are covered.
|
||||
|
||||
The daemon consumes any provider identically (`Daemon::on_announcement`):
|
||||
|
||||
1. verify the signature — drop on failure;
|
||||
2. index the announcement (all of them — discovery is the open tier);
|
||||
3. auto-fetch only when **all** of these hold:
|
||||
- the author key is trusted (or is the local daemon),
|
||||
- the root is already pinned locally and incomplete,
|
||||
- at least one announced provider is on the peer allowlist.
|
||||
|
||||
v1 ships one implementation, `LanDiscovery`: mDNS sightings are turned
|
||||
into locally-authored announcements ("peer N may hold pinned root R").
|
||||
It proves the seam because the daemon's entire auto-sync path runs
|
||||
through `subscribe()` — swap the provider and nothing above changes.
|
||||
|
||||
## The redoal provider (future)
|
||||
|
||||
redoal turns a shared physical gesture (two phones shaken together, a
|
||||
tap pattern, ...) into a high-entropy shared secret between the people
|
||||
present. That secret derives a gossip topic:
|
||||
|
||||
```
|
||||
TopicKey = BLAKE3-derive_key("redoal/v1/topic", gesture_secret)
|
||||
```
|
||||
|
||||
`RedoalDiscovery` would:
|
||||
|
||||
- join the iroh-gossip swarm for that TopicId (iroh-gossip rides the
|
||||
same iroh endpoint varde already has — no new transport);
|
||||
- `subscribe(topic)`: yield gossip messages decoded as
|
||||
`SignedAnnouncement`s. Verification and consent stay in the daemon —
|
||||
the provider is a dumb pipe by design;
|
||||
- `announce(topic, ann)`: broadcast to the swarm. Unlike the LAN
|
||||
provider (where announce is a no-op because mDNS already advertises
|
||||
presence), gossip announce actively publishes — the daemon must only
|
||||
call it for content the user shared to that topic.
|
||||
|
||||
### Trust bootstrap
|
||||
|
||||
The gesture is the consent ceremony. Deriving from the same secret:
|
||||
|
||||
```
|
||||
author trust: the gesture exchange includes both parties' public keys
|
||||
(signed with the gesture key), so each side adds the
|
||||
other to the *author* allowlist for that topic.
|
||||
peer trust: announcements carry provider NodeIds; on a gestured
|
||||
topic, providers named by a trusted author get scoped
|
||||
peer trust (serve/fetch for that topic's roots only).
|
||||
```
|
||||
|
||||
That last point needs one extension to the v1 model: today peer trust
|
||||
is global (`meta.json` allowlist). Topic-scoped trust would add
|
||||
`trusted_for: {topic → keys}`, checked in the same two places the
|
||||
global list is checked now (provider accept gate, fetch candidate
|
||||
filter). The seam anticipates this: both checks already live in the
|
||||
daemon, not in the provider.
|
||||
|
||||
### What stays true regardless of provider
|
||||
|
||||
- Discovery open, replication explicit: indexing an announcement never
|
||||
moves bytes; only the trust checks above do.
|
||||
- All fetched data is BLAKE3-verified by iroh-blobs.
|
||||
- Metered/rate-limit posture applies unchanged — providers sit above
|
||||
the transport, the limiters below it.
|
||||
|
||||
## Sizing
|
||||
|
||||
`RedoalDiscovery` is roughly: iroh-gossip dependency, ~200 lines of
|
||||
provider glue, the topic-scoped trust extension in `meta.json`, and a
|
||||
`varde-ctl topic join <secret>` command. No daemon architecture changes.
|
||||
Reference in New Issue
Block a user