mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
Stage 2 of the Marmot resync. A member leaf carries two unrelated keys — the MLS BasicCredential identity, which is the member's Nostr account key, and the MLS leaf signature key MLS generates per device — and MLS never checks that the account agreed to the leaf key next to it. Without a proof, anyone able to author a leaf can claim any account's identity. This is also what makes us classifiable at all. MDK decides Legacy vs Current purely on whether a group requires extension 0xf2f1 or component 0x8009; we required neither, so profile classification errored out before any component check ran. Adds the common authorization-proof envelope (foundation/authorization-proofs.md): 104 fixed-width bytes of signer pubkey, big-endian uint64 timestamp and BIP-340 signature, with event-id reconstruction. The created_at bounds are load-bearing twice over — the lower bound rejects zero, and the upper bound (2^53-1) catches a uint64 whose top bit is set, which reads back negative as a Kotlin Long. Deliberately absent: any comparison of created_at against a local clock. A proof authorizes a long-lived key binding, not a one-time operation, and a wall-clock rule would let skew make two members reach different verdicts on the same Commit. The component itself signs a kind-450 template through NostrSigner rather than raw BIP-340, which is the whole point of the indirection: a NIP-46 bunker or NIP-55 app can produce a proof without exposing arbitrary signing. create() therefore re-verifies everything the signer returned — pubkey, timestamp, kind, tags, content, recomputed id, signature — since an external signer is free to substitute a stale or altered event. Also adds the app-component id registry, and the RFC 9420 signature-scheme mapping to MlsCiphersuite (declared outside the companion: an enum's entries initialize before its companion object, so entry constructor arguments cannot read companion properties). Tested two ways. Sixteen tests pin the spec's published fixture — canonical event serialization, event id, signature, the 104-byte layout — and check that every signed input actually binds, including a ciphersuite change that leaves the signature scheme untouched. Six more validate the proofs in marmot-current-profile.json: those come from a separate implementation, for randomly generated keys, which is the interop property a fixed vector cannot establish. Full quartz marmot suite: 395 tests, 0 failures. Nothing reads or writes these on a real leaf yet — the carrier is the app_data_dictionary, which is Stage 1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kCuA6tc4JQzHPCDd39GHq