Add verifyChecksum method to Mnemonics class.

- The verifyChecksum() method uses the calculateChecksumIndex() method internally to obtain the checksum index, which is then used to confirm that the checksum word is correct.
- Added tests for all verifyChecksum() scenarios.
- Created MnemonicsDataProvider to more easily provide the data used in MnemonicsTest.
This commit is contained in:
Severiano Jaramillo 2024-04-12 17:06:38 -07:00
parent 53bc55ec9f
commit c690d839da
4 changed files with 86 additions and 25 deletions

View file

@ -1,11 +1,16 @@
package net.agorise.shared.crypto.crc package net.agorise.shared.crypto.crc
/** /**
* Provides functionality to calculate CRC-32 checksums.
*
* 0xEDB88320u is the reversed representation of the IEEE CRC-32 polynomial: 0x04C11DB7 * 0xEDB88320u is the reversed representation of the IEEE CRC-32 polynomial: 0x04C11DB7
*/ */
class Crc32(private val polynomial: UInt = 0xEDB88320u) { class Crc32(polynomial: UInt = 0xEDB88320u) {
internal val lookupTable: List<UInt> = populateLookupTable(polynomial) internal val lookupTable: List<UInt> = populateLookupTable(polynomial)
/**
* Calculates the CRC-32 checksum on the given byte array.
*/
fun crc32(bytes: ByteArray): UInt { fun crc32(bytes: ByteArray): UInt {
var crc = INITIAL_CRC32 var crc = INITIAL_CRC32
for (byte in bytes) { for (byte in bytes) {
@ -15,6 +20,9 @@ class Crc32(private val polynomial: UInt = 0xEDB88320u) {
return crc.inv() return crc.inv()
} }
/**
* Populates the lookup table with the given polynomial to calculate CRC-32 checksums faster.
*/
private fun populateLookupTable(polynomial: UInt): List<UInt> { private fun populateLookupTable(polynomial: UInt): List<UInt> {
return (0 until 256).map { index -> return (0 until 256).map { index ->
(0 until 8).fold(index.toUInt()) { crc, _ -> (0 until 8).fold(index.toUInt()) { crc, _ ->

View file

@ -99,24 +99,23 @@ class Mnemonics {
) )
/** /**
* Verifies that the * Obtains that the checksum index and verifies it corresponds to the checksum word.
*/ */
// private fun verifyChecksum(words: Array<String>, prefixLen: Int): Boolean { internal fun verifyChecksum(words: List<String>, prefixLen: Int): Boolean {
// val seedLength = SEED_LENGTH if (words.size != SEED_LENGTH + 1) {
// return false // Checksum word is not present, we cannot verify
// if (words.size != seedLength + 1) { }
// return false // Checksum word is not present, we cannot verify
// } val result = calculateChecksumIndex(words.dropLast(1), prefixLen)
//
// val (checksumIndex, err) = calculateChecksumIndex(words.sliceArray(0 until words.size - 1), prefixLen) // Obtain checksum index, or return false if checksum index is not present
// if (err != null) { val checksumIndex = result.getOrNull()?.toInt() ?: return false
// return false
// } val calculatedChecksumWord = words[checksumIndex]
// val calculatedChecksumWord = words[checksumIndex] val checksumWord = words[SEED_LENGTH]
// val checksumWord = words[seedLength]
// return calculatedChecksumWord == checksumWord
// return calculatedChecksumWord == checksumWord }
// }
/** /**
* Calculates a checksum using CRC-32 algorithm on the seed (SEED_LENGTH words) as follows: * Calculates a checksum using CRC-32 algorithm on the seed (SEED_LENGTH words) as follows:

View file

@ -0,0 +1,17 @@
package net.agorise.shared.stargate.mnemonics
object MnemonicsDataProvider {
val invalidSizeSeed = "invalid size seed".split(" ")
val invalidChecksumSeed =
("sequence atlas unveil summon pebbles tuesday beer rudely snake rockets different " +
"fuselage woven tagged bested dented vegan hover rapid fawns obvious muppet " +
"randomly seasons summon").split(" ")
val validEnglishSeed =
("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(" ")
val validSpanishSeed =
("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(" ")
}

View file

@ -2,16 +2,17 @@ package net.agorise.shared.stargate.mnemonics
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
class MnemonicsTest { class MnemonicsTest {
private val mnemonics = Mnemonics() private val mnemonics = Mnemonics()
@Test @Test
fun `given an invalid seed - when calculateChecksumIndex is called - then result is failure`() { fun `given an invalid size seed - when calculateChecksumIndex is called - then result is failure`() {
val invalidSeed = "invalid seed".split(" ") val invalidSizeSeed = MnemonicsDataProvider.invalidSizeSeed
val result = mnemonics.calculateChecksumIndex(invalidSeed, 3) val result = mnemonics.calculateChecksumIndex(invalidSizeSeed, 3)
assertTrue(result.isFailure) assertTrue(result.isFailure)
} }
@ -19,9 +20,9 @@ class MnemonicsTest {
@Test @Test
fun `given a valid english seed - when calculateChecksumIndex is called - then result is correct`() { fun `given a valid english seed - when calculateChecksumIndex is called - then result is correct`() {
val expectedChecksumIndex = 22u 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 validEnglishSeed = MnemonicsDataProvider.validEnglishSeed.dropLast(1)
val result = mnemonics.calculateChecksumIndex(seed, 3) val result = mnemonics.calculateChecksumIndex(validEnglishSeed, 3)
val actualChecksumIndex = result.getOrThrow() val actualChecksumIndex = result.getOrThrow()
assertEquals(expectedChecksumIndex, actualChecksumIndex) assertEquals(expectedChecksumIndex, actualChecksumIndex)
@ -30,11 +31,47 @@ class MnemonicsTest {
@Test @Test
fun `given a valid spanish seed - when calculateChecksumIndex is called - then result is correct`() { fun `given a valid spanish seed - when calculateChecksumIndex is called - then result is correct`() {
val expectedChecksumIndex = 7u 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 validSpanishSeed = MnemonicsDataProvider.validSpanishSeed.dropLast(1)
val result = mnemonics.calculateChecksumIndex(seed, 4) val result = mnemonics.calculateChecksumIndex(validSpanishSeed, 4)
val actualChecksumIndex = result.getOrThrow() val actualChecksumIndex = result.getOrThrow()
assertEquals(expectedChecksumIndex, actualChecksumIndex) assertEquals(expectedChecksumIndex, actualChecksumIndex)
} }
@Test
fun `given an invalid size seed - when verifyChecksum is called - then result is false`() {
val invalidSizeSeed = MnemonicsDataProvider.invalidSizeSeed
val result = mnemonics.verifyChecksum(invalidSizeSeed, 3)
assertFalse(result)
}
@Test
fun `given an invalid checksum seed - when verifyChecksum is called - then result is false`() {
val invalidChecksumSeed = MnemonicsDataProvider.invalidChecksumSeed
val result = mnemonics.verifyChecksum(invalidChecksumSeed, 3)
assertFalse(result)
}
@Test
fun `given a valid english seed - when verifyChecksum is called - then result is true`() {
val validEnglishSeed = MnemonicsDataProvider.validEnglishSeed
val result = mnemonics.verifyChecksum(validEnglishSeed, 3)
assertTrue(result)
}
@Test
fun `given a valid spanish seed - when verifyChecksum is called - then result is true`() {
val validSpanishSeed = MnemonicsDataProvider.validSpanishSeed
val result = mnemonics.verifyChecksum(validSpanishSeed, 4)
assertTrue(result)
}
} }