diff options
10 files changed, 364 insertions, 40 deletions
diff --git a/.idea/dictionaries/user.xml b/.idea/dictionaries/user.xml index 55989af..75c46ba 100644 --- a/.idea/dictionaries/user.xml +++ b/.idea/dictionaries/user.xml @@ -1,6 +1,8 @@ <component name="ProjectDictionaryState"> <dictionary name="user"> <words> + <w>ecdhe</w> + <w>eddsa</w> <w>nacl</w> </words> </dictionary> diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index fdc392f..b3e9cbd 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -16,5 +16,10 @@ <option name="name" value="MavenRepo" /> <option name="url" value="https://repo.maven.apache.org/maven2/" /> </remote-repository> + <remote-repository> + <option name="id" value="BintrayJCenter" /> + <option name="name" value="BintrayJCenter" /> + <option name="url" value="https://jcenter.bintray.com/" /> + </remote-repository> </component> </project>
\ No newline at end of file diff --git a/build.gradle b/build.gradle index 7e06be1..b87f250 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ plugins { } repositories { mavenCentral() + jcenter() } group 'net.taler' version '0.0.1' @@ -44,7 +45,10 @@ kotlin { androidMain { dependencies { implementation kotlin('stdlib-jdk8') - implementation 'org.bouncycastle:bcprov-jdk15on:1.65.01' + // TODO Android +// implementation "com.goterl.lazycode:lazysodium-android:4.1.1@aar" + implementation "com.goterl.lazycode:lazysodium-java:4.2.6" + implementation 'net.java.dev.jna:jna:5.5.0@aar' } } androidTest { @@ -57,6 +61,7 @@ kotlin { dependencies { implementation kotlin('stdlib-js') implementation npm('tweetnacl', '1.0.3') + implementation npm('ed2curve', '0.3.0') } } jsTest { diff --git a/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt b/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt index 509fabb..0bc933a 100644 --- a/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt +++ b/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt @@ -1,22 +1,69 @@ package net.taler.wallet.kotlin.crypto -import org.bouncycastle.jce.provider.BouncyCastleProvider -import java.security.MessageDigest -import java.security.Security +import com.goterl.lazycode.lazysodium.LazySodiumJava +import com.goterl.lazycode.lazysodium.SodiumJava +import com.goterl.lazycode.lazysodium.interfaces.Hash +import com.goterl.lazycode.lazysodium.interfaces.KeyExchange +import com.goterl.lazycode.lazysodium.interfaces.Sign +import com.goterl.lazycode.lazysodium.utils.Key -actual object CryptoFactory { - actual fun getCrypto(): Crypto = CryptoJvmImpl +internal actual object CryptoFactory { + internal actual fun getCrypto(): Crypto = CryptoJvmImpl } -object CryptoJvmImpl : Crypto { +internal object CryptoJvmImpl : Crypto { - init { - Security.addProvider(BouncyCastleProvider()) - } + private val sodium = LazySodiumJava(SodiumJava()) override fun sha512(input: ByteArray): ByteArray { - val digest = MessageDigest.getInstance("SHA-512", "BC") - return digest.digest(input) + val output = ByteArray(Hash.SHA512_BYTES) + sodium.cryptoHashSha512(output, input, input.size.toLong()) + return output + } + + override fun eddsaGetPublic(eddsaPrivateKey: ByteArray): ByteArray { + return sodium.cryptoSignSeedKeypair(eddsaPrivateKey).publicKey.asBytes + } + + override fun ecdheGetPublic(ecdhePrivateKey: ByteArray): ByteArray { + return sodium.cryptoScalarMultBase(Key.fromBytes(ecdhePrivateKey)).asBytes + } + + override fun createEddsaKeyPair(): EddsaKeyPair { + val privateKey = sodium.randomBytesBuf(KeyExchange.SEEDBYTES) + val publicKey = eddsaGetPublic(privateKey) + return EddsaKeyPair(privateKey, publicKey) + } + + override fun createEcdheKeyPair(): EcdheKeyPair { + val privateKey = sodium.randomBytesBuf(KeyExchange.SEEDBYTES) + val publicKey = ecdheGetPublic(privateKey) + return EcdheKeyPair(privateKey, publicKey) + } + + override fun eddsaSign(msg: ByteArray, eddsaPrivateKey: ByteArray): ByteArray { + val privateKey = sodium.cryptoSignSeedKeypair(eddsaPrivateKey).secretKey.asBytes + val signatureBytes = ByteArray(Sign.BYTES) + sodium.cryptoSignDetached(signatureBytes, msg, msg.size.toLong(), privateKey) + return signatureBytes + } + + override fun eddsaVerify(msg: ByteArray, sig: ByteArray, eddsaPub: ByteArray): Boolean { + return sodium.cryptoSignVerifyDetached(sig, msg, msg.size, eddsaPub) + } + + override fun keyExchangeEddsaEcdhe(eddsaPrivateKey: ByteArray, ecdhePublicKey: ByteArray): ByteArray { + val ph = sha512(eddsaPrivateKey) + val a = ph.copyOfRange(0, 32) + val x = sodium.cryptoScalarMult(Key.fromBytes(a), Key.fromBytes(ecdhePublicKey)).asBytes + return sha512(x) + } + + override fun keyExchangeEcdheEddsa(ecdhePrivateKey: ByteArray, eddsaPublicKey: ByteArray): ByteArray { + val curve25519Pub = ByteArray(KeyExchange.PUBLICKEYBYTES) + sodium.convertPublicKeyEd25519ToCurve25519(curve25519Pub, eddsaPublicKey) + val x = sodium.cryptoScalarMult(Key.fromBytes(ecdhePrivateKey), Key.fromBytes(curve25519Pub)).asBytes + return sha512(x) } } diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Crypto.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Crypto.kt index a5eeb69..3735c31 100644 --- a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Crypto.kt +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Crypto.kt @@ -1,11 +1,22 @@ package net.taler.wallet.kotlin.crypto -interface Crypto { +internal interface Crypto { fun sha512(input: ByteArray): ByteArray + fun eddsaGetPublic(eddsaPrivateKey: ByteArray): ByteArray + fun ecdheGetPublic(ecdhePrivateKey: ByteArray): ByteArray + fun createEddsaKeyPair(): EddsaKeyPair + fun createEcdheKeyPair(): EcdheKeyPair + fun eddsaSign(msg: ByteArray, eddsaPrivateKey: ByteArray): ByteArray + fun eddsaVerify(msg: ByteArray, sig: ByteArray, eddsaPub: ByteArray): Boolean + fun keyExchangeEddsaEcdhe(eddsaPrivateKey: ByteArray, ecdhePublicKey: ByteArray): ByteArray + fun keyExchangeEcdheEddsa(ecdhePrivateKey: ByteArray, eddsaPublicKey: ByteArray): ByteArray } -expect object CryptoFactory { - fun getCrypto(): Crypto +class EddsaKeyPair(val privateKey: ByteArray, val publicKey: ByteArray) +class EcdheKeyPair(val privateKey: ByteArray, val publicKey: ByteArray) + +internal expect object CryptoFactory { + internal fun getCrypto(): Crypto } private val hexArray = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/Base32CrockfordTest.kt b/src/commonTest/kotlin/net/taler/wallet/kotlin/Base32CrockfordTest.kt index daa505a..4a52a47 100644 --- a/src/commonTest/kotlin/net/taler/wallet/kotlin/Base32CrockfordTest.kt +++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/Base32CrockfordTest.kt @@ -5,6 +5,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue +@ExperimentalStdlibApi class Base32CrockfordTest { private class TestVector(val value: ByteArray, val encoding: List<String>) @@ -94,4 +95,13 @@ class Base32CrockfordTest { assertTrue(bytes contentEquals Base32Crockford.decode("FVCK")) } + @Test + fun testEncodingDecoding() { + val input = "Hello, World" + val encoded = Base32Crockford.encode(input.encodeToByteArray()) + val decoded = Base32Crockford.decode(encoded) + val output = decoded.decodeToString() + assertEquals(input, output) + } + } diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/EllipticCurveTest.kt b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/EllipticCurveTest.kt new file mode 100644 index 0000000..c74d9ff --- /dev/null +++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/EllipticCurveTest.kt @@ -0,0 +1,96 @@ +package net.taler.wallet.kotlin.crypto + +import net.taler.wallet.kotlin.Base32Crockford +import kotlin.random.Random +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class EllipticCurveTest { + + private val crypto = CryptoFactory.getCrypto() + + @Test + fun testExchangeTvgEddsaKey() { + val pri = "9TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD40" + val pub = "8GSJZ649T2PXMKZC01Y4ANNBE7MF14QVK9SQEC4E46ZHKCVG8AS0" + val pubBytes = crypto.eddsaGetPublic(Base32Crockford.decode(pri)) + assertEquals(pub, Base32Crockford.encode(pubBytes)) + } + + @Test + fun testExchangeTvgEcdheKey() { + val pri = "X4T4N0M8PVQXQEBW2BA7049KFSM7J437NSDFC6GDNM3N5J9367A0" + val pub = "M997P494MS6A95G1P0QYWW2VNPSHSX5Q6JBY5B9YMNYWP0B50X3G" + val pubBytes = crypto.ecdheGetPublic(Base32Crockford.decode(pri)) + assertEquals(pub, Base32Crockford.encode(pubBytes)) + } + + @Test + fun testCreateEddsaKeyPair() { + val pair1 = crypto.createEddsaKeyPair() + val pair2 = crypto.createEddsaKeyPair() + assertFalse(pair1.privateKey contentEquals pair2.privateKey) + assertFalse(pair1.publicKey contentEquals pair2.publicKey) + } + + @Test + fun testCreateEcdheKeyPair() { + val pair1 = crypto.createEcdheKeyPair() + val pair2 = crypto.createEcdheKeyPair() + assertFalse(pair1.privateKey contentEquals pair2.privateKey) + assertFalse(pair1.publicKey contentEquals pair2.publicKey) + } + + @Test + @ExperimentalStdlibApi + fun testEddsaSignAndVerify() { + val msg = "Hallo world!".encodeToByteArray() + val pri = "9TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD40" + val expectedSig = + "Z6H76JXPJFP3JBGSF54XBF0BVXDJ0CJBK4YT9GVR1AT916ZD57KP53YZN5G67A4YN95WGMZKQW7744483P5JDF06B6S7TMK195QGP20" + val sig = crypto.eddsaSign(msg, Base32Crockford.decode(pri)) + assertEquals(expectedSig, Base32Crockford.encode(sig)) + + val pub = crypto.eddsaGetPublic(Base32Crockford.decode(pri)) + assertTrue(crypto.eddsaVerify(msg, sig, pub)) + + val wrongSig = Random.nextBytes(64) + assertFalse(crypto.eddsaVerify(msg, wrongSig, pub)) + + val wrongPub = Random.nextBytes(32) + assertFalse(crypto.eddsaVerify(msg, sig, wrongPub)) + + val wrongMsg = Random.nextBytes(16) + assertFalse(crypto.eddsaVerify(wrongMsg, sig, pub)) + } + + @Test + fun testExchangeTvgEddsaEcdh() { + val ecdhePrivateKey = "4AFZWMSGTVCHZPQ0R81NWXDCK4N58G7SDBBE5KXE080Y50370JJG" + val ecdhePublicKey = "FXFN5GPAFTKVPWJDPVXQ87167S8T82T5ZV8CDYC0NH2AE14X0M30" + val ecdhePublicKeyBytes = crypto.ecdheGetPublic(Base32Crockford.decode(ecdhePrivateKey)) + assertEquals(ecdhePublicKey, Base32Crockford.encode(ecdhePublicKeyBytes)) + + val eddsaPrivateKey = "1KG54M8T3X8BSFSZXCR3SQBSR7Y9P53NX61M864S7TEVMJ2XVPF0" + val eddsaPublicKey = "7BXWKG6N224C57RTDV8XEAHR108HG78NMA995BE8QAT5GC1S7E80" + val eddsaPublicKeyBytes = crypto.eddsaGetPublic(Base32Crockford.decode(eddsaPrivateKey)) + assertEquals(eddsaPublicKey, Base32Crockford.encode(eddsaPublicKeyBytes)) + + val keyMaterial = + "PKZ42Z56SVK2796HG1QYBRJ6ZQM2T9QGA3JA4AAZ8G7CWK9FPX175Q9JE5P0ZAX3HWWPHAQV4DPCK10R9X3SAXHRV0WF06BHEC2ZTKR" + val keyMaterial1Bytes = crypto.keyExchangeEddsaEcdhe( + Base32Crockford.decode(eddsaPrivateKey), + Base32Crockford.decode(ecdhePublicKey) + ) + assertEquals(keyMaterial, Base32Crockford.encode(keyMaterial1Bytes)) + + val keyMaterial2Bytes = crypto.keyExchangeEcdheEddsa( + Base32Crockford.decode(ecdhePrivateKey), + Base32Crockford.decode(eddsaPublicKey) + ) + assertEquals(keyMaterial, Base32Crockford.encode(keyMaterial2Bytes)) + } + +} diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/Sha512Test.kt b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/Sha512Test.kt index f41080f..c401b73 100644 --- a/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/Sha512Test.kt +++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/Sha512Test.kt @@ -1,5 +1,6 @@ package net.taler.wallet.kotlin.crypto +import net.taler.wallet.kotlin.Base32Crockford import kotlin.test.Test import kotlin.test.assertEquals @@ -36,7 +37,8 @@ class Sha512Test { fun testAbc896bits() { assertEquals( "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909", - crypto.sha512("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu".encodeToByteArray()).toHexString() + crypto.sha512("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu".encodeToByteArray()) + .toHexString() ) } @@ -49,4 +51,12 @@ class Sha512Test { ) } + @Test + fun testExchangeTvgHashCode() { + val input = "91JPRV3F5GG4EKJN41A62V35E8" + val output = + "CW96WR74JS8T53EC8GKSGD49QKH4ZNFTZXDAWMMV5GJ1E4BM6B8GPN5NVHDJ8ZVXNCW7Q4WBYCV61HCA3PZC2YJD850DT29RHHN7ESR" + assertEquals(output, Base32Crockford.encode(crypto.sha512(Base32Crockford.decode(input)))) + } + } diff --git a/src/jsMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt b/src/jsMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt index e1786d9..e36b833 100644 --- a/src/jsMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt +++ b/src/jsMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt @@ -3,35 +3,113 @@ package net.taler.wallet.kotlin.crypto import org.khronos.webgl.Uint8Array import org.khronos.webgl.get -actual object CryptoFactory { - actual fun getCrypto(): Crypto = CryptoJsImpl +internal actual object CryptoFactory { + internal actual fun getCrypto(): Crypto = CryptoJsImpl } -object CryptoJsImpl : Crypto { +internal object CryptoJsImpl : Crypto { override fun sha512(input: ByteArray): ByteArray { return nacl.hash(input.toUint8Array()).toByteArray() } -} + override fun eddsaGetPublic(eddsaPrivateKey: ByteArray): ByteArray { + val pair = nacl.sign.keyPair.fromSeed(eddsaPrivateKey.toUint8Array()) + return pair.publicKey.toByteArray() + } -private fun Uint8Array.toByteArray(): ByteArray { - val result = ByteArray(this.length) - for (i in 0 until this.length) { - result[i] = this[i] + override fun ecdheGetPublic(ecdhePrivateKey: ByteArray): ByteArray { + return nacl.scalarMult.base(ecdhePrivateKey.toUint8Array()).toByteArray() + } + + override fun createEddsaKeyPair(): EddsaKeyPair { + val privateKey = nacl.randomBytes(32).toByteArray() + val publicKey = eddsaGetPublic(privateKey) + return EddsaKeyPair(privateKey, publicKey) + } + + override fun createEcdheKeyPair(): EcdheKeyPair { + val privateKey = nacl.randomBytes(32).toByteArray() + val publicKey = ecdheGetPublic(privateKey) + return EcdheKeyPair(privateKey, publicKey) + } + + override fun eddsaSign(msg: ByteArray, eddsaPrivateKey: ByteArray): ByteArray { + val privateKey = nacl.sign.keyPair.fromSeed(eddsaPrivateKey.toUint8Array()).secretKey + return nacl.sign.detached(msg.toUint8Array(), privateKey).toByteArray() + } + + override fun eddsaVerify(msg: ByteArray, sig: ByteArray, eddsaPub: ByteArray): Boolean { + return nacl.sign.detached.verify(msg.toUint8Array(), sig.toUint8Array(), eddsaPub.toUint8Array()) + } + + override fun keyExchangeEddsaEcdhe(eddsaPrivateKey: ByteArray, ecdhePublicKey: ByteArray): ByteArray { + val ph = sha512(eddsaPrivateKey) + val a = ph.copyOfRange(0, 32) + val x = nacl.scalarMult(a.toUint8Array(), ecdhePublicKey.toUint8Array()).toByteArray() + return sha512(x) + } + + override fun keyExchangeEcdheEddsa(ecdhePrivateKey: ByteArray, eddsaPublicKey: ByteArray): ByteArray { + val curve25519Pub = + ed2curve.convertPublicKey(eddsaPublicKey.toUint8Array()) ?: throw Error("invalid public key") + val x = nacl.scalarMult(ecdhePrivateKey.toUint8Array(), curve25519Pub).toByteArray() + return sha512(x) + } + + private fun Uint8Array.toByteArray(): ByteArray { + val result = ByteArray(this.length) + for (i in 0 until this.length) result[i] = this[i] + return result + } + + private fun ByteArray.toUint8Array(): Uint8Array { + return Uint8Array(this.toTypedArray()) } - return result -} -private fun ByteArray.toUint8Array():Uint8Array { - return Uint8Array(this.toTypedArray()) } @Suppress("ClassName") @JsModule("tweetnacl") @JsNonModule -external class nacl { +private external class nacl { + companion object { fun hash(input: Uint8Array): Uint8Array + fun scalarMult(n: Uint8Array, p: Uint8Array): Uint8Array + fun randomBytes(n: Int): Uint8Array + } + + class scalarMult { + companion object { + fun base(n: Uint8Array): Uint8Array + } + } + + class sign { + companion object { + fun detached(msg: Uint8Array, secretKey: Uint8Array): Uint8Array + } + class detached { + companion object { + fun verify(msg: Uint8Array, sig: Uint8Array, publicKey: Uint8Array): Boolean + } + } + class keyPair { + companion object { + fun fromSeed(seed: Uint8Array): KeyPair + } + } + } +} + +private class KeyPair(val publicKey: Uint8Array, @Suppress("unused") val secretKey: Uint8Array) + +@Suppress("ClassName") +@JsModule("ed2curve") +@JsNonModule +private external class ed2curve { + companion object { + fun convertPublicKey(pk: Uint8Array): Uint8Array? } } diff --git a/src/linuxMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt b/src/linuxMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt index d1ec46a..f5eca15 100644 --- a/src/linuxMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt +++ b/src/linuxMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt @@ -1,26 +1,86 @@ package net.taler.wallet.kotlin.crypto +import kotlinx.cinterop.CValuesRef +import kotlinx.cinterop.UByteVar +import kotlinx.cinterop.refTo import org.libsodium.* -import kotlinx.cinterop.* -actual object CryptoFactory { +internal actual object CryptoFactory { @ExperimentalUnsignedTypes - actual fun getCrypto(): Crypto = CryptoNativeImpl + internal actual fun getCrypto(): Crypto = CryptoNativeImpl } @ExperimentalUnsignedTypes -object CryptoNativeImpl : Crypto { +internal object CryptoNativeImpl : Crypto { override fun sha512(input: ByteArray): ByteArray { val output = ByteArray(crypto_hash_sha512_bytes().toInt()) - val cInput = if (input.isEmpty()) null else input.toUByteArray().refTo(0) - val cInputSize = input.size.toULong() - output.usePinned { pinned -> - @Suppress("UNCHECKED_CAST") - val cOutput = pinned.addressOf(0) as CValuesRef<UByteVar> - crypto_hash_sha512(cOutput, cInput, cInputSize) - } + val cInput = if (input.isEmpty()) null else input.toCValuesRef() + crypto_hash_sha512(output.toCValuesRef(), cInput, input.size.toULong()) return output } + override fun eddsaGetPublic(eddsaPrivateKey: ByteArray): ByteArray { + val publicKey = ByteArray(crypto_sign_PUBLICKEYBYTES.toInt()) + val privateKey = ByteArray(crypto_sign_SECRETKEYBYTES.toInt()) + crypto_sign_seed_keypair(publicKey.toCValuesRef(), privateKey.toCValuesRef(), eddsaPrivateKey.toCValuesRef()) + return publicKey + } + + override fun ecdheGetPublic(ecdhePrivateKey: ByteArray): ByteArray { + val publicKey = ByteArray(crypto_scalarmult_BYTES.toInt()) + crypto_scalarmult_base(publicKey.toCValuesRef(), ecdhePrivateKey.toCValuesRef()) + return publicKey + } + + override fun createEddsaKeyPair(): EddsaKeyPair { + val privateKey = ByteArray(crypto_sign_SECRETKEYBYTES.toInt()) + randombytes(privateKey.toCValuesRef(), crypto_sign_SECRETKEYBYTES.toULong()) + val publicKey = eddsaGetPublic(privateKey) + return EddsaKeyPair(privateKey, publicKey) + } + + override fun createEcdheKeyPair(): EcdheKeyPair { + val privateKey = ByteArray(crypto_scalarmult_BYTES.toInt()) + randombytes(privateKey.toCValuesRef(), crypto_scalarmult_BYTES.toULong()) + val publicKey = ecdheGetPublic(privateKey) + return EcdheKeyPair(privateKey, publicKey) + } + + override fun eddsaSign(msg: ByteArray, eddsaPrivateKey: ByteArray): ByteArray { + val publicKey = ByteArray(crypto_sign_PUBLICKEYBYTES.toInt()) + val privateKey = ByteArray(crypto_sign_SECRETKEYBYTES.toInt()) + crypto_sign_seed_keypair(publicKey.toCValuesRef(), privateKey.toCValuesRef(), eddsaPrivateKey.toCValuesRef()) + + val signatureBytes = ByteArray(crypto_sign_BYTES.toInt()) + crypto_sign_detached(signatureBytes.toCValuesRef(), null, msg.toCValuesRef(), msg.size.toULong(), privateKey.toCValuesRef()) + return signatureBytes + } + + override fun eddsaVerify(msg: ByteArray, sig: ByteArray, eddsaPub: ByteArray): Boolean { + return crypto_sign_verify_detached(sig.toCValuesRef(), msg.toCValuesRef(), msg.size.toULong(), eddsaPub.toCValuesRef()) == 0 + } + + override fun keyExchangeEddsaEcdhe(eddsaPrivateKey: ByteArray, ecdhePublicKey: ByteArray): ByteArray { + val privateKey = sha512(eddsaPrivateKey).copyOfRange(0, 32) + val sharedKey = ByteArray(crypto_scalarmult_BYTES.toInt()) + crypto_scalarmult(sharedKey.toCValuesRef(), privateKey.toCValuesRef(), ecdhePublicKey.toCValuesRef()) + return sha512(sharedKey) + } + + override fun keyExchangeEcdheEddsa(ecdhePrivateKey: ByteArray, eddsaPublicKey: ByteArray): ByteArray { + val curve25519Pub = ByteArray(crypto_scalarmult_curve25519_BYTES.toInt()) + val cCurve25519Pub = curve25519Pub.toCValuesRef() + crypto_sign_ed25519_pk_to_curve25519(cCurve25519Pub, eddsaPublicKey.toCValuesRef()) + + val sharedKey = ByteArray(crypto_scalarmult_BYTES.toInt()) + crypto_scalarmult(sharedKey.toCValuesRef(), ecdhePrivateKey.toCValuesRef(), cCurve25519Pub) + return sha512(sharedKey) + } + + private fun ByteArray.toCValuesRef(): CValuesRef<UByteVar> { + @Suppress("UNCHECKED_CAST") + return this.refTo(0) as CValuesRef<UByteVar> + } + } |