Commit graph

2 commits

Author SHA1 Message Date
Scott Reimers
8b2881d84a Phase 2d: profile posts signed by the posting identity
Display metadata (display_name, bio, avatar_cid) is no longer broadcast via
the ProfileUpdate direct push when the user edits their name. It travels as
a signed public post with VisibilityIntent::Profile, authored by the posting
identity, and propagates through the normal neighbor-manifest CDN path.

Core pieces:
- `types::ProfilePostContent` — JSON payload serialized into the post's
  content field. Ed25519 signature by the posting secret over length-prefixed
  display_name + bio + 32-byte avatar_cid (or zeros) + timestamp.
- `crypto::{sign,verify}_profile` with strict length prefixing to prevent
  extension attacks.
- New `profile` module: `build_profile_post`, `verify_profile_post`,
  `apply_profile_post_if_applicable`. Last-writer-wins by timestamp.
- `control::receive_post` now verifies Profile-intent posts upfront (same
  as Control) so bogus signatures never enter storage, and applies them
  after store so the `profiles` row updates atomically with the insert.

Node API:
- `Node::set_profile` rewritten: builds a signed Profile post, stores under
  intent=Profile, applies it locally (upserts the profiles row keyed by the
  posting identity), then propagates via `update_neighbor_manifests_as`.
  Stops calling `network.push_profile` — display changes no longer trigger
  a direct wire push.
- `Node::my_profile` / `has_profile` read by `default_posting_id` instead of
  `node_id`, matching where the row is written now.

ProfileUpdate (0x50) and push_profile stay for now — they still carry
routing-only data (anchors, recent_peers, preferred_peers) via
`sanitized_for_network_broadcast` and are used by `set_anchors` /
`set_public_visible`. Removing the routing fields would be a broader
cleanup; scoped out of this phase.

Tests: roundtrip verify+store, wrong-author rejection (not stored), and
older-timestamp ignored. 114 / 114 core tests pass.
2026-04-22 22:30:27 -04:00
Scott Reimers
36b6a466d2 Phase 2b: control-post flow (delete/visibility) + remove BlobDeleteNotice
Replaces two persona-signed direct pushes with CDN-propagated control posts:
a single `VisibilityIntent::Control` post type whose content is a signed
`ControlOp` the receiver verifies and applies. Deletes and visibility updates
now flow through the same neighbor-manifest CDN path as regular content — no
direct recipient push needed for persona-signed ops.

Core pieces:
- `VisibilityIntent::Control` + `VisibilityIntent::Profile` variants.
- `ControlOp::DeletePost` / `ControlOp::UpdateVisibility` (JSON, ed25519-signed
  by the target post's author over op-specific byte strings).
- `crypto::{sign,verify}_control_{delete,visibility}` signing primitives.
- `control::build_delete_control_post` + `build_visibility_control_post`
  for authors to construct control posts.
- `control::receive_post` — unified incoming-post path used by all 6 receive
  sites. Verifies control signatures BEFORE storing, so bogus controls never
  enter storage and can't be re-propagated via neighbor-manifest diffs.
- `control::apply_control_post_if_applicable` — executes the op under the
  same storage guard as the insert.

Feed filter:
- Feeds (`get_feed`, `get_feed_page`, `list_posts_page`,
  `list_posts_reverse_chron`) now exclude `Control` and `Profile` posts so
  they propagate + tombstone without surfacing.
- Sync/export path (`list_posts_with_visibility`) keeps its own unfiltered
  query so control posts still propagate via CDN.

Wire protocol:
- `SyncPost` carries `intent: Option<VisibilityIntent>` so control posts
  arrive with their intent preserved.
- `BlobDeleteNotice` (0x95) removed — orphan blobs on remote holders evict
  naturally via LRU rather than via a persona-signed push. Code path,
  payload, sender, tests, and `delete_blob_with_cdn_notify` all gone.

Tests: control delete roundtrip (apply + tombstone) and wrong-author
rejection (not stored, not applied). 112/112 core tests pass.
2026-04-22 21:17:34 -04:00