From 338c1f001279180ff092172c6682c2bafc74493f Mon Sep 17 00:00:00 2001 From: Johannes Leupold Date: Tue, 13 Aug 2024 15:33:51 +0200 Subject: [PATCH] Add Ed25519 tests --- .../ed25519/Ed25519.kt | 2 +- .../kotlin/crypto/ed25519/Ed25519Test.kt | 267 ++++++++++++++++++ .../crypto/ristretto255/Ristretto255Test.kt | 6 +- 3 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 multiplatform-crypto-libsodium-bindings/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ed25519/Ed25519Test.kt 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 index d734b04..667017e 100644 --- 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 @@ -101,7 +101,7 @@ object Ed25519 : Ed25519LowLevel() { companion object { val IDENTITY: Point = Point(UByteArray(crypto_core_ed25519_BYTES)) - val BASE: Point = scalarMultiplicationBase(Scalar.ONE) + val BASE: Point = scalarMultiplicationBaseNoClamp(Scalar.ONE) fun fromHash(hash: UByteArray): Point = pointFromHash(hash) 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 0000000..c2c537c --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ed25519/Ed25519Test.kt @@ -0,0 +1,267 @@ +package com.ionspin.kotlin.crypto.ed25519 + +import com.ionspin.kotlin.crypto.LibsodiumInitializer +import com.ionspin.kotlin.crypto.hash.Hash +import com.ionspin.kotlin.crypto.util.LibsodiumUtil +import com.ionspin.kotlin.crypto.util.encodeToUByteArray +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 fromHashTestVectors = arrayOf( + "The sodium crypto library compiled to WebAssembly and pure JavaScript" to "50127230808e661643a11badce3c7220ab8de25f890528694f5155ab9c5d5339", + "using Emscripten, with automatically generated wrappers to" to "546d28c823c00b7d1c355c2f3ed6faaed2b7f406b45568c83f14b00ad88c212d", + "make it easy to use in web applications." to "69f1db12b628f6a0573c3ca440dbfe23c161d0a832cf4ca263ed33d15f337780", + "The complete library weighs 188 KB" to "9ad2302066752dccc14e26d7da4bb7a839c211a7e46f558ff106c632106d8f71", + "(minified, gzipped, includes pure JS + WebAssembly versions)" to "07787c86d65d8157b0e7bbf634c46e638f7dc88c560f60dfd1f5e85de64d681c", + "and can run in a web browser as well as server-side." to "c33fedca4b8e6fdd7ecc4109ec624f81900d8c207e1497297f4ca87c154c0640", + ) + + // 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 { Ed25519.isValidPoint(p.encoded) } + assertTrue { Ed25519.isValidPoint(q.encoded) } + assertTrue { Ed25519.isValidPoint(r.encoded) } + } + } + + @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.isValidPoint(LibsodiumUtil.fromHex(hexEncoded)) } + } + + for (hexEncoded in basePointSmallMultiplesNoClamp) { + assertTrue { Ed25519.isValidPoint(LibsodiumUtil.fromHex(hexEncoded)) } + } + + for (hexEncoded in basePointSmallMultiplesClamped) { + assertTrue { Ed25519.isValidPoint(LibsodiumUtil.fromHex(hexEncoded)) } + } + } + } + + @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.scalarMultiplicationBaseNoClamp(n)) + assertEquals(p, Ed25519.scalarMultiplicationNoClamp(b, n)) + assertEquals(p, n.multiplyWithBaseNoClamp()) + + for (j in 0..