perf: reduce ByteArray allocations in verify and sign paths
- Replace 3-way ByteArray concatenation for tagged hash inputs with single pre-sized array + copyInto calls (avoids 3 intermediate arrays) - Add U256.fromBytes(bytes, offset) overload to decode from a slice without copyOfRange allocation - Build signature output using toBytesInto instead of concatenation - Apply same pattern to signSchnorr's nonce and challenge hash inputs https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
This commit is contained in:
@@ -138,7 +138,12 @@ object Secp256k1 {
|
|||||||
dBytes
|
dBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
val rand = sha256(NONCE_PREFIX + t + pBytes + data)
|
val nonceInput = ByteArray(64 + 32 + 32 + data.size)
|
||||||
|
NONCE_PREFIX.copyInto(nonceInput, 0)
|
||||||
|
t.copyInto(nonceInput, 64)
|
||||||
|
pBytes.copyInto(nonceInput, 96)
|
||||||
|
data.copyInto(nonceInput, 128)
|
||||||
|
val rand = sha256(nonceInput)
|
||||||
val k0 = ScalarN.reduce(U256.fromBytes(rand))
|
val k0 = ScalarN.reduce(U256.fromBytes(rand))
|
||||||
require(!U256.isZero(k0))
|
require(!U256.isZero(k0))
|
||||||
|
|
||||||
@@ -149,12 +154,18 @@ object Secp256k1 {
|
|||||||
check(ECPoint.toAffine(rPoint, rx, ry))
|
check(ECPoint.toAffine(rPoint, rx, ry))
|
||||||
|
|
||||||
val k = if (ECPoint.hasEvenY(ry)) k0 else ScalarN.neg(k0)
|
val k = if (ECPoint.hasEvenY(ry)) k0 else ScalarN.neg(k0)
|
||||||
val rBytes = U256.toBytes(rx)
|
val chalInput = ByteArray(64 + 32 + 32 + data.size)
|
||||||
val eHash = sha256(CHALLENGE_PREFIX + rBytes + pBytes + data)
|
CHALLENGE_PREFIX.copyInto(chalInput, 0)
|
||||||
|
U256.toBytesInto(rx, chalInput, 64)
|
||||||
|
pBytes.copyInto(chalInput, 96)
|
||||||
|
data.copyInto(chalInput, 128)
|
||||||
|
val eHash = sha256(chalInput)
|
||||||
val e = ScalarN.reduce(U256.fromBytes(eHash))
|
val e = ScalarN.reduce(U256.fromBytes(eHash))
|
||||||
|
|
||||||
val s = ScalarN.add(k, ScalarN.mul(e, d))
|
val sScalar = ScalarN.add(k, ScalarN.mul(e, d))
|
||||||
val sig = rBytes + U256.toBytes(s)
|
val sig = ByteArray(64)
|
||||||
|
U256.toBytesInto(rx, sig, 0)
|
||||||
|
U256.toBytesInto(sScalar, sig, 32)
|
||||||
|
|
||||||
require(verifySchnorr(sig, data, pBytes)) { "Signature self-verification failed" }
|
require(verifySchnorr(sig, data, pBytes)) { "Signature self-verification failed" }
|
||||||
return sig
|
return sig
|
||||||
@@ -186,12 +197,18 @@ object Secp256k1 {
|
|||||||
val py = IntArray(8)
|
val py = IntArray(8)
|
||||||
if (!ECPoint.liftX(px, py, U256.fromBytes(pub))) return false
|
if (!ECPoint.liftX(px, py, U256.fromBytes(pub))) return false
|
||||||
|
|
||||||
val r = U256.fromBytes(signature.copyOfRange(0, 32))
|
val r = U256.fromBytes(signature, 0)
|
||||||
if (U256.cmp(r, FieldP.P) >= 0) return false
|
if (U256.cmp(r, FieldP.P) >= 0) return false
|
||||||
val s = U256.fromBytes(signature.copyOfRange(32, 64))
|
val s = U256.fromBytes(signature, 32)
|
||||||
if (U256.cmp(s, ScalarN.N) >= 0) return false
|
if (U256.cmp(s, ScalarN.N) >= 0) return false
|
||||||
|
|
||||||
val eHash = sha256(CHALLENGE_PREFIX + signature.copyOfRange(0, 32) + pub + data)
|
// Build challenge hash input in a single array: prefix(64) + r(32) + pub(32) + data(N)
|
||||||
|
val hashInput = ByteArray(64 + 32 + 32 + data.size)
|
||||||
|
CHALLENGE_PREFIX.copyInto(hashInput, 0)
|
||||||
|
signature.copyInto(hashInput, 64, 0, 32) // r bytes from signature
|
||||||
|
pub.copyInto(hashInput, 96)
|
||||||
|
data.copyInto(hashInput, 128)
|
||||||
|
val eHash = sha256(hashInput)
|
||||||
val e = ScalarN.reduce(U256.fromBytes(eHash))
|
val e = ScalarN.reduce(U256.fromBytes(eHash))
|
||||||
|
|
||||||
// R = s·G + (-e)·P via Shamir's trick
|
// R = s·G + (-e)·P via Shamir's trick
|
||||||
|
|||||||
@@ -216,11 +216,16 @@ internal object U256 {
|
|||||||
// ==================== Serialization ====================
|
// ==================== Serialization ====================
|
||||||
|
|
||||||
/** Decode a big-endian 32-byte array into little-endian IntArray(8). */
|
/** Decode a big-endian 32-byte array into little-endian IntArray(8). */
|
||||||
fun fromBytes(bytes: ByteArray): IntArray {
|
fun fromBytes(bytes: ByteArray): IntArray = fromBytes(bytes, 0)
|
||||||
require(bytes.size == 32)
|
|
||||||
|
/** Decode 32 big-endian bytes starting at [offset] into little-endian IntArray(8). */
|
||||||
|
fun fromBytes(
|
||||||
|
bytes: ByteArray,
|
||||||
|
offset: Int,
|
||||||
|
): IntArray {
|
||||||
val r = IntArray(8)
|
val r = IntArray(8)
|
||||||
for (i in 0 until 8) {
|
for (i in 0 until 8) {
|
||||||
val o = 28 - i * 4
|
val o = offset + 28 - i * 4
|
||||||
r[i] = ((bytes[o].toInt() and 0xFF) shl 24) or
|
r[i] = ((bytes[o].toInt() and 0xFF) shl 24) or
|
||||||
((bytes[o + 1].toInt() and 0xFF) shl 16) or
|
((bytes[o + 1].toInt() and 0xFF) shl 16) or
|
||||||
((bytes[o + 2].toInt() and 0xFF) shl 8) or
|
((bytes[o + 2].toInt() and 0xFF) shl 8) or
|
||||||
|
|||||||
Reference in New Issue
Block a user