From 438a782cb184ccc02f3a30ccf4c837fc604fa32e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 17:45:08 +0000 Subject: [PATCH] Scaffold :shared KMP module and :desktop Compose app for bunker signing Adds a Kotlin Multiplatform :shared module (androidTarget + jvm("desktop")) depending on the multiplatform com.vitorpamplona.quartz:quartz coordinate (which resolves quartz-android for Android and quartz-jvm for desktop via Gradle variant-aware resolution), plus a new :desktop module scaffolded with Compose Multiplatform Desktop and packaging config for deb/msi/dmg installers. The :app module is untouched and still builds. --- build.gradle.kts | 5 +++ desktop/build.gradle.kts | 40 +++++++++++++++++++ .../greenart7c3/nostrsigner/desktop/Main.kt | 23 +++++++++++ gradle/libs.versions.toml | 11 +++++ settings.gradle.kts | 2 + shared/build.gradle.kts | 37 +++++++++++++++++ .../nostrsigner/shared/BunkerSigner.kt | 19 +++++++++ 7 files changed, 137 insertions(+) create mode 100644 desktop/build.gradle.kts create mode 100644 desktop/src/main/kotlin/com/greenart7c3/nostrsigner/desktop/Main.kt create mode 100644 shared/build.gradle.kts create mode 100644 shared/src/commonMain/kotlin/com/greenart7c3/nostrsigner/shared/BunkerSigner.kt diff --git a/build.gradle.kts b/build.gradle.kts index eeb5660e..2dce2366 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,11 @@ plugins { alias(libs.plugins.serialization) apply false alias(libs.plugins.kotlin.ksp) version libs.versions.ksp.get() apply false alias(libs.plugins.gradle.ktlint) version libs.versions.ktlint.get() apply false + alias(libs.plugins.kotlinMultiplatform) apply false + alias(libs.plugins.jetbrainsCompose) apply false + alias(libs.plugins.jetbrainsComposeCompiler) apply false + alias(libs.plugins.androidKotlinMultiplatformLibrary) apply false + alias(libs.plugins.kotlinJvm) apply false } tasks.register("installGitHook") { diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts new file mode 100644 index 00000000..a6d6c045 --- /dev/null +++ b/desktop/build.gradle.kts @@ -0,0 +1,40 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + alias(libs.plugins.kotlinJvm) + alias(libs.plugins.jetbrainsCompose) + alias(libs.plugins.jetbrainsComposeCompiler) + alias(libs.plugins.gradle.ktlint) version libs.versions.ktlint.get() +} + +kotlin { + jvmToolchain(21) +} + +dependencies { + implementation(project(":shared")) + + implementation(compose.desktop.currentOs) + implementation(compose.material3) + implementation(compose.materialIconsExtended) + + implementation(libs.quartz.multiplatform) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.core) // zxing core, for bunker connection QR codes + implementation(libs.java.keyring) + implementation(libs.androidx.sqlite.bundled) + + testImplementation(kotlin("test")) +} + +compose.desktop { + application { + mainClass = "com.greenart7c3.nostrsigner.desktop.MainKt" + + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "Amber Bunker" + packageVersion = "1.0.0" + } + } +} diff --git a/desktop/src/main/kotlin/com/greenart7c3/nostrsigner/desktop/Main.kt b/desktop/src/main/kotlin/com/greenart7c3/nostrsigner/desktop/Main.kt new file mode 100644 index 00000000..73b2af9b --- /dev/null +++ b/desktop/src/main/kotlin/com/greenart7c3/nostrsigner/desktop/Main.kt @@ -0,0 +1,23 @@ +package com.greenart7c3.nostrsigner.desktop + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application + +fun main() = application { + Window(onCloseRequest = ::exitApplication, title = "Amber Bunker") { + App() + } +} + +@Composable +fun App() { + MaterialTheme { + Surface { + Text("Amber Bunker Desktop") + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 08ae52fc..103b6c21 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,6 +33,9 @@ coroutinesTest = "1.11.0" kmpTor = "2.6.0" kmpTorResource = "409.5.0" secp256k1Jni = "0.23.0" +composeMultiplatform = "1.11.1" +androidxSqliteBundled = "2.6.2" +javaKeyring = "1.0.4" [libraries] datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" } @@ -53,6 +56,7 @@ material-icons-extended = { module = "androidx.compose.material:material-icons-e material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "nav_version" } quartz = { module = "com.vitorpamplona.quartz:quartz-android", version.ref = "quartz" } +quartz-multiplatform = { module = "com.vitorpamplona.quartz:quartz", version.ref = "quartz" } kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "collections" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomKtx" } room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" } @@ -79,15 +83,22 @@ paging-common = { group = "androidx.paging", name = "paging-common", version.ref paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "pagingCommon" } paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "pagingCommon" } mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutinesTest" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutinesTest" } kmptor-runtime = { module = "io.matthewnelson.kmp-tor:runtime", version.ref = "kmpTor" } kmptor-resource-exec = { module = "io.matthewnelson.kmp-tor:resource-exec-tor", version.ref = "kmpTorResource" } secp256k1-jni-jvm = { module = "fr.acinq.secp256k1:secp256k1-kmp-jni-jvm", version.ref = "secp256k1Jni" } +androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "androidxSqliteBundled" } +java-keyring = { module = "com.github.javakeyring:java-keyring", version.ref = "javaKeyring" } [plugins] androidLibrary = { id = "com.android.library", version.ref = "agp" } +androidKotlinMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } androidApplication = { id = "com.android.application", version.ref = "agp" } gradle_ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "ktlint" } serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } kotlin_ksp = { id = "com.google.devtools.ksp", version = "ksp" } jetbrainsComposeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } diff --git a/settings.gradle.kts b/settings.gradle.kts index b5119ecd..1109824a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,3 +17,5 @@ dependencyResolutionManagement { } rootProject.name = "Nostr Signer" include(":app") +include(":shared") +include(":desktop") diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts new file mode 100644 index 00000000..aaab59a9 --- /dev/null +++ b/shared/build.gradle.kts @@ -0,0 +1,37 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.androidKotlinMultiplatformLibrary) + alias(libs.plugins.gradle.ktlint) version libs.versions.ktlint.get() +} + +kotlin { + android { + namespace = "com.greenart7c3.nostrsigner.shared" + compileSdk = 37 + minSdk = 26 + + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } + + withHostTest {} + } + jvm("desktop") { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } + } + + sourceSets { + commonMain.dependencies { + implementation(libs.quartz.multiplatform) + implementation(libs.kotlinx.coroutines.core) + } + commonTest.dependencies { + implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) + } + } +} diff --git a/shared/src/commonMain/kotlin/com/greenart7c3/nostrsigner/shared/BunkerSigner.kt b/shared/src/commonMain/kotlin/com/greenart7c3/nostrsigner/shared/BunkerSigner.kt new file mode 100644 index 00000000..3be4cb86 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/greenart7c3/nostrsigner/shared/BunkerSigner.kt @@ -0,0 +1,19 @@ +package com.greenart7c3.nostrsigner.shared + +import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair +import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal + +/** Thin, platform-agnostic wrapper around a Quartz [NostrSignerInternal] used by the bunker signing engine. */ +class BunkerSigner(keyPair: KeyPair) { + private val signer = NostrSignerInternal(keyPair) + + val pubKey: String get() = signer.pubKey + + suspend fun nip44Encrypt(plainText: String, toPublicKey: String): String = signer.nip44Encrypt(plainText, toPublicKey) + + suspend fun nip44Decrypt(cipherText: String, fromPublicKey: String): String = signer.nip44Decrypt(cipherText, fromPublicKey) + + suspend fun nip04Encrypt(plainText: String, toPublicKey: String): String = signer.nip04Encrypt(plainText, toPublicKey) + + suspend fun nip04Decrypt(cipherText: String, fromPublicKey: String): String = signer.nip04Decrypt(cipherText, fromPublicKey) +}