Two guards in `FrostSigningManager` that were correct only while every ceremony ran in a room of its own. A parent's Marmot room is about to host its subgroups' ceremonies, and both of these read a ceremony's room as though that could only mean one thing. Neither fails loudly. **`completedKey`'s last fallback is "a ceremony held in this very room", and that stops being the room's own key.** The fallback is not decoration: a room's `GroupKeyState` is signed before the room exists and filed as it is created, so every member welcomed after that -- an invite, a reinstall -- has a room and no state, and lands here. The parent's room will hold a *completed* ceremony whose threshold key belongs to the child, so those members would resolve the child's key for the parent and the group would author events as its own subgroup, with a valid signature and nothing on screen to say so. The certificate a subgroup is born with is exactly one of those events. `getLatestOwnSessionForChatRoom` is the same query with `parentChatRoomId IS NULL`. A ceremony run to make a subgroup is never the room's own key, and that column is all that has to be read to know it. **`signingPath` has one case it cannot self-check, and there are now two rooms in it.** Everywhere else a candidate path is right exactly when walking it reaches the room, which makes the function self-checking rather than trusting -- and the path decides what key the group signs as, so it must never come off a proposal. The exception is the room a ceremony ran in, signing the statement that lets the room the ceremony's key derives be created. That room is not derived from the key at all, so nothing rederives. It used to mean "a NIP-17 room whose ceremony is its own", gated on `mlsGroupState == null`. It now also means "the parent's room, where the ceremony claims that parent" -- without which a subgroup's key state would be signed as the bare threshold key, an identity no room answers to. `key.parentChatRoomId` is an unverified claim off a proposal and admitting it here grants nothing. The signature it enables is by the *child's* key over the *child's* own id, both derived from a ceremony every signer contributed to and approved twice. A member who put a false parent on a proposal ends up with a key state for a room made from a key they helped make, which is what telling the truth would have got them. Both inputs are still read from this device's own database, so a proposer chooses nothing: naming some other ceremony this device holds a share for gets no path, and a session with no path signs as the threshold key. 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…