diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt index 1c60863d..844f735c 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt @@ -4,10 +4,10 @@ import android.annotation.SuppressLint import android.content.Context import android.content.SharedPreferences import com.greenart7c3.nostrsigner.models.Account -import com.greenart7c3.nostrsigner.models.hexToByteArray -import com.greenart7c3.nostrsigner.models.toHexKey import com.greenart7c3.nostrsigner.service.KeyPair import com.greenart7c3.nostrsigner.service.toNpub +import com.vitorpamplona.quartz.encoders.hexToByteArray +import com.vitorpamplona.quartz.encoders.toHexKey import fr.acinq.secp256k1.jni.BuildConfig import org.json.JSONObject import java.io.File diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/HexUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/HexUtils.kt deleted file mode 100644 index 50fb6596..00000000 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/HexUtils.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.greenart7c3.nostrsigner.models - -import fr.acinq.secp256k1.Hex - -/** Makes the distinction between String and Hex **/ -typealias HexKey = String - -fun ByteArray.toHexKey(): HexKey { - return Hex.encode(this) -} - -fun HexKey.hexToByteArray(): ByteArray { - return Hex.decode(this) -} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/IntentData.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/IntentData.kt index 1108f0c7..bce4d2e4 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/IntentData.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/IntentData.kt @@ -1,5 +1,7 @@ package com.greenart7c3.nostrsigner.models +import com.vitorpamplona.quartz.encoders.HexKey + class IntentData( val data: String, val name: String, @@ -14,5 +16,6 @@ enum class SignerType { NIP04_DECRYPT, NIP44_ENCRYPT, NIP44_DECRYPT, - GET_PUBLIC_KEY + GET_PUBLIC_KEY, + DECRYPT_ZAP_EVENT } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt index 3e4fe35f..dc3ebc2d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt @@ -16,6 +16,7 @@ object IntentUtils { "nip44_decrypt" -> SignerType.NIP44_DECRYPT "nip44_encrypt" -> SignerType.NIP44_ENCRYPT "get_public_key" -> SignerType.GET_PUBLIC_KEY + "decrypt_zap_event" -> SignerType.DECRYPT_ZAP_EVENT else -> SignerType.SIGN_EVENT } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/KeyPair.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/KeyPair.kt index 70032c2e..d21e189b 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/KeyPair.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/KeyPair.kt @@ -1,6 +1,6 @@ package com.greenart7c3.nostrsigner.service -import com.greenart7c3.nostrsigner.models.toHexKey +import com.vitorpamplona.quartz.encoders.toHexKey class KeyPair( privKey: ByteArray? = null, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/ATag.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/ATag.kt index d139da51..68c9bb60 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/ATag.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/ATag.kt @@ -2,9 +2,9 @@ package com.greenart7c3.nostrsigner.service.model import android.util.Log import androidx.compose.runtime.Immutable -import com.greenart7c3.nostrsigner.models.toHexKey import com.greenart7c3.nostrsigner.service.bechToBytes import com.greenart7c3.nostrsigner.service.nip19.Tlv +import com.vitorpamplona.quartz.encoders.toHexKey import fr.acinq.secp256k1.Hex @Immutable diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/Event.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/Event.kt index 85acf59b..bbdaa74e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/Event.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/Event.kt @@ -13,11 +13,11 @@ import com.google.gson.JsonPrimitive import com.google.gson.JsonSerializationContext import com.google.gson.JsonSerializer import com.google.gson.annotations.SerializedName -import com.greenart7c3.nostrsigner.models.HexKey import com.greenart7c3.nostrsigner.models.TimeUtils -import com.greenart7c3.nostrsigner.models.toHexKey import com.greenart7c3.nostrsigner.service.CryptoUtils import com.greenart7c3.nostrsigner.service.KeyPair +import com.vitorpamplona.quartz.encoders.HexKey +import com.vitorpamplona.quartz.encoders.toHexKey import fr.acinq.secp256k1.Hex import java.lang.reflect.Type import java.math.BigDecimal diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/EventInterface.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/EventInterface.kt index 23ff47c0..8a588442 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/EventInterface.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/EventInterface.kt @@ -1,7 +1,7 @@ package com.greenart7c3.nostrsigner.service.model import androidx.compose.runtime.Immutable -import com.greenart7c3.nostrsigner.models.HexKey +import com.vitorpamplona.quartz.encoders.HexKey import java.math.BigDecimal @Immutable diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/GeneralListEvent.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/GeneralListEvent.kt index e2cde7aa..f4bc181d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/model/GeneralListEvent.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/model/GeneralListEvent.kt @@ -3,9 +3,9 @@ package com.greenart7c3.nostrsigner.service.model import android.util.Log import androidx.compose.runtime.Immutable import com.google.gson.reflect.TypeToken -import com.greenart7c3.nostrsigner.models.HexKey -import com.greenart7c3.nostrsigner.models.hexToByteArray import com.greenart7c3.nostrsigner.service.CryptoUtils +import com.vitorpamplona.quartz.encoders.HexKey +import com.vitorpamplona.quartz.encoders.hexToByteArray @Immutable abstract class GeneralListEvent( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt index c200f4ec..30871ff0 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -3,6 +3,7 @@ package com.greenart7c3.nostrsigner.ui import android.annotation.SuppressLint import android.app.Activity.RESULT_OK import android.content.Intent +import android.util.Log import android.widget.Toast import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -59,7 +60,6 @@ import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.IntentData import com.greenart7c3.nostrsigner.models.SignerType import com.greenart7c3.nostrsigner.models.TimeUtils -import com.greenart7c3.nostrsigner.models.toHexKey import com.greenart7c3.nostrsigner.service.CryptoUtils import com.greenart7c3.nostrsigner.service.IntentUtils import com.greenart7c3.nostrsigner.service.getAppCompatActivity @@ -76,9 +76,22 @@ import com.vitorpamplona.quartz.crypto.CryptoUtils.getSharedSecretNIP44 import com.vitorpamplona.quartz.crypto.Nip44Version import com.vitorpamplona.quartz.crypto.decodeNIP44 import com.vitorpamplona.quartz.crypto.encodeNIP44 +import com.vitorpamplona.quartz.encoders.Bech32 +import com.vitorpamplona.quartz.encoders.hexToByteArray +import com.vitorpamplona.quartz.encoders.toHexKey +import com.vitorpamplona.quartz.events.LnZapPrivateEvent +import com.vitorpamplona.quartz.events.LnZapRequestEvent import fr.acinq.secp256k1.Hex +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout import org.json.JSONObject +import java.nio.charset.Charset +import java.security.SecureRandom +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec import com.vitorpamplona.quartz.encoders.hexToByteArray as hexToByteArray1 @SuppressLint("UnusedMaterialScaffoldPaddingParameter") @@ -156,9 +169,11 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j } ) } - SignerType.NIP04_DECRYPT, SignerType.NIP04_ENCRYPT, SignerType.NIP44_ENCRYPT, SignerType.NIP44_DECRYPT -> { + SignerType.NIP04_DECRYPT, SignerType.NIP04_ENCRYPT, SignerType.NIP44_ENCRYPT, SignerType.NIP44_DECRYPT, SignerType.DECRYPT_ZAP_EVENT -> { val key = if (it.type == SignerType.NIP04_DECRYPT || it.type == SignerType.NIP44_DECRYPT) { "$packageName-decryptnip4" + } else if (it.type == SignerType.DECRYPT_ZAP_EVENT) { + "$packageName-DECRYPT_ZAP_EVENT" } else { "$packageName-encryptnip4" } @@ -174,66 +189,132 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j it.type, { try { - val sig = try { - when (it.type) { - SignerType.NIP04_DECRYPT -> { - CryptoUtils.decrypt( - it.data, - account.keyPair.privKey, - Hex.decode(it.pubKey) - ) - } - SignerType.NIP04_ENCRYPT -> { - CryptoUtils.encrypt( - it.data, - account.keyPair.privKey, - Hex.decode(it.pubKey) - ) - } - SignerType.NIP44_ENCRYPT -> { - val sharedSecret = getSharedSecretNIP44(account.keyPair.privKey, it.pubKey.hexToByteArray1()) + coroutineScope.launch(Dispatchers.IO) { + val sig = try { + withTimeout(1000L) { + async { + when (it.type) { + SignerType.DECRYPT_ZAP_EVENT -> { + val event = com.vitorpamplona.quartz.events.Event.fromJson(it.data) as LnZapRequestEvent + val loggedInPrivateKey = account.keyPair.privKey - encodeNIP44( - encryptNIP44( - it.data, - sharedSecret - ) - ) - } - else -> { - val toDecrypt = decodeNIP44(it.data) ?: return@EncryptDecryptData - when (toDecrypt.v) { - Nip44Version.NIP04.versionCode -> decryptNIP04(toDecrypt, account.keyPair.privKey, it.pubKey.hexToByteArray1()) - Nip44Version.NIP44.versionCode -> decryptNIP44(toDecrypt, account.keyPair.privKey, it.pubKey.hexToByteArray1()) - else -> null - } - } - } ?: "Could not decrypt the message" - } catch (e: Exception) { - "Could not decrypt the message" - } + if (event.isPrivateZap()) { + val recipientPK = event.zappedAuthor().firstOrNull() + val recipientPost = event.zappedPost().firstOrNull() + if (recipientPK == account.keyPair.pubKey.toHexKey()) { + // if the receiver is logged in, these are the params. + val privateKeyToUse = loggedInPrivateKey + val pubkeyToUse = event.pubKey + + event.getPrivateZapEvent(privateKeyToUse, pubkeyToUse)?.toJson() ?: "" + } else { + // if the sender is logged in, these are the params + val altPubkeyToUse = recipientPK + val altPrivateKeyToUse = if (recipientPost != null) { + LnZapRequestEvent.createEncryptionPrivateKey( + loggedInPrivateKey.toHexKey(), + recipientPost, + event.createdAt + ) + } else if (recipientPK != null) { + LnZapRequestEvent.createEncryptionPrivateKey( + loggedInPrivateKey.toHexKey(), + recipientPK, + event.createdAt + ) + } else { + null + } + + try { + if (altPrivateKeyToUse != null && altPubkeyToUse != null) { + val altPubKeyFromPrivate = com.vitorpamplona.quartz.crypto.CryptoUtils.pubkeyCreate(altPrivateKeyToUse).toHexKey() + + if (altPubKeyFromPrivate == event.pubKey) { + val result = event.getPrivateZapEvent(altPrivateKeyToUse, altPubkeyToUse) + + result?.toJson() ?: "" + } else { + null + } + } else { + null + } + } catch (e: Exception) { + Log.e("Account", "Failed to create pubkey for ZapRequest ${event.id}", e) + null + } + } + } else { + null + } + } + SignerType.NIP04_DECRYPT -> { + CryptoUtils.decrypt( + it.data, + account.keyPair.privKey, + Hex.decode(it.pubKey) + ) + } + SignerType.NIP04_ENCRYPT -> { + CryptoUtils.encrypt( + it.data, + account.keyPair.privKey, + Hex.decode(it.pubKey) + ) + } + SignerType.NIP44_ENCRYPT -> { + val sharedSecret = getSharedSecretNIP44(account.keyPair.privKey, it.pubKey.hexToByteArray1()) + + encodeNIP44( + encryptNIP44( + it.data, + sharedSecret + ) + ) + } + else -> { + val toDecrypt = decodeNIP44(it.data) ?: return@async "Could not decrypt the message" + when (toDecrypt.v) { + Nip44Version.NIP04.versionCode -> decryptNIP04(toDecrypt, account.keyPair.privKey, it.pubKey.hexToByteArray1()) + Nip44Version.NIP44.versionCode -> decryptNIP44(toDecrypt, account.keyPair.privKey, it.pubKey.hexToByteArray1()) + else -> null + } + } + } ?: "Could not decrypt the message" + }.await() + } + } catch (e: Exception) { + "Could not decrypt the message" + } - coroutineScope.launch { val activity = context.getAppCompatActivity() if (packageName != null) { account.savedApps[key] = remember.value LocalPreferences.saveToEncryptedStorage(account) val intent = Intent() - intent.putExtra("signature", sig) + if (sig == "Could not decrypt the message" && (it.type == SignerType.DECRYPT_ZAP_EVENT)) { + intent.putExtra("signature", "") + } else { + intent.putExtra("signature", sig) + } if (it.type == SignerType.NIP44_DECRYPT || it.type == SignerType.NIP04_DECRYPT) { intent.putExtra("id", it.id) } activity?.setResult(RESULT_OK, intent) } else { clipboardManager.setText(AnnotatedString(sig)) - Toast.makeText( - context, - context.getString(R.string.signature_copied_to_the_clipboard), - Toast.LENGTH_SHORT - ).show() + coroutineScope.launch { + Toast.makeText( + context, + context.getString(R.string.signature_copied_to_the_clipboard), + Toast.LENGTH_SHORT + ).show() + } } activity?.finish() } + return@EncryptDecryptData } catch (e: Exception) { val message = if (it.type == SignerType.NIP04_ENCRYPT) "encrypt" else "decrypt" @@ -278,29 +359,128 @@ fun MainScreen(account: Account, accountStateViewModel: AccountStateViewModel, j return@EventData } - val id = event.id.hexToByteArray1() - val sig = CryptoUtils.sign(id, account.keyPair.privKey).toHexKey() + val localEvent = com.vitorpamplona.quartz.events.Event.fromJson(it.data) + if (localEvent is LnZapRequestEvent) { + val isPrivateZap = localEvent.tags.any { tag -> tag.any { t -> t == "anon" } } + val originalNoteId = localEvent.zappedPost()[0] + val pubkey = localEvent.zappedAuthor()[0] + Log.d("originalNoteId", originalNoteId) + var privkey = account.keyPair.privKey + var pubKey = account.keyPair.pubKey.toHexKey() + if (isPrivateZap) { + val encryptionPrivateKey = LnZapRequestEvent.createEncryptionPrivateKey( + privkey.toHexKey(), + originalNoteId, + localEvent.createdAt + ) + val noteJson = (LnZapPrivateEvent.create(privkey, listOf(localEvent.tags[0], localEvent.tags[1]), localEvent.content)).toJson() + val encryptedContent = encryptPrivateZapMessage( + noteJson, + encryptionPrivateKey, + pubkey.hexToByteArray() + ) + var tags = localEvent.tags.filter { !it.contains("anon") } + tags = tags + listOf(listOf("anon", encryptedContent)) + Log.d("tags", tags.toString()) + privkey = encryptionPrivateKey // sign event with generated privkey + pubKey = com.vitorpamplona.quartz.crypto.CryptoUtils.pubkeyCreate(encryptionPrivateKey).toHexKey() // updated event with according pubkey - coroutineScope.launch { - val activity = context.getAppCompatActivity() - if (packageName != null) { - account.savedApps[key] = remember.value - LocalPreferences.saveToEncryptedStorage(account) - val intent = Intent() - val signedEvent = Event(event.id, event.pubKey, event.createdAt, event.kind, event.tags, event.content, sig) - intent.putExtra("event", signedEvent.toJson()) - intent.putExtra("signature", sig) + val id = com.vitorpamplona.quartz.events.Event.generateId( + pubKey, + localEvent.createdAt, + LnZapRequestEvent.kind, + tags, + "" + ) + val sig = com.vitorpamplona.quartz.crypto.CryptoUtils.sign(id, privkey) + val event = LnZapRequestEvent(id.toHexKey(), pubKey, localEvent.createdAt, tags, "", sig.toHexKey()) + coroutineScope.launch { + val activity = context.getAppCompatActivity() + if (packageName != null) { + account.savedApps[key] = remember.value + LocalPreferences.saveToEncryptedStorage(account) + val intent = Intent() + intent.putExtra("event", event.toJson()) + intent.putExtra("signature", event.toJson()) - activity?.setResult(RESULT_OK, intent) + activity?.setResult(RESULT_OK, intent) + } else { + clipboardManager.setText(AnnotatedString(event.toJson())) + Toast.makeText( + context, + context.getString(R.string.signature_copied_to_the_clipboard), + Toast.LENGTH_SHORT + ).show() + } + activity?.finish() + } } else { - clipboardManager.setText(AnnotatedString(sig)) - Toast.makeText( - context, - context.getString(R.string.signature_copied_to_the_clipboard), - Toast.LENGTH_SHORT - ).show() + val id = event.id.hexToByteArray1() + val sig = CryptoUtils.sign(id, account.keyPair.privKey).toHexKey() + + coroutineScope.launch { + val activity = context.getAppCompatActivity() + if (packageName != null) { + account.savedApps[key] = remember.value + LocalPreferences.saveToEncryptedStorage(account) + val intent = Intent() + val signedEvent = Event( + event.id, + event.pubKey, + event.createdAt, + event.kind, + event.tags, + event.content, + sig + ) + intent.putExtra("event", signedEvent.toJson()) + intent.putExtra("signature", sig) + + activity?.setResult(RESULT_OK, intent) + } else { + clipboardManager.setText(AnnotatedString(sig)) + Toast.makeText( + context, + context.getString(R.string.signature_copied_to_the_clipboard), + Toast.LENGTH_SHORT + ).show() + } + activity?.finish() + } + } + } else { + val id = event.id.hexToByteArray1() + val sig = CryptoUtils.sign(id, account.keyPair.privKey).toHexKey() + + coroutineScope.launch { + val activity = context.getAppCompatActivity() + if (packageName != null) { + account.savedApps[key] = remember.value + LocalPreferences.saveToEncryptedStorage(account) + val intent = Intent() + val signedEvent = Event( + event.id, + event.pubKey, + event.createdAt, + event.kind, + event.tags, + event.content, + sig + ) + intent.putExtra("event", signedEvent.toJson()) + intent.putExtra("signature", sig) + + activity?.setResult(RESULT_OK, intent) + } else { + clipboardManager.setText(AnnotatedString(sig)) + Toast.makeText( + context, + context.getString(R.string.signature_copied_to_the_clipboard), + Toast.LENGTH_SHORT + ).show() + } + activity?.finish() } - activity?.finish() } }, { @@ -665,3 +845,22 @@ fun EventRow( ) } } + +fun encryptPrivateZapMessage(msg: String, privkey: ByteArray, pubkey: ByteArray): String { + val sharedSecret = com.vitorpamplona.quartz.crypto.CryptoUtils.getSharedSecretNIP04(privkey, pubkey) + val iv = ByteArray(16) + SecureRandom().nextBytes(iv) + + val keySpec = SecretKeySpec(sharedSecret, "AES") + val ivSpec = IvParameterSpec(iv) + + val utf8message = msg.toByteArray(Charset.forName("utf-8")) + val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec) + val encryptedMsg = cipher.doFinal(utf8message) + + val encryptedMsgBech32 = Bech32.encode("pzap", Bech32.eight2five(encryptedMsg), Bech32.Encoding.Bech32) + val ivBech32 = Bech32.encode("iv", Bech32.eight2five(iv), Bech32.Encoding.Bech32) + + return encryptedMsgBech32 + "_" + ivBech32 +}