diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..a469d63b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +# EditorConfig is awesome: https://editorconfig.org + +# top-most EditorConfig file +root = true + +[*.{kt,kts}] +indent_size = 4 +indent_style = space +max_line_length = 120 \ No newline at end of file diff --git a/multiplatform-crypto-libsodium-bindings/build.gradle.kts b/multiplatform-crypto-libsodium-bindings/build.gradle.kts index 4fbeb9a4..2b39ae88 100644 --- a/multiplatform-crypto-libsodium-bindings/build.gradle.kts +++ b/multiplatform-crypto-libsodium-bindings/build.gradle.kts @@ -21,7 +21,6 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.jetbrains.dokka.Platform plugins { kotlin(PluginsDeps.multiplatform) @@ -672,15 +671,16 @@ tasks { } - if (getHostOsName() == "linux" && getHostArchitecture() == "x86-64") { - val jvmTest by getting(Test::class) { - testLogging { - events("PASSED", "FAILED", "SKIPPED") - exceptionFormat = TestExceptionFormat.FULL - showStandardStreams = true - showStackTraces = true - } + val jvmTest by getting(Test::class) { + testLogging { + events("PASSED", "FAILED", "SKIPPED") + exceptionFormat = TestExceptionFormat.FULL + showStandardStreams = true + showStackTraces = true } + } + + if (getHostOsName() == "linux" && getHostArchitecture() == "x86-64") { val linuxX64Test by getting(KotlinNativeTest::class) { diff --git a/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/ed25519/Ed25519.kt b/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/ed25519/Ed25519.kt new file mode 100644 index 00000000..27bfca2d --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/ed25519/Ed25519.kt @@ -0,0 +1,150 @@ +package com.ionspin.kotlin.crypto.ed25519 + +import com.ionspin.kotlin.crypto.util.LibsodiumUtil + +/** + * Created by Johannes Leupold + * johannes.leupold@kobil.com + * on 12-Aug-2024 + */ + +const val crypto_core_ed25519_BYTES = 32 +const val crypto_core_ed25519_UNIFORMBYTES = 32 +const val crypto_core_ed25519_HASHBYTES = 64 +const val crypto_core_ed25519_SCALARBYTES = 32 +const val crypto_core_ed25519_NONREDUCEDSCALARBYTES = 64 + +const val crypto_scalarmult_ed25519_BYTES = 32U +const val crypto_scalarmult_ed25519_SCALARBYTES = 32U + +expect object Ed25519LowLevel { + fun isValidPoint(encoded: UByteArray): Boolean + fun addPoints(p: UByteArray, q: UByteArray): UByteArray + fun subtractPoints(p: UByteArray, q: UByteArray): UByteArray + fun pointFromUniform(uniform: UByteArray): UByteArray + fun randomPoint(): UByteArray + fun randomScalar(): UByteArray + fun invertScalar(scalar: UByteArray): UByteArray + fun negateScalar(scalar: UByteArray): UByteArray + fun complementScalar(scalar: UByteArray): UByteArray + fun addScalars(x: UByteArray, y: UByteArray): UByteArray + fun subtractScalars(x: UByteArray, y: UByteArray): UByteArray + fun multiplyScalars(x: UByteArray, y: UByteArray): UByteArray + fun reduceScalar(scalar: UByteArray): UByteArray + fun scalarMultiplication(n: UByteArray, p: UByteArray): UByteArray + fun scalarMultiplicationNoClamp(n: UByteArray, p: UByteArray): UByteArray + fun scalarMultiplicationBase(n: UByteArray): UByteArray + fun scalarMultiplicationBaseNoClamp(n: UByteArray): UByteArray +} + +object Ed25519 { + data class Point(val encoded: UByteArray) { + companion object { + val IDENTITY: Point = Point(UByteArray(crypto_core_ed25519_BYTES)) + val BASE: Point = multiplyBaseNoClamp(Scalar.ONE) + + fun fromUniform(uniform: UByteArray): Point = Point(Ed25519LowLevel.pointFromUniform(uniform)) + + fun random(): Point = Point(Ed25519LowLevel.randomPoint()) + + fun multiplyBase(n: Scalar): Point = Point(Ed25519LowLevel.scalarMultiplicationBase(n.encoded)) + + fun multiplyBaseNoClamp(n: Scalar): Point = + Point(Ed25519LowLevel.scalarMultiplicationBaseNoClamp(n.encoded)) + + fun fromHex(hex: String): Point = Point(LibsodiumUtil.fromHex(hex)) + } + + val isValid: Boolean + get() = Ed25519LowLevel.isValidPoint(encoded) + + operator fun plus(q: Point): Point = Point(Ed25519LowLevel.addPoints(this.encoded, q.encoded)) + operator fun minus(q: Point): Point = Point(Ed25519LowLevel.subtractPoints(this.encoded, q.encoded)) + + operator fun times(n: Scalar): Point = Point(Ed25519LowLevel.scalarMultiplication(n.encoded, this.encoded)) + fun times(n: Scalar, clamp: Boolean): Point = + if (clamp) times(n) else Point(Ed25519LowLevel.scalarMultiplicationNoClamp(n.encoded, this.encoded)) + + fun toHex(): String = LibsodiumUtil.toHex(encoded) + + override fun equals(other: Any?): Boolean = (other as? Point)?.encoded?.contentEquals(encoded) == true + override fun hashCode(): Int = encoded.contentHashCode() + } + + data class Scalar(val encoded: UByteArray) { + companion object { + val ZERO = fromUInt(0U) + val ONE = fromUInt(1U) + val TWO = fromUInt(2U) + + fun random(): Scalar = Scalar(Ed25519LowLevel.randomScalar()) + + fun fromUInt(i: UInt): Scalar = fromULong(i.toULong()) + + fun fromULong(l: ULong): Scalar { + val encoded = UByteArray(crypto_core_ed25519_SCALARBYTES) + var rem = l + + for (i in 0..7) { + encoded[i] = (rem and 0xffU).toUByte() + rem = rem shr 8 + } + + return Scalar(encoded) + } + + fun fromHex(hex: String): Scalar { + require(hex.length <= 2 * crypto_core_ed25519_NONREDUCEDSCALARBYTES) { + "Scalars must be at most $crypto_core_ed25519_NONREDUCEDSCALARBYTES bytes long" + } + + if (hex.length > 2 * crypto_core_ed25519_SCALARBYTES) { + val encoded = LibsodiumUtil.fromHex(hex.padEnd(2 * crypto_core_ed25519_NONREDUCEDSCALARBYTES, '0')) + // Scalars are encoded in little-endian order, so the end can be padded with zeroes up to the size of a + // non-reduced scalar. After decoding, it is reduced, to obtain a scalar in the canonical range + return Scalar(Ed25519LowLevel.reduceScalar(encoded)) + } else { + val encoded = LibsodiumUtil.fromHex(hex.padEnd(2 * crypto_core_ed25519_SCALARBYTES, '0')) + // Scalars are encoded in little-endian order, so the end can be padded with zeroes up to the size of a + // scalar. + return Scalar(encoded) + } + } + } + + operator fun plus(y: Scalar): Scalar = Scalar(Ed25519LowLevel.addScalars(this.encoded, y.encoded)) + operator fun plus(y: UInt): Scalar = this + fromUInt(y) + operator fun plus(y: ULong): Scalar = this + fromULong(y) + + operator fun minus(y: Scalar): Scalar = Scalar(Ed25519LowLevel.subtractScalars(this.encoded, y.encoded)) + operator fun minus(y: UInt): Scalar = this - fromUInt(y) + operator fun minus(y: ULong): Scalar = this - fromULong(y) + + operator fun times(y: Scalar): Scalar = Scalar(Ed25519LowLevel.multiplyScalars(this.encoded, y.encoded)) + operator fun times(y: UInt): Scalar = this * fromUInt(y) + operator fun times(y: ULong): Scalar = this * fromULong(y) + + operator fun div(y: Scalar): Scalar = times(y.invert()) + operator fun div(y: UInt): Scalar = this / fromUInt(y) + operator fun div(y: ULong): Scalar = this / fromULong(y) + + operator fun unaryMinus(): Scalar = Scalar(Ed25519LowLevel.negateScalar(this.encoded)) + + operator fun times(p: Point): Point = p.times(this) + fun times(p: Point, clamp: Boolean): Point = + if (clamp) p.times(this) else p.times(this, clamp) + + fun reduce(): Scalar = Scalar(Ed25519LowLevel.reduceScalar(this.encoded)) + fun invert(): Scalar = Scalar(Ed25519LowLevel.invertScalar(this.encoded)) + fun complement(): Scalar = Scalar(Ed25519LowLevel.complementScalar(this.encoded)) + + fun multiplyWithBase(): Point = Point.multiplyBase(this) + + fun multiplyWithBaseNoClamp(): Point = Point.multiplyBaseNoClamp(this) + + fun toHex(): String = LibsodiumUtil.toHex(encoded) + + override fun equals(other: Any?): Boolean = (other as? Scalar)?.encoded?.contentEquals(encoded) == true + override fun hashCode(): Int = encoded.contentHashCode() + } +} \ No newline at end of file diff --git a/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/ristretto255/Ristretto255.kt b/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/ristretto255/Ristretto255.kt new file mode 100644 index 00000000..faa1c9f0 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/ristretto255/Ristretto255.kt @@ -0,0 +1,141 @@ +package com.ionspin.kotlin.crypto.ristretto255 + +import com.ionspin.kotlin.crypto.util.LibsodiumUtil + +/** + * Created by Johannes Leupold + * johannes.leupold@kobil.com + * on 12-Aug-2024 + */ + +const val crypto_core_ristretto255_BYTES = 32 +const val crypto_core_ristretto255_HASHBYTES = 64 +const val crypto_core_ristretto255_SCALARBYTES = 32 +const val crypto_core_ristretto255_NONREDUCEDSCALARBYTES = 64 + +const val crypto_scalarmult_ristretto255_BYTES = 32U +const val crypto_scalarmult_ristretto255_SCALARBYTES = 32U + +expect object Ristretto255LowLevel { + fun isValidPoint(encoded: UByteArray): Boolean + fun addPoints(p: UByteArray, q: UByteArray): UByteArray + fun subtractPoints(p: UByteArray, q: UByteArray): UByteArray + fun pointFromHash(hash: UByteArray): UByteArray + fun randomPoint(): UByteArray + fun randomScalar(): UByteArray + fun invertScalar(scalar: UByteArray): UByteArray + fun negateScalar(scalar: UByteArray): UByteArray + fun complementScalar(scalar: UByteArray): UByteArray + fun addScalars(x: UByteArray, y: UByteArray): UByteArray + fun subtractScalars(x: UByteArray, y: UByteArray): UByteArray + fun multiplyScalars(x: UByteArray, y: UByteArray): UByteArray + fun reduceScalar(scalar: UByteArray): UByteArray + fun scalarMultiplication(n: UByteArray, p: UByteArray): UByteArray + fun scalarMultiplicationBase(n: UByteArray): UByteArray +} + +object Ristretto255 { + data class Point(val encoded: UByteArray) { + companion object { + val IDENTITY: Point = Point(UByteArray(crypto_core_ristretto255_BYTES)) + val BASE: Point = multiplyBase(Scalar.ONE) + + fun fromHash(hash: UByteArray): Point = Point(Ristretto255LowLevel.pointFromHash(hash)) + + fun random(): Point = Point(Ristretto255LowLevel.randomPoint()) + + fun multiplyBase(n: Scalar): Point = Point(Ristretto255LowLevel.scalarMultiplicationBase(n.encoded)) + + fun fromHex(hex: String): Point = Point(LibsodiumUtil.fromHex(hex)) + } + + val isValid: Boolean + get() = Ristretto255LowLevel.isValidPoint(this.encoded) + + operator fun plus(q: Point): Point = Point(Ristretto255LowLevel.addPoints(this.encoded, q.encoded)) + operator fun minus(q: Point): Point = Point(Ristretto255LowLevel.subtractPoints(this.encoded, q.encoded)) + + operator fun times(n: Scalar): Point = Point(Ristretto255LowLevel.scalarMultiplication(n.encoded, this.encoded)) + + fun toHex(): String = LibsodiumUtil.toHex(encoded) + + override fun equals(other: Any?): Boolean = (other as? Point)?.encoded?.contentEquals(encoded) == true + override fun hashCode(): Int = encoded.contentHashCode() + } + + data class Scalar(val encoded: UByteArray) { + companion object { + val ZERO = fromUInt(0U) + val ONE = fromUInt(1U) + val TWO = fromUInt(2U) + + fun random(): Scalar = Scalar(Ristretto255LowLevel.randomScalar()) + + fun fromUInt(i: UInt): Scalar = fromULong(i.toULong()) + + fun fromULong(l: ULong): Scalar { + val encoded = UByteArray(crypto_core_ristretto255_SCALARBYTES) + var rem = l + + for (i in 0..7) { + encoded[i] = (rem and 0xffU).toUByte() + rem = rem shr 8 + } + + return Scalar(encoded) + } + + fun fromHex(hex: String): Scalar { + require(hex.length <= 2 * crypto_core_ristretto255_NONREDUCEDSCALARBYTES) { + "Scalars must be at most $crypto_core_ristretto255_NONREDUCEDSCALARBYTES bytes long" + } + + if (hex.length > 2 * crypto_core_ristretto255_SCALARBYTES) { + val encoded = + LibsodiumUtil.fromHex(hex.padEnd(2 * crypto_core_ristretto255_NONREDUCEDSCALARBYTES, '0')) + // Scalars are encoded in little-endian order, so the end can be padded with zeroes up to the size of a + // non-reduced scalar. After decoding, it is reduced, to obtain a scalar in the canonical range + return Scalar(Ristretto255LowLevel.reduceScalar(encoded)) + } else { + val encoded = LibsodiumUtil.fromHex(hex.padEnd(2 * crypto_core_ristretto255_SCALARBYTES, '0')) + // Scalars are encoded in little-endian order, so the end can be padded with zeroes up to the size of a + // scalar. + return Scalar(encoded) + } + } + } + + operator fun plus(y: Scalar): Scalar = Scalar(Ristretto255LowLevel.addScalars(this.encoded, y.encoded)) + operator fun plus(y: UInt): Scalar = this + fromUInt(y) + operator fun plus(y: ULong): Scalar = this + fromULong(y) + + operator fun minus(y: Scalar): Scalar = Scalar(Ristretto255LowLevel.subtractScalars(this.encoded, y.encoded)) + operator fun minus(y: UInt): Scalar = this - fromUInt(y) + operator fun minus(y: ULong): Scalar = this - fromULong(y) + + operator fun times(y: Scalar): Scalar = Scalar(Ristretto255LowLevel.multiplyScalars(this.encoded, y.encoded)) + operator fun times(y: UInt): Scalar = this * fromUInt(y) + operator fun times(y: ULong): Scalar = this * fromULong(y) + + operator fun div(y: Scalar): Scalar = + Scalar(Ristretto255LowLevel.multiplyScalars(this.encoded, y.invert().encoded)) + + operator fun div(y: UInt): Scalar = this / fromUInt(y) + operator fun div(y: ULong): Scalar = this / fromULong(y) + + operator fun unaryMinus(): Scalar = Scalar(Ristretto255LowLevel.negateScalar(this.encoded)) + + operator fun times(p: Point): Point = p.times(this) + + fun reduce(): Scalar = Scalar(Ristretto255LowLevel.reduceScalar(this.encoded)) + fun invert(): Scalar = Scalar(Ristretto255LowLevel.invertScalar(this.encoded)) + fun complement(): Scalar = Scalar(Ristretto255LowLevel.complementScalar(this.encoded)) + + fun multiplyWithBase(): Point = Point.multiplyBase(this) + + fun toHex(): String = LibsodiumUtil.toHex(encoded) + + override fun equals(other: Any?): Boolean = (other as? Scalar)?.encoded?.contentEquals(encoded) == true + override fun hashCode(): Int = encoded.contentHashCode() + } +} \ No newline at end of file diff --git a/multiplatform-crypto-libsodium-bindings/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ed25519/Ed25519Test.kt b/multiplatform-crypto-libsodium-bindings/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ed25519/Ed25519Test.kt new file mode 100644 index 00000000..1eadcd3a --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ed25519/Ed25519Test.kt @@ -0,0 +1,243 @@ +package com.ionspin.kotlin.crypto.ed25519 + +import com.ionspin.kotlin.crypto.LibsodiumInitializer +import com.ionspin.kotlin.crypto.util.LibsodiumUtil +import com.ionspin.kotlin.crypto.util.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotEquals +import kotlin.test.assertTrue + +class Ed25519Test { + // Test vectors from https://github.com/jedisct1/libsodium/blob/master/test/default/core_ed25519.c + val badEncodings = arrayOf( + "0000000000000000000000000000000000000000000000000000000000000000", + "0100000000000000000000000000000000000000000000000000000000000000", + "0200000000000000000000000000000000000000000000000000000000000000", + // Non canonical encodings + "f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "f5ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + ) + + // Test vectors generated with sodium.js + private val fromUniformTestVectors = arrayOf( + "d5d31a04bf9cd6b4f3f014ab57f95d439a0bd741e71f1ecb580143235545255e" to "cb9fff40134270e80e0dcfcdc66aa4ebf02cd27c9d9d26adfdf78d0012ad1b62", + "9d2e8fc82097672be7b3eb9b9ac74d0cd22087ce04a202a51e88702dceab88a1" to "6b1f76c95d2a201a25b77e73de875637e250acb8e22c44230b2c21bb5a45bb15", + "7863e96b9a73ffb45df22e2692f395d24b5d7acf745c5fa536818fd00e3ba6f6" to "43be765b38f32d815203e1657c261545366f15b24af2a97694b9320b4a36c407", + "1dfd309d25f6a2c6e0358cddf8dcf8c0fd018ccc7eb799d71fa829640cb5adb3" to "4c6b7015631f4063d85f3b195c7dfcb699a242b3449dc9b4abce8948df88a28e", + "40bc69ec71804975dfcbd90b18ca5d9d0117b2e15cacf61e21960b33742a9d55" to "722b608070036ad2e82927338c5edca18f2d0e6f8ed393321ed3704269af1f29", + "06c705d68c6224de01437208d7af2b3d933c1822abbe8f551b584cba073dc645" to "bb713b72bf705cc5a3daf299b787d28d47fdb39dc98a13082657b4137081624f", + "e4307d89b2e904063a6a16c9cf09b4225e0b5f4dd2367f08b11bf7787fa626d3" to "f10dea3347ab6792fac62ee6825dad3e4915f15287506db8067ecdbf00f0f30a", + "6f61fe548ff2cd7bc64d1d3cf4a707a8efba8247e906042d76e98b730f5d1d4d" to "5c43c14cb548b09ac8180c627bcf76bd7720aca21ef72cc13c5584e34ec23ff6", + ) + + // Test vectors generated with sodium.js + private val basePointSmallMultiplesNoClamp = arrayOf( + // This is the basepoint + "5866666666666666666666666666666666666666666666666666666666666666", + // These are small multiples of the basepoint + "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022", + "d4b4f5784868c3020403246717ec169ff79e26608ea126a1ab69ee77d1b16712", + "2f1132ca61ab38dff00f2fea3228f24c6c71d58085b80e47e19515cb27e8d047", + "edc876d6831fd2105d0b4389ca2e283166469289146e2ce06faefe98b22548df", + "f47e49f9d07ad2c1606b4d94067c41f9777d4ffda709b71da1d88628fce34d85", + "b862409fb5c4c4123df2abf7462b88f041ad36dd6864ce872fd5472be363c5b1", + "b4b937fca95b2f1e93e41e62fc3c78818ff38a66096fad6e7973e5c90006d321", + "c0f1225584444ec730446e231390781ffdd2f256e9fcbeb2f40dddc2c2233d7f", + "2c7be86ab07488ba43e8e03d85a67625cfbf98c8544de4c877241b7aaafc7fe3", + "1337036ac32d8f30d4589c3c1c595812ce0fff40e37c6f5a97ab213f318290ad", + "f9e42d2edc81d23367967352b47e4856b82578634e6c1de72280ce8b60ce70c0", + "801f40eaaee1ef8723279a28b2cf4037b889dad222604678748b53ed0db0db92", + "39289c8998fd69835c26b619e89848a7bf02b7cb7ad1ba1581cbc4506f2550ce", + "df5c2eadc44c6d94a19a9aa118afe5ac3193d26401f76251f522ff042dfbcb92", + "eb2767c137ab7ad8279c078eff116ab0786ead3a2e0f989f72c37f82f2969670", + ) + + // Test vectors generated with sodium.js + // Because of clamping, the lowest three bits of the scalar are cleared to make it a multiple of the cofactor (8) + // This makes two scalars yield the same result if they only differ in the lowest three bits. Because of this, for + // these test vectors, the scalars used to obtain them are set to s = i * 4 + 1 where i is the index + val basePointSmallMultiplesClamped = arrayOf( + "693e47972caf527c7883ad1b39822f026f47db2ab0e1919955b8993aa04411d1", + "693e47972caf527c7883ad1b39822f026f47db2ab0e1919955b8993aa04411d1", + "c9877dfd1ccda6393a15aed8aba06798456798355f2a9da4e182fecd40290157", + "c9877dfd1ccda6393a15aed8aba06798456798355f2a9da4e182fecd40290157", + "33598cc739b5da481888220cc8d584ba6c385a4c489cb6305446fd78d591bd96", + "33598cc739b5da481888220cc8d584ba6c385a4c489cb6305446fd78d591bd96", + "b46a44945eaff85c6de56812f8b035f01f6680a6f37f74bc6aa992bd0ef2d32a", + "b46a44945eaff85c6de56812f8b035f01f6680a6f37f74bc6aa992bd0ef2d32a", + "31b532ff5943a5c73690714ceb6414b99d50b0daee2b2d994ea78adf7ac28f4f", + "31b532ff5943a5c73690714ceb6414b99d50b0daee2b2d994ea78adf7ac28f4f", + "140fcdae065d38753b1b563c61ab588da04e7b822a5575483d123fb96f30868d", + "140fcdae065d38753b1b563c61ab588da04e7b822a5575483d123fb96f30868d", + "cb920ec5b5ebcce941d7e84c9ade21d4628c2b020b3c32f7e1b07fbb825c145d", + "cb920ec5b5ebcce941d7e84c9ade21d4628c2b020b3c32f7e1b07fbb825c145d", + "d479546534fa8a146475623ca938efe42c6d561732088f8c3fd687ffff15210b", + "d479546534fa8a146475623ca938efe42c6d561732088f8c3fd687ffff15210b", + ) + + @Test + fun testRandomPoint() = runTest { + LibsodiumInitializer.initializeWithCallback { + val p = Ed25519.Point.random() + val q = Ed25519.Point.random() + val r = Ed25519.Point.random() + + assertNotEquals(p, q) + assertNotEquals(q, r) + assertNotEquals(r, p) + + assertTrue { p.isValid } + assertTrue { q.isValid } + assertTrue { r.isValid } + } + } + + @Test + fun testPointHexConversion() = runTest { + LibsodiumInitializer.initializeWithCallback { + repeat(10) { + val p = Ed25519.Point.random() + + assertEquals(p, Ed25519.Point.fromHex(p.toHex())) + } + } + } + + @Test + fun testIsValidPoint() = runTest { + LibsodiumInitializer.initializeWithCallback { + for (hexEncoded in badEncodings) { + assertFalse { Ed25519.Point.fromHex(hexEncoded).isValid } + } + + for (hexEncoded in basePointSmallMultiplesNoClamp) { + assertTrue { Ed25519.Point.fromHex(hexEncoded).isValid } + } + + for (hexEncoded in basePointSmallMultiplesClamped) { + assertTrue { Ed25519.Point.fromHex(hexEncoded).isValid } + } + } + } + + @Test + fun testPointArithmeticNoClamp() = runTest { + LibsodiumInitializer.initializeWithCallback { + for (i in basePointSmallMultiplesNoClamp.indices) { + val p = Ed25519.Point.fromHex(basePointSmallMultiplesNoClamp[i]) + val b = Ed25519.Point.BASE + val n = Ed25519.Scalar.fromUInt(i.toUInt() + 1U) + + assertEquals(p, Ed25519.Point.multiplyBaseNoClamp(n)) + assertEquals(p, b.times(n, false)) + assertEquals(p, n.multiplyWithBaseNoClamp()) + + for (j in 0.. + p.usePinned { pPinned -> + q.usePinned { qPinned -> + crypto_core_ed25519_add(resultPinned.toPtr(), pPinned.toPtr(), qPinned.toPtr()) + .ensureLibsodiumSuccess() + } + } + } + + return result + } + + actual fun subtractPoints(p: UByteArray, q: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_BYTES) + + result.usePinned { resultPinned -> + p.usePinned { pPinned -> + q.usePinned { qPinned -> + crypto_core_ed25519_sub(resultPinned.toPtr(), pPinned.toPtr(), qPinned.toPtr()) + .ensureLibsodiumSuccess() + } + } + } + + return result + } + + actual fun pointFromUniform(uniform: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_BYTES) + + result.usePinned { resultPinned -> + uniform.usePinned { uniformPinned -> + crypto_core_ed25519_from_uniform(resultPinned.toPtr(), uniformPinned.toPtr()) + .ensureLibsodiumSuccess() + } + } + + return result + } + + actual fun randomPoint(): UByteArray = UByteArray(crypto_core_ed25519_BYTES).apply { + usePinned { crypto_core_ed25519_random(it.toPtr()) } + } + + actual fun randomScalar(): UByteArray = UByteArray(crypto_core_ed25519_SCALARBYTES).apply { + usePinned { crypto_core_ed25519_scalar_random(it.toPtr()) } + } + + actual fun invertScalar(scalar: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_SCALARBYTES) + + result.usePinned { resultPinned -> + scalar.usePinned { scalarPinned -> + crypto_core_ed25519_scalar_invert(resultPinned.toPtr(), scalarPinned.toPtr()).ensureLibsodiumSuccess() + } + } + + + return result + } + + actual fun negateScalar(scalar: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_SCALARBYTES) + + result.usePinned { resultPinned -> + scalar.usePinned { scalarPinned -> + crypto_core_ed25519_scalar_negate(resultPinned.toPtr(), scalarPinned.toPtr()) + } + } + + + return result + } + + actual fun complementScalar(scalar: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_SCALARBYTES) + + result.usePinned { resultPinned -> + scalar.usePinned { scalarPinned -> + crypto_core_ed25519_scalar_complement(resultPinned.toPtr(), scalarPinned.toPtr()) + } + } + + return result + } + + actual fun addScalars(x: UByteArray, y: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_SCALARBYTES) + + result.usePinned { resultPinned -> + x.usePinned { xPinned -> + y.usePinned { yPinned -> + crypto_core_ed25519_scalar_add(resultPinned.toPtr(), xPinned.toPtr(), yPinned.toPtr()) + } + } + } + + return result + } + + actual fun subtractScalars(x: UByteArray, y: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_SCALARBYTES) + + result.usePinned { resultPinned -> + x.usePinned { xPinned -> + y.usePinned { yPinned -> + crypto_core_ed25519_scalar_sub(resultPinned.toPtr(), xPinned.toPtr(), yPinned.toPtr()) + } + } + } + + return result + } + + actual fun multiplyScalars(x: UByteArray, y: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_SCALARBYTES) + + result.usePinned { resultPinned -> + x.usePinned { xPinned -> + y.usePinned { yPinned -> + crypto_core_ed25519_scalar_mul(resultPinned.toPtr(), xPinned.toPtr(), yPinned.toPtr()) + } + } + } + + return result + } + + actual fun reduceScalar(scalar: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_SCALARBYTES) + + result.usePinned { resultPinned -> + scalar.usePinned { scalarPinned -> + crypto_core_ed25519_scalar_reduce(resultPinned.toPtr(), scalarPinned.toPtr()) + } + } + + return result + } + + actual fun scalarMultiplication(n: UByteArray, p: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_BYTES) + + result.usePinned { resultPinned -> + n.usePinned { nPinned -> + p.usePinned { pPinned -> + crypto_scalarmult_ed25519(resultPinned.toPtr(), nPinned.toPtr(), pPinned.toPtr()) + .ensureLibsodiumSuccess() + } + } + } + + + return result + } + + actual fun scalarMultiplicationNoClamp(n: UByteArray, p: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_BYTES) + + result.usePinned { resultPinned -> + n.usePinned { nPinned -> + p.usePinned { pPinned -> + crypto_scalarmult_ed25519_noclamp(resultPinned.toPtr(), nPinned.toPtr(), pPinned.toPtr()) + .ensureLibsodiumSuccess() + } + } + } + + + return result + } + + actual fun scalarMultiplicationBase(n: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_BYTES) + + result.usePinned { resultPinned -> + n.usePinned { nPinned -> + crypto_scalarmult_ed25519_base(resultPinned.toPtr(), nPinned.toPtr()) + .ensureLibsodiumSuccess() + } + } + + return result + } + + actual fun scalarMultiplicationBaseNoClamp(n: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ed25519_BYTES) + + result.usePinned { resultPinned -> + n.usePinned { nPinned -> + crypto_scalarmult_ed25519_base_noclamp(resultPinned.toPtr(), nPinned.toPtr()) + .ensureLibsodiumSuccess() + } + } + + return result + } +} \ No newline at end of file diff --git a/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/ristretto255/Ristretto255LowLevel.kt b/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/ristretto255/Ristretto255LowLevel.kt new file mode 100644 index 00000000..61b9595c --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/ristretto255/Ristretto255LowLevel.kt @@ -0,0 +1,201 @@ +package com.ionspin.kotlin.crypto.ristretto255 + +import com.ionspin.kotlin.crypto.GeneralLibsodiumException.Companion.ensureLibsodiumSuccess +import com.ionspin.kotlin.crypto.util.toPtr +import kotlinx.cinterop.usePinned +import libsodium.crypto_core_ristretto255_add +import libsodium.crypto_core_ristretto255_from_hash +import libsodium.crypto_core_ristretto255_is_valid_point +import libsodium.crypto_core_ristretto255_random +import libsodium.crypto_core_ristretto255_scalar_add +import libsodium.crypto_core_ristretto255_scalar_complement +import libsodium.crypto_core_ristretto255_scalar_invert +import libsodium.crypto_core_ristretto255_scalar_mul +import libsodium.crypto_core_ristretto255_scalar_negate +import libsodium.crypto_core_ristretto255_scalar_random +import libsodium.crypto_core_ristretto255_scalar_reduce +import libsodium.crypto_core_ristretto255_scalar_sub +import libsodium.crypto_core_ristretto255_sub +import libsodium.crypto_scalarmult_ristretto255 +import libsodium.crypto_scalarmult_ristretto255_base + + +actual object Ristretto255LowLevel { + actual fun isValidPoint(encoded: UByteArray): Boolean { + return encoded.usePinned { crypto_core_ristretto255_is_valid_point(it.toPtr()) == 1 } + } + + actual fun addPoints(p: UByteArray, q: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ristretto255_BYTES) + + result.usePinned { resultPinned -> + p.usePinned { pPinned -> + q.usePinned { qPinned -> + crypto_core_ristretto255_add(resultPinned.toPtr(), pPinned.toPtr(), qPinned.toPtr()) + .ensureLibsodiumSuccess() + } + } + } + + return result + } + + actual fun subtractPoints(p: UByteArray, q: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ristretto255_BYTES) + + result.usePinned { resultPinned -> + p.usePinned { pPinned -> + q.usePinned { qPinned -> + crypto_core_ristretto255_sub(resultPinned.toPtr(), pPinned.toPtr(), qPinned.toPtr()) + .ensureLibsodiumSuccess() + } + } + } + + return result + } + + actual fun pointFromHash(hash: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ristretto255_BYTES) + + result.usePinned { resultPinned -> + hash.usePinned { hashPinned -> + crypto_core_ristretto255_from_hash(resultPinned.toPtr(), hashPinned.toPtr()) + } + } + + return result + } + + actual fun randomPoint(): UByteArray = UByteArray(crypto_core_ristretto255_BYTES).apply { + usePinned { crypto_core_ristretto255_random(it.toPtr()) } + } + + actual fun randomScalar(): UByteArray = UByteArray(crypto_core_ristretto255_SCALARBYTES).apply { + usePinned { crypto_core_ristretto255_scalar_random(it.toPtr()) } + } + + actual fun invertScalar(scalar: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ristretto255_SCALARBYTES) + + result.usePinned { resultPinned -> + scalar.usePinned { scalarPinned -> + crypto_core_ristretto255_scalar_invert( + resultPinned.toPtr(), + scalarPinned.toPtr() + ).ensureLibsodiumSuccess() + } + } + + + return result + } + + actual fun negateScalar(scalar: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ristretto255_SCALARBYTES) + + result.usePinned { resultPinned -> + scalar.usePinned { scalarPinned -> + crypto_core_ristretto255_scalar_negate(resultPinned.toPtr(), scalarPinned.toPtr()) + } + } + + + return result + } + + actual fun complementScalar(scalar: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ristretto255_SCALARBYTES) + + result.usePinned { resultPinned -> + scalar.usePinned { scalarPinned -> + crypto_core_ristretto255_scalar_complement(resultPinned.toPtr(), scalarPinned.toPtr()) + } + } + + return result + } + + actual fun addScalars(x: UByteArray, y: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ristretto255_SCALARBYTES) + + result.usePinned { resultPinned -> + x.usePinned { xPinned -> + y.usePinned { yPinned -> + crypto_core_ristretto255_scalar_add(resultPinned.toPtr(), xPinned.toPtr(), yPinned.toPtr()) + } + } + } + + return result + } + + actual fun subtractScalars(x: UByteArray, y: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ristretto255_SCALARBYTES) + + result.usePinned { resultPinned -> + x.usePinned { xPinned -> + y.usePinned { yPinned -> + crypto_core_ristretto255_scalar_sub(resultPinned.toPtr(), xPinned.toPtr(), yPinned.toPtr()) + } + } + } + + return result + } + + actual fun multiplyScalars(x: UByteArray, y: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ristretto255_SCALARBYTES) + + result.usePinned { resultPinned -> + x.usePinned { xPinned -> + y.usePinned { yPinned -> + crypto_core_ristretto255_scalar_mul(resultPinned.toPtr(), xPinned.toPtr(), yPinned.toPtr()) + } + } + } + + return result + } + + actual fun reduceScalar(scalar: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ristretto255_SCALARBYTES) + + result.usePinned { resultPinned -> + scalar.usePinned { scalarPinned -> + crypto_core_ristretto255_scalar_reduce(resultPinned.toPtr(), scalarPinned.toPtr()) + } + } + + return result + } + + actual fun scalarMultiplication(n: UByteArray, p: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ristretto255_BYTES) + + result.usePinned { resultPinned -> + n.usePinned { nPinned -> + p.usePinned { pPinned -> + crypto_scalarmult_ristretto255(resultPinned.toPtr(), nPinned.toPtr(), pPinned.toPtr()) + .ensureLibsodiumSuccess() + } + } + } + + + return result + } + + actual fun scalarMultiplicationBase(n: UByteArray): UByteArray { + val result = UByteArray(crypto_core_ristretto255_BYTES) + + result.usePinned { resultPinned -> + n.usePinned { nPinned -> + crypto_scalarmult_ristretto255_base(resultPinned.toPtr(), nPinned.toPtr()) + .ensureLibsodiumSuccess() + } + } + + return result + } +} \ No newline at end of file diff --git a/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/util/ConversionUtil.kt b/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/util/ConversionUtil.kt index 360c7d76..8a882f67 100644 --- a/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/util/ConversionUtil.kt +++ b/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/util/ConversionUtil.kt @@ -4,7 +4,6 @@ import kotlinx.cinterop.CPointer import kotlinx.cinterop.Pinned import kotlinx.cinterop.UByteVar import kotlinx.cinterop.addressOf -import kotlinx.cinterop.toCPointer /** * Created by Ugljesa Jovanovic diff --git a/sodiumWrapper/makeMacosIosWatchosTvos.sh b/sodiumWrapper/makeMacosIosWatchosTvos.sh index 1b6ac2f5..8a984e9e 100755 --- a/sodiumWrapper/makeMacosIosWatchosTvos.sh +++ b/sodiumWrapper/makeMacosIosWatchosTvos.sh @@ -1,6 +1,6 @@ ./configureMacos64.sh cd libsodium -./dist-build/apple-xcframework.sh +LIBSODIUM_FULL_BUILD=1 ./dist-build/apple-xcframework.sh mkdir ../static-ios mkdir ../static-ios-simulators