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:
Claude
2026-04-11 15:02:27 +00:00
parent 61e8471c37
commit 944562ddc8
4 changed files with 55 additions and 0 deletions
@@ -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)