feat: add SHA-256 benchmark comparing Android native vs our C vs Kotlin
Three SHA-256 implementations benchmarked on the same phone: - sha256Android: Android's MessageDigest (BoringSSL + ARM64 CE hardware) - sha256OurC: Our C SHA-256 (ARM64 CE hardware via JNI) - sha256Kotlin: Kotlin's sha256 (platform MessageDigest on Android) Also add nativeSha256 JNI method to expose our hardware SHA-256. Why our C matches ACINQ on ARM64 despite different architectures: - Our 4x64: 16 MUL+UMULH pairs per field mul - ACINQ 5x52: 25 MUL+UMULH pairs per field mul - We save 9 multiply pairs (~27ns on Cortex-X3) per fe_mul - ACINQ saves ~6 normalizations with lazy reduction (~6ns) - Net advantage: ~21ns per mul × 500 muls/verify > 0 - Plus our SHA-256 uses hardware CE, ACINQ's is software https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
This commit is contained in:
+34
@@ -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)
|
||||
|
||||
+2
@@ -89,6 +89,8 @@ object Secp256k1C {
|
||||
xonlyPub: ByteArray,
|
||||
scalar: ByteArray,
|
||||
): ByteArray?
|
||||
|
||||
@JvmStatic external fun nativeSha256(data: ByteArray): ByteArray?
|
||||
}
|
||||
|
||||
actual object Secp256k1InstanceC {
|
||||
|
||||
@@ -84,4 +84,6 @@ object Secp256k1C {
|
||||
xonlyPub: ByteArray,
|
||||
scalar: ByteArray,
|
||||
): ByteArray?
|
||||
|
||||
@JvmStatic external fun nativeSha256(data: ByteArray): ByteArray?
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user