Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(BAE): updating jsMain Ed25519 keys for use in TS #92

Merged
merged 3 commits into from
Oct 3, 2023
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
@@ -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
Loading