diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt new file mode 100644 index 000000000..8d7ca91f2 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.experimental.decoupling + +import com.vitorpamplona.quartz.experimental.decoupling.setup.EncryptionKeyListEvent +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray +import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner +import com.vitorpamplona.quartz.nip44Encryption.Nip44 + +class DecoupledCipher { + fun innerEncrypt( + content: String, + privKey: ByteArray, + toPublicKey: HexKey, + ) = Nip44 + .encrypt(content, privKey, toPublicKey.hexToByteArray()) + .encodePayload() + + fun innerDecrypt( + ciphertext: String, + privKey: ByteArray, + fromPublicKey: HexKey, + ) = Nip44.decrypt( + payload = ciphertext, + privateKey = privKey, + pubKey = fromPublicKey.hexToByteArray(), + ) + + fun encrypt( + decryptedContent: String, + toPublicKey: HexKey, + fromKeyList: EncryptionKeyListEvent, + toKeyList: EncryptionKeyListEvent, + signer: NostrSigner, + onReady: (String) -> Unit, + ) { + val toKeys = toKeyList.keys() + val sendToKey = if (toKeys.isEmpty()) toKeyList.pubKey else toKeys.random().pubkey + + val fromKeys = fromKeyList.keys() + + // uses the main key + if (fromKeys.isEmpty()) { + signer.nip44Encrypt(decryptedContent, sendToKey, onReady) + } else { + val keyToUse = fromKeys.random() + + EncryptionKeyCache.getOrLoad( + deriveFromPubKey = signer.pubKey, + nonce = keyToUse.nonce, + load = { onLoaded -> + signer.deriveKey(keyToUse.nonce) { newPrivKey -> + onLoaded(newPrivKey.hexToByteArray()) + } + }, + ) { derivedPrivKey -> + onReady(innerEncrypt(decryptedContent, derivedPrivKey, sendToKey)) + } + } + } + + fun decrypt( + encryptedContent: String, + fromPublicKey: HexKey, + toPublicKey: HexKey, + fromKeyList: EncryptionKeyListEvent, + toEncryptedKeyList: EncryptionKeyListEvent, + signer: NostrSigner, + onReady: (String) -> Unit, + ) { + val fromKeys = fromKeyList.keys() + val sentFromKey = if (fromKeys.isEmpty()) fromKeyList.pubKey else fromKeys.random().pubkey + + val keyToUse = toEncryptedKeyList.keys().firstOrNull { it.pubkey == toPublicKey } + + // uses the main key + if (signer.pubKey == toPublicKey) { + signer.nip44Decrypt(encryptedContent, sentFromKey, onReady) + } else if (keyToUse != null) { + EncryptionKeyCache.getOrLoad( + deriveFromPubKey = signer.pubKey, + nonce = keyToUse.nonce, + load = { onLoaded -> + signer.deriveKey(keyToUse.nonce) { newPrivKey -> + onLoaded(newPrivKey.hexToByteArray()) + } + }, + ) { derivedPrivKey -> + innerDecrypt(encryptedContent, derivedPrivKey, sentFromKey)?.let { onReady(it) } + } + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt new file mode 100644 index 000000000..0cddc52af --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.experimental.decoupling + +import android.util.LruCache +import com.vitorpamplona.quartz.nip01Core.core.HexKey + +object EncryptionKeyCache { + private val sharedNonceKeyCache = LruCache(50) + + private fun idx( + key: HexKey, + nonce: HexKey, + ) = EncryptionKeyCacheKey(key, nonce) + + fun put( + deriveFromPubKey: HexKey, + nonce: HexKey, + privKey: ByteArray, + ) = sharedNonceKeyCache.put(idx(deriveFromPubKey, nonce), privKey) + + fun get( + deriveFromPubKey: HexKey, + nonce: HexKey, + ) = sharedNonceKeyCache.get(idx(deriveFromPubKey, nonce)) + + inline fun getOrLoad( + deriveFromPubKey: HexKey, + nonce: HexKey, + load: (onLoaded: (privKey: ByteArray) -> Unit) -> Unit, + crossinline whenReady: (privKey: ByteArray) -> Unit, + ) { + val cachedPrivKey = get(deriveFromPubKey, nonce) + if (cachedPrivKey != null) { + whenReady(cachedPrivKey) + return + } + + load { newPrivKey -> + put(deriveFromPubKey, nonce, newPrivKey) + whenReady(newPrivKey) + } + } +} + +// keeps a cache per signed-in user +private data class EncryptionKeyCacheKey( + val pubkey: HexKey, + val nonce: HexKey, +) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt new file mode 100644 index 000000000..15fbbad7a --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.experimental.decoupling + +import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair +import com.vitorpamplona.quartz.nip44Encryption.crypto.Hkdf +import com.vitorpamplona.quartz.utils.sha256.sha256 + +class EncryptionKeyDerivation { + companion object { + private val saltPrefix = "nip44kd".toByteArray(Charsets.UTF_8) + + fun derivePrivateKey( + privKey: ByteArray, + nonce: ByteArray?, + ): ByteArray { + if (nonce == null) { + return privKey + } + return sha256(Hkdf().extract(privKey, saltPrefix + nonce)) + } + + fun deriveKeyPair( + privKey: ByteArray, + nonce: ByteArray?, + ): KeyPair = KeyPair(privKey = derivePrivateKey(privKey, nonce)) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt new file mode 100644 index 000000000..e55232c0c --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.experimental.decoupling.setup + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.experimental.decoupling.setup.tags.KeyTag +import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.signers.eventUpdate +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class EncryptionKeyListEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) { + fun keys() = tags.mapNotNull(KeyTag::parse) + + companion object { + const val KIND = 10044 + const val ALT = "Encryption keys" + + fun add( + key: KeyTag, + currentEvent: EncryptionKeyListEvent, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventUpdate(currentEvent, createdAt) { + alt(ALT) + key(key) + initializer() + } + + fun remove( + key: KeyTag, + currentEvent: EncryptionKeyListEvent, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventUpdate(currentEvent, createdAt) { + alt(ALT) + removeIf(KeyTag::isSameKey, key.toTagArray()) + initializer() + } + + fun build( + keys: List, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, "", createdAt) { + alt(ALT) + keys(keys) + initializer() + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt new file mode 100644 index 000000000..a2a761236 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.experimental.decoupling.setup + +import com.vitorpamplona.quartz.experimental.decoupling.setup.tags.KeyTag +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder + +fun TagArrayBuilder.key( + key: HexKey, + nonce: HexKey, +) = add(KeyTag.assemble(key, nonce)) + +fun TagArrayBuilder.key(key: KeyTag) = add(key.toTagArray()) + +fun TagArrayBuilder.keys(keys: List) = addAll(keys.map { it.toTagArray() }) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt new file mode 100644 index 000000000..4cc4a1707 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.experimental.decoupling.setup.tags + +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class KeyTag( + val pubkey: HexKey, + val nonce: HexKey, +) { + fun toTagArray() = assemble(pubkey, nonce) + + companion object { + const val TAG_NAME = "n" + + fun isSameKey( + tag1: Array, + tag2: Array, + ): Boolean { + ensure(tag1.has(1)) { return false } + ensure(tag2.has(1)) { return false } + ensure(tag1[0] == tag2[0]) { return false } + ensure(tag1[1] == tag2[1]) { return false } + return true + } + + @JvmStatic + fun parse(tag: Array): KeyTag? { + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + ensure(tag[2].isNotEmpty()) { return null } + return KeyTag(tag[1], tag[2]) + } + + @JvmStatic + fun assemble( + key: HexKey, + nonce: HexKey, + ) = arrayOf(TAG_NAME, key, nonce) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt index cb9e70b98..d5f401c9b 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt @@ -73,6 +73,11 @@ abstract class NostrSigner( onReady: (LnZapPrivateEvent) -> Unit, ) + abstract fun deriveKey( + nonce: HexKey, + onReady: (HexKey) -> Unit, + ) + fun decrypt( encryptedContent: String, fromPublicKey: HexKey, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt index c49e52bc2..a2d568d6e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt @@ -80,4 +80,11 @@ class NostrSignerInternal( ) { signerSync.decryptZapEvent(event)?.let { onReady(it) } } + + override fun deriveKey( + nonce: HexKey, + onReady: (HexKey) -> Unit, + ) { + signerSync.deriveKey(nonce)?.let { onReady(it) } + } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt index 112e0eae4..3376d70fc 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.quartz.nip01Core.signers import android.util.Log +import com.vitorpamplona.quartz.experimental.decoupling.EncryptionKeyDerivation import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray @@ -128,4 +129,10 @@ class NostrSignerSync( } fun decryptZapEvent(event: LnZapRequestEvent): LnZapPrivateEvent? = PrivateZapRequestBuilder().decryptZapEvent(event, this) + + fun deriveKey(nonce: HexKey): HexKey? { + if (keyPair.privKey == null) return null + + return EncryptionKeyDerivation.derivePrivateKey(keyPair.privKey, nonce.hexToByteArray()).toHexKey() + } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/ExternalSignerLauncher.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/ExternalSignerLauncher.kt index f375dad42..fd59617c0 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/ExternalSignerLauncher.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/ExternalSignerLauncher.kt @@ -46,6 +46,7 @@ enum class SignerType { NIP44_DECRYPT, GET_PUBLIC_KEY, DECRYPT_ZAP_EVENT, + DERIVE_KEY, } class Permission( @@ -219,6 +220,7 @@ class ExternalSignerLauncher( SignerType.NIP44_DECRYPT -> "nip44_decrypt" SignerType.GET_PUBLIC_KEY -> "get_public_key" SignerType.DECRYPT_ZAP_EVENT -> "decrypt_zap_event" + SignerType.DERIVE_KEY -> "derive_key" } intent.putExtra("type", signerType) intent.putExtra("pubKey", pubKey) @@ -318,6 +320,15 @@ class ExternalSignerLauncher( return kotlin.Result.success(null) } + fun hashCodeFields( + str1: String, + onReady: (String) -> Unit, + ): Int { + var result = str1.hashCode() + result = 31 * result + onReady.hashCode() + return result + } + fun hashCodeFields( str1: String, str2: String, @@ -398,4 +409,27 @@ class ExternalSignerLauncher( }, ) } + + fun deriveKey( + nonce: HexKey, + signerType: SignerType = SignerType.DERIVE_KEY, + onReady: (String) -> Unit, + ) { + getDataFromResolver(signerType, arrayOf(nonce)).fold( + onFailure = { }, + onSuccess = { + if (it == null) { + openSignerApp( + nonce, + signerType, + "", + hashCodeFields(nonce, onReady).toString(), + onReady, + ) + } else { + onReady(it) + } + }, + ) + } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/NostrSignerExternal.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/NostrSignerExternal.kt index b1e5bf32e..bf4c2164d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/NostrSignerExternal.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/NostrSignerExternal.kt @@ -133,6 +133,17 @@ class NostrSignerExternal( ) } + override fun deriveKey( + nonce: HexKey, + onReady: (HexKey) -> Unit, + ) { + launcher.deriveKey( + nonce, + SignerType.DERIVE_KEY, + onReady, + ) + } + override fun decryptZapEvent( event: LnZapRequestEvent, onReady: (LnZapPrivateEvent) -> Unit,