mirror of
https://github.com/mollyim/monero-wallet-sdk.git
synced 2025-04-14 04:53:06 -04:00
lib: add mnemonic tests
This commit is contained in:
parent
16ff7b06db
commit
cb79e3421d
@ -28,6 +28,7 @@ androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "
|
||||
androidx-ui = { module = "androidx.compose.ui:ui" }
|
||||
androidx-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
|
||||
androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
|
||||
kotlin-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines"}
|
||||
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
|
||||
|
@ -105,6 +105,7 @@ dependencies {
|
||||
implementation(libs.androidx.lifecycle.service)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
|
||||
testImplementation(libs.kotlin.junit)
|
||||
testImplementation(testLibs.junit)
|
||||
testImplementation(testLibs.mockk)
|
||||
testImplementation(testLibs.truth)
|
||||
|
@ -3,41 +3,65 @@ package im.molly.monero.mnemonics
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.molly.monero.parseHex
|
||||
import org.junit.Test
|
||||
import java.util.Locale
|
||||
|
||||
class MoneroMnemonicTest {
|
||||
|
||||
data class TestCase(val entropy: String, val words: String, val language: String)
|
||||
data class TestCase(val key: String, val words: String, val language: String) {
|
||||
val entropy = key.parseHex()
|
||||
}
|
||||
|
||||
private val testVector = listOf(
|
||||
private val testCases = listOf(
|
||||
TestCase(
|
||||
entropy = "3b094ca7218f175e91fa2402b4ae239a2fe8262792a3e718533a1a357a1e4109",
|
||||
key = "3b094ca7218f175e91fa2402b4ae239a2fe8262792a3e718533a1a357a1e4109",
|
||||
words = "tavern judge beyond bifocals deepest mural onward dummy eagle diode gained vacation rally cause firm idled jerseys moat vigilant upload bobsled jobs cunning doing jobs",
|
||||
language = "en",
|
||||
),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun validateKnownMnemonics() {
|
||||
testVector.forEach {
|
||||
fun testKnownMnemonics() {
|
||||
testCases.forEach {
|
||||
validateMnemonicGeneration(it)
|
||||
validateEntropyRecovery(it)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun testEmptyEntropy() {
|
||||
MoneroMnemonic.generateMnemonic(ByteArray(0))
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun testInvalidEntropy() {
|
||||
MoneroMnemonic.generateMnemonic(ByteArray(2))
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun testEmptyWords() {
|
||||
MoneroMnemonic.recoverEntropy("")
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun testInvalidLanguage() {
|
||||
MoneroMnemonic.generateMnemonic(ByteArray(32), Locale("ZZ"))
|
||||
}
|
||||
|
||||
private fun validateMnemonicGeneration(testCase: TestCase) {
|
||||
val mnemonicCode = MoneroMnemonic.generateMnemonic(testCase.entropy.parseHex())
|
||||
val mnemonicCode =
|
||||
MoneroMnemonic.generateMnemonic(testCase.entropy, Locale(testCase.language))
|
||||
assertMnemonicCode(mnemonicCode, testCase)
|
||||
}
|
||||
|
||||
private fun validateEntropyRecovery(testCase: TestCase) {
|
||||
val mnemonicCode = MoneroMnemonic.recoverEntropy(testCase.words)
|
||||
assertThat(mnemonicCode).isNotNull()
|
||||
assertMnemonicCode(mnemonicCode!!, testCase)
|
||||
assertMnemonicCode(mnemonicCode, testCase)
|
||||
}
|
||||
|
||||
private fun assertMnemonicCode(mnemonicCode: MnemonicCode, testCase: TestCase) {
|
||||
with(mnemonicCode) {
|
||||
assertThat(entropy).isEqualTo(testCase.entropy.parseHex())
|
||||
private fun assertMnemonicCode(mnemonicCode: MnemonicCode?, testCase: TestCase) {
|
||||
assertThat(mnemonicCode).isNotNull()
|
||||
with(mnemonicCode!!) {
|
||||
assertThat(entropy).isEqualTo(testCase.entropy)
|
||||
assertThat(String(words)).isEqualTo(testCase.words)
|
||||
assertThat(locale.language).isEqualTo(testCase.language)
|
||||
}
|
||||
|
@ -12,12 +12,15 @@ class MnemonicCode private constructor(
|
||||
val locale: Locale,
|
||||
) : Destroyable, Closeable, Iterable<CharArray> {
|
||||
|
||||
constructor(entropy: ByteArray, words: CharBuffer, locale: Locale) : this(
|
||||
constructor(entropy: ByteArray, words: CharBuffer, locale: Locale = Locale.ENGLISH) : this(
|
||||
entropy.clone(),
|
||||
words.array().copyOfRange(words.position(), words.remaining()),
|
||||
locale,
|
||||
)
|
||||
|
||||
internal val isNonZero
|
||||
get() = !MessageDigest.isEqual(_entropy, ByteArray(_entropy.size))
|
||||
|
||||
val entropy: ByteArray
|
||||
get() = checkNotDestroyed { _entropy.clone() }
|
||||
|
||||
@ -66,9 +69,9 @@ class MnemonicCode private constructor(
|
||||
protected fun finalize() = destroy()
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
this === other || (other is MnemonicCode && MessageDigest.isEqual(entropy, other.entropy))
|
||||
this === other || (other is MnemonicCode && MessageDigest.isEqual(_entropy, other._entropy))
|
||||
|
||||
override fun hashCode(): Int = entropy.contentHashCode()
|
||||
override fun hashCode(): Int = _entropy.contentHashCode()
|
||||
|
||||
private inline fun <T> checkNotDestroyed(block: () -> T): T {
|
||||
check(!destroyed) { "MnemonicCode has already been destroyed" }
|
||||
|
@ -7,7 +7,6 @@ import java.nio.CharBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
object MoneroMnemonic {
|
||||
init {
|
||||
NativeLoader.loadMnemonicsLibrary()
|
||||
|
@ -1,9 +1,9 @@
|
||||
package im.molly.monero
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class SecretKeyTest {
|
||||
|
||||
@ -14,20 +14,29 @@ class SecretKeyTest {
|
||||
if (size == 32) {
|
||||
assertThat(SecretKey(secret).bytes).hasLength(size)
|
||||
} else {
|
||||
assertThrows(RuntimeException::class.java) { SecretKey(secret) }
|
||||
assertFailsWith<IllegalArgumentException> { SecretKey(secret) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `secret key copies buffer`() {
|
||||
val secretBytes = Random.nextBytes(32)
|
||||
val key = SecretKey(secretBytes)
|
||||
|
||||
assertThat(key.bytes).isEqualTo(secretBytes)
|
||||
secretBytes.fill(0)
|
||||
assertThat(key.bytes).isNotEqualTo(secretBytes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `secret keys cannot be zero`() {
|
||||
assertThrows(RuntimeException::class.java) { SecretKey(ByteArray(32)).bytes }
|
||||
assertFailsWith<IllegalStateException> { SecretKey(ByteArray(32)).bytes }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when key is destroyed secret is zeroed`() {
|
||||
val secretBytes = Random.nextBytes(32)
|
||||
|
||||
val key = SecretKey(secretBytes)
|
||||
|
||||
assertThat(key.destroyed).isFalse()
|
||||
@ -37,20 +46,20 @@ class SecretKeyTest {
|
||||
|
||||
assertThat(key.destroyed).isTrue()
|
||||
assertThat(key.isNonZero).isFalse()
|
||||
assertThrows(RuntimeException::class.java) { key.bytes }
|
||||
assertFailsWith<IllegalStateException> { key.bytes }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two keys with same secret are the same`() {
|
||||
fun `two keys with same secret are equal`() {
|
||||
val secret = Random.nextBytes(32)
|
||||
|
||||
val key = SecretKey(secret)
|
||||
val sameKey = SecretKey(secret)
|
||||
val anotherKey = randomSecretKey()
|
||||
val differentKey = randomSecretKey()
|
||||
|
||||
assertThat(key).isEqualTo(sameKey)
|
||||
assertThat(sameKey).isNotEqualTo(anotherKey)
|
||||
assertThat(anotherKey).isNotEqualTo(key)
|
||||
assertThat(sameKey).isNotEqualTo(differentKey)
|
||||
assertThat(differentKey).isNotEqualTo(key)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -64,7 +73,6 @@ class SecretKeyTest {
|
||||
@Test
|
||||
fun `keys are not equal to their destroyed versions`() {
|
||||
val secret = Random.nextBytes(32)
|
||||
|
||||
val key = SecretKey(secret)
|
||||
val destroyed = SecretKey(secret).also { it.destroy() }
|
||||
|
||||
@ -73,9 +81,9 @@ class SecretKeyTest {
|
||||
|
||||
@Test
|
||||
fun `destroyed keys are equal`() {
|
||||
val destroyed = randomSecretKey().also { it.destroy() }
|
||||
val anotherDestroyed = randomSecretKey().also { it.destroy() }
|
||||
val destroyed1 = randomSecretKey().also { it.destroy() }
|
||||
val destroyed2 = randomSecretKey().also { it.destroy() }
|
||||
|
||||
assertThat(destroyed).isEqualTo(anotherDestroyed)
|
||||
assertThat(destroyed1).isEqualTo(destroyed2)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,105 @@
|
||||
package im.molly.monero.mnemonic
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.molly.monero.mnemonics.MnemonicCode
|
||||
import org.junit.Test
|
||||
import java.nio.CharBuffer
|
||||
import java.util.Locale
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class MnemonicCodeTest {
|
||||
|
||||
private fun randomEntropy(size: Int = 32): ByteArray = Random.nextBytes(size)
|
||||
|
||||
private fun charBufferOf(str: String): CharBuffer = CharBuffer.wrap(str.toCharArray())
|
||||
|
||||
@Test
|
||||
fun `mnemonic copies entropy and words`() {
|
||||
val entropy = randomEntropy()
|
||||
val words = charBufferOf("arbre soleil maison")
|
||||
val locale = Locale.FRANCE
|
||||
|
||||
val mnemonic = MnemonicCode(entropy, words, locale)
|
||||
|
||||
assertThat(mnemonic.entropy).isEqualTo(entropy)
|
||||
assertThat(mnemonic.words).isEqualTo(words.array())
|
||||
assertThat(mnemonic.locale).isEqualTo(locale)
|
||||
|
||||
entropy.fill(0)
|
||||
words.put("modified".toCharArray())
|
||||
|
||||
assertThat(mnemonic.entropy).isNotEqualTo(entropy)
|
||||
assertThat(mnemonic.words).isNotEqualTo(words.array())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `destroyed mnemonic code zeroes entropy and words`() {
|
||||
val entropy = randomEntropy()
|
||||
val words = charBufferOf("test mnemonic")
|
||||
|
||||
val mnemonic = MnemonicCode(entropy, words)
|
||||
|
||||
mnemonic.destroy()
|
||||
|
||||
assertThat(mnemonic.destroyed).isTrue()
|
||||
assertThat(mnemonic.isNonZero).isFalse()
|
||||
assertFailsWith<IllegalStateException> { mnemonic.words }
|
||||
assertFailsWith<IllegalStateException> { mnemonic.entropy }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two mnemonics with same entropy are equal`() {
|
||||
val entropy = randomEntropy()
|
||||
val words = charBufferOf("test mnemonic")
|
||||
val locale = Locale.ENGLISH
|
||||
|
||||
val mnemonic = MnemonicCode(entropy, words, locale)
|
||||
val sameMnemonic = MnemonicCode(entropy, words, locale)
|
||||
val differentMnemonic = MnemonicCode(randomEntropy(), words, locale)
|
||||
|
||||
assertThat(mnemonic).isEqualTo(sameMnemonic)
|
||||
assertThat(differentMnemonic).isNotEqualTo(mnemonic)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `iterator correctly iterates words`() {
|
||||
val words = charBufferOf("word1 word2 word3")
|
||||
val mnemonic = MnemonicCode(randomEntropy(), words)
|
||||
|
||||
val iteratedWords = mnemonic.map { String(it) }
|
||||
|
||||
assertThat(iteratedWords).containsExactly("word1", "word2", "word3").inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calling next on iterator without checking hasNext throws exception`() {
|
||||
val words = charBufferOf("test mnemonic")
|
||||
val mnemonic = MnemonicCode(randomEntropy(), words)
|
||||
val iterator = mnemonic.iterator()
|
||||
|
||||
iterator.next()
|
||||
iterator.next()
|
||||
|
||||
assertFailsWith<NoSuchElementException> { iterator.next() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `mnemonics are not equal to their destroyed versions`() {
|
||||
val entropy = randomEntropy()
|
||||
val words = charBufferOf("test mnemonic")
|
||||
|
||||
val mnemonic = MnemonicCode(entropy, words)
|
||||
val destroyed = MnemonicCode(entropy, words).also { it.destroy() }
|
||||
|
||||
assertThat(mnemonic).isNotEqualTo(destroyed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `destroyed mnemonics are equal`() {
|
||||
val destroyed1 = MnemonicCode(randomEntropy(), charBufferOf("word1")).also { it.destroy() }
|
||||
val destroyed2 = MnemonicCode(randomEntropy(), charBufferOf("word2")).also { it.destroy() }
|
||||
|
||||
assertThat(destroyed1).isEqualTo(destroyed2)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user