From 9e12335231f3fa4c4222bea71c4a1737a15a4994 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 2 Oct 2026 23:07:00 -0700 Subject: White Noise attachments (encrypted-media-v2) Pictures and files now travel in White Noise groups and direct chats, both ways. A file is sealed under the group's media secret for the epoch its message goes out in (MLS exporter "encrypted-media" -> HKDF -> ChaCha20-Poly1305, AAD bound to hash, type and name), uploaded to Blossom, and sent as a kind 9 with a v2 imeta tag. A commit landing mid-upload moves the epoch, so the file is sealed again rather than sent under a stale key. Receivers keep each epoch's media secret for as long as a message from it is in the log, so older pictures still open. Groups founded here now carry and require marmot.group.encrypted-media.v2 (blossom-v1 on blossom.ditto.pub and nostr.download). Checked against MDK's wn: our pictures decrypt in wn, wn's in ours, and wn uploads into our groups by our policy. Co-Authored-By: Claude Opus 5.5 --- src/marmot-client.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/marmot-client.js') diff --git a/src/marmot-client.js b/src/marmot-client.js index 7352e05..d63cb04 100644 --- a/src/marmot-client.js +++ b/src/marmot-client.js @@ -400,6 +400,18 @@ export function marmotClient({ scope, pubkey, identity, on, relays = GROUP_RELAY pubkey, groups, load, start, stop, resubscribe, ready, announce, lookup, welcome, accept, create, send, add, kick, rename, leave, forget, event: (kind, content, tags) => M.appEvent(pubkey, kind, content, tags), + // attachments: seal for the epoch a message is about to go out in, and + // open with the secret of the epoch it came in + media: { + read: M.readMediaTag, + tag: M.mediaTag, + seal: (g, plain, meta) => ({ epoch: g.tip.epoch, ...M.sealMedia(M.mediaSecret(g.tip), plain, meta) }), + open(g, epoch, cipher, ref) { + const secret = M.mediaSecretAt(g, epoch) || (g.tip.epoch === epoch ? M.mediaSecret(g.tip) : null); + if (!secret) throw new Error('media key for this epoch is gone'); + return M.openMedia(secret, cipher, ref); + }, + }, view: (g) => M.groupView(g.tip), peer: (g) => M.directPeer(g), accounts: (g) => M.accounts(g.tip), -- cgit v1.2.3