diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/CryptoBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/CryptoBenchmark.kt new file mode 100644 index 000000000..13e862dc0 --- /dev/null +++ b/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/CryptoBenchmark.kt @@ -0,0 +1,102 @@ +package com.vitorpamplona.amethyst.benchmark + +import androidx.benchmark.junit4.BenchmarkRule +import androidx.benchmark.junit4.measureRepeated +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.vitorpamplona.quartz.crypto.CryptoUtils +import com.vitorpamplona.quartz.crypto.KeyPair +import com.vitorpamplona.quartz.encoders.Bech32 +import com.vitorpamplona.quartz.encoders.Hex +import com.vitorpamplona.quartz.encoders.bechToBytes +import com.vitorpamplona.quartz.encoders.toNpub +import com.vitorpamplona.quartz.events.Event +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNotNull +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class CryptoBenchmark { + + @get:Rule + val benchmarkRule = BenchmarkRule() + + @Test + fun getSharedKeyNip04() { + val keyPair1 = KeyPair() + val keyPair2 = KeyPair() + + benchmarkRule.measureRepeated { + assertNotNull(CryptoUtils.getSharedSecretNIP04(keyPair1.privKey!!, keyPair2.pubKey)) + } + } + + @Test + fun getSharedKeyNip44() { + val keyPair1 = KeyPair() + val keyPair2 = KeyPair() + + benchmarkRule.measureRepeated { + assertNotNull(CryptoUtils.getSharedSecretNIP24(keyPair1.privKey!!, keyPair2.pubKey)) + } + } + + @Test + fun computeSharedKeyNip04() { + val keyPair1 = KeyPair() + val keyPair2 = KeyPair() + + benchmarkRule.measureRepeated { + assertNotNull(CryptoUtils.computeSharedSecretNIP04(keyPair1.privKey!!, keyPair2.pubKey)) + } + } + + @Test + fun computeSharedKeyNip44() { + val keyPair1 = KeyPair() + val keyPair2 = KeyPair() + + benchmarkRule.measureRepeated { + assertNotNull(CryptoUtils.computeSharedSecretNIP24(keyPair1.privKey!!, keyPair2.pubKey)) + } + } + + @Test + fun random() { + benchmarkRule.measureRepeated { + assertNotNull(CryptoUtils.random(1000)) + } + } + + @Test + fun sha256() { + val keyPair = KeyPair() + + benchmarkRule.measureRepeated { + assertNotNull(CryptoUtils.sha256(keyPair.pubKey)) + } + } + + @Test + fun sign() { + val keyPair = KeyPair() + val msg = CryptoUtils.sha256(CryptoUtils.random(1000)) + + benchmarkRule.measureRepeated { + assertNotNull(CryptoUtils.sign(msg, keyPair.privKey!!)) + } + } + + @Test + fun verify() { + val keyPair = KeyPair() + val msg = CryptoUtils.sha256(CryptoUtils.random(1000)) + val signature = CryptoUtils.sign(msg, keyPair.privKey!!) + + benchmarkRule.measureRepeated { + assertNotNull(CryptoUtils.verifySignature(signature, msg, keyPair.pubKey)) + } + } + +} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/crypto/CryptoUtils.kt b/quartz/src/main/java/com/vitorpamplona/quartz/crypto/CryptoUtils.kt index 0817c059b..9d479675b 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/crypto/CryptoUtils.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/crypto/CryptoUtils.kt @@ -1,10 +1,11 @@ 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.events.Event import com.vitorpamplona.quartz.encoders.Hex +import com.vitorpamplona.quartz.events.Event import fr.acinq.secp256k1.Secp256k1 import java.security.MessageDigest import java.security.SecureRandom @@ -15,6 +16,9 @@ import javax.crypto.spec.SecretKeySpec object CryptoUtils { + private val sharedKeyCache04 = LruCache(200) + private val sharedKeyCache24 = LruCache(200) + private val secp256k1 = Secp256k1.get() private val libSodium = SodiumAndroid() private val random = SecureRandom() @@ -27,8 +31,10 @@ object CryptoUtils { /** * Provides a 32B "private key" aka random number */ - fun privkeyCreate(): ByteArray { - val bytes = ByteArray(32) + fun privkeyCreate() = random(32) + + fun random(size: Int): ByteArray { + val bytes = ByteArray(size) random.nextBytes(bytes) return bytes } @@ -52,12 +58,6 @@ object CryptoUtils { return MessageDigest.getInstance("SHA-256").digest(data) } - /** - * @return 32B shared secret - */ - fun getSharedSecretNIP04(privateKey: ByteArray, pubKey: ByteArray): ByteArray = - secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33) - fun encryptNIP04(msg: String, privateKey: ByteArray, pubKey: ByteArray): String { val info = encryptNIP04(msg, getSharedSecretNIP04(privateKey, pubKey)) val encryptionInfo = EncryptedInfoString( @@ -150,7 +150,39 @@ object CryptoUtils { /** * @return 32B shared secret */ - fun getSharedSecretNIP24(privateKey: ByteArray, pubKey: ByteArray): ByteArray = + fun getSharedSecretNIP04(privateKey: ByteArray, pubKey: ByteArray): ByteArray { + val hash = combinedHashCode(privateKey, pubKey) + val preComputed = sharedKeyCache04[hash] + if (preComputed != null) return preComputed + + val computed = computeSharedSecretNIP04(privateKey, pubKey) + sharedKeyCache04.put(hash, computed) + return computed + } + + /** + * @return 32B shared secret + */ + fun computeSharedSecretNIP04(privateKey: ByteArray, pubKey: ByteArray): ByteArray = + secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33) + + /** + * @return 32B shared secret + */ + fun getSharedSecretNIP24(privateKey: ByteArray, pubKey: ByteArray): ByteArray { + val hash = combinedHashCode(privateKey, pubKey) + val preComputed = sharedKeyCache24[hash] + if (preComputed != null) return preComputed + + val computed = computeSharedSecretNIP24(privateKey, pubKey) + sharedKeyCache24.put(hash, computed) + return computed + } + + /** + * @return 32B shared secret + */ + fun computeSharedSecretNIP24(privateKey: ByteArray, pubKey: ByteArray): ByteArray = sha256(secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33)) } @@ -213,6 +245,13 @@ fun decodeJackson(json: String): EncryptedInfo { ) } +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 +} + /* OLD Versions used for the Benchmark