From de2a13b73cbc1b817d0ae8908111b48b13016e77 Mon Sep 17 00:00:00 2001 From: Curtish Date: Thu, 18 Apr 2024 15:11:50 +0100 Subject: [PATCH] js EdHDKey + external --- .../atala/prism/apollo/derivation/EdHDKey.kt | 12 +-- .../atala/prism/apollo/derivation/EdHDKey.kt | 88 +++++++++++++++++++ .../apollo/utils/external/Ed25519_Bip32.kt | 22 +++++ 3 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 apollo/src/jsMain/kotlin/io/iohk/atala/prism/apollo/derivation/EdHDKey.kt create mode 100644 apollo/src/jsMain/kotlin/io/iohk/atala/prism/apollo/utils/external/Ed25519_Bip32.kt diff --git a/apollo/src/allButJSMain/kotlin/io/iohk/atala/prism/apollo/derivation/EdHDKey.kt b/apollo/src/allButJSMain/kotlin/io/iohk/atala/prism/apollo/derivation/EdHDKey.kt index fb7ddf52f..115bddab4 100644 --- a/apollo/src/allButJSMain/kotlin/io/iohk/atala/prism/apollo/derivation/EdHDKey.kt +++ b/apollo/src/allButJSMain/kotlin/io/iohk/atala/prism/apollo/derivation/EdHDKey.kt @@ -1,19 +1,13 @@ package io.iohk.atala.prism.apollo.derivation -import com.ionspin.kotlin.bignum.integer.BigInteger import com.ionspin.kotlin.bignum.integer.toBigInteger import ed25519_bip32.XPrvWrapper import io.iohk.atala.prism.apollo.utils.ECConfig -import kotlin.js.ExperimentalJsExport -import kotlin.js.JsExport -import kotlin.js.JsName /** * Represents and HDKey with its derive methods */ -@OptIn(ExperimentalJsExport::class) -@JsExport class EdHDKey( val privateKey: ByteArray, val chainCode: ByteArray, @@ -22,15 +16,11 @@ class EdHDKey( val index: BigIntegerWrapper = BigIntegerWrapper(0) ) { /** - * Constructs a new EdHDKey object from a seed, depth, and child index. + * Constructs a new EdHDKey object from a seed * * @param seed The seed used to derive the private key and chain code. - * @param depth The depth of the HDKey. - * @param childIndex The child index of the HDKey. - * * @throws IllegalArgumentException if the seed length is not equal to 64. */ - fun initFromSeed(seed: ByteArray): EdHDKey { require(seed.size == 64) { "Seed expected byte length to be ${ECConfig.PRIVATE_KEY_BYTE_SIZE}" diff --git a/apollo/src/jsMain/kotlin/io/iohk/atala/prism/apollo/derivation/EdHDKey.kt b/apollo/src/jsMain/kotlin/io/iohk/atala/prism/apollo/derivation/EdHDKey.kt new file mode 100644 index 000000000..b65ba08af --- /dev/null +++ b/apollo/src/jsMain/kotlin/io/iohk/atala/prism/apollo/derivation/EdHDKey.kt @@ -0,0 +1,88 @@ +package io.iohk.atala.prism.apollo.derivation + +import com.ionspin.kotlin.bignum.integer.toBigInteger +import io.iohk.atala.prism.apollo.utils.ECConfig +import io.iohk.atala.prism.apollo.utils.external.ed25519_bip32 + +/** + * Represents and HDKey with its derive methods + */ +@OptIn(ExperimentalJsExport::class) +@JsExport +class EdHDKey( + val privateKey: ByteArray, + val chainCode: ByteArray, + val publicKey: ByteArray? = null, + val depth: Int = 0, + val index: BigIntegerWrapper = BigIntegerWrapper(0) +) { + /** + * Constructs a new EdHDKey object from a seed + * + * @param seed The seed used to derive the private key and chain code. + * @throws IllegalArgumentException if the seed length is not equal to 64. + */ + fun initFromSeed(seed: ByteArray): EdHDKey { + require(seed.size == 64) { + "Seed expected byte length to be ${ECConfig.PRIVATE_KEY_BYTE_SIZE}" + } + + val key = seed.sliceArray(0 until 32) + val chainCode = seed.sliceArray(32 until seed.size) + val wrapper = ed25519_bip32.XPrvWrapper.from_nonextended_noforce(key, chainCode) + + return EdHDKey( + privateKey = wrapper.extended_secret_key(), + chainCode = wrapper.chain_code(), + ) + } + + /** + * Method to derive an HDKey by a path + * + * @param path value used to derive a key + */ + fun derive(path: String): EdHDKey { + if (!path.matches(Regex("^[mM].*"))) { + throw Error("Path must start with \"m\" or \"M\"") + } + if (Regex("^[mM]'?$").matches(path)) { + return this + } + val parts = path.replace(Regex("^[mM]'?/"), "").split("/") + var child = this + + for (c in parts) { + val m = Regex("^(\\d+)('?)$").find(c)?.groupValues + if (m == null || m.size != 3) { + throw Error("Invalid child index: $c") + } + val idx = m[1].toBigInteger() + if (idx >= HDKey.HARDENED_OFFSET) { + throw Error("Invalid index") + } + val finalIdx = if (m[2] == "'") idx + HDKey.HARDENED_OFFSET else idx + + child = child.deriveChild(BigIntegerWrapper(finalIdx)) + } + + return child + } + + /** + * Method to derive an HDKey child by index + * + * @param index value used to derive a key + */ + fun deriveChild(wrappedIndex: BigIntegerWrapper): EdHDKey { + val wrapper = ed25519_bip32.XPrvWrapper.from_extended_and_chaincode(privateKey, chainCode) + val derived = wrapper.derive(wrappedIndex.value.uintValue()) + + return EdHDKey( + privateKey = derived.extended_secret_key(), + chainCode = derived.chain_code(), + depth = depth + 1, + index = wrappedIndex + ) + } +} \ No newline at end of file diff --git a/apollo/src/jsMain/kotlin/io/iohk/atala/prism/apollo/utils/external/Ed25519_Bip32.kt b/apollo/src/jsMain/kotlin/io/iohk/atala/prism/apollo/utils/external/Ed25519_Bip32.kt new file mode 100644 index 000000000..ff416bdb3 --- /dev/null +++ b/apollo/src/jsMain/kotlin/io/iohk/atala/prism/apollo/utils/external/Ed25519_Bip32.kt @@ -0,0 +1,22 @@ +package io.iohk.atala.prism.apollo.utils.external + +external interface XPrvWrapper { + fun public(): ByteArray + + fun derive(index: Any): XPrvWrapper + + fun extended_secret_key(): ByteArray + + fun chain_code(): ByteArray + + var from_nonextended_noforce: (js_bytes: ByteArray, js_chain_code: ByteArray) -> XPrvWrapper + + var from_extended_and_chaincode: (js_bytes: ByteArray, js_chain_code: ByteArray) -> XPrvWrapper +} + +external interface ed25519_bip32_export { + var XPrvWrapper: XPrvWrapper +} + +@JsModule("ed25519_bip32_export") +external val ed25519_bip32: ed25519_bip32_export