Skip to content

Commit

Permalink
feat(BAE): updating jsMain Ed25519 keys for use in TS (#92)
Browse files Browse the repository at this point in the history
Co-authored-by: Ahmed Moussa <[email protected]>
Signed-off-by: Curtis <[email protected]>
  • Loading branch information
curtis-h and hamada147 committed May 20, 2024
1 parent 9bc0392 commit fba402c
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 15 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,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,31 @@
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

/**
* Resolve the given ByteArray into the raw key value
* @param bytes - ByteArray to be parsed, either Encoded or Raw data
* @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 == encodedLength) {
return Buffer.from(buffer.toByteArray().decodeToString().base64UrlDecodedBytes)
}

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

throw Error("invalid raw key")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ actual class KMMEdKeyPair actual constructor(
override fun generateKeyPair(): KMMEdKeyPair {
val ed25519 = eddsa("ed25519")
val rnd = rand(32)
val secret = Buffer.from(rnd).toByteArray()
val secret = Buffer.from(rnd)
val keypair = ed25519.keyFromSecret(secret)
val public = keypair.getPublic()

return KMMEdKeyPair(KMMEdPrivateKey(secret), KMMEdPublicKey(public))
return KMMEdKeyPair(KMMEdPrivateKey(secret.toByteArray()), KMMEdPublicKey(public))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,43 @@
package io.iohk.atala.prism.apollo.utils

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

@ExperimentalJsExport
@JsExport
actual class KMMEdPrivateKey(val raw: ByteArray) {
actual class KMMEdPrivateKey(bytes: ByteArray) {
val raw: Buffer
private val keyPair: eddsa.KeyPair

init {
val ed25519 = eddsa("ed25519")

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

/**
* Base64 url encodes the raw value
* @return Buffer
*/
fun getEncoded(): Buffer {
return Buffer.from(raw.toByteArray().base64UrlEncoded)
}

/**
* PublicKey associated with this PrivateKey
* @return KMMEdPublicKey
*/
fun publicKey(): KMMEdPublicKey {
return KMMEdPublicKey(keyPair.getPublic())
}

/**
* Cryptographically sign a given [message]
* @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,19 +1,42 @@
package io.iohk.atala.prism.apollo.utils

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

@ExperimentalJsExport
@JsExport
actual class KMMEdPublicKey(val raw: ByteArray) {
actual class KMMEdPublicKey(bytes: ByteArray) {
val raw: Buffer
private val keyPair: eddsa.KeyPair

init {
val ed25519 = eddsa("ed25519")

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

// TODO: Report a bug in elliptic, this method is not expecting a Buffer (bytes)
// Internally it expects to find an array, if not Buffer.slice.concat fails when Array.slice.concat doesn't
// Must keep this...
keyPair = ed25519.keyFromPublic(pub)
}

/**
* Base64 url encodes the raw value
* @return Buffer
*/
fun getEncoded(): Buffer {
return Buffer.from(raw.toByteArray().base64UrlEncoded.encodeToByteArray())
}

/**
* 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)
}

/**
* Base64 url encodes the raw value
* @return Buffer
*/
fun getEncoded(): Buffer {
return Buffer.from(raw.toByteArray().base64UrlEncoded)
}

/**
* PublicKey associated with this PrivateKey
* @return KMMX25519PublicKey
*/
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,22 @@
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)
}

/**
* Base64 url encodes the raw value
* @return Buffer
*/
fun getEncoded(): Buffer {
return Buffer.from(raw.toByteArray().base64UrlEncoded.encodeToByteArray())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS")
@file:JsModule("elliptic")

/* ktlint-disable */
package io.iohk.atala.prism.apollo.utils.external

import node.buffer.Buffer
Expand Down Expand Up @@ -232,7 +231,7 @@ open external class eddsa(name: String /* "ed25519" */) {
open fun keyFromPublic(pub: _eddsa_KeyPair): _eddsa_KeyPair
open fun keyFromPublic(pub: base.BasePoint): _eddsa_KeyPair
open fun keyFromSecret(secret: String): _eddsa_KeyPair
open fun keyFromSecret(secret: ByteArray): _eddsa_KeyPair
open fun keyFromSecret(secret: Buffer): _eddsa_KeyPair
open fun makeSignature(sig: _eddsa_Signature): _eddsa_Signature
open fun makeSignature(sig: String): _eddsa_Signature
open fun decodePoint(bytes: String): base.BasePoint
Expand Down Expand Up @@ -268,4 +267,3 @@ open external class eddsa(name: String /* "ed25519" */) {
set(value) = definedExternally
}
}
/* ktlint-disable */
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
@file:Suppress("ktlint")

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 +27,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 +68,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
Loading

0 comments on commit fba402c

Please sign in to comment.