mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
Finishes Stage 3 and lands most of Stage 4. Image crypto (0x8002). GroupBlossomImageCrypto implements the current-profile scheme, which breaks from MIP-01 in three ways: image_key IS the AEAD key rather than an HKDF seed, image_upload_key IS the Blossom-auth secret rather than a seed, and the AAD is domain-separated and binds the media type where MIP-01 used an empty AAD. That last one closes a real hole — with no AAD a blob could be replayed as a different media type. MarmotMediaType implements the frozen canonicalization; it uses ASCII case folding explicitly, because a locale-aware lowercase would map a dotted capital I to a dotless one and change the AAD bytes. Decryption verifies the content hash before attempting the AEAD: the blob is addressed by hash, so a store returning different bytes is broken or hostile, and finding out through an authentication failure loses that distinction. The MIP-01 scheme stays for groups already on disk and nothing falls back between them, because the component id is the version. Transport. kind:30443 gains a current-profile builder emitting the required tag set and omitting the two tags the spec forbids: encoding (a receiver decodes each field by the rule that defines it, never by a negotiated marker) and relays (discovery is the author's NIP-65 write set). Validation is profile-aware, told apart by the presence of app_components rather than a version tag. KeyPackage relay discovery moves to the NIP-65 write set. publishRelaysFor no longer prefers a kind:10051 list — publishing only where a now-removed list points would make us invisible to a conformant peer, which looks in the NIP-65 set and nowhere else. The legacy list is unioned in rather than substituted, so peers that have not migrated keep finding us. Deduplication now uses SHA-256 over the recovered MLS bytes rather than the Nostr event id, which the transport spec forbids as a dedup key. The old scheme collapsed nothing it was supposed to: relays redeliver, and every transport copy of one MLS message carries its own fresh ephemeral pubkey and therefore a different event id — so cross-relay duplicates always got through, and a hostile republisher could mint unlimited distinct ids for a single message. Dedup necessarily moved after outer decryption, since there is nothing to hash before that, and OutboundGroupEvent now carries the id so a publisher suppresses its own echo by MLS identity. Also enforces the KeyPackage Lifetime bound (present, current, at most 7,261,200 seconds) and validates the embedded account identity proof on inbound current-profile KeyPackages — the app_components tag is only an advertisement, so the decoded LeafNode is what decides. Fifteen new tests. Full quartz jvmTest: 4,557 tests, 0 failures. commons and cli compile. Left for Stage 6: bounded retained-candidate trial decryption for kind:445. Its rule is defined over the retained-state set convergence owns, so it cannot land ahead of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kCuA6tc4JQzHPCDd39GHq