From da02f3666395bdfc274289332fc5134ed07b58f4 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sat, 29 Jul 2023 16:38:55 -0400 Subject: [PATCH] Refactoring to clarify the use of crypto functions --- .../vitorpamplona/amethyst/CryptoUtilsTest.kt | 26 +- .../amethyst/GiftWrapEventTest.kt | 110 +++----- .../amethyst/service/CryptoUtils.kt | 237 ++++++------------ .../amethyst/service/SodiumUtils.kt | 75 ++++++ .../amethyst/service/model/ATag.kt | 2 +- .../service/model/GeneralListEvent.kt | 8 +- .../amethyst/service/model/GiftWrapEvent.kt | 8 +- .../service/model/LnZapPaymentRequestEvent.kt | 6 +- .../model/LnZapPaymentResponseEvent.kt | 4 +- .../service/model/LnZapRequestEvent.kt | 4 +- .../amethyst/service/model/MuteListEvent.kt | 6 +- .../amethyst/service/model/PrivateDmEvent.kt | 6 +- .../service/model/SealedGossipEvent.kt | 8 +- .../amethyst/service/nip19/Nip19.kt | 9 +- 14 files changed, 227 insertions(+), 282 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/SodiumUtils.kt diff --git a/app/src/androidTest/java/com/vitorpamplona/amethyst/CryptoUtilsTest.kt b/app/src/androidTest/java/com/vitorpamplona/amethyst/CryptoUtilsTest.kt index d5119a45a..d3c845a3d 100644 --- a/app/src/androidTest/java/com/vitorpamplona/amethyst/CryptoUtilsTest.kt +++ b/app/src/androidTest/java/com/vitorpamplona/amethyst/CryptoUtilsTest.kt @@ -24,7 +24,7 @@ class CryptoUtilsTest { val privateKey = "f410f88bcec6cbfda04d6a273c7b1dd8bba144cd45b71e87109cfa11dd7ed561" val publicKey = "765cd7cf91d3ad07423d114d5a39c61d52b2cdbc18ba055ddbbeec71fbe2aa2f" - val key = CryptoUtils.getSharedSecretXChaCha(privateKey = privateKey.hexToByteArray(), pubKey = publicKey.hexToByteArray()) + val key = CryptoUtils.getSharedSecretNIP24(privateKey = privateKey.hexToByteArray(), pubKey = publicKey.hexToByteArray()) assertEquals("577c966f499dddd8e8dcc34e8f352e283cc177e53ae372794947e0b8ede7cfd8", key.toHexKey()) } @@ -34,8 +34,8 @@ class CryptoUtilsTest { val sender = KeyPair() val receiver = KeyPair() - val sharedSecret1 = CryptoUtils.getSharedSecretXChaCha(sender.privKey!!, receiver.pubKey) - val sharedSecret2 = CryptoUtils.getSharedSecretXChaCha(receiver.privKey!!, sender.pubKey) + val sharedSecret1 = CryptoUtils.getSharedSecretNIP24(sender.privKey!!, receiver.pubKey) + val sharedSecret2 = CryptoUtils.getSharedSecretNIP24(receiver.privKey!!, sender.pubKey) assertEquals(sharedSecret1.toHexKey(), sharedSecret2.toHexKey()) @@ -53,8 +53,8 @@ class CryptoUtilsTest { val privateKey = CryptoUtils.privkeyCreate() val publicKey = CryptoUtils.pubkeyCreate(privateKey) - val encrypted = CryptoUtils.encrypt(msg, privateKey, publicKey) - val decrypted = CryptoUtils.decrypt(encrypted, privateKey, publicKey) + val encrypted = CryptoUtils.encryptNIP04(msg, privateKey, publicKey) + val decrypted = CryptoUtils.decryptNIP04(encrypted, privateKey, publicKey) assertEquals(msg, decrypted) } @@ -66,8 +66,8 @@ class CryptoUtilsTest { val privateKey = CryptoUtils.privkeyCreate() val publicKey = CryptoUtils.pubkeyCreate(privateKey) - val encrypted = CryptoUtils.encryptXChaCha(msg, privateKey, publicKey) - val decrypted = CryptoUtils.decryptXChaCha(encrypted, privateKey, publicKey) + val encrypted = CryptoUtils.encryptNIP24(msg, privateKey, publicKey) + val decrypted = CryptoUtils.decryptNIP24(encrypted, privateKey, publicKey) assertEquals(msg, decrypted) } @@ -78,10 +78,10 @@ class CryptoUtilsTest { val privateKey = CryptoUtils.privkeyCreate() val publicKey = CryptoUtils.pubkeyCreate(privateKey) - val sharedSecret = CryptoUtils.getSharedSecret(privateKey, publicKey) + val sharedSecret = CryptoUtils.getSharedSecretNIP04(privateKey, publicKey) - val encrypted = CryptoUtils.encrypt(msg, sharedSecret) - val decrypted = CryptoUtils.decrypt(encrypted, sharedSecret) + val encrypted = CryptoUtils.encryptNIP04(msg, sharedSecret) + val decrypted = CryptoUtils.decryptNIP04(encrypted, sharedSecret) assertEquals(msg, decrypted) } @@ -92,10 +92,10 @@ class CryptoUtilsTest { val privateKey = CryptoUtils.privkeyCreate() val publicKey = CryptoUtils.pubkeyCreate(privateKey) - val sharedSecret = CryptoUtils.getSharedSecretXChaCha(privateKey, publicKey) + val sharedSecret = CryptoUtils.getSharedSecretNIP24(privateKey, publicKey) - val encrypted = CryptoUtils.encryptXChaCha(msg, sharedSecret) - val decrypted = CryptoUtils.decryptXChaCha(encrypted, sharedSecret) + val encrypted = CryptoUtils.encryptNIP24(msg, sharedSecret) + val decrypted = CryptoUtils.decryptNIP24(encrypted, sharedSecret) assertEquals(msg, decrypted) } diff --git a/app/src/androidTest/java/com/vitorpamplona/amethyst/GiftWrapEventTest.kt b/app/src/androidTest/java/com/vitorpamplona/amethyst/GiftWrapEventTest.kt index f7a2b0147..e622adb58 100644 --- a/app/src/androidTest/java/com/vitorpamplona/amethyst/GiftWrapEventTest.kt +++ b/app/src/androidTest/java/com/vitorpamplona/amethyst/GiftWrapEventTest.kt @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.hexToByteArray import com.vitorpamplona.amethyst.model.toHexKey import com.vitorpamplona.amethyst.service.CryptoUtils @@ -8,6 +9,7 @@ import com.vitorpamplona.amethyst.service.KeyPair import com.vitorpamplona.amethyst.service.model.ChatMessageEvent import com.vitorpamplona.amethyst.service.model.Event import com.vitorpamplona.amethyst.service.model.GiftWrapEvent +import com.vitorpamplona.amethyst.service.model.Gossip import com.vitorpamplona.amethyst.service.model.NIP24Factory import com.vitorpamplona.amethyst.service.model.SealedGossipEvent import com.vitorpamplona.amethyst.service.relays.Client @@ -34,18 +36,6 @@ class GiftWrapEventTest { sender.privKey!! ) - events.forEach { - if (it.recipientPubKey() == sender.pubKey.toHexKey()) { - println(sender.privKey!!.toHexKey()) - println(it.toJson()) - } - - if (it.recipientPubKey() == receiver.pubKey.toHexKey()) { - println(receiver.privKey!!.toHexKey()) - println(it.toJson()) - } - } - // Simulate Receiver val eventsReceiverGets = events.filter { it.isTaggedUser(receiver.pubKey.toHexKey()) } eventsReceiverGets.forEach { @@ -424,23 +414,11 @@ class GiftWrapEventTest { } """.trimIndent() - val privateKey = "de6152a85a0dea3b09a08a6f8139a314d498a7b52f7e5c28858b64270abd4c70".hexToByteArray() - val wrap = Event.fromJson(json, Client.lenient) as GiftWrapEvent + val privateKey = "de6152a85a0dea3b09a08a6f8139a314d498a7b52f7e5c28858b64270abd4c70" + val gossip = unwrapUnsealGossip(json, privateKey) - wrap.checkSignature() - - assertEquals(CryptoUtils.pubkeyCreate(privateKey).toHexKey(), wrap.recipientPubKey()) - - val event = wrap.unwrap(privateKey) - assertNotNull(event) - - if (event is SealedGossipEvent) { - val innerData = event.unseal(privateKey) - assertNotNull(innerData) - assertEquals("Hola, que tal?", innerData?.content) - } else { - fail("Inner event is not a Sealed Gossip") - } + assertNotNull(gossip) + assertEquals("Hola, que tal?", gossip?.content) } @Test @@ -462,23 +440,11 @@ class GiftWrapEventTest { } """.trimIndent() - val privateKey = "409ff7654141eaa16cd2161fe5bd127aeaef71f270c67587474b78998a8e3533".hexToByteArray() - val wrap = Event.fromJson(json, Client.lenient) as GiftWrapEvent + val privateKey = "409ff7654141eaa16cd2161fe5bd127aeaef71f270c67587474b78998a8e3533" + val gossip = unwrapUnsealGossip(json, privateKey) - wrap.checkSignature() - - assertEquals(CryptoUtils.pubkeyCreate(privateKey).toHexKey(), wrap.recipientPubKey()) - - val event = wrap.unwrap(privateKey) - assertNotNull(event) - - if (event is SealedGossipEvent) { - val innerData = event.unseal(privateKey) - assertNotNull(innerData) - assertEquals("Hola, que tal?", innerData?.content) - } else { - fail("Inner event is not a Sealed Gossip") - } + assertNotNull(gossip) + assertEquals("Hola, que tal?", gossip?.content) } @Test @@ -503,24 +469,11 @@ class GiftWrapEventTest { } """.trimIndent() - val privateKey = "09e0051fdf5fdd9dd7a54713583006442cbdbf87bdcdab1a402f26e527d56771".hexToByteArray() - val wrap = Event.fromJson(json, Client.lenient) as GiftWrapEvent + val privateKey = "09e0051fdf5fdd9dd7a54713583006442cbdbf87bdcdab1a402f26e527d56771" + val gossip = unwrapUnsealGossip(json, privateKey) - wrap.checkSignature() - - assertEquals(CryptoUtils.pubkeyCreate(privateKey).toHexKey(), wrap.recipientPubKey()) - - val event = wrap.unwrap(privateKey) - assertNotNull(event) - - if (event is SealedGossipEvent) { - val innerData = event.unseal(privateKey) - assertNotNull(innerData) - assertEquals("test", innerData?.content) - } else { - println(event?.toJson()) - fail() - } + assertNotNull(gossip) + assertEquals("test", gossip?.content) } @Test @@ -542,28 +495,35 @@ class GiftWrapEventTest { } """.trimIndent() - val privateKey = "09e0051fdf5fdd9dd7a54713583006442cbdbf87bdcdab1a402f26e527d56771".hexToByteArray() - val wrap = Event.fromJson(json, Client.lenient) as GiftWrapEvent + val privateKey = "09e0051fdf5fdd9dd7a54713583006442cbdbf87bdcdab1a402f26e527d56771" + val gossip = unwrapUnsealGossip(json, privateKey) + + assertEquals("asdfasdfasdf", gossip?.content) + assertEquals(0L, gossip?.createdAt) + assertEquals("827ba09d32ab81d62c60f657b350198c8aaba84372dab9ad3f4f6b8b7274b707", gossip?.id) + assertEquals(14, gossip?.kind) + assertEquals("subject", gossip?.tags?.firstOrNull()?.get(0)) + assertEquals("test", gossip?.tags?.firstOrNull()?.get(1)) + } + + fun unwrapUnsealGossip(json: String, privateKey: HexKey): Gossip? { + val pkBytes = privateKey.hexToByteArray() + + val wrap = Event.fromJson(json, Client.lenient) as GiftWrapEvent wrap.checkSignature() - assertEquals(CryptoUtils.pubkeyCreate(privateKey).toHexKey(), wrap.recipientPubKey()) + assertEquals(CryptoUtils.pubkeyCreate(pkBytes).toHexKey(), wrap.recipientPubKey()) - val event = wrap.unwrap(privateKey) + val event = wrap.unwrap(pkBytes) assertNotNull(event) - if (event is SealedGossipEvent) { - val innerData = event.unseal(privateKey) - assertNotNull(innerData) - assertEquals("asdfasdfasdf", innerData?.content) - assertEquals(0L, innerData?.createdAt) - assertEquals("827ba09d32ab81d62c60f657b350198c8aaba84372dab9ad3f4f6b8b7274b707", innerData?.id) - assertEquals(14, innerData?.kind) - assertEquals("subject", innerData?.tags?.firstOrNull()?.get(0)) - assertEquals("test", innerData?.tags?.firstOrNull()?.get(1)) + return if (event is SealedGossipEvent) { + return event.unseal(pkBytes) } else { println(event?.toJson()) - fail() + fail("Event is not a Sealed Gossip") + null } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/CryptoUtils.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/CryptoUtils.kt index 48586be20..f879c11e0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/CryptoUtils.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/CryptoUtils.kt @@ -1,10 +1,6 @@ package com.vitorpamplona.amethyst.service -import com.goterl.lazysodium.LazySodium -import com.goterl.lazysodium.LazySodiumAndroid -import com.goterl.lazysodium.Sodium import com.goterl.lazysodium.SodiumAndroid -import com.goterl.lazysodium.utils.Base64MessageEncoder import com.goterl.lazysodium.utils.Key import fr.acinq.secp256k1.Hex import fr.acinq.secp256k1.Secp256k1 @@ -34,86 +30,6 @@ object CryptoUtils { fun sign(data: ByteArray, privKey: ByteArray): ByteArray = secp256k1.signSchnorr(data, privKey, null) - fun encrypt(msg: String, privateKey: ByteArray, pubKey: ByteArray): String { - val sharedSecret = getSharedSecret(privateKey, pubKey) - return encrypt(msg, sharedSecret) - } - - fun encrypt(msg: String, sharedSecret: ByteArray): String { - val iv = ByteArray(16) - random.nextBytes(iv) - val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(iv)) - val ivBase64 = Base64.getEncoder().encodeToString(iv) - val encryptedMsg = cipher.doFinal(msg.toByteArray()) - val encryptedMsgBase64 = Base64.getEncoder().encodeToString(encryptedMsg) - return "$encryptedMsgBase64?iv=$ivBase64" - } - - fun decrypt(msg: String, privateKey: ByteArray, pubKey: ByteArray): String { - val sharedSecret = getSharedSecret(privateKey, pubKey) - return decrypt(msg, sharedSecret) - } - - fun decrypt(msg: String, sharedSecret: ByteArray): String { - val parts = msg.split("?iv=") - val iv = parts[1].run { Base64.getDecoder().decode(this) } - val encryptedMsg = parts.first().run { Base64.getDecoder().decode(this) } - val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(iv)) - return String(cipher.doFinal(encryptedMsg)) - } - - fun encryptXChaCha(msg: String, privateKey: ByteArray, pubKey: ByteArray): EncryptedInfo { - val sharedSecret = getSharedSecretXChaCha(privateKey, pubKey) - return encryptXChaCha(msg, sharedSecret) - } - - fun encryptXChaCha(msg: String, sharedSecret: ByteArray): EncryptedInfo { - val lazySodium = LazySodiumAndroid(SodiumAndroid(), Base64MessageEncoder()) - - val key = Key.fromBytes(sharedSecret) - - val nonce: ByteArray = lazySodium.nonce(24) - val messageBytes: ByteArray = msg.toByteArray() - - val cipher = lazySodium.cryptoStreamXChaCha20Xor( - messageBytes = messageBytes, - nonce = nonce, - key = key - ) - - val cipherBase64 = Base64.getEncoder().encodeToString(cipher) - val nonceBase64 = Base64.getEncoder().encodeToString(nonce) - - return EncryptedInfo( - ciphertext = cipherBase64, - nonce = nonceBase64, - v = Nip44Version.XChaCha20.versionCode - ) - } - - fun decryptXChaCha(encryptedInfo: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? { - val sharedSecret = getSharedSecretXChaCha(privateKey, pubKey) - return decryptXChaCha(encryptedInfo, sharedSecret) - } - - fun decryptXChaCha(encryptedInfo: EncryptedInfo, sharedSecret: ByteArray): String? { - val lazySodium = LazySodiumAndroid(SodiumAndroid(), Base64MessageEncoder()) - - val key = Key.fromBytes(sharedSecret) - val nonceBytes = Base64.getDecoder().decode(encryptedInfo.nonce) - val messageBytes = Base64.getDecoder().decode(encryptedInfo.ciphertext) - - val cipher = lazySodium.cryptoStreamXChaCha20Xor( - messageBytes = messageBytes, - nonce = nonceBytes, - key = key - ) - - return cipher?.decodeToString() - } - fun verifySignature( signature: ByteArray, hash: ByteArray, @@ -130,100 +46,87 @@ object CryptoUtils { /** * @return 32B shared secret */ - fun getSharedSecret(privateKey: ByteArray, pubKey: ByteArray): ByteArray = + fun getSharedSecretNIP04(privateKey: ByteArray, pubKey: ByteArray): ByteArray = secp256k1.pubKeyTweakMul(Hex.decode("02") + pubKey, privateKey).copyOfRange(1, 33) + fun encryptNIP04(msg: String, privateKey: ByteArray, pubKey: ByteArray): String { + val sharedSecret = getSharedSecretNIP04(privateKey, pubKey) + return encryptNIP04(msg, sharedSecret) + } + + fun encryptNIP04(msg: String, sharedSecret: ByteArray): String { + val iv = ByteArray(16) + random.nextBytes(iv) + val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(iv)) + val ivBase64 = Base64.getEncoder().encodeToString(iv) + val encryptedMsg = cipher.doFinal(msg.toByteArray()) + val encryptedMsgBase64 = Base64.getEncoder().encodeToString(encryptedMsg) + return "$encryptedMsgBase64?iv=$ivBase64" + } + + fun decryptNIP04(msg: String, privateKey: ByteArray, pubKey: ByteArray): String { + val sharedSecret = getSharedSecretNIP04(privateKey, pubKey) + return decryptNIP04(msg, sharedSecret) + } + + fun decryptNIP04(msg: String, sharedSecret: ByteArray): String { + val parts = msg.split("?iv=") + val iv = parts[1].run { Base64.getDecoder().decode(this) } + val encryptedMsg = parts.first().run { Base64.getDecoder().decode(this) } + val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(iv)) + return String(cipher.doFinal(encryptedMsg)) + } + + fun encryptNIP24(msg: String, privateKey: ByteArray, pubKey: ByteArray): EncryptedInfo { + val sharedSecret = getSharedSecretNIP24(privateKey, pubKey) + return encryptNIP24(msg, sharedSecret) + } + + fun encryptNIP24(msg: String, sharedSecret: ByteArray): EncryptedInfo { + val nonce = ByteArray(24) + random.nextBytes(nonce) + + val cipher = SodiumAndroid().cryptoStreamXChaCha20Xor( + messageBytes = msg.toByteArray(), + nonce = nonce, + key = Key.fromBytes(sharedSecret) + ) + + val cipherBase64 = Base64.getEncoder().encodeToString(cipher) + val nonceBase64 = Base64.getEncoder().encodeToString(nonce) + + return EncryptedInfo( + ciphertext = cipherBase64, + nonce = nonceBase64, + v = Nip44Version.XChaCha20.versionCode + ) + } + + fun decryptNIP24(encryptedInfo: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? { + val sharedSecret = getSharedSecretNIP24(privateKey, pubKey) + return decryptNIP24(encryptedInfo, sharedSecret) + } + + fun decryptNIP24(encryptedInfo: EncryptedInfo, sharedSecret: ByteArray): String? { + return SodiumAndroid().cryptoStreamXChaCha20Xor( + messageBytes = Base64.getDecoder().decode(encryptedInfo.ciphertext), + nonce = Base64.getDecoder().decode(encryptedInfo.nonce), + key = Key.fromBytes(sharedSecret) + )?.decodeToString() + } + /** * @return 32B shared secret */ - fun getSharedSecretXChaCha(privateKey: ByteArray, pubKey: ByteArray): ByteArray = + fun getSharedSecretNIP24(privateKey: ByteArray, pubKey: ByteArray): ByteArray = sha256(secp256k1.pubKeyTweakMul(Hex.decode("02") + pubKey, privateKey).copyOfRange(1, 33)) } -fun Int.toByteArray(): ByteArray { - val bytes = ByteArray(4) - (0..3).forEach { - bytes[3 - it] = ((this ushr (8 * it)) and 0xFFFF).toByte() - } - return bytes -} - data class EncryptedInfo(val ciphertext: String, val nonce: String, val v: Int) enum class Nip44Version(val versionCode: Int) { Reserved(0), XChaCha20(1) } - -fun Sodium.crypto_stream_xchacha20_xor_ic( - cipher: ByteArray, - message: ByteArray, - messageLen: Long, - nonce: ByteArray, - ic: Long, - key: ByteArray -): Int { - /** - * - * unsigned char k2[crypto_core_hchacha20_OUTPUTBYTES]; - - crypto_core_hchacha20(k2, n, k, NULL); - return crypto_stream_chacha20_xor_ic( - c, m, mlen, n + crypto_core_hchacha20_INPUTBYTES, ic, k2); - */ - - val k2 = ByteArray(32) - - val nonceChaCha = nonce.drop(16).toByteArray() - assert(nonceChaCha.size == 8) - - crypto_core_hchacha20(k2, nonce, key, null) - return crypto_stream_chacha20_xor_ic( - cipher, - message, - messageLen, - nonceChaCha, - ic, - k2 - ) -} - -fun Sodium.crypto_stream_xchacha20_xor( - cipher: ByteArray, - message: ByteArray, - messageLen: Long, - nonce: ByteArray, - key: ByteArray -): Int { - return crypto_stream_xchacha20_xor_ic(cipher, message, messageLen, nonce, 0, key) -} - -fun LazySodium.cryptoStreamXChaCha20Xor( - cipher: ByteArray, - message: ByteArray, - messageLen: Long, - nonce: ByteArray, - key: ByteArray -): Boolean { - require(!(messageLen < 0 || messageLen > message.size)) { "messageLen out of bounds: $messageLen" } - return successful( - getSodium().crypto_stream_xchacha20_xor( - cipher, - message, - messageLen, - nonce, - key - ) - ) -} - -private fun LazySodium.cryptoStreamXChaCha20Xor( - messageBytes: ByteArray, - nonce: ByteArray, - key: Key -): ByteArray? { - val mLen = messageBytes.size - val cipher = ByteArray(mLen) - val sucessful = cryptoStreamXChaCha20Xor(cipher, messageBytes, mLen.toLong(), nonce, key.asBytes) - return if (sucessful) cipher else null -} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/SodiumUtils.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/SodiumUtils.kt new file mode 100644 index 000000000..5cf64e8aa --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/SodiumUtils.kt @@ -0,0 +1,75 @@ +package com.vitorpamplona.amethyst.service + +import com.goterl.lazysodium.Sodium +import com.goterl.lazysodium.SodiumAndroid +import com.goterl.lazysodium.utils.Key + +fun Sodium.crypto_stream_xchacha20_xor_ic( + cipher: ByteArray, + message: ByteArray, + messageLen: Long, + nonce: ByteArray, + ic: Long, + key: ByteArray +): Int { + /** + * C++ Code: + * + * unsigned char k2[crypto_core_hchacha20_OUTPUTBYTES]; + * crypto_core_hchacha20(k2, n, k, NULL); + * return crypto_stream_chacha20_xor_ic( + * c, m, mlen, n + crypto_core_hchacha20_INPUTBYTES, ic, k2); + */ + val k2 = ByteArray(32) + + val nonceChaCha = nonce.drop(16).toByteArray() + assert(nonceChaCha.size == 8) + + crypto_core_hchacha20(k2, nonce, key, null) + return crypto_stream_chacha20_xor_ic( + cipher, + message, + messageLen, + nonceChaCha, + ic, + k2 + ) +} + +fun Sodium.crypto_stream_xchacha20_xor( + cipher: ByteArray, + message: ByteArray, + messageLen: Long, + nonce: ByteArray, + key: ByteArray +): Int { + return crypto_stream_xchacha20_xor_ic(cipher, message, messageLen, nonce, 0, key) +} + +fun SodiumAndroid.cryptoStreamXChaCha20Xor( + cipher: ByteArray, + message: ByteArray, + messageLen: Long, + nonce: ByteArray, + key: ByteArray +): Boolean { + require(!(messageLen < 0 || messageLen > message.size)) { "messageLen out of bounds: $messageLen" } + return crypto_stream_xchacha20_xor( + cipher, + message, + messageLen, + nonce, + key + ) == 0 +} + +fun SodiumAndroid.cryptoStreamXChaCha20Xor( + messageBytes: ByteArray, + nonce: ByteArray, + key: Key +): ByteArray? { + val mLen = messageBytes.size + val cipher = ByteArray(mLen) + val sucessful = cryptoStreamXChaCha20Xor(cipher, messageBytes, mLen.toLong(), nonce, key.asBytes) + return if (sucessful) cipher else null +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ATag.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ATag.kt index ec5c80946..970faa9b9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ATag.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ATag.kt @@ -6,7 +6,7 @@ import com.vitorpamplona.amethyst.model.hexToByteArray import com.vitorpamplona.amethyst.model.toHexKey import com.vitorpamplona.amethyst.service.bechToBytes import com.vitorpamplona.amethyst.service.nip19.Tlv -import com.vitorpamplona.amethyst.service.toByteArray +import com.vitorpamplona.amethyst.service.nip19.toByteArray import com.vitorpamplona.amethyst.service.toNAddress import fr.acinq.secp256k1.Hex diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/GeneralListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/GeneralListEvent.kt index 84234bcb7..17713ac8d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/GeneralListEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/GeneralListEvent.kt @@ -28,9 +28,9 @@ abstract class GeneralListEvent( if (content.isBlank()) return null return try { - val sharedSecret = CryptoUtils.getSharedSecret(privKey, pubKey.hexToByteArray()) + val sharedSecret = CryptoUtils.getSharedSecretNIP04(privKey, pubKey.hexToByteArray()) - return CryptoUtils.decrypt(content, sharedSecret) + return CryptoUtils.decryptNIP04(content, sharedSecret) } catch (e: Exception) { Log.w("GeneralList", "Error decrypting the message ${e.message} for ${dTag()}") null @@ -91,7 +91,7 @@ abstract class GeneralListEvent( } val msg = gson.toJson(privTags) - return CryptoUtils.encrypt( + return CryptoUtils.encryptNIP04( msg, privateKey, pubKey @@ -102,7 +102,7 @@ abstract class GeneralListEvent( privateTags: List>? = null, privateKey: ByteArray ): String { - return CryptoUtils.encrypt( + return CryptoUtils.encryptNIP04( msg = gson.toJson(privateTags), privateKey = privateKey, pubKey = CryptoUtils.pubkeyCreate(privateKey) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/GiftWrapEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/GiftWrapEvent.kt index ec6cf4753..1365dd3c2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/GiftWrapEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/GiftWrapEvent.kt @@ -40,14 +40,14 @@ class GiftWrapEvent( if (content.isBlank()) return null return try { - val sharedSecret = CryptoUtils.getSharedSecretXChaCha(privKey, pubKey.hexToByteArray()) + val sharedSecret = CryptoUtils.getSharedSecretNIP24(privKey, pubKey.hexToByteArray()) val toDecrypt = gson.fromJson( content, EncryptedInfo::class.java ) - return CryptoUtils.decryptXChaCha(toDecrypt, sharedSecret) + return CryptoUtils.decryptNIP24(toDecrypt, sharedSecret) } catch (e: Exception) { Log.w("GeneralList", "Error decrypting the message ${e.message}") null @@ -65,10 +65,10 @@ class GiftWrapEvent( createdAt: Long = TimeUtils.now() ): GiftWrapEvent { val privateKey = CryptoUtils.privkeyCreate() // GiftWrap is always a random key - val sharedSecret = CryptoUtils.getSharedSecretXChaCha(privateKey, recipientPubKey.hexToByteArray()) + val sharedSecret = CryptoUtils.getSharedSecretNIP24(privateKey, recipientPubKey.hexToByteArray()) val content = gson.toJson( - CryptoUtils.encryptXChaCha( + CryptoUtils.encryptNIP24( gson.toJson(event), sharedSecret ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt index 067f8e6bc..726a355a1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt @@ -35,9 +35,9 @@ class LnZapPaymentRequestEvent( } return try { - val sharedSecret = CryptoUtils.getSharedSecret(privKey, pubkey) + val sharedSecret = CryptoUtils.getSharedSecretNIP04(privKey, pubkey) - val jsonText = CryptoUtils.decrypt(content, sharedSecret) + val jsonText = CryptoUtils.decryptNIP04(content, sharedSecret) val payInvoiceMethod = gson.fromJson(jsonText, Request::class.java) @@ -62,7 +62,7 @@ class LnZapPaymentRequestEvent( val pubKey = CryptoUtils.pubkeyCreate(privateKey) val serializedRequest = gson.toJson(PayInvoiceMethod.create(lnInvoice)) - val content = CryptoUtils.encrypt( + val content = CryptoUtils.encryptNIP04( serializedRequest, privateKey, walletServicePubkey.hexToByteArray() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt index 7fdcf7674..fe6e5cc6e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt @@ -30,9 +30,9 @@ class LnZapPaymentResponseEvent( private fun decrypt(privKey: ByteArray, pubKey: ByteArray): String? { return try { - val sharedSecret = CryptoUtils.getSharedSecret(privKey, pubKey) + val sharedSecret = CryptoUtils.getSharedSecretNIP04(privKey, pubKey) - val retVal = CryptoUtils.decrypt(content, sharedSecret) + val retVal = CryptoUtils.decryptNIP04(content, sharedSecret) if (retVal.startsWith(PrivateDmEvent.nip18Advertisement)) { retVal.substring(16) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt index 9412d8ddc..ab60da265 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt @@ -136,7 +136,7 @@ class LnZapRequestEvent( } private fun encryptPrivateZapMessage(msg: String, privkey: ByteArray, pubkey: ByteArray): String { - var sharedSecret = CryptoUtils.getSharedSecret(privkey, pubkey) + var sharedSecret = CryptoUtils.getSharedSecretNIP04(privkey, pubkey) val iv = ByteArray(16) SecureRandom().nextBytes(iv) @@ -155,7 +155,7 @@ class LnZapRequestEvent( } private fun decryptPrivateZapMessage(msg: String, privkey: ByteArray, pubkey: ByteArray): String { - var sharedSecret = CryptoUtils.getSharedSecret(privkey, pubkey) + var sharedSecret = CryptoUtils.getSharedSecretNIP04(privkey, pubkey) if (sharedSecret.size != 16 && sharedSecret.size != 32) { throw IllegalArgumentException("Invalid shared secret size") } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/MuteListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/MuteListEvent.kt index e2b870b3a..cdcd0b366 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/MuteListEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/MuteListEvent.kt @@ -23,9 +23,9 @@ class MuteListEvent( fun plainContent(privKey: ByteArray): String? { return try { - val sharedSecret = CryptoUtils.getSharedSecret(privKey, pubKey.hexToByteArray()) + val sharedSecret = CryptoUtils.getSharedSecretNIP04(privKey, pubKey.hexToByteArray()) - return CryptoUtils.decrypt(content, sharedSecret) + return CryptoUtils.decryptNIP04(content, sharedSecret) } catch (e: Exception) { Log.w("BookmarkList", "Error decrypting the message ${e.message}") null @@ -87,7 +87,7 @@ class MuteListEvent( } val msg = gson.toJson(privTags) - val content = CryptoUtils.encrypt( + val content = CryptoUtils.encryptNIP04( msg, privateKey, pubKey diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt index c1d41e845..d89ad01d1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt @@ -55,9 +55,9 @@ class PrivateDmEvent( fun plainContent(privKey: ByteArray, pubKey: ByteArray): String? { return try { - val sharedSecret = CryptoUtils.getSharedSecret(privKey, pubKey) + val sharedSecret = CryptoUtils.getSharedSecretNIP04(privKey, pubKey) - val retVal = CryptoUtils.decrypt(content, sharedSecret) + val retVal = CryptoUtils.decryptNIP04(content, sharedSecret) if (retVal.startsWith(nip18Advertisement)) { retVal.substring(16) @@ -89,7 +89,7 @@ class PrivateDmEvent( zapRaiserAmount: Long?, geohash: String? = null ): PrivateDmEvent { - val content = CryptoUtils.encrypt( + val content = CryptoUtils.encryptNIP04( if (advertiseNip18) { nip18Advertisement } else { "" } + msg, privateKey, recipientPubKey diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/SealedGossipEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/SealedGossipEvent.kt index d0333654f..4e12cfdd5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/SealedGossipEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/SealedGossipEvent.kt @@ -38,14 +38,14 @@ class SealedGossipEvent( if (content.isBlank()) return null return try { - val sharedSecret = CryptoUtils.getSharedSecretXChaCha(privKey, pubKey.hexToByteArray()) + val sharedSecret = CryptoUtils.getSharedSecretNIP24(privKey, pubKey.hexToByteArray()) val toDecrypt = gson.fromJson( content, EncryptedInfo::class.java ) - return CryptoUtils.decryptXChaCha(toDecrypt, sharedSecret) + return CryptoUtils.decryptNIP24(toDecrypt, sharedSecret) } catch (e: Exception) { Log.w("GeneralList", "Error decrypting the message ${e.message}") null @@ -71,10 +71,10 @@ class SealedGossipEvent( privateKey: ByteArray, createdAt: Long = TimeUtils.now() ): SealedGossipEvent { - val sharedSecret = CryptoUtils.getSharedSecretXChaCha(privateKey, encryptTo.hexToByteArray()) + val sharedSecret = CryptoUtils.getSharedSecretNIP24(privateKey, encryptTo.hexToByteArray()) val content = gson.toJson( - CryptoUtils.encryptXChaCha( + CryptoUtils.encryptNIP24( gson.toJson(gossip), sharedSecret ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt index 2ce81532e..39a5b0cb0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.hexToByteArray import com.vitorpamplona.amethyst.model.toHexKey import com.vitorpamplona.amethyst.service.bechToBytes -import com.vitorpamplona.amethyst.service.toByteArray import com.vitorpamplona.amethyst.service.toNEvent import java.util.regex.Pattern @@ -170,3 +169,11 @@ object Nip19 { return fullArray.toNEvent() } } + +fun Int.toByteArray(): ByteArray { + val bytes = ByteArray(4) + (0..3).forEach { + bytes[3 - it] = ((this ushr (8 * it)) and 0xFFFF).toByte() + } + return bytes +}