Skip to content

Commit

Permalink
feat(ED25519): add ED25519 jsMain implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
curtis-h committed Jul 21, 2023
1 parent c18c0eb commit 4234c95
Show file tree
Hide file tree
Showing 21 changed files with 242 additions and 30 deletions.
2 changes: 2 additions & 0 deletions base-asymmetric-encryption/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ kotlin {
}
val jsMain by getting {
dependencies {
implementation(project(":base64"))

implementation(npm("elliptic", "6.5.4"))
implementation(npm("@types/elliptic", "6.4.14"))
implementation(npm("@noble/secp256k1", "2.0.0"))
Expand Down
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")
}
}
Loading

0 comments on commit 4234c95

Please sign in to comment.