diff --git a/shared/stargate/build.gradle.kts b/shared/stargate/build.gradle.kts index 41ade1a..becc940 100644 --- a/shared/stargate/build.gradle.kts +++ b/shared/stargate/build.gradle.kts @@ -20,11 +20,16 @@ kotlin { sourceSets { commonMain.dependencies { + implementation(projects.shared.crypto) + + implementation(libs.cryptography.bigint) implementation(libs.ktor.client.cio) implementation(libs.ktor.client.core) implementation(libs.ktor.client.websockets) + } - implementation(libs.cryptography.bigint) + commonTest.dependencies { + implementation(libs.kotlin.test) } } } diff --git a/shared/stargate/src/commonMain/kotlin/net/agorise/shared/stargate/mnemonics/Mnemonics.kt b/shared/stargate/src/commonMain/kotlin/net/agorise/shared/stargate/mnemonics/Mnemonics.kt index be2bd17..69be353 100644 --- a/shared/stargate/src/commonMain/kotlin/net/agorise/shared/stargate/mnemonics/Mnemonics.kt +++ b/shared/stargate/src/commonMain/kotlin/net/agorise/shared/stargate/mnemonics/Mnemonics.kt @@ -2,6 +2,8 @@ package net.agorise.shared.stargate.mnemonics import dev.whyoleg.cryptography.bigint.BigInt import dev.whyoleg.cryptography.bigint.toBigInt +import io.ktor.utils.io.core.toByteArray +import net.agorise.shared.crypto.crc.Crc32 /** * Provides functionality to create and validate mnemonics. @@ -115,33 +117,31 @@ class Mnemonics { // // return calculatedChecksumWord == checksumWord // } -// -// /** -// * Calculates a checksum (using CRC algorithm) on first 24 words. -// */ -// @OptIn(ExperimentalUnsignedTypes::class) -// private fun calculateChecksumIndex(words: Array, prefixLen: Int): Result { -// val trimmedRunes = mutableListOf() -// -// if (words.size != SEED_LENGTH) { -// return Result.failure(Exception("Words not equal to seed length")) -// } -// -// for (word in words) { -// val trimmedWord = if (word.length > prefixLen) { -// word.substring(0, prefixLen) -// } else { -// word -// } -// trimmedRunes.addAll(trimmedWord.toCharArray().toList()) -// } -// -// val checksum = CRC32().apply { -// update(trimmedRunes.toCharArray().concatToString().toByteArray(Charsets.UTF_8)) -// }.value -// -// return Result.success(checksum % SEED_LENGTH.toUInt()) -// } + + /** + * Calculates a checksum using CRC-32 algorithm on the seed (SEED_LENGTH words) as follows: + * - Takes prefixLen chars (not bytes) from each word and concatenates them + * - Calculates the CRC-32 checksum on the concatenated bytes + * - Returns the mod of checksum by SEED_LENGTH, to get the checksum word + */ + internal fun calculateChecksumIndex(words: List, prefixLen: Int): Result { + if (words.size != SEED_LENGTH) { + return Result.failure(Exception("Words not equal to seed length")) + } + + val trimmedWords = mutableListOf() + + words.forEach { word -> + trimmedWords.addAll(word.take(prefixLen).toList()) + } + + // TODO Use DI to get an instance of CRC32 + val crc32 = Crc32() + + val checksum = crc32.crc32(trimmedWords.joinToString("").toByteArray()) + + return Result.success(checksum % SEED_LENGTH.toUInt()) + } companion object { // Each language should have exactly this number of words diff --git a/shared/stargate/src/commonTest/kotlin/net/agorise/shared/stargate/mnemonics/MnemonicsTest.kt b/shared/stargate/src/commonTest/kotlin/net/agorise/shared/stargate/mnemonics/MnemonicsTest.kt new file mode 100644 index 0000000..e3a9188 --- /dev/null +++ b/shared/stargate/src/commonTest/kotlin/net/agorise/shared/stargate/mnemonics/MnemonicsTest.kt @@ -0,0 +1,40 @@ +package net.agorise.shared.stargate.mnemonics + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class MnemonicsTest { + private val mnemonics = Mnemonics() + + @Test + fun `given an invalid seed - when calculateChecksumIndex is called - then result is failure`() { + val invalidSeed = "invalid seed".split(" ") + + val result = mnemonics.calculateChecksumIndex(invalidSeed, 3) + + assertTrue(result.isFailure) + } + + @Test + fun `given a valid english seed - when calculateChecksumIndex is called - then result is correct`() { + val expectedChecksumIndex = 22u + val seed = "sequence atlas unveil summon pebbles tuesday beer rudely snake rockets different fuselage woven tagged bested dented vegan hover rapid fawns obvious muppet randomly seasons randomly".split(" ").dropLast(1) + + val result = mnemonics.calculateChecksumIndex(seed, 3) + val actualChecksumIndex = result.getOrThrow() + + assertEquals(expectedChecksumIndex, actualChecksumIndex) + } + + @Test + fun `given a valid spanish seed - when calculateChecksumIndex is called - then result is correct`() { + val expectedChecksumIndex = 7u + val seed = "perfil lujo faja puma favor pedir detalle doble carbón neón paella cuarto ánimo cuento conga correr dental moneda león donar entero logro realidad acceso doble".split(" ").dropLast(1) + + val result = mnemonics.calculateChecksumIndex(seed, 4) + val actualChecksumIndex = result.getOrThrow() + + assertEquals(expectedChecksumIndex, actualChecksumIndex) + } +}