Add calculateChecksumIndex() method to Mnemonics class.

- The calculateChecksumIndex() method uses the newly added method to calculate the CRC-32 to obtain the index of the word to be used for the seed checksum.
- Added a few unit tests to validate the correct functionality of the calculateChecksumIndex() method.
This commit is contained in:
Severiano Jaramillo 2024-04-12 16:25:18 -07:00
parent 7dab5227dc
commit 53bc55ec9f
3 changed files with 73 additions and 28 deletions

View file

@ -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)
}
}
}

View file

@ -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<String>, prefixLen: Int): Result<UInt> {
// val trimmedRunes = mutableListOf<Char>()
//
// 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<String>, prefixLen: Int): Result<UInt> {
if (words.size != SEED_LENGTH) {
return Result.failure(Exception("Words not equal to seed length"))
}
val trimmedWords = mutableListOf<Char>()
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

View file

@ -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)
}
}