diff options
| author | Adam Soltys <[email protected]> | 2026-10-02 23:07:00 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-02 23:07:09 -0700 |
| commit | 9e12335231f3fa4c4222bea71c4a1737a15a4994 (patch) | |
| tree | ef043ccb2edb285732b6bd5e40ce961328eba859 /src/marmot-client.js | |
| parent | dccc4a5559f23424d6092009e71375c11efe3784 (diff) | |
| download | coinosv3-9e12335231f3fa4c4222bea71c4a1737a15a4994.tar.gz coinosv3-9e12335231f3fa4c4222bea71c4a1737a15a4994.zip | |
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 <[email protected]>
Diffstat (limited to 'src/marmot-client.js')
| -rw-r--r-- | src/marmot-client.js | 12 |
1 files changed, 12 insertions, 0 deletions
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), |
