diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/Base64Image.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/Base64Image.kt index 41e5204c8..572785284 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/Base64Image.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/Base64Image.kt @@ -35,7 +35,7 @@ import coil3.request.ImageRequest import coil3.request.Options import com.vitorpamplona.amethyst.commons.richtext.RichTextParser.Companion.base64contentPattern import com.vitorpamplona.quartz.nip01Core.toHexKey -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 import java.util.Base64 @Stable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/EncryptFiles.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/EncryptFiles.kt index 28e40e032..c8151e468 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/EncryptFiles.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/EncryptFiles.kt @@ -25,7 +25,7 @@ import android.net.Uri import androidx.core.net.toUri import com.vitorpamplona.quartz.nip01Core.toHexKey import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 import java.io.File class EncryptFilesResult( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/FileHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/FileHeader.kt index e22655d38..173a46eb4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/FileHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/FileHeader.kt @@ -32,7 +32,7 @@ import com.vitorpamplona.amethyst.commons.blurhash.toBlurhash import com.vitorpamplona.amethyst.service.Blurhash import com.vitorpamplona.quartz.nip01Core.toHexKey import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 import kotlinx.coroutines.CancellationException import java.io.IOException diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt index a6dabb114..ac5a33fad 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt @@ -38,7 +38,7 @@ import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.blossom.BlossomAuthorizationEvent import com.vitorpamplona.quartz.nip01Core.HexKey import com.vitorpamplona.quartz.nip01Core.toHexKey -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 import okhttp3.MediaType.Companion.toMediaType import okhttp3.Request import okhttp3.RequestBody diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index 721a8ca2f..eec79997b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -97,7 +97,7 @@ import com.vitorpamplona.quartz.nip01Core.toHexKey import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.Dispatchers diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/EventBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/EventBenchmark.kt index ef2aeca89..160c87ae9 100644 --- a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/EventBenchmark.kt +++ b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/EventBenchmark.kt @@ -26,6 +26,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.vitorpamplona.quartz.EventFactory import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper import com.vitorpamplona.quartz.nip01Core.verify +import com.vitorpamplona.quartz.nip01Core.verifyId +import com.vitorpamplona.quartz.nip01Core.verifySignature import com.vitorpamplona.quartz.utils.TimeUtils import junit.framework.TestCase.assertTrue import org.junit.Rule @@ -42,6 +44,15 @@ import org.junit.runner.RunWith class EventBenchmark { @get:Rule val benchmarkRule = BenchmarkRule() + @Test + fun parseComplete() { + benchmarkRule.measureRepeated { + val tree = EventMapper.mapper.readTree(reqResponseEvent) + val event = EventMapper.fromJson(tree[2]) + assertTrue(event.verify()) + } + } + @Test fun parseREQString() { benchmarkRule.measureRepeated { EventMapper.mapper.readTree(reqResponseEvent) } @@ -54,13 +65,23 @@ class EventBenchmark { benchmarkRule.measureRepeated { EventMapper.fromJson(msg[2]) } } + @Test + fun checkId() { + val msg = EventMapper.mapper.readTree(reqResponseEvent) + val event = EventMapper.fromJson(msg[2]) + benchmarkRule.measureRepeated { + // Should pass + assertTrue(event.verifyId()) + } + } + @Test fun checkSignature() { val msg = EventMapper.mapper.readTree(reqResponseEvent) val event = EventMapper.fromJson(msg[2]) benchmarkRule.measureRepeated { // Should pass - assertTrue(event.verify()) + assertTrue(event.verifySignature()) } } diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/Sha256Benchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/Sha256Benchmark.kt index 5885c2236..7ffff23ae 100644 --- a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/Sha256Benchmark.kt +++ b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/Sha256Benchmark.kt @@ -25,11 +25,12 @@ import androidx.benchmark.junit4.measureRepeated import androidx.test.ext.junit.runners.AndroidJUnit4 import com.vitorpamplona.quartz.nip01Core.EventHasher import com.vitorpamplona.quartz.nip01Core.core.Event -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 import junit.framework.TestCase.assertNotNull import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import java.security.MessageDigest /** * Benchmark, which will execute on an Android device. @@ -43,7 +44,7 @@ class Sha256Benchmark { val benchmarkRule = BenchmarkRule() @Test - fun sha256() { + fun sha256Pool() { val event = Event.fromJson(largeKind1Event) val byteArray = EventHasher.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content).toByteArray() @@ -52,4 +53,28 @@ class Sha256Benchmark { assertNotNull(sha256(byteArray)) } } + + @Test + fun sha256NewEachTime() { + val event = Event.fromJson(largeKind1Event) + val byteArray = EventHasher.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content).toByteArray() + + benchmarkRule.measureRepeated { + val digest = MessageDigest.getInstance("SHA-256") + assertNotNull(digest.digest(byteArray)) + } + } + + @Test + fun sha256Reuse() { + val event = Event.fromJson(largeKind1Event) + val byteArray = EventHasher.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content).toByteArray() + + val digest = MessageDigest.getInstance("SHA-256") + + benchmarkRule.measureRepeated { + assertNotNull(digest.digest(byteArray)) + digest.reset() + } + } } diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/SignVerifyBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/SignVerifyBenchmark.kt index 01dbf62a4..29043a4d1 100644 --- a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/SignVerifyBenchmark.kt +++ b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/SignVerifyBenchmark.kt @@ -26,7 +26,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.crypto.Nip01 import com.vitorpamplona.quartz.utils.RandomInstance -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 import junit.framework.TestCase.assertNotNull import org.junit.Rule import org.junit.Test diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/robohash/RobohashAssembler.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/robohash/RobohashAssembler.kt index eab71fe67..9c0402dda 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/robohash/RobohashAssembler.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/robohash/RobohashAssembler.kt @@ -86,7 +86,7 @@ import com.vitorpamplona.amethyst.commons.robohash.parts.mouth7Happy import com.vitorpamplona.amethyst.commons.robohash.parts.mouth8Buttons import com.vitorpamplona.amethyst.commons.robohash.parts.mouth9Closed import com.vitorpamplona.quartz.utils.Hex -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 val Black = SolidColor(Color.Black) val Gray = SolidColor(Color(0xFF6d6e70)) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/EventHasher.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/EventHasher.kt index f694cd627..e596772e7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/EventHasher.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/EventHasher.kt @@ -20,40 +20,46 @@ */ package com.vitorpamplona.quartz.nip01Core +import com.fasterxml.jackson.databind.node.ArrayNode import com.fasterxml.jackson.databind.node.JsonNodeFactory import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 class EventHasher { companion object { + fun makeJsonObjectForId( + pubKey: HexKey, + createdAt: Long, + kind: Int, + tags: Array>, + content: String, + ): ArrayNode { + val factory = JsonNodeFactory.instance + return factory.arrayNode(6).apply { + add(0) + add(pubKey) + add(createdAt) + add(kind) + add( + factory.arrayNode(tags.size).apply { + tags.forEach { tag -> + add( + factory.arrayNode(tag.size).apply { tag.forEach { add(it) } }, + ) + } + }, + ) + add(content) + } + } + fun makeJsonForId( pubKey: HexKey, createdAt: Long, kind: Int, tags: Array>, content: String, - ): String { - val factory = JsonNodeFactory.instance - val rawEvent = - factory.arrayNode(6).apply { - add(0) - add(pubKey) - add(createdAt) - add(kind) - add( - factory.arrayNode(tags.size).apply { - tags.forEach { tag -> - add( - factory.arrayNode(tag.size).apply { tag.forEach { add(it) } }, - ) - } - }, - ) - add(content) - } - - return EventMapper.toJson(rawEvent) - } + ): String = EventMapper.toJson(makeJsonObjectForId(pubKey, createdAt, kind, tags, content)) fun hashIdBytes( pubKey: HexKey, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt index aa9ffe4da..cbcbd3b75 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt @@ -54,7 +54,6 @@ data class Address( Address(parts[0].toInt(), parts[1], parts[2]) } else { Log.w("AddressableId", "Error parsing. Pubkey is not hex: $addressId") - throw RuntimeException("It shouldn't get here.") null } } catch (t: Throwable) { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39Mnemonics.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39Mnemonics.kt index afbbaa9b1..11fe2f7a7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39Mnemonics.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39Mnemonics.kt @@ -21,7 +21,7 @@ package com.vitorpamplona.quartz.nip06KeyDerivation import com.vitorpamplona.quartz.nip49PrivKeyEnc.PBKDF -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 // CODE FROM: https://github.com/ACINQ/bitcoin-kmp/ diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v1.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v1.kt index f4d30df87..1d1357329 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v1.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v1.kt @@ -24,7 +24,7 @@ import android.util.Log import com.vitorpamplona.quartz.utils.LibSodiumInstance import com.vitorpamplona.quartz.utils.RandomInstance import com.vitorpamplona.quartz.utils.Secp256k1Instance -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 import java.util.Base64 class Nip44v1 { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/SignString.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/SignString.kt index 99f6d213b..d96718340 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/SignString.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/SignString.kt @@ -22,7 +22,7 @@ package com.vitorpamplona.quartz.nip55AndroidSigner import com.vitorpamplona.quartz.nip01Core.crypto.Nip01 import com.vitorpamplona.quartz.utils.RandomInstance -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 fun signString( message: String, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip57Zaps/PrivateZapEncryption.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip57Zaps/PrivateZapEncryption.kt index 61228c675..1588feb57 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip57Zaps/PrivateZapEncryption.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip57Zaps/PrivateZapEncryption.kt @@ -22,7 +22,7 @@ package com.vitorpamplona.quartz.nip57Zaps import com.vitorpamplona.quartz.nip04Dm.crypto.Nip04 import com.vitorpamplona.quartz.nip19Bech32.bech32.Bech32 -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 import java.nio.charset.Charset import java.security.SecureRandom import javax.crypto.BadPaddingException diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/PayloadHashTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/PayloadHashTag.kt index e607ee950..437624e56 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/PayloadHashTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/PayloadHashTag.kt @@ -22,7 +22,7 @@ package com.vitorpamplona.quartz.nip98HttpAuth.tags import com.vitorpamplona.quartz.nip01Core.HexKey import com.vitorpamplona.quartz.nip01Core.toHexKey -import com.vitorpamplona.quartz.utils.sha256 +import com.vitorpamplona.quartz.utils.sha256.sha256 class PayloadHashTag { companion object { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/Sha256.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/sha256/Sha256.kt similarity index 83% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/Sha256.kt rename to quartz/src/main/java/com/vitorpamplona/quartz/utils/sha256/Sha256.kt index c33dc0b7d..9bd9a065f 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/utils/Sha256.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/sha256/Sha256.kt @@ -18,11 +18,8 @@ * 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.utils +package com.vitorpamplona.quartz.utils.sha256 -import java.security.MessageDigest +val pool = Sha256Pool(5) // max parallel operations -fun sha256(data: ByteArray): ByteArray { - // Creates a new buffer every time - return MessageDigest.getInstance("SHA-256").digest(data) -} +fun sha256(data: ByteArray) = pool.hash(data) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/sha256/Sha256Pool.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/sha256/Sha256Pool.kt new file mode 100644 index 000000000..d94294be4 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/sha256/Sha256Pool.kt @@ -0,0 +1,60 @@ +/** + * 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.utils.sha256 + +import android.util.Log +import java.security.MessageDigest +import java.util.concurrent.ArrayBlockingQueue + +class Sha256Pool( + size: Int, +) { + private val pool = ArrayBlockingQueue(size) + + private fun digest() = MessageDigest.getInstance("SHA-256") + + init { + repeat(size) { + pool.add(digest()) + } + } + + private fun acquire(): MessageDigest { + if (pool.size < 1) { + Log.w("SHA256Pool", "Pool running low in available digests") + } + return pool.take() + } + + private fun release(digest: MessageDigest) { + digest.reset() + pool.put(digest) + } + + fun hash(byteArray: ByteArray): ByteArray { + val digest = acquire() + try { + return digest.digest(byteArray) + } finally { + release(digest) + } + } +}