feat(subgroups): schema 19 -- the two things a ceremony's own room used to answer
A subgroup's ChillDKG is about to move out of the sibling NIP-17 room derived from its admins and into the parent's Marmot room. That room answers two questions the sibling room answered for free, and it answers both of them wrong. **Who the ceremony is with.** The sibling room's membership *was* the participant set -- it was derived from it -- so `memberPublicKeys(localChatRoom)` was the admin list, and three callers read it that way: `broadcast`'s p-tags, the birth certificate's `adminPublicKeys`, and the members `MarmotGroupCreation` welcomes into the child. The parent's room is a superset, so each of those would silently name every person the subgroup is *not*. `participantPublicKeys` is the set the ceremony was opened on, sorted and comma-joined the way `publicShares` already stores a list. **What the room it makes is called.** The sibling room carried the subgroup's name as its subject, put there by the coordinator and delivered to everyone else by `getOrCreateNip17ChatRoom` reading the proposal's `subject` tag. A ceremony in the parent's room has no room of its own to be named, and the parent's name is the one name a child must not take -- `proposeBirthCertificate` normalises this string into the certificate the parent's quorum signs, and `MarmotGroupCreation` normalises it again onto the room. `subject` is where it lands instead. Both are read off the proposal, which has carried both since `7bccf243` -- the p-tags and the `subject` tag. Nothing new goes on the wire; what changes is where it is kept. Both are nullable and both fall back to the room, which is correct for every row written before this: a ceremony in its own NIP-17 room ran over exactly that room's members and was named after it. **Sorted, because two devices assemble the set differently.** The coordinator builds it from a picker; every other device builds it from the proposal's p-tags plus the sender. `DkgSession.formatParticipants` sorts so those are the same string, which is what makes it something a query can match on. Two queries, both of which exist because `DkgSession.chatRoomId` is about to stop identifying a ceremony: `getLatestSubgroupSessionFor(room, parent, admins)` is what "may I open another" means for a subgroup once the parent's room hosts every one the group ever runs. `getLatestSessionFor(room, parent)` cannot answer it -- two subgroups of one parent share both columns -- and scoping on the room alone would refuse a group's second subgroup on the strength of its first. `getLatestOwnSessionForChatRoom(room)` is `getLatestSessionForChatRoom` with `parentChatRoomId IS NULL`. It exists for `FrostSigningManager.completedKey`'s last fallback, which is the one every parent member welcomed after the group's own ceremony lands on: the parent's room will hold a *completed* ceremony whose key is the child's, and a ceremony run to make a subgroup is never the room's own key. The claim is unverified, but the only direction it can be abused in is a member excluding a ceremony they themselves proposed, whose key they would simply not have proposed. `AutoMigration(18, 19)`: two nullable columns, a shape Room migrates itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
45cc80b538
commit
1e7ddd844a
4 files changed
+5833
-11
No files matched your search
@@ -177,7 +177,7 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
|
||||
UnsignedNostrEvent::class,
|
||||
Zap::class
|
||||
],
|
||||
version = 18,
|
||||
version = 19,
|
||||
autoMigrations = [
|
||||
// v2 only adds the DkgSession/DkgParticipantMessage tables, so Room can
|
||||
// generate the migration itself — nothing existing changes shape.
|
||||
@@ -298,6 +298,20 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
|
||||
// clock, which is correct for them: nothing that predates subgroups ran
|
||||
// two ceremonies in one room.
|
||||
AutoMigration(from = 17, to = 18),
|
||||
// v19 adds two nullable DkgSession columns, both of which the ceremony's
|
||||
// room used to answer and no longer can: a subgroup's ChillDKG now runs
|
||||
// in the **parent's** Marmot room rather than in a NIP-17 room derived
|
||||
// from the child's admins. See docs/subgroups.md.
|
||||
//
|
||||
// `participantPublicKeys` is who the ceremony is with -- a subset of the
|
||||
// parent's members, so the room's roster is the wrong set -- and
|
||||
// `subject` is what the room it will make is to be called, which used to
|
||||
// be the ceremony room's own name. Both are read off the proposal, which
|
||||
// has carried both since before this move; what changed is where they
|
||||
// land. Rows written before this read back null and fall back to the
|
||||
// room, which is correct for them: a ceremony held in its own NIP-17 room
|
||||
// ran over exactly that room's members and was named after it.
|
||||
AutoMigration(from = 18, to = 19),
|
||||
]
|
||||
)
|
||||
@ColumnTypeConverters(MantraConverters::class)
|
||||
|
||||
Reference in new issue
Block a user