477d560d18
Enables non-members to join a group without a Welcome message by using an external commit with HPKE key encapsulation. HPKE extensions (Hpke.kt): - deriveKeyPair(): DHKEM(X25519) key derivation from seed - setupBaseSExport(): HPKE sender with export-only context - setupBaseRExport(): HPKE receiver with export-only context - keyScheduleFull(): Full key schedule returning exporter_secret - HpkeExportContext: Export secrets via labeled expand MlsGroup joiner side: - externalJoin(groupInfoBytes, identity): Static method for joining via external commit. Performs HPKE encapsulation to external_pub, derives init_secret, adds self to tree, creates Commit with ExternalInit proposal and UpdatePath. MlsGroup member side: - externalPub(): Returns the group's HPKE public key for external joins - groupInfo(): Returns GroupInfo with ratchet_tree + external_pub extensions - deriveExternalInitSecret(): Derives init_secret from ExternalInit kem_output - processCommit handles ExternalInit proposals by overriding init_secret RatchetTree fix: - setLeaf() now expands _leafCount when setting a leaf beyond current size, fixing external joins where the new member extends the tree Test: testExternalJoin verifies Alice creates group, Zara joins via external commit, Alice processes the commit, both at epoch 1 with 2 members. All 121 MLS tests pass (41 interop + 80 unit), 0 failures. https://claude.ai/code/session_01NocQDWj2Y92FugjfgazzL3