Moves to NIP-44v2
This commit is contained in:
@@ -1,32 +1,26 @@
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
|
||||
import android.util.Log
|
||||
import android.util.LruCache
|
||||
import com.goterl.lazysodium.SodiumAndroid
|
||||
import com.goterl.lazysodium.utils.Key
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
import java.util.Base64
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
|
||||
object CryptoUtils {
|
||||
private val sharedKeyCache04 = LruCache<Int, ByteArray>(200)
|
||||
private val sharedKeyCache44 = LruCache<Int, ByteArray>(200)
|
||||
|
||||
private val secp256k1 = Secp256k1.get()
|
||||
private val libSodium = SodiumAndroid()
|
||||
private val random = SecureRandom()
|
||||
private val h02 = Hex.decode("02")
|
||||
|
||||
private val nip04 = Nip04(secp256k1, random)
|
||||
private val nip44v1 = Nip44v1(secp256k1, random)
|
||||
private val nip44v2 = Nip44v2(secp256k1, random)
|
||||
|
||||
fun clearCache() {
|
||||
sharedKeyCache04.evictAll()
|
||||
sharedKeyCache44.evictAll()
|
||||
nip04.clearCache()
|
||||
nip44v1.clearCache()
|
||||
nip44v2.clearCache()
|
||||
}
|
||||
|
||||
fun randomInt(bound: Int): Int {
|
||||
@@ -63,242 +57,174 @@ object CryptoUtils {
|
||||
return MessageDigest.getInstance("SHA-256").digest(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* NIP 04 Utils
|
||||
*/
|
||||
fun encryptNIP04(msg: String, privateKey: ByteArray, pubKey: ByteArray): String {
|
||||
val info = encryptNIP04(msg, getSharedSecretNIP04(privateKey, pubKey))
|
||||
val encryptionInfo = EncryptedInfoString(
|
||||
v = info.v,
|
||||
nonce = Base64.getEncoder().encodeToString(info.nonce),
|
||||
ciphertext = Base64.getEncoder().encodeToString(info.ciphertext)
|
||||
)
|
||||
return "${encryptionInfo.ciphertext}?iv=${encryptionInfo.nonce}"
|
||||
return nip04.encrypt(msg, privateKey, pubKey)
|
||||
}
|
||||
|
||||
fun encryptNIP04Json(msg: String, privateKey: ByteArray, pubKey: ByteArray): EncryptedInfo {
|
||||
return encryptNIP04(msg, getSharedSecretNIP04(privateKey, pubKey))
|
||||
}
|
||||
|
||||
fun encryptNIP04(msg: String, sharedSecret: ByteArray): EncryptedInfo {
|
||||
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 EncryptedInfo(encryptedMsg, iv, Nip44Version.NIP04.versionCode)
|
||||
fun encryptNIP04(msg: String, sharedSecret: ByteArray): Nip04.EncryptedInfo {
|
||||
return nip04.encrypt(msg, sharedSecret)
|
||||
}
|
||||
|
||||
fun decryptNIP04(msg: String, privateKey: ByteArray, pubKey: ByteArray): String {
|
||||
val sharedSecret = getSharedSecretNIP04(privateKey, pubKey)
|
||||
return decryptNIP04(msg, sharedSecret)
|
||||
return nip04.decrypt(msg, privateKey, pubKey)
|
||||
}
|
||||
|
||||
fun decryptNIP04(encryptedInfo: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String {
|
||||
val sharedSecret = getSharedSecretNIP04(privateKey, pubKey)
|
||||
return decryptNIP04(encryptedInfo.ciphertext, encryptedInfo.nonce, sharedSecret)
|
||||
fun decryptNIP04(encryptedInfo: Nip04.EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String {
|
||||
return nip04.decrypt(encryptedInfo, privateKey, pubKey)
|
||||
}
|
||||
|
||||
fun decryptNIP04(msg: String, sharedSecret: ByteArray): String {
|
||||
val parts = msg.split("?iv=")
|
||||
return decryptNIP04(parts[0], parts[1], sharedSecret)
|
||||
return nip04.decrypt(msg, sharedSecret)
|
||||
}
|
||||
|
||||
private fun decryptNIP04(cipher: String, nonce: String, sharedSecret: ByteArray): String {
|
||||
val iv = Base64.getDecoder().decode(nonce)
|
||||
val encryptedMsg = Base64.getDecoder().decode(cipher)
|
||||
return decryptNIP04(encryptedMsg, iv, sharedSecret)
|
||||
return nip04.decrypt(cipher, nonce, sharedSecret)
|
||||
}
|
||||
|
||||
private fun decryptNIP04(encryptedMsg: ByteArray, iv: ByteArray, sharedSecret: ByteArray): String {
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(iv))
|
||||
return String(cipher.doFinal(encryptedMsg))
|
||||
return nip04.decrypt(encryptedMsg, iv, sharedSecret)
|
||||
}
|
||||
|
||||
fun encryptNIP44(msg: String, privateKey: ByteArray, pubKey: ByteArray): EncryptedInfo {
|
||||
val sharedSecret = getSharedSecretNIP44(privateKey, pubKey)
|
||||
return encryptNIP44(msg, sharedSecret)
|
||||
}
|
||||
|
||||
fun encryptNIP44(msg: String, sharedSecret: ByteArray): EncryptedInfo {
|
||||
val nonce = ByteArray(24)
|
||||
random.nextBytes(nonce)
|
||||
|
||||
val cipher = cryptoStreamXChaCha20Xor(
|
||||
libSodium = libSodium,
|
||||
messageBytes = msg.toByteArray(),//compress(msg),
|
||||
nonce = nonce,
|
||||
key = Key.fromBytes(sharedSecret)
|
||||
)
|
||||
|
||||
return EncryptedInfo(
|
||||
ciphertext = cipher ?: ByteArray(0),
|
||||
nonce = nonce,
|
||||
v = Nip44Version.NIP44.versionCode
|
||||
)
|
||||
}
|
||||
|
||||
fun decryptNIP44(encryptedInfo: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? {
|
||||
val sharedSecret = getSharedSecretNIP44(privateKey, pubKey)
|
||||
return decryptNIP44(encryptedInfo, sharedSecret)
|
||||
}
|
||||
|
||||
fun decryptNIP44(encryptedInfo: EncryptedInfo, sharedSecret: ByteArray): String? {
|
||||
return cryptoStreamXChaCha20Xor(
|
||||
libSodium = libSodium,
|
||||
messageBytes = encryptedInfo.ciphertext,
|
||||
nonce = encryptedInfo.nonce,
|
||||
key = Key.fromBytes(sharedSecret)
|
||||
)?.decodeToString() //?.let { decompress(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 32B shared secret
|
||||
*/
|
||||
fun getSharedSecretNIP04(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
|
||||
val hash = combinedHashCode(privateKey, pubKey)
|
||||
val preComputed = sharedKeyCache04[hash]
|
||||
if (preComputed != null) return preComputed
|
||||
return nip04.getSharedSecret(privateKey, pubKey)
|
||||
}
|
||||
|
||||
val computed = computeSharedSecretNIP04(privateKey, pubKey)
|
||||
sharedKeyCache04.put(hash, computed)
|
||||
return computed
|
||||
fun computeSharedSecretNIP04(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
|
||||
return nip04.computeSharedSecret(privateKey, pubKey)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NIP 44v1 Utils
|
||||
*/
|
||||
|
||||
fun encryptNIP44v1(msg: String, privateKey: ByteArray, pubKey: ByteArray): Nip44v1.EncryptedInfo {
|
||||
return nip44v1.encrypt(msg, privateKey, pubKey)
|
||||
}
|
||||
|
||||
fun encryptNIP44v1(msg: String, sharedSecret: ByteArray): Nip44v1.EncryptedInfo {
|
||||
return nip44v1.encrypt(msg, sharedSecret)
|
||||
}
|
||||
|
||||
fun decryptNIP44v1(encryptedInfo: Nip44v1.EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? {
|
||||
return nip44v1.decrypt(encryptedInfo, privateKey, pubKey)
|
||||
}
|
||||
|
||||
fun decryptNIP44v1(encryptedInfo: String, privateKey: ByteArray, pubKey: ByteArray): String? {
|
||||
return nip44v1.decrypt(encryptedInfo, privateKey, pubKey)
|
||||
}
|
||||
|
||||
fun decryptNIP44v1(encryptedInfo: Nip44v1.EncryptedInfo, sharedSecret: ByteArray): String? {
|
||||
return nip44v1.decrypt(encryptedInfo, sharedSecret)
|
||||
}
|
||||
|
||||
fun getSharedSecretNIP44v1(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
|
||||
return nip44v1.getSharedSecret(privateKey, pubKey)
|
||||
}
|
||||
|
||||
fun computeSharedSecretNIP44v1(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
|
||||
return nip44v1.computeSharedSecret(privateKey, pubKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 32B shared secret
|
||||
* NIP 44v2 Utils
|
||||
*/
|
||||
fun computeSharedSecretNIP04(privateKey: ByteArray, pubKey: ByteArray): ByteArray =
|
||||
secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33)
|
||||
|
||||
/**
|
||||
* @return 32B shared secret
|
||||
*/
|
||||
fun getSharedSecretNIP44(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
|
||||
val hash = combinedHashCode(privateKey, pubKey)
|
||||
val preComputed = sharedKeyCache44[hash]
|
||||
if (preComputed != null) return preComputed
|
||||
|
||||
val computed = computeSharedSecretNIP44(privateKey, pubKey)
|
||||
sharedKeyCache44.put(hash, computed)
|
||||
return computed
|
||||
fun encryptNIP44v2(msg: String, privateKey: ByteArray, pubKey: ByteArray): Nip44v2.EncryptedInfo {
|
||||
return nip44v2.encrypt(msg, privateKey, pubKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 32B shared secret
|
||||
*/
|
||||
fun computeSharedSecretNIP44(privateKey: ByteArray, pubKey: ByteArray): ByteArray =
|
||||
sha256(secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33))
|
||||
}
|
||||
|
||||
data class EncryptedInfo(val ciphertext: ByteArray, val nonce: ByteArray, val v: Int)
|
||||
data class EncryptedInfoString(val ciphertext: String, val nonce: String, val v: Int)
|
||||
|
||||
enum class Nip44Version(val versionCode: Int) {
|
||||
NIP04(0),
|
||||
NIP44(1)
|
||||
}
|
||||
|
||||
|
||||
fun encodeNIP44(info: EncryptedInfo): String {
|
||||
return encodeByteArray(info)
|
||||
}
|
||||
|
||||
fun decodeNIP44(str: String): EncryptedInfo? {
|
||||
if (str.isEmpty()) return null
|
||||
return if (str[0] == '{') {
|
||||
decodeJackson(str)
|
||||
} else {
|
||||
decodeByteArray(str)
|
||||
fun encryptNIP44v2(msg: String, sharedSecret: ByteArray): Nip44v2.EncryptedInfo {
|
||||
return nip44v2.encrypt(msg, sharedSecret)
|
||||
}
|
||||
}
|
||||
|
||||
fun encodeByteArray(info: EncryptedInfo): String {
|
||||
return Base64.getEncoder().encodeToString(byteArrayOf(info.v.toByte()) + info.nonce + info.ciphertext)
|
||||
}
|
||||
|
||||
fun decodeByteArray(base64: String): EncryptedInfo? {
|
||||
return try {
|
||||
val byteArray = Base64.getDecoder().decode(base64)
|
||||
return EncryptedInfo(
|
||||
v = byteArray[0].toInt(),
|
||||
nonce = byteArray.copyOfRange(1, 25),
|
||||
ciphertext = byteArray.copyOfRange(25, byteArray.size)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.w("CryptoUtils", "Unable to Parse encrypted payload: ${base64}")
|
||||
null
|
||||
fun decryptNIP44v2(encryptedInfo: Nip44v2.EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? {
|
||||
return nip44v2.decrypt(encryptedInfo, privateKey, pubKey)
|
||||
}
|
||||
}
|
||||
|
||||
fun encodeJackson(info: EncryptedInfo): String {
|
||||
return Event.mapper.writeValueAsString(
|
||||
EncryptedInfoString(
|
||||
v = info.v,
|
||||
nonce = Base64.getEncoder().encodeToString(info.nonce),
|
||||
ciphertext = Base64.getEncoder().encodeToString(info.ciphertext)
|
||||
)
|
||||
)
|
||||
}
|
||||
fun decryptNIP44v2(encryptedInfo: String, privateKey: ByteArray, pubKey: ByteArray): String? {
|
||||
return nip44v2.decrypt(encryptedInfo, privateKey, pubKey)
|
||||
}
|
||||
|
||||
fun decodeJackson(json: String): EncryptedInfo {
|
||||
val info = Event.mapper.readValue(json, EncryptedInfoString::class.java)
|
||||
return EncryptedInfo(
|
||||
v = info.v,
|
||||
nonce = Base64.getDecoder().decode(info.nonce),
|
||||
ciphertext = Base64.getDecoder().decode(info.ciphertext)
|
||||
)
|
||||
}
|
||||
fun decryptNIP44v2(encryptedInfo: Nip44v2.EncryptedInfo, sharedSecret: ByteArray): String? {
|
||||
return nip44v2.decrypt(encryptedInfo, sharedSecret)
|
||||
}
|
||||
|
||||
fun combinedHashCode(a: ByteArray, b: ByteArray): Int {
|
||||
var result = 1
|
||||
for (element in a) result = 31 * result + element
|
||||
for (element in b) result = 31 * result + element
|
||||
return result
|
||||
}
|
||||
fun getSharedSecretNIP44v2(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
|
||||
return nip44v2.getConversationKey(privateKey, pubKey)
|
||||
}
|
||||
|
||||
/*
|
||||
OLD Versions used for the Benchmark
|
||||
fun computeSharedSecretNIP44v2(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
|
||||
return nip44v2.computeConversationKey(privateKey, pubKey)
|
||||
}
|
||||
|
||||
fun encodeKotlin(info: EncryptedInfo): String {
|
||||
return Json.encodeToString(
|
||||
EncryptedInfoString(
|
||||
v = info.v,
|
||||
nonce = Base64.getEncoder().encodeToString(info.nonce),
|
||||
ciphertext = Base64.getEncoder().encodeToString(info.ciphertext)
|
||||
)
|
||||
)
|
||||
}
|
||||
fun decryptNIP44(payload: String, privateKey: ByteArray, pubKey: ByteArray): String? {
|
||||
if (payload.isEmpty()) return null
|
||||
return if (payload[0] == '{') {
|
||||
decryptNIP44FromJackson(payload, privateKey, pubKey)
|
||||
} else {
|
||||
decryptNIP44FromBase64(payload, privateKey, pubKey)
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeKotlin(json: String): EncryptedInfo {
|
||||
val info = Json.decodeFromString<EncryptedInfoString>(json)
|
||||
return EncryptedInfo(
|
||||
v = info.v,
|
||||
nonce = Base64.getDecoder().decode(info.nonce),
|
||||
ciphertext = Base64.getDecoder().decode(info.ciphertext)
|
||||
)
|
||||
}
|
||||
data class EncryptedInfoString(val ciphertext: String, val nonce: String, val v: Int, val mac: String?)
|
||||
|
||||
fun encodeCSV(info: EncryptedInfo): String {
|
||||
return "${info.v},${Base64.getEncoder().encodeToString(info.nonce)},${Base64.getEncoder().encodeToString(info.ciphertext)}"
|
||||
}
|
||||
fun decryptNIP44FromJackson(json: String, privateKey: ByteArray, pubKey: ByteArray): String? {
|
||||
return try {
|
||||
val info = Event.mapper.readValue(json, EncryptedInfoString::class.java)
|
||||
|
||||
fun decodeCSV(base64: String): EncryptedInfo {
|
||||
val parts = base64.split(",")
|
||||
return EncryptedInfo(
|
||||
v = parts[0].toInt(),
|
||||
nonce = Base64.getDecoder().decode(parts[1]),
|
||||
ciphertext = Base64.getDecoder().decode(parts[2])
|
||||
)
|
||||
}
|
||||
when (info.v) {
|
||||
Nip04.EncryptedInfo.v -> {
|
||||
val encryptedInfo = Nip04.EncryptedInfo(
|
||||
ciphertext = Base64.getDecoder().decode(info.ciphertext),
|
||||
nonce = Base64.getDecoder().decode(info.nonce)
|
||||
)
|
||||
decryptNIP04(encryptedInfo, privateKey, pubKey)
|
||||
}
|
||||
Nip44v1.EncryptedInfo.v -> {
|
||||
val encryptedInfo = Nip44v1.EncryptedInfo(
|
||||
ciphertext = Base64.getDecoder().decode(info.ciphertext),
|
||||
nonce = Base64.getDecoder().decode(info.nonce)
|
||||
)
|
||||
decryptNIP44v1(encryptedInfo, privateKey, pubKey)
|
||||
}
|
||||
Nip44v2.EncryptedInfo.v -> {
|
||||
val encryptedInfo = Nip44v2.EncryptedInfo(
|
||||
ciphertext = Base64.getDecoder().decode(info.ciphertext),
|
||||
nonce = Base64.getDecoder().decode(info.nonce),
|
||||
mac = Base64.getDecoder().decode(info.mac)
|
||||
)
|
||||
decryptNIP44v2(encryptedInfo, privateKey, pubKey)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("CryptoUtils", "Could not identify the version for NIP44 payload ${json}")
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun decryptNIP44FromBase64(payload: String, privateKey: ByteArray, pubKey: ByteArray): String? {
|
||||
if (payload.isEmpty()) return null
|
||||
|
||||
fun compress(input: String): ByteArray {
|
||||
return DeflaterInputStream(input.toByteArray().inputStream()).readBytes()
|
||||
}
|
||||
return try {
|
||||
val byteArray = Base64.getDecoder().decode(payload)
|
||||
|
||||
fun decompress(inputBytes: ByteArray): String {
|
||||
return InflaterInputStream(inputBytes.inputStream()).bufferedReader().use { it.readText() }
|
||||
}
|
||||
when (byteArray[0].toInt()) {
|
||||
Nip04.EncryptedInfo.v -> decryptNIP04(payload, privateKey, pubKey)
|
||||
Nip44v1.EncryptedInfo.v -> decryptNIP44v1(payload, privateKey, pubKey)
|
||||
Nip44v2.EncryptedInfo.v -> decryptNIP44v2(payload, privateKey, pubKey)
|
||||
else -> null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("CryptoUtils", "Could not identify the version for NIP44 payload ${payload}")
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class Hkdf(val algorithm: String = "HmacSHA256", val hashLen: Int = 32) {
|
||||
fun extract(key: ByteArray, salt: ByteArray): ByteArray {
|
||||
val mac = Mac.getInstance(algorithm)
|
||||
mac.init(SecretKeySpec(salt, algorithm))
|
||||
return mac.doFinal(key)
|
||||
}
|
||||
|
||||
fun expand(key: ByteArray, nonce: ByteArray, outputLength: Int): ByteArray {
|
||||
check(key.size == hashLen)
|
||||
check(nonce.size == hashLen)
|
||||
|
||||
val n = if (outputLength % hashLen == 0) outputLength / hashLen else outputLength / hashLen + 1
|
||||
var hashRound = ByteArray(0)
|
||||
val generatedBytes = ByteBuffer.allocate(Math.multiplyExact(n, hashLen))
|
||||
val mac = Mac.getInstance(algorithm)
|
||||
mac.init(SecretKeySpec(key, algorithm))
|
||||
for (roundNum in 1..n) {
|
||||
mac.reset()
|
||||
val t = ByteBuffer.allocate(hashRound.size + nonce.size + 1)
|
||||
t.put(hashRound)
|
||||
t.put(nonce)
|
||||
t.put(roundNum.toByte())
|
||||
hashRound = mac.doFinal(t.array())
|
||||
generatedBytes.put(hashRound)
|
||||
}
|
||||
val result = ByteArray(outputLength)
|
||||
generatedBytes.rewind()
|
||||
generatedBytes[result, 0, outputLength]
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
|
||||
import android.util.Log
|
||||
import android.util.LruCache
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
import java.util.Base64
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class Nip04(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
private val sharedKeyCache = SharedKeyCache()
|
||||
private val h02 = Hex.decode("02")
|
||||
|
||||
fun clearCache() {
|
||||
sharedKeyCache.clearCache()
|
||||
}
|
||||
|
||||
fun encrypt(msg: String, privateKey: ByteArray, pubKey: ByteArray): String {
|
||||
return encrypt(msg, getSharedSecret(privateKey, pubKey)).encodeToNIP04()
|
||||
}
|
||||
|
||||
fun encrypt(msg: String, sharedSecret: ByteArray): EncryptedInfo {
|
||||
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 EncryptedInfo(encryptedMsg, iv)
|
||||
}
|
||||
|
||||
fun decrypt(msg: String, privateKey: ByteArray, pubKey: ByteArray): String {
|
||||
val sharedSecret = getSharedSecret(privateKey, pubKey)
|
||||
return decrypt(msg, sharedSecret)
|
||||
}
|
||||
|
||||
fun decrypt(encryptedInfo: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String {
|
||||
val sharedSecret = getSharedSecret(privateKey, pubKey)
|
||||
return decrypt(encryptedInfo.ciphertext, encryptedInfo.nonce, sharedSecret)
|
||||
}
|
||||
|
||||
fun decrypt(msg: String, sharedSecret: ByteArray): String {
|
||||
val decoded = EncryptedInfo.decodeFromNIP04(msg)
|
||||
check(decoded != null) {
|
||||
"Unable to decode msg $msg as NIP04"
|
||||
}
|
||||
return decrypt(decoded.ciphertext, decoded.nonce, sharedSecret)
|
||||
}
|
||||
|
||||
fun decrypt(cipher: String, nonce: String, sharedSecret: ByteArray): String {
|
||||
val iv = Base64.getDecoder().decode(nonce)
|
||||
val encryptedMsg = Base64.getDecoder().decode(cipher)
|
||||
return decrypt(encryptedMsg, iv, sharedSecret)
|
||||
}
|
||||
|
||||
fun decrypt(encryptedMsg: ByteArray, iv: ByteArray, sharedSecret: ByteArray): String {
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(iv))
|
||||
return String(cipher.doFinal(encryptedMsg))
|
||||
}
|
||||
|
||||
fun getSharedSecret(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
|
||||
val preComputed = sharedKeyCache.get(privateKey, pubKey)
|
||||
if (preComputed != null) return preComputed
|
||||
|
||||
val computed = computeSharedSecret(privateKey, pubKey)
|
||||
sharedKeyCache.add(privateKey, pubKey, computed)
|
||||
return computed
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 32B shared secret
|
||||
*/
|
||||
fun computeSharedSecret(privateKey: ByteArray, pubKey: ByteArray): ByteArray =
|
||||
secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33)
|
||||
|
||||
class EncryptedInfo(
|
||||
val ciphertext: ByteArray,
|
||||
val nonce: ByteArray
|
||||
) {
|
||||
companion object {
|
||||
const val v: Int = 0
|
||||
|
||||
fun decodePayload(payload: String): EncryptedInfo? {
|
||||
return try {
|
||||
val byteArray = Base64.getDecoder().decode(payload)
|
||||
check(byteArray[0].toInt() == Nip44v1.EncryptedInfo.v)
|
||||
return EncryptedInfo(
|
||||
nonce = byteArray.copyOfRange(1, 25),
|
||||
ciphertext = byteArray.copyOfRange(25, byteArray.size)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.w("NIP04", "Unable to Parse encrypted payload: ${payload}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeFromNIP04(payload: String): EncryptedInfo? {
|
||||
return try {
|
||||
val parts = payload.split("?iv=")
|
||||
EncryptedInfo(
|
||||
ciphertext = Base64.getDecoder().decode(parts[0]),
|
||||
nonce = Base64.getDecoder().decode(parts[1])
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.w("NIP04", "Unable to Parse encrypted payload: ${payload}")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun encodePayload(): String {
|
||||
return Base64.getEncoder().encodeToString(
|
||||
byteArrayOf(v.toByte()) + nonce + ciphertext
|
||||
)
|
||||
}
|
||||
|
||||
fun encodeToNIP04(): String {
|
||||
val nonce = Base64.getEncoder().encodeToString(nonce)
|
||||
val ciphertext = Base64.getEncoder().encodeToString(ciphertext)
|
||||
return "${ciphertext}?iv=${nonce}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
|
||||
import android.util.Log
|
||||
import com.goterl.lazysodium.SodiumAndroid
|
||||
import com.goterl.lazysodium.utils.Key
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
import java.util.Base64
|
||||
|
||||
class Nip44v1(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
private val sharedKeyCache = SharedKeyCache()
|
||||
private val h02 = Hex.decode("02")
|
||||
private val libSodium = SodiumAndroid()
|
||||
|
||||
fun clearCache() {
|
||||
sharedKeyCache.clearCache()
|
||||
}
|
||||
|
||||
fun encrypt(msg: String, privateKey: ByteArray, pubKey: ByteArray): EncryptedInfo {
|
||||
val sharedSecret = getSharedSecret(privateKey, pubKey)
|
||||
return encrypt(msg, sharedSecret)
|
||||
}
|
||||
|
||||
fun encrypt(msg: String, sharedSecret: ByteArray): EncryptedInfo {
|
||||
val nonce = ByteArray(24)
|
||||
random.nextBytes(nonce)
|
||||
|
||||
val cipher = cryptoStreamXChaCha20Xor(
|
||||
libSodium = libSodium,
|
||||
messageBytes = msg.toByteArray(),
|
||||
nonce = nonce,
|
||||
key = Key.fromBytes(sharedSecret)
|
||||
)
|
||||
|
||||
return EncryptedInfo(
|
||||
ciphertext = cipher ?: ByteArray(0),
|
||||
nonce = nonce,
|
||||
)
|
||||
}
|
||||
|
||||
fun decrypt(payload: String, privateKey: ByteArray, pubKey: ByteArray): String? {
|
||||
val sharedSecret = getSharedSecret(privateKey, pubKey)
|
||||
return decrypt(payload, sharedSecret)
|
||||
}
|
||||
|
||||
fun decrypt(encryptedInfo: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? {
|
||||
val sharedSecret = getSharedSecret(privateKey, pubKey)
|
||||
return decrypt(encryptedInfo, sharedSecret)
|
||||
}
|
||||
|
||||
fun decrypt(payload: String, sharedSecret: ByteArray): String? {
|
||||
val encryptedInfo = EncryptedInfo.decodePayload(payload) ?: return null
|
||||
return decrypt(encryptedInfo, sharedSecret)
|
||||
}
|
||||
|
||||
fun decrypt(encryptedInfo: EncryptedInfo, sharedSecret: ByteArray): String? {
|
||||
return cryptoStreamXChaCha20Xor(
|
||||
libSodium = libSodium,
|
||||
messageBytes = encryptedInfo.ciphertext,
|
||||
nonce = encryptedInfo.nonce,
|
||||
key = Key.fromBytes(sharedSecret)
|
||||
)?.decodeToString()
|
||||
}
|
||||
|
||||
fun getSharedSecret(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
|
||||
val preComputed = sharedKeyCache.get(privateKey, pubKey)
|
||||
if (preComputed != null) return preComputed
|
||||
|
||||
val computed = computeSharedSecret(privateKey, pubKey)
|
||||
sharedKeyCache.add(privateKey, pubKey, computed)
|
||||
return computed
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 32B shared secret
|
||||
*/
|
||||
fun computeSharedSecret(privateKey: ByteArray, pubKey: ByteArray): ByteArray =
|
||||
sha256(
|
||||
secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33)
|
||||
)
|
||||
|
||||
fun sha256(data: ByteArray): ByteArray {
|
||||
// Creates a new buffer every time
|
||||
return MessageDigest.getInstance("SHA-256").digest(data)
|
||||
}
|
||||
|
||||
class EncryptedInfo(
|
||||
val ciphertext: ByteArray,
|
||||
val nonce: ByteArray
|
||||
) {
|
||||
companion object {
|
||||
const val v: Int = 1
|
||||
|
||||
fun decodePayload(payload: String): EncryptedInfo? {
|
||||
return try {
|
||||
val byteArray = Base64.getDecoder().decode(payload)
|
||||
check(byteArray[0].toInt() == v)
|
||||
return EncryptedInfo(
|
||||
nonce = byteArray.copyOfRange(1, 25),
|
||||
ciphertext = byteArray.copyOfRange(25, byteArray.size)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.w("NIP44v1", "Unable to Parse encrypted payload: ${payload}")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun encodePayload(): String {
|
||||
return Base64.getEncoder().encodeToString(
|
||||
byteArrayOf(v.toByte()) + nonce + ciphertext
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
|
||||
import android.util.Log
|
||||
import com.goterl.lazysodium.LazySodiumAndroid
|
||||
import com.goterl.lazysodium.SodiumAndroid
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.security.SecureRandom
|
||||
import java.util.Base64
|
||||
import kotlin.experimental.and
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.log2
|
||||
|
||||
|
||||
class Nip44v2(val secp256k1: Secp256k1, val random: SecureRandom) {
|
||||
private val sharedKeyCache = SharedKeyCache()
|
||||
|
||||
private val libSodium = SodiumAndroid()
|
||||
private val lazySodium = LazySodiumAndroid(libSodium)
|
||||
private val hkdf = Hkdf()
|
||||
|
||||
private val h02 = Hex.decode("02")
|
||||
private val hashLength = 32
|
||||
|
||||
private val minPlaintextSize: Int = 0x0001 // 1b msg => padded to 32b
|
||||
private val maxPlaintextSize: Int = 0xffff // 65535 (64kb-1) => padded to 64kb
|
||||
|
||||
fun clearCache() {
|
||||
sharedKeyCache.clearCache()
|
||||
}
|
||||
|
||||
fun encrypt(msg: String, privateKey: ByteArray, pubKey: ByteArray): EncryptedInfo {
|
||||
return encrypt(msg, getConversationKey(privateKey, pubKey))
|
||||
}
|
||||
|
||||
fun encrypt(plaintext: String, conversationKey: ByteArray): EncryptedInfo {
|
||||
val nonce = ByteArray(hashLength)
|
||||
random.nextBytes(nonce)
|
||||
return encryptWithNonce(plaintext, conversationKey, nonce)
|
||||
}
|
||||
|
||||
fun encryptWithNonce(plaintext: String, conversationKey: ByteArray, nonce: ByteArray): EncryptedInfo {
|
||||
val messageKeys = getMessageKeys(conversationKey, nonce)
|
||||
val padded = pad(plaintext)
|
||||
|
||||
val ciphertext = ByteArray(padded.size)
|
||||
|
||||
lazySodium.cryptoStreamChaCha20IetfXor(
|
||||
ciphertext, padded, padded.size.toLong(), messageKeys.chachaNonce, messageKeys.chachaKey
|
||||
)
|
||||
|
||||
val mac = hmacAad(messageKeys.hmacKey, ciphertext, nonce)
|
||||
|
||||
return EncryptedInfo(
|
||||
nonce = nonce,
|
||||
ciphertext = ciphertext,
|
||||
mac = mac
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun decrypt(payload: String, privateKey: ByteArray, pubKey: ByteArray): String? {
|
||||
return decrypt(payload, getConversationKey(privateKey, pubKey))
|
||||
}
|
||||
|
||||
fun decrypt(decoded: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String? {
|
||||
return decrypt(decoded, getConversationKey(privateKey, pubKey))
|
||||
}
|
||||
|
||||
fun decrypt(payload: String, conversationKey: ByteArray): String? {
|
||||
val decoded = EncryptedInfo.decodePayload(payload) ?: return null
|
||||
return decrypt(decoded, conversationKey)
|
||||
}
|
||||
|
||||
fun decrypt(decoded: EncryptedInfo, conversationKey: ByteArray): String? {
|
||||
val messageKey = getMessageKeys(conversationKey, decoded.nonce)
|
||||
val calculatedMac = hmacAad(messageKey.hmacKey, decoded.ciphertext, decoded.nonce)
|
||||
|
||||
check(calculatedMac.contentEquals(decoded.mac)) {
|
||||
"Invalid Mac: Calculated ${calculatedMac.toHexKey()}, decoded: ${decoded.mac.toHexKey()}"
|
||||
}
|
||||
|
||||
val mLen = decoded.ciphertext.size.toLong()
|
||||
val padded = ByteArray(decoded.ciphertext.size)
|
||||
|
||||
lazySodium.cryptoStreamChaCha20IetfXor(
|
||||
padded, decoded.ciphertext, mLen, messageKey.chachaNonce, messageKey.chachaKey
|
||||
)
|
||||
|
||||
return unpad(padded)
|
||||
}
|
||||
|
||||
fun getConversationKey(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
|
||||
val preComputed = sharedKeyCache.get(privateKey, pubKey)
|
||||
if (preComputed != null) return preComputed
|
||||
|
||||
val computed = computeConversationKey(privateKey, pubKey)
|
||||
sharedKeyCache.add(privateKey, pubKey, computed)
|
||||
return computed
|
||||
}
|
||||
|
||||
fun calcPaddedLen(len: Int): Int {
|
||||
check(len > 0) {
|
||||
"expected positive integer"
|
||||
}
|
||||
if (len <= 32) return 32
|
||||
val nextPower = 1 shl (floor(log2(len - 1f)) + 1).toInt()
|
||||
val chunk = if (nextPower <= 256) 32 else nextPower / 8
|
||||
return chunk * (floor((len - 1f) / chunk).toInt() + 1)
|
||||
}
|
||||
|
||||
fun pad(plaintext: String): ByteArray {
|
||||
val unpadded = plaintext.toByteArray(Charsets.UTF_8)
|
||||
val unpaddedLen = unpadded.size
|
||||
|
||||
check(unpaddedLen > 0) {
|
||||
"Message is empty ($unpaddedLen): $plaintext"
|
||||
}
|
||||
|
||||
check(unpaddedLen <= maxPlaintextSize) {
|
||||
"Message is too long ($unpaddedLen): $plaintext"
|
||||
}
|
||||
|
||||
val prefix = ByteBuffer.allocate(2).order(ByteOrder.BIG_ENDIAN).putShort(unpaddedLen.toShort()).array()
|
||||
val suffix = ByteArray(calcPaddedLen(unpaddedLen) - unpaddedLen)
|
||||
return ByteBuffer.wrap(prefix + unpadded + suffix).array()
|
||||
}
|
||||
|
||||
private fun bytesToInt(byte1: Byte, byte2: Byte, bigEndian: Boolean): Int {
|
||||
return if (bigEndian)
|
||||
(byte1.toInt() and 0xFF shl 8 or (byte2.toInt() and 0xFF))
|
||||
else
|
||||
(byte2.toInt() and 0xFF shl 8 or (byte1.toInt() and 0xFF))
|
||||
}
|
||||
|
||||
fun unpad(padded: ByteArray): String {
|
||||
val unpaddedLen: Int = bytesToInt(padded[0], padded[1], true)
|
||||
val unpadded = padded.sliceArray(2 until 2 + unpaddedLen)
|
||||
|
||||
check(
|
||||
unpaddedLen in minPlaintextSize..maxPlaintextSize
|
||||
&& unpadded.size == unpaddedLen
|
||||
&& padded.size == 2 + calcPaddedLen(unpaddedLen)) {
|
||||
"invalid padding ${unpadded.size} != $unpaddedLen"
|
||||
}
|
||||
|
||||
return unpadded.decodeToString()
|
||||
}
|
||||
|
||||
fun hmacAad(key: ByteArray, message: ByteArray, aad: ByteArray): ByteArray {
|
||||
check (aad.size == hashLength) {
|
||||
"AAD associated data must be 32 bytes, but it was ${aad.size} bytes"
|
||||
}
|
||||
|
||||
return hkdf.extract(aad + message, key)
|
||||
}
|
||||
|
||||
|
||||
fun getMessageKeys(conversationKey: ByteArray, nonce: ByteArray): MessageKey {
|
||||
val keys = hkdf.expand(conversationKey, nonce, 76)
|
||||
return MessageKey(
|
||||
chachaKey = keys.copyOfRange(0, 32),
|
||||
chachaNonce = keys.copyOfRange(32, 44),
|
||||
hmacKey = keys.copyOfRange(44, 76),
|
||||
)
|
||||
}
|
||||
|
||||
class MessageKey(
|
||||
val chachaKey: ByteArray,
|
||||
val chachaNonce: ByteArray,
|
||||
val hmacKey: ByteArray
|
||||
)
|
||||
|
||||
/**
|
||||
* @return 32B shared secret
|
||||
*/
|
||||
fun computeConversationKey(privateKey: ByteArray, pubKey: ByteArray): ByteArray {
|
||||
val sharedX = secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33)
|
||||
return hkdf.extract(sharedX, "nip44-v2".toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
class EncryptedInfo(
|
||||
val nonce: ByteArray,
|
||||
val ciphertext: ByteArray,
|
||||
val mac: ByteArray
|
||||
) {
|
||||
companion object {
|
||||
const val v: Int = 2
|
||||
|
||||
fun decodePayload(payload: String): EncryptedInfo? {
|
||||
check(payload.length >= 132 || payload.length <= 87472) {
|
||||
"Invalid payload length ${payload.length} for ${payload}"
|
||||
}
|
||||
check(payload[0] != '#') {
|
||||
"Unknown encryption version ${payload.get(0)}"
|
||||
}
|
||||
|
||||
return try {
|
||||
val byteArray = Base64.getDecoder().decode(payload)
|
||||
check(byteArray[0].toInt() == v)
|
||||
return EncryptedInfo(
|
||||
nonce = byteArray.copyOfRange(1, 33),
|
||||
ciphertext = byteArray.copyOfRange(33, byteArray.size-32),
|
||||
mac = byteArray.copyOfRange(byteArray.size-32, byteArray.size)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.w("NIP44v2", "Unable to Parse encrypted payload: $payload")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun encodePayload(): String {
|
||||
return Base64.getEncoder().encodeToString(
|
||||
byteArrayOf(v.toByte()) + nonce + ciphertext + mac
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
|
||||
import android.util.LruCache
|
||||
|
||||
class SharedKeyCache {
|
||||
private val sharedKeyCache = LruCache<Int, ByteArray>(200)
|
||||
|
||||
fun clearCache() {
|
||||
sharedKeyCache.evictAll()
|
||||
}
|
||||
|
||||
fun combinedHashCode(a: ByteArray, b: ByteArray): Int {
|
||||
var result = 1
|
||||
for (element in a) result = 31 * result + element
|
||||
for (element in b) result = 31 * result + element
|
||||
return result
|
||||
}
|
||||
|
||||
fun get(privateKey: ByteArray, pubKey: ByteArray): ByteArray? {
|
||||
return sharedKeyCache[combinedHashCode(privateKey, pubKey)]
|
||||
}
|
||||
|
||||
fun add(privateKey: ByteArray, pubKey: ByteArray, secret: ByteArray) {
|
||||
sharedKeyCache.put(combinedHashCode(privateKey, pubKey), secret)
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ class ChatMessageEvent(
|
||||
subject?.let {
|
||||
tags.add(arrayOf("subject", it))
|
||||
}
|
||||
tags.add(arrayOf("alt", alt))
|
||||
//tags.add(arrayOf("alt", alt))
|
||||
|
||||
signer.sign(createdAt, kind, tags.toTypedArray(), msg, onReady)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.crypto.Nip44Version
|
||||
import com.vitorpamplona.quartz.crypto.decodeNIP44
|
||||
import com.vitorpamplona.quartz.crypto.encodeNIP44
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.signers.NostrSignerInternal
|
||||
@@ -31,7 +24,6 @@ class GiftWrapEvent(
|
||||
onReady(it)
|
||||
return
|
||||
}
|
||||
|
||||
unwrap(signer) { gift ->
|
||||
if (gift is WrappedEvent) {
|
||||
gift.host = this
|
||||
@@ -72,7 +64,7 @@ class GiftWrapEvent(
|
||||
) {
|
||||
val signer = NostrSignerInternal(KeyPair()) // GiftWrap is always a random key
|
||||
val serializedContent = toJson(event)
|
||||
val tags = arrayOf(arrayOf("p", recipientPubKey), arrayOf("alt", alt))
|
||||
val tags = arrayOf(arrayOf("p", recipientPubKey))
|
||||
|
||||
signer.nip44Encrypt(serializedContent, recipientPubKey) {
|
||||
signer.sign(createdAt, kind, tags, it, onReady)
|
||||
|
||||
@@ -4,15 +4,9 @@ import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.Nip44Version
|
||||
import com.vitorpamplona.quartz.crypto.decodeNIP44
|
||||
import com.vitorpamplona.quartz.crypto.encodeNIP44
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import java.util.UUID
|
||||
|
||||
@Immutable
|
||||
class SealedGossipEvent(
|
||||
|
||||
@@ -3,19 +3,13 @@ package com.vitorpamplona.quartz.signers
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.crypto.Nip44Version
|
||||
import com.vitorpamplona.quartz.crypto.decodeNIP44
|
||||
import com.vitorpamplona.quartz.crypto.encodeNIP44
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.EventFactory
|
||||
import com.vitorpamplona.quartz.events.LnZapPrivateEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class NostrSignerInternal(val keyPair: KeyPair): NostrSigner(keyPair.pubKey.toHexKey()) {
|
||||
override fun <T: Event> sign(
|
||||
@@ -94,28 +88,23 @@ class NostrSignerInternal(val keyPair: KeyPair): NostrSigner(keyPair.pubKey.toHe
|
||||
override fun nip44Encrypt(decryptedContent: String, toPublicKey: HexKey, onReady: (String)-> Unit) {
|
||||
if (keyPair.privKey == null) return
|
||||
|
||||
val sharedSecret = CryptoUtils.getSharedSecretNIP44(keyPair.privKey, toPublicKey.hexToByteArray())
|
||||
|
||||
onReady(
|
||||
encodeNIP44(
|
||||
CryptoUtils.encryptNIP44(
|
||||
decryptedContent,
|
||||
sharedSecret
|
||||
)
|
||||
)
|
||||
CryptoUtils.encryptNIP44v2(
|
||||
decryptedContent,
|
||||
keyPair.privKey,
|
||||
toPublicKey.hexToByteArray()
|
||||
).encodePayload()
|
||||
)
|
||||
}
|
||||
|
||||
override fun nip44Decrypt(encryptedContent: String, fromPublicKey: HexKey, onReady: (String)-> Unit) {
|
||||
if (keyPair.privKey == null) return
|
||||
|
||||
val toDecrypt = decodeNIP44(encryptedContent) ?: return
|
||||
|
||||
when (toDecrypt.v) {
|
||||
Nip44Version.NIP04.versionCode -> CryptoUtils.decryptNIP04(toDecrypt, keyPair.privKey, fromPublicKey.hexToByteArray())
|
||||
Nip44Version.NIP44.versionCode -> CryptoUtils.decryptNIP44(toDecrypt, keyPair.privKey, fromPublicKey.hexToByteArray())
|
||||
else -> null
|
||||
}?.let {
|
||||
CryptoUtils.decryptNIP44(
|
||||
payload = encryptedContent,
|
||||
privateKey = keyPair.privKey,
|
||||
pubKey = fromPublicKey.hexToByteArray()
|
||||
)?.let {
|
||||
onReady(it)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user