`ChillDkgRitualManager` ran on gift wraps only. It now runs on whichever transport the room it is in has -- a gift-wrapped rumor in a NIP-17 room, an MLS application message in a Marmot one -- which is the shape `FrostSigningManager` has had since `113eda9f`, and for the same underlying reason: a group has to say something before it owns the room the saying is about. Nothing is wired to the new arm yet. A group's own ceremony still runs in its NIP-17 room and a subgroup's still runs in the sibling room derived from its admins; what changes here is that the manager stops assuming which. **`broadcast` reads the room and writes to the matching store.** MLS state present is what marks a room Marmot -- the same reading `sendChatMessage` makes when it chooses between a group event and gift wraps. `replayStoredMessages` has to make that reading again, because a ceremony's backlog is in whichever store its messages were queued in and looking in the wrong one is a stall with nothing to blame it on. **The p-tags stay on both, unlike `FrostSigningManager`'s, and this is the one thing here that would be a disaster to get wrong.** There a p-tag is an address and a group event needs none, because the message is encrypted to the whole tree and the signer set comes from the ceremony's host keys either way. Here the p-tag set *is* the participant set: `acceptProposal` builds `n` out of it on every device and `n` is hashed into the session identity, so a Marmot proposal without them leaves every receiver unable to say what they were invited to. In a room whose membership is wider than the ceremony they are also the only thing marking who is in it, and a member who publishes a host key is in that group's signing quorum for good. Inside MLS encryption, naming them leaks nothing. They are also read off the *session* rather than off the room now, via `participantsOf`, and sorted. A room whose membership is wider than the ceremony is exactly the case this is for. **`processRitualPayload` takes an `Event`.** The rumor as its sender wrote it, which is the shape both transports hand over -- `NostrDao` already rebuilt one from a decrypted gift wrap for the FROST arm and now does the same here. `sig` is empty on both and nothing reads it: what vouches for the author is the seal or the MLS frame, not a signature on the payload. `record` and `isFromCoordinator` follow. **`proposeRitual` grows two parameters and splits its guard.** `participantPublicKeys` defaults to the room's members, which is the whole story where the room is the participant set; `subject` defaults to the room's name, but only for a ceremony with no parent -- falling back there would name a child after its parent. The guard now asks two different questions. A ceremony with no parent is scoped by room as before. One with a parent is matched on `(room, parent, admins)` and folds only into a ceremony that is still *running*: a completed one means that subgroup was made, and a group is entitled to a second run by the same people. That was impossible while the ceremony room was derived from its admins -- asking again handed back the first ceremony's key, so the "new" subgroup was the old one under a new name. A new `require`, because the old invariant stops holding for free: everyone in a ceremony has to be able to hear it. A gift wrap is sealed per p-tag so this was true by construction; a group event reaches the MLS tree and nobody else, so a participant outside it is an `n` that can never be met. Placed behind the duplicate guard, so a running ceremony is still handed back whatever the room's rows have since done. **`NostrDao` dispatches the DKG kinds on the Marmot arm** beside the FROST ones it already did, and `ChatMessage.applyInnerEvent` returns null for them the way it does for `FrostSigningEvents.ALL` -- the manager writes the transcript of a ceremony itself, and an "unsupported" row would be a second, worse account of it. A member of the room who was not p-tagged never reaches either: their `acceptProposal` drops the proposal and no session is opened. **A ceremony with a parent says so in the transcript.** The line lands in a room that already has a key, and "started a shared key ceremony. It will take 2 of 3 members to sign with the key" reads there as though *this* group were getting a new one. It is not -- the quorum quoted is the child's, over the child's admins -- and that is the sort of misreading that gets somebody to approve a step they did not follow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This is a Kotlin Multiplatform project targeting Android, iOS, Desktop (JVM).
-
/composeApp is for code that will be shared across your Compose Multiplatform applications. It contains several subfolders:
- commonMain is for code that’s common for all targets.
- Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app, the iosMain folder would be the right place for such calls. Similarly, if you want to edit the Desktop (JVM) specific part, the jvmMain folder is the appropriate location.
-
/iosApp contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform, you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.
Build and Run Android Application
To build and run the development version of the Android app, use the run configuration from the run widget in your IDE’s toolbar or build it directly from the terminal:
- on macOS/Linux
./gradlew :composeApp:assembleDebug - on Windows
.\gradlew.bat :composeApp:assembleDebug
Build and Run Desktop (JVM) Application
To build and run the development version of the desktop app, use the run configuration from the run widget in your IDE’s toolbar or run it directly from the terminal:
- on macOS/Linux
./gradlew :composeApp:run - on Windows
.\gradlew.bat :composeApp:run
Build and Run iOS Application
To build and run the development version of the iOS app, use the run configuration from the run widget in your IDE’s toolbar or open the /iosApp directory in Xcode and run it from there.
Learn more about Kotlin Multiplatform…