diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/Secp256k1CBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/Secp256k1CBenchmark.kt index c33d9df1e..c3ac6eb2f 100644 --- a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/Secp256k1CBenchmark.kt +++ b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/Secp256k1CBenchmark.kt @@ -214,6 +214,40 @@ class Secp256k1CBenchmark { } } + // ==================== SHA-256 Comparison ==================== + + private val sha256Input = ByteArray(160) { it.toByte() } // BIP-340 tagged hash size + + @Test + fun sha256Android() { + // Android's native SHA-256 (BoringSSL with ARM64 Crypto Extensions) + val md = java.security.MessageDigest.getInstance("SHA-256") + benchmarkRule.measureRepeated { + md.reset() + md.update(sha256Input) + md.digest() + } + } + + @Test + fun sha256OurC() { + // Our C SHA-256 (ARM64 CE hardware acceleration) + Secp256k1InstanceC.init() + benchmarkRule.measureRepeated { + com.vitorpamplona.quartz.utils.Secp256k1C + .nativeSha256(sha256Input) + } + } + + @Test + fun sha256Kotlin() { + // Kotlin SHA-256 (uses platform MessageDigest on Android) + benchmarkRule.measureRepeated { + com.vitorpamplona.quartz.utils.sha256 + .sha256(sha256Input) + } + } + private fun hexToBytes(hex: String): ByteArray { val len = hex.length / 2 val result = ByteArray(len) diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/Secp256k1InstanceC.android.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/Secp256k1InstanceC.android.kt index adc8cb42d..9c9c3b6d7 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/Secp256k1InstanceC.android.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/Secp256k1InstanceC.android.kt @@ -89,6 +89,8 @@ object Secp256k1C { xonlyPub: ByteArray, scalar: ByteArray, ): ByteArray? + + @JvmStatic external fun nativeSha256(data: ByteArray): ByteArray? } actual object Secp256k1InstanceC { diff --git a/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/Secp256k1C.kt b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/Secp256k1C.kt index ca24e6427..079f65024 100644 --- a/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/Secp256k1C.kt +++ b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/Secp256k1C.kt @@ -84,4 +84,6 @@ object Secp256k1C { xonlyPub: ByteArray, scalar: ByteArray, ): ByteArray? + + @JvmStatic external fun nativeSha256(data: ByteArray): ByteArray? } diff --git a/quartz/src/main/c/secp256k1/jni_bridge.c b/quartz/src/main/c/secp256k1/jni_bridge.c index 6b4126da9..80337b257 100644 --- a/quartz/src/main/c/secp256k1/jni_bridge.c +++ b/quartz/src/main/c/secp256k1/jni_bridge.c @@ -273,3 +273,20 @@ Java_com_vitorpamplona_quartz_utils_Secp256k1C_nativeEcdhXOnly( if (!secp256k1c_ecdh_xonly(result, pub, sc)) return NULL; return make_bytes(env, result, 32); } + +/* ==================== SHA-256 (for benchmarking hardware vs software) ==================== */ + +JNIEXPORT jbyteArray JNICALL +Java_com_vitorpamplona_quartz_utils_Secp256k1C_nativeSha256( + JNIEnv *env, jclass cls, jbyteArray data +) { + (void)cls; + jint len = (*env)->GetArrayLength(env, data); + uint8_t *buf = (uint8_t *)(*env)->GetByteArrayElements(env, data, NULL); + if (!buf) return NULL; + + uint8_t out[32]; + secp256k1_sha256_hash(out, buf, (size_t)len); + (*env)->ReleaseByteArrayElements(env, data, (jbyte *)buf, JNI_ABORT); + return make_bytes(env, out, 32); +}