Skip to content

Commit

Permalink
feat(ED25519): ED25519 jsMain implementation (#76)
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 87e580a commit b70809a
Show file tree
Hide file tree
Showing 20 changed files with 240 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import java.security.KeyPairGenerator

actual class KMMEdKeyPair actual constructor(actual val privateKey: KMMEdPrivateKey, actual val publicKey: KMMEdPublicKey) {
actual companion object : Ed25519KeyPairGeneration {
override fun generateEd25519KeyPair(): KMMEdKeyPair {
override fun generateKeyPair(): KMMEdKeyPair {
val provider = BouncyCastleProvider()
val generator = KeyPairGenerator.getInstance("Ed25519", provider)
val javaKeyPair: KeyPair = generator.generateKeyPair()
Expand All @@ -16,4 +16,12 @@ actual class KMMEdKeyPair actual constructor(actual val privateKey: KMMEdPrivate
)
}
}

actual fun sign(message: ByteArray): ByteArray {
throw NotImplementedError("Not implemented")
}

actual fun verify(message: ByteArray, sig: ByteArray): Boolean {
throw NotImplementedError("Not implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ package io.iohk.atala.prism.apollo.utils

import java.security.PrivateKey

actual class KMMEdPrivateKey(val nativeValue: PrivateKey)
actual class KMMEdPrivateKey(val nativeValue: PrivateKey) {
actual fun sign(message: ByteArray): ByteArray {
throw NotImplementedError("Not implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ package io.iohk.atala.prism.apollo.utils

import java.security.PublicKey

actual class KMMEdPublicKey(val nativeValue: PublicKey)
actual class KMMEdPublicKey(val nativeValue: PublicKey) {
actual fun verify(message: ByteArray, sig: ByteArray): Boolean {
throw NotImplementedError("Not implemented")
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package io.iohk.atala.prism.apollo.utils

import kotlin.js.ExperimentalJsExport
import kotlin.js.JsExport

@ExperimentalJsExport
@JsExport
interface Ed25519KeyPairGeneration {
fun generateEd25519KeyPair(): KMMEdKeyPair
fun generateKeyPair(): KMMEdKeyPair
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ expect class KMMEdKeyPair(privateKey: KMMEdPrivateKey, publicKey: KMMEdPublicKey
val publicKey: KMMEdPublicKey

companion object : Ed25519KeyPairGeneration

fun sign(message: ByteArray): ByteArray

fun verify(message: ByteArray, sig: ByteArray): Boolean
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package io.iohk.atala.prism.apollo.utils

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

public expect class KMMEdPublicKey
public expect class KMMEdPublicKey {
fun verify(message: ByteArray, sig: ByteArray): Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.iohk.atala.prism.apollo.utils

import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

// un-ignore when JVM implementation is done and remove jsTest version of these tests
@Ignore
class KMMEdKeyPairTestsIgnored {
@Test
fun testGenerateKeyPair() {
val keyPair = KMMEdKeyPair.generateKeyPair()

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

@Test
fun testSignMessage() {
val keyPair = KMMEdKeyPair.generateKeyPair()
val message = "testing".encodeToByteArray()
val sig = keyPair.sign(message)

assertNotNull(sig)
}

@Test
fun testVerifyMessage() {
val keyPair = KMMEdKeyPair.generateKeyPair()
val msgHash = "testing".encodeToByteArray()
val sig = keyPair.sign(msgHash)
val verified = keyPair.verify(msgHash, sig)

assertTrue(verified)
}

@Test
fun testVerifyWithAnotherKeyPairFails() {
val keyPair = KMMEdKeyPair.generateKeyPair()
val msgHash = "testing".encodeToByteArray()
val sig = keyPair.sign(msgHash)

val wrongKeyPair = KMMEdKeyPair.generateKeyPair()
val verified = wrongKeyPair.verify(msgHash, sig)

assertFalse(verified)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,19 @@ actual class KMMEdKeyPair actual constructor(
) {

actual companion object : Ed25519KeyPairGeneration {
override fun generateEd25519KeyPair(): KMMEdKeyPair {
override fun generateKeyPair(): KMMEdKeyPair {
val privateKey = KMMEdPrivateKey()
return KMMEdKeyPair(privateKey, privateKey.publicKey())
}
}

@Throws(RuntimeException::class)
actual fun sign(message: ByteArray): ByteArray {
return privateKey.sign(message)
}

@Throws(RuntimeException::class)
actual fun verify(message: ByteArray, sig: ByteArray): Boolean {
return publicKey.verify(message, sig)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import platform.Foundation.NSError
public actual class KMMEdPrivateKey(val raw: ByteArray = Ed25519.createPrivateKey().toByteArray()) {

@Throws(RuntimeException::class)
public fun sign(data: ByteArray): ByteArray {
actual fun sign(message: ByteArray): ByteArray {
memScoped {
val errorRef = alloc<ObjCObjectVar<NSError?>>()
val result = Ed25519.signWithPrivateKey(raw.toNSData(), data.toNSData(), errorRef.ptr)
val result = Ed25519.signWithPrivateKey(raw.toNSData(), message.toNSData(), errorRef.ptr)
errorRef.value?.let { throw RuntimeException(it.localizedDescription()) }
return result?.toByteArray() ?: throw RuntimeException("Null result")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import platform.Foundation.NSError
public actual class KMMEdPublicKey(val raw: ByteArray) {

@Throws(RuntimeException::class)
public fun verify(data: ByteArray, sig: ByteArray): Boolean {
actual fun verify(message: ByteArray, sig: ByteArray): Boolean {
memScoped {
val errorRef = alloc<ObjCObjectVar<NSError?>>()
val result = Ed25519.verifyWithPublicKey(raw.toNSData(), sig.toNSData(), data.toNSData(), errorRef.ptr)
val result = Ed25519.verifyWithPublicKey(raw.toNSData(), sig.toNSData(), message.toNSData(), errorRef.ptr)
errorRef.value?.let { throw RuntimeException(it.localizedDescription()) }
return result?.boolValue ?: throw RuntimeException("Null result")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
package io.iohk.atala.prism.apollo.utils

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

@ExperimentalJsExport
@JsExport
actual class KMMEdKeyPair actual constructor(
actual val privateKey: KMMEdPrivateKey,
actual val publicKey: KMMEdPublicKey
) {
init {
// TODO: we will use this lib for JS https://github.com/indutny/elliptic
throw NotImplementedError("Ed25519 is yet to be implemented in JS")
actual fun sign(message: ByteArray): ByteArray {
return privateKey.sign(message)
}

actual fun verify(message: ByteArray, sig: ByteArray): Boolean {
return publicKey.verify(message, sig)
}

actual companion object : Ed25519KeyPairGeneration {
override fun generateEd25519KeyPair(): KMMEdKeyPair {
// TODO: we will use this lib for JS https://github.com/indutny/elliptic
throw NotImplementedError("Ed25519 is yet to be implemented in JS")
override fun generateKeyPair(): KMMEdKeyPair {
val ed25519 = eddsa("ed25519")
val rnd = rand(32)
val secret = Buffer.from(rnd).toByteArray()
val keypair = ed25519.keyFromSecret(secret)
val public = keypair.getPublic()

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

actual class KMMEdPrivateKey {
import io.iohk.atala.prism.apollo.utils.external.eddsa
import node.buffer.Buffer

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

init {
// TODO: we will use this lib for JS https://github.com/indutny/elliptic
throw NotImplementedError("Ed25519 is yet to be implemented in JS")
val ed25519 = eddsa("ed25519")

keyPair = ed25519.keyFromSecret(raw)
}

actual fun sign(message: ByteArray): ByteArray {
val sig = keyPair.sign(Buffer.from(message))

return sig.toHex().encodeToByteArray()
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
package io.iohk.atala.prism.apollo.utils

actual class KMMEdPublicKey {
import io.iohk.atala.prism.apollo.utils.external.eddsa
import node.buffer.Buffer

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

init {
// TODO: we will use this lib for JS https://github.com/indutny/elliptic
throw NotImplementedError("Ed25519 is yet to be implemented in JS")
val ed25519 = eddsa("ed25519")

keyPair = ed25519.keyFromPublic(raw)
}

actual fun verify(message: ByteArray, sig: ByteArray): Boolean {
return keyPair.verify(Buffer.from(message), sig.decodeToString())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
/* ktlint-disable */
package io.iohk.atala.prism.apollo.utils.external

import node.buffer.Buffer
import org.khronos.webgl.Uint8Array
import io.iohk.atala.prism.apollo.utils.external.eddsa.KeyPair as _eddsa_KeyPair
import io.iohk.atala.prism.apollo.utils.external.eddsa.KeyPairOptions as _eddsa_KeyPairOptions
import io.iohk.atala.prism.apollo.utils.external.eddsa.Signature as _eddsa_Signature
import org.khronos.webgl.Uint8Array

external var utils: Any

external var rand: Any
external fun rand(len: Number): Uint8Array

external var version: Number

Expand Down Expand Up @@ -224,9 +225,11 @@ open external class eddsa(name: String /* "ed25519" */) {
open fun verify(message: String, sig: _eddsa_Signature, pub: Any /* String | Buffer | eddsa.Point | eddsa.KeyPair */): Boolean
open fun hashInt(): BN
open fun keyFromPublic(pub: String): _eddsa_KeyPair
open fun keyFromPublic(pub: Any): _eddsa_KeyPair
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 makeSignature(sig: _eddsa_Signature): _eddsa_Signature
open fun makeSignature(sig: String): _eddsa_Signature
open fun decodePoint(bytes: String): base.BasePoint
Expand All @@ -239,14 +242,17 @@ open external class eddsa(name: String /* "ed25519" */) {
open class Signature {
constructor(eddsa: eddsa, sig: _eddsa_Signature)
constructor(eddsa: eddsa, sig: String)
open fun toBytes(): Buffer
open fun toHex(): String
}
open class KeyPair(eddsa: eddsa, params: _eddsa_KeyPairOptions) {
open fun sign(message: String): _eddsa_Signature
open fun sign(message: Buffer): _eddsa_Signature
open fun verify(message: String, sig: _eddsa_Signature): Boolean
open fun verify(message: String, sig: String): Boolean
open fun getSecret(enc: String /* "hex" */): String
open fun getPublic(enc: String /* "hex" */): String
open fun verify(message: Buffer, sig: Any): Boolean
open fun getSecret(enc: String? = definedExternally /* "hex" */): String
open fun getPublic(enc: String? = definedExternally /* "hex" */): ByteArray

companion object {
fun fromPublic(eddsa: eddsa, pub: String): _eddsa_KeyPair
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.iohk.atala.prism.apollo.utils

import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

class KMMEdKeyPairTests {
@Test
fun testGenerateKeyPair() {
val keyPair = KMMEdKeyPair.generateKeyPair()

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

@Test
fun testSignMessage() {
val keyPair = KMMEdKeyPair.generateKeyPair()
val message = "testing".encodeToByteArray()
val sig = keyPair.sign(message)

assertNotNull(sig)
}

@Test
fun testVerifyMessage() {
val keyPair = KMMEdKeyPair.generateKeyPair()
val msgHash = "testing".encodeToByteArray()
val sig = keyPair.sign(msgHash)
val verified = keyPair.verify(msgHash, sig)

assertTrue(verified)
}

@Test
fun testVerifyWithAnotherKeyPairFails() {
val keyPair = KMMEdKeyPair.generateKeyPair()
val msgHash = "testing".encodeToByteArray()
val sig = keyPair.sign(msgHash)

val wrongKeyPair = KMMEdKeyPair.generateKeyPair()
val verified = wrongKeyPair.verify(msgHash, sig)

assertFalse(verified)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import java.security.KeyPairGenerator

actual class KMMEdKeyPair actual constructor(actual val privateKey: KMMEdPrivateKey, actual val publicKey: KMMEdPublicKey) {
actual companion object : Ed25519KeyPairGeneration {
override fun generateEd25519KeyPair(): KMMEdKeyPair {
override fun generateKeyPair(): KMMEdKeyPair {
val provider = BouncyCastleProvider()
val generator = KeyPairGenerator.getInstance("Ed25519", provider)
val javaKeyPair: KeyPair = generator.generateKeyPair()
Expand All @@ -16,4 +16,12 @@ actual class KMMEdKeyPair actual constructor(actual val privateKey: KMMEdPrivate
)
}
}

actual fun sign(message: ByteArray): ByteArray {
TODO("Not yet implemented")
}

actual fun verify(message: ByteArray, sig: ByteArray): Boolean {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ package io.iohk.atala.prism.apollo.utils

import java.security.PrivateKey

actual class KMMEdPrivateKey(val nativeValue: PrivateKey)
actual class KMMEdPrivateKey(val nativeValue: PrivateKey) {
actual fun sign(message: ByteArray): ByteArray {
TODO("Not yet implemented")
}
}
Loading

0 comments on commit b70809a

Please sign in to comment.