mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-26 17:52:29 +01:00
Enables decryption by nip04 and nip44 on NostrWalletConnect objects, NIP-51 lists and NIP-04 messages
This commit is contained in:
parent
0f86e3d8fb
commit
cb4a73bb9c
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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.crypto.nip04
|
||||
|
||||
import com.vitorpamplona.quartz.crypto.nip01.Nip01
|
||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Test
|
||||
import java.security.SecureRandom
|
||||
|
||||
class Nip04Test {
|
||||
private val random = SecureRandom()
|
||||
private val nip01 = Nip01(Secp256k1.get(), random)
|
||||
private val nip04 = Nip04(Secp256k1.get(), random)
|
||||
|
||||
val sk1 = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe".hexToByteArray()
|
||||
val sk2 = "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220".hexToByteArray()
|
||||
val pk1 = nip01.pubkeyCreate(sk1)
|
||||
val pk2 = nip01.pubkeyCreate(sk2)
|
||||
|
||||
val expectedShared = "7ce22696eb0e303ddaa491bdf2a56b79d249f2d861b8e012a933e01dc4beba81"
|
||||
|
||||
@Test
|
||||
fun conversationKeyTest() {
|
||||
assertEquals(
|
||||
expectedShared,
|
||||
nip04.computeSharedSecret(sk2, pk1).toHexKey(),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
expectedShared,
|
||||
nip04.computeSharedSecret(sk1, pk2).toHexKey(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun encryptDecryptTest() {
|
||||
val message = "testing"
|
||||
val cipher = nip04.encrypt(message, sk2, pk1)
|
||||
|
||||
assertEquals(message, nip04.decrypt(cipher, sk2, pk1))
|
||||
assertEquals(message, nip04.decrypt(cipher, sk1, pk2))
|
||||
|
||||
val cipher2 = nip04.encrypt(message, sk1, pk2)
|
||||
|
||||
assertEquals(message, nip04.decrypt(cipher2, sk2, pk1))
|
||||
assertEquals(message, nip04.decrypt(cipher2, sk1, pk2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decryptTest() {
|
||||
val cipher = "zJxfaJ32rN5Dg1ODjOlEew==?iv=EV5bUjcc4OX2Km/zPp4ndQ=="
|
||||
|
||||
assertEquals("nanana", nip04.decrypt(cipher, nip04.computeSharedSecret(sk2, pk1)))
|
||||
assertEquals("nanana", nip04.decrypt(cipher, nip04.computeSharedSecret(sk1, pk2)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decryptLargePayloadTest() {
|
||||
val ciphertext =
|
||||
"6f8dMstm+udOu7yipSn33orTmwQpWbtfuY95NH+eTU1kArysWJIDkYgI2D25EAGIDJsNd45jOJ2NbVOhFiL3ZP/NWsTwXokk34iyHyA/lkjzugQ1bHXoMD1fP/Ay4hB4al1NHb8HXHKZaxPrErwdRDb8qa/I6dXb/1xxyVvNQBHHvmsM5yIFaPwnCN1DZqXf2KbTA/Ekz7Hy+7R+Sy3TXLQDFpWYqykppkXc7Fs0qSuPRyxz5+anuN0dxZa9GTwTEnBrZPbthKkNRrvZMdTGJ6WumOh9aUq8OJJWy9aOgsXvs7qjN1UqcCqQqYaVnEOhCaqWNDsVtsFrVDj+SaLIBvCiomwF4C4nIgngJ5I69tx0UNI0q+ZnvOGQZ7m1PpW2NYP7Yw43HJNdeUEQAmdCPnh/PJwzLTnIxHmQU7n7SPlMdV0SFa6H8y2HHvex697GAkyE5t8c2uO24OnqIwF1tR3blIqXzTSRl0GA6QvrSj2p4UtnWjvF7xT7RiIEyTtgU/AsihTrXyXzWWZaIBJogpgw6erlZqWjCH7sZy/WoGYEiblobOAqMYxax6vRbeuGtoYksr/myX+x9rfLrYuoDRTw4woXOLmMrrj+Mf0TbAgc3SjdkqdsPU1553rlSqIEZXuFgoWmxvVQDtekgTYyS97G81TDSK9nTJT5ilku8NVq2LgtBXGwsNIw/xekcOUzJke3kpnFPutNaexR1VF3ohIuqRKYRGcd8ADJP2lfwMcaGRiplAmFoaVS1YUhQwYFNq9rMLf7YauRGV4BJg/t9srdGxf5RoKCvRo+XM/nLxxysTR9MVaEP/3lDqjwChMxs+eWfLHE5vRWV8hUEqdrWNZV29gsx5nQpzJ4PARGZVu310pQzc6JAlc2XAhhFk6RamkYJnmCSMnb/RblzIATBi2kNrCVAlaXIon188inB62rEpZGPkRIP7PUfu27S/elLQHBHeGDsxOXsBRo1gl3te+raoBHsxo6zvRnYbwdAQa5taDE63eh+fT6kFI+xYmXNAQkU8Dp0MVhEh4JQI06Ni/AKrvYpC95TXXIphZcF+/Pv/vaGkhG2X9S3uhugwWK?iv=2vWkOQQi0WynNJz/aZ4k2g=="
|
||||
|
||||
val expected = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
|
||||
|
||||
assertEquals(expected, nip04.decrypt(ciphertext, nip04.computeSharedSecret(sk2, pk1)))
|
||||
assertEquals(expected, nip04.decrypt(ciphertext, nip04.computeSharedSecret(sk1, pk2)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isNIP04Encode() {
|
||||
assertTrue(Nip04.isNIP04("Xj/oZZolaItdyQ5v7xYFpA==?iv=+a6zagBp+mr5m1aFbHQ8lA=="))
|
||||
assertTrue(Nip04.isNIP04("zJxfaJ32rN5Dg1ODjOlEew==?iv=EV5bUjcc4OX2Km/zPp4ndQ=="))
|
||||
assertTrue(Nip04.isNIP04("6f8dMstm+udOu7yipSn33orTmwQpWbtfuY95NH+eTU1kArysWJIDkYgI2D25EAGIDJsNd45jOJ2NbVOhFiL3ZP/NWsTwXokk34iyHyA/lkjzugQ1bHXoMD1fP/Ay4hB4al1NHb8HXHKZaxPrErwdRDb8qa/I6dXb/1xxyVvNQBHHvmsM5yIFaPwnCN1DZqXf2KbTA/Ekz7Hy+7R+Sy3TXLQDFpWYqykppkXc7Fs0qSuPRyxz5+anuN0dxZa9GTwTEnBrZPbthKkNRrvZMdTGJ6WumOh9aUq8OJJWy9aOgsXvs7qjN1UqcCqQqYaVnEOhCaqWNDsVtsFrVDj+SaLIBvCiomwF4C4nIgngJ5I69tx0UNI0q+ZnvOGQZ7m1PpW2NYP7Yw43HJNdeUEQAmdCPnh/PJwzLTnIxHmQU7n7SPlMdV0SFa6H8y2HHvex697GAkyE5t8c2uO24OnqIwF1tR3blIqXzTSRl0GA6QvrSj2p4UtnWjvF7xT7RiIEyTtgU/AsihTrXyXzWWZaIBJogpgw6erlZqWjCH7sZy/WoGYEiblobOAqMYxax6vRbeuGtoYksr/myX+x9rfLrYuoDRTw4woXOLmMrrj+Mf0TbAgc3SjdkqdsPU1553rlSqIEZXuFgoWmxvVQDtekgTYyS97G81TDSK9nTJT5ilku8NVq2LgtBXGwsNIw/xekcOUzJke3kpnFPutNaexR1VF3ohIuqRKYRGcd8ADJP2lfwMcaGRiplAmFoaVS1YUhQwYFNq9rMLf7YauRGV4BJg/t9srdGxf5RoKCvRo+XM/nLxxysTR9MVaEP/3lDqjwChMxs+eWfLHE5vRWV8hUEqdrWNZV29gsx5nQpzJ4PARGZVu310pQzc6JAlc2XAhhFk6RamkYJnmCSMnb/RblzIATBi2kNrCVAlaXIon188inB62rEpZGPkRIP7PUfu27S/elLQHBHeGDsxOXsBRo1gl3te+raoBHsxo6zvRnYbwdAQa5taDE63eh+fT6kFI+xYmXNAQkU8Dp0MVhEh4JQI06Ni/AKrvYpC95TXXIphZcF+/Pv/vaGkhG2X9S3uhugwWK?iv=2vWkOQQi0WynNJz/aZ4k2g=="))
|
||||
}
|
||||
}
|
@ -20,10 +20,13 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.encoders
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class HexEncodingTest {
|
||||
val testHex = "48a72b485d38338627ec9d427583551f9af4f016c739b8ec0d6313540a8b12cf"
|
||||
|
||||
|
@ -81,6 +81,17 @@ object CryptoUtils {
|
||||
return nip01.sha256(data)
|
||||
}
|
||||
|
||||
fun decrypt(
|
||||
msg: String,
|
||||
privateKey: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): String? =
|
||||
if (Nip04.isNIP04(msg)) {
|
||||
decryptNIP04(msg, privateKey, pubKey)
|
||||
} else {
|
||||
decryptNIP44(msg, privateKey, pubKey)
|
||||
}
|
||||
|
||||
/** NIP 04 Utils */
|
||||
fun encryptNIP04(
|
||||
msg: String,
|
||||
|
@ -22,7 +22,6 @@ package com.vitorpamplona.quartz.crypto.nip04
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.quartz.crypto.SharedKeyCache
|
||||
import com.vitorpamplona.quartz.crypto.nip44.Nip44v1
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import java.security.SecureRandom
|
||||
@ -128,6 +127,10 @@ class Nip04(
|
||||
pubKey: ByteArray,
|
||||
): ByteArray = secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33)
|
||||
|
||||
companion object {
|
||||
fun isNIP04(encoded: String) = EncryptedInfo.isNIP04(encoded)
|
||||
}
|
||||
|
||||
class EncryptedInfo(
|
||||
val ciphertext: ByteArray,
|
||||
val nonce: ByteArray,
|
||||
@ -138,7 +141,7 @@ class Nip04(
|
||||
fun decodePayload(payload: String): EncryptedInfo? {
|
||||
return try {
|
||||
val byteArray = Base64.getDecoder().decode(payload)
|
||||
check(byteArray[0].toInt() == Nip44v1.EncryptedInfo.V)
|
||||
check(byteArray[0].toInt() == V)
|
||||
return EncryptedInfo(
|
||||
nonce = byteArray.copyOfRange(1, 25),
|
||||
ciphertext = byteArray.copyOfRange(25, byteArray.size),
|
||||
@ -149,6 +152,11 @@ class Nip04(
|
||||
}
|
||||
}
|
||||
|
||||
fun isNIP04(encoded: String): Boolean {
|
||||
val l = encoded.length
|
||||
return encoded[l - 28] == '?' && encoded[l - 27] == 'i' && encoded[l - 26] == 'v' && encoded[l - 25] == '='
|
||||
}
|
||||
|
||||
fun decodeFromNIP04(payload: String): EncryptedInfo? =
|
||||
try {
|
||||
val parts = payload.split("?iv=")
|
||||
|
@ -108,7 +108,7 @@ abstract class GeneralListEvent(
|
||||
}
|
||||
|
||||
try {
|
||||
signer.nip04Decrypt(content, pubKey) {
|
||||
signer.decrypt(content, pubKey) {
|
||||
privateTagsCache = mapper.readValue<Array<Array<String>>>(it)
|
||||
privateTagsCache?.let { onReady(it) }
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ class LnZapPaymentRequestEvent(
|
||||
}
|
||||
|
||||
try {
|
||||
signer.nip04Decrypt(content, talkingWith(signer.pubKey)) { jsonText ->
|
||||
signer.decrypt(content, talkingWith(signer.pubKey)) { jsonText ->
|
||||
val payInvoiceMethod = mapper.readValue(jsonText, Request::class.java)
|
||||
|
||||
lnInvoice = (payInvoiceMethod as? PayInvoiceMethod)?.params?.invoice
|
||||
|
@ -57,7 +57,7 @@ class LnZapPaymentResponseEvent(
|
||||
onReady: (String) -> Unit,
|
||||
) {
|
||||
try {
|
||||
signer.nip04Decrypt(content, talkingWith(signer.pubKey)) { content -> onReady(content) }
|
||||
signer.decrypt(content, talkingWith(signer.pubKey)) { content -> onReady(content) }
|
||||
} catch (e: Exception) {
|
||||
Log.w("PrivateDM", "Error decrypting the message ${e.message}")
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ class PrivateDmEvent(
|
||||
return
|
||||
}
|
||||
|
||||
signer.nip04Decrypt(content, talkingWith(signer.pubKey)) { retVal ->
|
||||
signer.decrypt(content, talkingWith(signer.pubKey)) { retVal ->
|
||||
val content =
|
||||
if (retVal.startsWith(NIP_18_ADVERTISEMENT)) {
|
||||
retVal.substring(16)
|
||||
|
@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.signers
|
||||
|
||||
import com.vitorpamplona.quartz.crypto.nip04.Nip04
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
@ -67,6 +68,18 @@ abstract class NostrSigner(
|
||||
onReady: (LnZapPrivateEvent) -> Unit,
|
||||
)
|
||||
|
||||
fun decrypt(
|
||||
encryptedContent: String,
|
||||
fromPublicKey: HexKey,
|
||||
onReady: (String) -> Unit,
|
||||
) {
|
||||
if (Nip04.isNIP04(encryptedContent)) {
|
||||
nip04Decrypt(encryptedContent, fromPublicKey, onReady)
|
||||
} else {
|
||||
nip44Decrypt(encryptedContent, fromPublicKey, onReady)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Event> assembleRumor(
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
|
Loading…
x
Reference in New Issue
Block a user