Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,25 @@ actual class Secp256k1Lib actual constructor() {
val privKey = BigInteger.parseString(privKeyString, 16)
val derivedPrivKey = BigInteger.parseString(derivedPrivKeyString, 16)
val added = (privKey + derivedPrivKey) % ECConfig.n
return added.toByteArray()
// ionspin BigInteger.toByteArray() is minimal-length big-endian; secp256k1 secrets must be 32 bytes (left-pad zeros).
return normalizeSecp256k1ScalarBytes(added.toByteArray())
}

private fun normalizeSecp256k1ScalarBytes(bytes: ByteArray): ByteArray {
val n = ECConfig.PRIVATE_KEY_BYTE_SIZE
var b = bytes
while (b.size > n && b[0] == 0.toByte()) {
b = b.copyOfRange(1, b.size)
}
require(b.size <= n) {
"Secp256k1 private scalar exceeds $n bytes after normalization (${b.size})"
}
if (b.size == n) {
return b
}
return ByteArray(n).also { out ->
b.copyInto(out, destinationOffset = n - b.size)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.hyperledger.identus.apollo.utils

import org.hyperledger.identus.apollo.derivation.Mnemonic
import org.hyperledger.identus.apollo.secp256k1.Secp256k1Lib
import kotlin.test.Test
import kotlin.test.assertEquals

Expand All @@ -20,4 +21,20 @@ class Secp256k1LibTestJS {
true
)
}

/**
* Regression: JS [derivePrivateKey] used ionspin [BigInteger.toByteArray], which is minimal-length.
* For (priv + tweak) % n == 1, that is a single non-zero byte — callers expect a fixed 32-byte secret.
* (This is unrelated to public-key 0x02/0x03/0x04 prefix bytes; those apply to encoded public points, not private scalars.)
*/
@Test
fun derivePrivateKey_leftPadsMinimalScalarTo32Bytes() {
val lib = Secp256k1Lib()
val priv = ByteArray(32) { 0 }.also { it[31] = 1 }
val tweak = ByteArray(32) { 0 }
val r = lib.derivePrivateKey(priv, tweak)!!
assertEquals(32, r.size)
assertEquals(0, r[0].toInt() and 0xff)
assertEquals(1, r[31].toInt() and 0xff)
}
}
Loading