Skip to content

Commit

Permalink
feat(BAE): updating jsMain X25519 keys for use in TS
Browse files Browse the repository at this point in the history
  • Loading branch information
curtis-h committed Oct 2, 2023
1 parent 85ca03b commit 6b209c6
Show file tree
Hide file tree
Showing 11 changed files with 238 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.iohk.atala.prism.apollo.utils

import kotlin.js.ExperimentalJsExport

@ExperimentalJsExport
expect class KMMEdKeyPair(privateKey: KMMEdPrivateKey, publicKey: KMMEdPublicKey) {
val privateKey: KMMEdPrivateKey
val publicKey: KMMEdPublicKey
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.iohk.atala.prism.apollo.utils

public expect class KMMEdPrivateKey {
fun publicKey(): KMMEdPublicKey
fun sign(message: ByteArray): ByteArray
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.iohk.atala.prism.apollo.utils

import kotlin.js.ExperimentalJsExport

@ExperimentalJsExport
expect class KMMX25519KeyPair(privateKey: KMMX25519PrivateKey, publicKey: KMMX25519PublicKey) {
val privateKey: KMMX25519PrivateKey
val publicKey: KMMX25519PublicKey
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.iohk.atala.prism.apollo.utils

import io.iohk.atala.prism.apollo.base64.base64UrlDecodedBytes
import node.buffer.Buffer

@ExperimentalJsExport
@JsExport
object Curve25519Parser {
val encodedLength = 43
val rawLength = 32

/**
* @function parseRaw
* resolve the raw key value from a given ByteArray
* @param bytes - ByteArray to be parsed, either Encoded or Raw format
* @throws Error - if bytes is neither Encoded nor Raw
* @return Buffer - raw key value
*/
fun parseRaw(bytes: ByteArray): Buffer {
val buffer = Buffer.from(bytes)

if (buffer.length == 43) {
return Buffer.from(buffer.toByteArray().decodeToString().base64UrlDecodedBytes)
}

if (buffer.length == 32) {
return buffer
}

throw Error("invalid raw key")
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.iohk.atala.prism.apollo.utils

import io.iohk.atala.prism.apollo.base64.base64UrlDecodedBytes
import io.iohk.atala.prism.apollo.base64.base64UrlEncoded
import io.iohk.atala.prism.apollo.utils.external.eddsa
import node.buffer.Buffer

Expand All @@ -13,28 +13,31 @@ actual class KMMEdPrivateKey(bytes: ByteArray) {
init {
val ed25519 = eddsa("ed25519")

raw = this.parseRaw(bytes)
raw = Curve25519Parser.parseRaw(bytes)
keyPair = ed25519.keyFromSecret(raw)
}

private fun parseRaw(bytes: ByteArray): Buffer {
val buffer = Buffer.from(bytes)

if (buffer.length === 43) {
return Buffer.from(buffer.toByteArray().decodeToString().base64UrlDecodedBytes)
}

if (buffer.length === 32) {
return buffer
}

throw Error("invalid raw key");
/**
* @function getEncoded
* @return Buffer - base64UrlEncoded version of the raw value
*/
fun getEncoded(): Buffer {
return Buffer.from(raw.toByteArray().base64UrlEncoded)
}

actual fun publicKey(): KMMEdPublicKey {
/**
* @function publicKey
* @return KMMEdPublicKey - corresponding PublicKey
*/
fun publicKey(): KMMEdPublicKey {
return KMMEdPublicKey(keyPair.getPublic())
}

/**
* @function sign
* @param message - the ByteArray to be signed
* @return ByteArray - signature Hex converted to ByteArray
*/
actual fun sign(message: ByteArray): ByteArray {
val sig = keyPair.sign(Buffer.from(message))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.iohk.atala.prism.apollo.utils

import io.iohk.atala.prism.apollo.base64.base64UrlDecodedBytes
import io.iohk.atala.prism.apollo.base64.base64UrlEncoded
import io.iohk.atala.prism.apollo.utils.external.eddsa
import node.buffer.Buffer
import node.buffer.BufferEncoding
Expand All @@ -14,7 +14,7 @@ actual class KMMEdPublicKey(bytes: ByteArray) {
init {
val ed25519 = eddsa("ed25519")

raw = parseRaw(bytes)
raw = Curve25519Parser.parseRaw(bytes)
val pub = raw.toString(BufferEncoding.hex)

// TODO: Report a bug in elliptic, this method is not expecting a Buffer (bytes)
Expand All @@ -23,20 +23,21 @@ actual class KMMEdPublicKey(bytes: ByteArray) {
keyPair = ed25519.keyFromPublic(pub)
}

private fun parseRaw(bytes: ByteArray): Buffer {
val buffer = Buffer.from(bytes)

if (buffer.length === 43) {
return Buffer.from(buffer.toByteArray().decodeToString().base64UrlDecodedBytes)
}

if (buffer.length === 32) {
return buffer
}

throw Error("invalid raw key");
/**
* @function getEncoded
* @return Buffer - base64UrlEncoded version of the raw value
*/
fun getEncoded(): Buffer {
return Buffer.from(raw.toByteArray().base64UrlEncoded.encodeToByteArray())
}

/**
* @function verify
* confirm a message signature was signed with the corresponding PrivateKey
* @param message - the message that was signed
* @param sig - signature
* @return Boolean
*/
actual fun verify(message: ByteArray, sig: ByteArray): Boolean {
return keyPair.verify(Buffer.from(message), sig.decodeToString())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.iohk.atala.prism.apollo.utils

import io.iohk.atala.prism.apollo.base64.base64UrlEncoded
import io.iohk.atala.prism.apollo.utils.external.generateKeyPair as stableLibGenerateKeyPair

@ExperimentalJsExport
Expand All @@ -12,12 +11,10 @@ actual class KMMX25519KeyPair actual constructor(
actual companion object : X25519KeyPairGeneration {
override fun generateKeyPair(): KMMX25519KeyPair {
val keyPair = stableLibGenerateKeyPair()
val secretBytes = keyPair.secretKey.buffer.toByteArray().base64UrlEncoded.encodeToByteArray()
val publicBytes = keyPair.publicKey.buffer.toByteArray().base64UrlEncoded.encodeToByteArray()

return KMMX25519KeyPair(
KMMX25519PrivateKey(secretBytes),
KMMX25519PublicKey(publicBytes)
KMMX25519PrivateKey(keyPair.secretKey.buffer.toByteArray()),
KMMX25519PublicKey(keyPair.publicKey.buffer.toByteArray())
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
package io.iohk.atala.prism.apollo.utils

import io.iohk.atala.prism.apollo.base64.base64UrlEncoded
import io.iohk.atala.prism.apollo.utils.external.KeyPair
import io.iohk.atala.prism.apollo.utils.external.generateKeyPairFromSeed
import node.buffer.Buffer

@ExperimentalJsExport
@JsExport
actual class KMMX25519PrivateKey(val raw: ByteArray)
actual class KMMX25519PrivateKey(bytes: ByteArray) {
val raw: Buffer

init {
raw = Curve25519Parser.parseRaw(bytes)
}

/**
* @function getEncoded
* @return Buffer - base64UrlEncoded version of the raw value
*/
fun getEncoded(): Buffer {
return Buffer.from(raw.toByteArray().base64UrlEncoded)
}

/**
* @function publicKey
* @return KMMX25519PublicKey - corresponding PublicKey
*/
fun publicKey(): KMMX25519PublicKey {
val publicBytes = getInstance().publicKey.buffer.toByteArray()

return KMMX25519PublicKey(publicBytes)
}

private fun getInstance(): KeyPair {
return generateKeyPairFromSeed(raw)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
package io.iohk.atala.prism.apollo.utils

import io.iohk.atala.prism.apollo.base64.base64UrlEncoded
import node.buffer.Buffer

@ExperimentalJsExport
@JsExport
actual class KMMX25519PublicKey(val raw: ByteArray)
actual class KMMX25519PublicKey(bytes: ByteArray) {
val raw: Buffer
init {
raw = Curve25519Parser.parseRaw(bytes)
}

/**
* @function getEncoded
* @return Buffer - base64UrlEncoded version of the raw value
*/
fun getEncoded(): Buffer {
return Buffer.from(raw.toByteArray().base64UrlEncoded.encodeToByteArray())
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
package io.iohk.atala.prism.apollo.utils

import node.buffer.Buffer
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

class KMMEdKeyPairTests {
private val raw = arrayOf(234, 155, 38, 115, 124, 211, 171, 185, 149, 186, 77, 255, 240, 94, 209, 65, 63, 214, 168, 213, 146, 68, 68, 196, 167, 211, 183, 80, 14, 166, 239, 217)
private val rawBytes = Buffer.from(raw).toByteArray()
private val encoded = arrayOf(54, 112, 115, 109, 99, 51, 122, 84, 113, 55, 109, 86, 117, 107, 51, 95, 56, 70, 55, 82, 81, 84, 95, 87, 113, 78, 87, 83, 82, 69, 84, 69, 112, 57, 79, 51, 85, 65, 54, 109, 55, 57, 107)
private val encodedBytes = Buffer.from(encoded).toByteArray()
private val publicRaw = arrayOf(207, 230, 188, 131, 200, 191, 223, 38, 163, 19, 244, 3, 35, 18, 5, 238, 195, 245, 155, 246, 139, 41, 51, 159, 202, 2, 46, 72, 150, 167, 68, 8)
private val publicRawBytes = Buffer.from(publicRaw).toByteArray()
private val publicEncoded = arrayOf(122, 45, 97, 56, 103, 56, 105, 95, 51, 121, 97, 106, 69, 95, 81, 68, 73, 120, 73, 70, 55, 115, 80, 49, 109, 95, 97, 76, 75, 84, 79, 102, 121, 103, 73, 117, 83, 74, 97, 110, 82, 65, 103)
private val publicEncodedBytes = Buffer.from(publicEncoded).toByteArray()

@Test
fun testGenerateKeyPair() {
val keyPair = KMMEdKeyPair.generateKeyPair()
Expand All @@ -15,6 +25,38 @@ class KMMEdKeyPairTests {
assertNotNull(keyPair.publicKey)
}

@Test
fun testConstructorRaw() {
val key = KMMEdPrivateKey(rawBytes)

assertTrue(key.raw.toByteArray() contentEquals rawBytes)
assertTrue(key.getEncoded().toByteArray() contentEquals encodedBytes)
}

@Test
fun testConstructorEncoded() {
val key = KMMEdPrivateKey(encodedBytes)

assertTrue(key.raw.toByteArray() contentEquals rawBytes)
assertTrue(key.getEncoded().toByteArray() contentEquals encodedBytes)
}

@Test
fun testGetEncoded() {
val key = KMMEdPrivateKey(rawBytes)

assertTrue(key.getEncoded().toByteArray() contentEquals encodedBytes)
}

@Test
fun testPublicKey() {
val privateKey = KMMEdPrivateKey(rawBytes)
val publicKey = privateKey.publicKey()

assertTrue(publicKey.raw.toByteArray() contentEquals publicRawBytes)
assertTrue(publicKey.getEncoded().toByteArray() contentEquals publicEncodedBytes)
}

@Test
fun testSignMessage() {
val keyPair = KMMEdKeyPair.generateKeyPair()
Expand All @@ -24,6 +66,20 @@ class KMMEdKeyPairTests {
assertNotNull(sig)
}

@Test
fun testSignMessageKnownValue() {
val privateKey = KMMEdPrivateKey(rawBytes)
val message = "testing".encodeToByteArray()
val sig = privateKey.sign(message)
val sigStr = Buffer.from(sig).toString()
val expectedBytes = byteArrayOf(67, 68, 57, 67, 68, 69, 52, 67, 49, 54, 50, 51, 65, 69, 57, 65, 51, 48, 55, 51, 69, 66, 50, 52, 49, 48, 67, 53, 53, 48, 52, 57, 53, 52, 70, 51, 57, 69, 68, 67, 68, 55, 66, 68, 57, 49, 57, 67, 54, 67, 49, 54, 67, 68, 54, 51, 52, 56, 48, 55, 50, 56, 53, 69, 66, 51, 70, 57, 69, 69, 51, 52, 52, 51, 57, 49, 66, 55, 65, 51, 55, 69, 54, 53, 53, 70, 56, 51, 49, 70, 68, 48, 57, 70, 50, 50, 52, 53, 68, 55, 66, 70, 50, 67, 48, 57, 70, 66, 69, 67, 57, 55, 55, 51, 50, 50, 49, 69, 65, 48, 52, 50, 70, 69, 69, 49, 48, 48)
val expectedStr = Buffer.from(expectedBytes).toString()

assertNotNull(sig)
assertTrue(expectedBytes contentEquals sig)
assertTrue(expectedStr contentEquals sigStr)
}

@Test
fun testVerifyMessage() {
val keyPair = KMMEdKeyPair.generateKeyPair()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.iohk.atala.prism.apollo.utils

import node.buffer.Buffer
import kotlin.test.Test
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

class KMMX25519Tests {
private val raw = arrayOf(51, 115, 246, 68, 98, 108, 130, 79, 66, 173, 201, 51, 112, 98, 163, 196, 188, 34, 100, 148, 28, 98, 236, 251, 234, 41, 3, 175, 80, 1, 64, 152)
private val rawBytes = Buffer.from(raw).toByteArray()
private val encoded = arrayOf(77, 51, 80, 50, 82, 71, 74, 115, 103, 107, 57, 67, 114, 99, 107, 122, 99, 71, 75, 106, 120, 76, 119, 105, 90, 74, 81, 99, 89, 117, 122, 55, 54, 105, 107, 68, 114, 49, 65, 66, 81, 74, 103)
private val encodedBytes = Buffer.from(encoded).toByteArray()
private val publicRaw = arrayOf(212, 97, 242, 116, 254, 39, 85, 254, 32, 125, 72, 58, 203, 231, 151, 68, 217, 36, 15, 137, 108, 58, 150, 193, 48, 67, 203, 34, 115, 180, 148, 27)
private val publicRawBytes = Buffer.from(publicRaw).toByteArray()
private val publicEncoded = arrayOf(49, 71, 72, 121, 100, 80, 52, 110, 86, 102, 52, 103, 102, 85, 103, 54, 121, 45, 101, 88, 82, 78, 107, 107, 68, 52, 108, 115, 79, 112, 98, 66, 77, 69, 80, 76, 73, 110, 79, 48, 108, 66, 115)
private val publicEncodedBytes = Buffer.from(publicEncoded).toByteArray()

@Test
fun testGenerateKeyPair() {
val keyPair = KMMX25519KeyPair.generateKeyPair()

assertNotNull(keyPair)
assertNotNull(keyPair.privateKey)
assertNotNull(keyPair.publicKey)
}

@Test
fun testConstructorRaw() {
val key = KMMX25519PrivateKey(rawBytes)

assertTrue(key.raw.toByteArray() contentEquals rawBytes)
assertTrue(key.getEncoded().toByteArray() contentEquals encodedBytes)
}

@Test
fun testConstructorEncoded() {
val key = KMMX25519PrivateKey(encodedBytes)

assertTrue(key.raw.toByteArray() contentEquals rawBytes)
assertTrue(key.getEncoded().toByteArray() contentEquals encodedBytes)
}

@Test
fun testGetEncoded() {
val key = KMMX25519PrivateKey(rawBytes)

assertTrue(key.getEncoded().toByteArray() contentEquals encodedBytes)
}

@Test
fun testPublicKey() {
val privateKey = KMMX25519PrivateKey(rawBytes)
val publicKey = privateKey.publicKey()

assertTrue(publicKey.raw.toByteArray() contentEquals publicRawBytes)
assertTrue(publicKey.getEncoded().toByteArray() contentEquals publicEncodedBytes)
}
}

0 comments on commit 6b209c6

Please sign in to comment.