diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/Secp256k1.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/Secp256k1.kt index 9c8650db8..2b9c5e41e 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/Secp256k1.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/Secp256k1.kt @@ -138,7 +138,12 @@ object Secp256k1 { 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)) require(!U256.isZero(k0)) @@ -149,12 +154,18 @@ object Secp256k1 { check(ECPoint.toAffine(rPoint, rx, ry)) val k = if (ECPoint.hasEvenY(ry)) k0 else ScalarN.neg(k0) - val rBytes = U256.toBytes(rx) - val eHash = sha256(CHALLENGE_PREFIX + rBytes + pBytes + data) + val chalInput = ByteArray(64 + 32 + 32 + data.size) + 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 s = ScalarN.add(k, ScalarN.mul(e, d)) - val sig = rBytes + U256.toBytes(s) + val sScalar = ScalarN.add(k, ScalarN.mul(e, d)) + val sig = ByteArray(64) + U256.toBytesInto(rx, sig, 0) + U256.toBytesInto(sScalar, sig, 32) require(verifySchnorr(sig, data, pBytes)) { "Signature self-verification failed" } return sig @@ -186,12 +197,18 @@ object Secp256k1 { val py = IntArray(8) 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 - val s = U256.fromBytes(signature.copyOfRange(32, 64)) + val s = U256.fromBytes(signature, 32) 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)) // R = s·G + (-e)·P via Shamir's trick diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/U256.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/U256.kt index 5cdc12838..15f29f063 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/U256.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/U256.kt @@ -216,11 +216,16 @@ internal object U256 { // ==================== Serialization ==================== /** Decode a big-endian 32-byte array into little-endian IntArray(8). */ - fun fromBytes(bytes: ByteArray): IntArray { - require(bytes.size == 32) + fun fromBytes(bytes: ByteArray): IntArray = fromBytes(bytes, 0) + + /** Decode 32 big-endian bytes starting at [offset] into little-endian IntArray(8). */ + fun fromBytes( + bytes: ByteArray, + offset: Int, + ): IntArray { val r = IntArray(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 ((bytes[o + 1].toInt() and 0xFF) shl 16) or ((bytes[o + 2].toInt() and 0xFF) shl 8) or