Skip to content

Commit

Permalink
fix: BigInteger has no corresponding exportable dataType in JS
Browse files Browse the repository at this point in the history
  • Loading branch information
hamada147 committed Aug 4, 2023
1 parent 7a65022 commit 5428579
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,55 @@ import kotlin.js.ExperimentalJsExport
import kotlin.js.JsExport
import kotlin.js.JsName

@OptIn(ExperimentalJsExport::class)
@JsExport
class BigIntegerWrapper {
internal val value: BigInteger

@JsName("initFromInt")
constructor(int: Int) {
value = BigInteger(int)
}

@JsName("initFromLong")
constructor(long: Long) {
value = BigInteger(long)
}

@JsName("initFromShort")
constructor(short: Short) {
value = BigInteger(short)
}

@JsName("initFromByte")
constructor(byte: Byte) {
value = BigInteger(byte)
}

@JsName("initFromString")
constructor(string: String) {
value = BigInteger.parseString(string)
}

@JsName("initFromBigInteger")
constructor(bigInteger: BigInteger) {
value = bigInteger
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as BigIntegerWrapper

return value == other.value
}

override fun hashCode(): Int {
return value.hashCode()
}
}

/**
* Represents and HDKey with its derive methods
*/
Expand All @@ -20,11 +69,11 @@ class HDKey(
val publicKey: ByteArray? = null,
val chainCode: ByteArray? = null,
val depth: Int = 0,
val childIndex: BigInteger = BigInteger(0),
val childIndex: BigIntegerWrapper = BigIntegerWrapper(0)
) {

@JsName("InitFromSeed")
constructor(seed: ByteArray, depth: Int, childIndex: BigInteger) : this(
constructor(seed: ByteArray, depth: Int, childIndex: BigIntegerWrapper) : this(
privateKey = sha512(key = "Bitcoin seed".encodeToByteArray(), input = seed).sliceArray(IntRange(0, 31)),
chainCode = sha512("Bitcoin seed".encodeToByteArray(), seed).sliceArray(32 until seed.size),
depth = depth,
Expand All @@ -35,6 +84,18 @@ class HDKey(
}
}

@JsName("InitFromSeedFromBigIntegerString")
constructor(seed: ByteArray, depth: Int, childIndex: Int) : this(
privateKey = sha512(key = "Bitcoin seed".encodeToByteArray(), input = seed).sliceArray(IntRange(0, 31)),
chainCode = sha512("Bitcoin seed".encodeToByteArray(), seed).sliceArray(32 until seed.size),
depth = depth,
childIndex = BigIntegerWrapper(childIndex)
) {
require(seed.size == 64) {
"Seed expected byte length to be ${ECConfig.PRIVATE_KEY_BYTE_SIZE}"
}
}

/**
* Method to derive an HDKey by a path
*
Expand All @@ -59,7 +120,7 @@ class HDKey(
throw Error("Invalid index")
}
val finalIdx = if (m[2] == "'") idx + HARDENED_OFFSET else idx
child = child.deriveChild(finalIdx)
child = child.deriveChild(BigIntegerWrapper(finalIdx))
}
return child
}
Expand All @@ -69,7 +130,8 @@ class HDKey(
*
* @param index value used to derive a key
*/
fun deriveChild(index: BigInteger): HDKey {
fun deriveChild(index: BigIntegerWrapper): HDKey {
val index = index.value
if (chainCode == null) {
throw Exception("No chainCode set")
}
Expand Down Expand Up @@ -103,13 +165,23 @@ class HDKey(
privateKey = opt.privateKey,
chainCode = opt.chainCode,
depth = opt.depth,
childIndex = opt.index
childIndex = BigIntegerWrapper(opt.index)
)
} catch (err: Error) {
this.deriveChild(index + 1)
this.deriveChild(BigIntegerWrapper(index + 1))
}
}

/**
* Method to derive an HDKey child by index
*
* @param index value used to derive a key as Int
*/
@JsName("deriveFromInt")
fun deriveChild(index: Int): HDKey {
return this.deriveChild(BigIntegerWrapper(BigInteger(index)))
}

fun getKMMSecp256k1PrivateKey(): KMMECSecp256k1PrivateKey {
privateKey?.let {
return KMMECSecp256k1PrivateKey.secp256k1FromByteArray(privateKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,27 @@ class HDKeyTest {
seed = seed.sliceArray(IntRange(0, 60))

assertFailsWith(IllegalArgumentException::class) {
HDKey(seed, depth, childIndex)
HDKey(seed, depth, BigIntegerWrapper(childIndex))
}
}

@Test
fun testConstructorWithSeed_thenRightPrivateKey() {
val depth = 0

val hdKey = HDKey(seed = seed, depth = depth, childIndex = childIndex)
val hdKey = HDKey(seed = seed, depth = depth, childIndex = BigIntegerWrapper(childIndex))

assertNotNull(hdKey.privateKey)
assertTrue(privateKey.base64UrlDecodedBytes.contentEquals(hdKey.privateKey!!))
assertNotNull(hdKey.chainCode)
assertEquals(depth, hdKey.depth)
assertEquals(childIndex, hdKey.childIndex)
assertEquals(BigIntegerWrapper(childIndex), hdKey.childIndex)
}

@Test
fun testDerive_whenIncorrectPath_thenThrowException() {
val depth = 1
val hdKey = HDKey(seed, depth, childIndex)
val hdKey = HDKey(seed, depth, BigIntegerWrapper(childIndex))
val path = "x/0"

assertFailsWith(Error::class) {
Expand All @@ -65,7 +65,7 @@ class HDKeyTest {
fun testDerive_whenCorrectPath_thenDeriveOk() {
val depth = 1

val hdKey = HDKey(seed, depth, childIndex)
val hdKey = HDKey(seed, depth, BigIntegerWrapper(childIndex))
val path = "m/0'/0'/0'"

val derPrivateKey = hdKey.derive(path)
Expand All @@ -78,11 +78,11 @@ class HDKeyTest {
val hdKey = HDKey(
privateKey = privateKey.encodeToByteArray(),
depth = depth,
childIndex = childIndex
childIndex = BigIntegerWrapper(childIndex)
)

assertFailsWith(Exception::class) {
hdKey.deriveChild(childIndex)
hdKey.deriveChild(BigIntegerWrapper(childIndex))
}
}

Expand All @@ -92,11 +92,11 @@ class HDKeyTest {
val hdKey = HDKey(
privateKey = privateKey.encodeToByteArray(),
depth = depth,
childIndex = childIndex
childIndex = BigIntegerWrapper(childIndex)
)

assertFailsWith(Exception::class) {
hdKey.deriveChild(childIndex)
hdKey.deriveChild(BigIntegerWrapper(childIndex))
}
}

Expand All @@ -108,19 +108,19 @@ class HDKeyTest {
val hdKey = HDKey(
privateKey = Random.Default.nextBytes(33),
depth = depth,
childIndex = childIndex
childIndex = BigIntegerWrapper(childIndex)
)

assertFailsWith(Exception::class) {
hdKey.deriveChild(childIndex)
hdKey.deriveChild(BigIntegerWrapper(childIndex))
}
}

@Test
fun testGetKMMSecp256k1PrivateKey_thenPrivateKeyNonNull() {
val depth = 1

val hdKey = HDKey(seed, depth, childIndex)
val hdKey = HDKey(seed, depth, BigIntegerWrapper(childIndex))
val key = hdKey.getKMMSecp256k1PrivateKey()
assertNotNull(key)
}
Expand Down
5 changes: 0 additions & 5 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ buildscript {
mavenCentral()
mavenLocal()
google()
maven("https://plugins.gradle.org/m2/")
// Needed for Kotlin coroutines that support new memory management mode
maven {
url = uri("https://maven.pkg.jetbrains.space/public/p/kotlinx-coroutines/maven")
}
}

dependencies {
Expand Down

0 comments on commit 5428579

Please sign in to comment.