Add simple tests for Account and UnifiedSpendingKey

test
This commit is contained in:
Carter Jernigan 2022-10-04 08:25:13 -04:00 committed by Carter Jernigan
parent 4c2fdeeb73
commit e95e43b83b
11 changed files with 141 additions and 11 deletions

1
.gitignore vendored
View File

@ -81,3 +81,4 @@ DecompileChecker.kt
backup-dbs/ backup-dbs/
*.db *.db
.DS_Store .DS_Store
/.idea/androidTestResultsUserPreferences.xml

View File

@ -287,8 +287,6 @@ dependencies {
// Tests // Tests
testImplementation(libs.kotlin.reflect) testImplementation(libs.kotlin.reflect)
testImplementation(libs.kotlin.test) testImplementation(libs.kotlin.test)
testImplementation(libs.mockito.junit)
testImplementation(libs.mockito.kotlin)
testImplementation(libs.bundles.junit) testImplementation(libs.bundles.junit)
testImplementation(libs.grpc.testing) testImplementation(libs.grpc.testing)

View File

@ -0,0 +1,18 @@
package cash.z.ecc.android.sdk.fixture
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
object WalletFixture {
val NETWORK = ZcashNetwork.Mainnet
const val SEED_PHRASE =
"kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree shock scan alpha just spot fluid toilet view dinner"
suspend fun getUnifiedSpendingKey(
seed: String = SEED_PHRASE,
network: ZcashNetwork = NETWORK,
account: Account = Account.DEFAULT
) = DerivationTool.deriveUnifiedSpendingKey(Mnemonics.MnemonicCode(seed).toEntropy(), network, account)
}

View File

@ -28,8 +28,7 @@ class TransparentTest(val expected: Expected, val network: ZcashNetwork) {
expected.tAccountPrivKey, expected.tAccountPrivKey,
DerivationTool.deriveTransparentAccountPrivateKey( DerivationTool.deriveTransparentAccountPrivateKey(
SEED, SEED,
network = network = network,
network,
Account.DEFAULT Account.DEFAULT
) )
) )

View File

@ -0,0 +1,51 @@
package cash.z.ecc.android.sdk.model
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.fixture.WalletFixture
import kotlinx.coroutines.test.runTest
import org.junit.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
class UnifiedSpendingKeyTest {
@Test
@SmallTest
fun factory_copies_bytes() = runTest {
val spendingKey = WalletFixture.getUnifiedSpendingKey()
val expected = spendingKey.copyBytes().copyOf()
val bytes = spendingKey.copyBytes()
val newSpendingKey = UnifiedSpendingKey.new(spendingKey.account, bytes)
bytes.clear()
assertContentEquals(expected, newSpendingKey.getOrThrow().copyBytes())
}
@Test
@SmallTest
fun get_copies_bytes() = runTest {
val spendingKey = WalletFixture.getUnifiedSpendingKey()
val expected = spendingKey.copyBytes()
val newSpendingKey = UnifiedSpendingKey.new(spendingKey.account, expected)
newSpendingKey.getOrThrow().copyBytes().clear()
assertContentEquals(expected, newSpendingKey.getOrThrow().copyBytes())
}
@Test
@SmallTest
fun toString_does_not_leak() = runTest {
assertEquals(
"UnifiedSpendingKey(account=Account(value=0))",
WalletFixture.getUnifiedSpendingKey().toString()
)
}
}
private fun ByteArray.clear() {
for (i in indices) {
this[i] = 0
}
}

View File

@ -0,0 +1,20 @@
package cash.z.ecc.android.sdk.tool
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.sdk.fixture.WalletFixture
import cash.z.ecc.android.sdk.model.Account
import kotlinx.coroutines.test.runTest
import org.junit.Test
import kotlin.test.assertContentEquals
class DerivationToolTest {
@Test
fun create_spending_key_does_not_mutate_passed_bytes() = runTest {
val bytesOne = Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy()
val bytesTwo = Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy()
DerivationTool.deriveUnifiedSpendingKey(bytesOne, WalletFixture.NETWORK, Account.DEFAULT)
assertContentEquals(bytesTwo, bytesOne)
}
}

View File

@ -233,7 +233,7 @@ internal class RustBackend private constructor(
createToAddress( createToAddress(
dataDbFile.absolutePath, dataDbFile.absolutePath,
usk.account.value, usk.account.value,
usk.bytes.byteArray, usk.copyBytes(),
to, to,
value, value,
memo ?: ByteArray(0), memo ?: ByteArray(0),
@ -252,7 +252,7 @@ internal class RustBackend private constructor(
shieldToAddress( shieldToAddress(
dataDbFile.absolutePath, dataDbFile.absolutePath,
usk.account.value, usk.account.value,
usk.bytes.byteArray, usk.copyBytes(),
memo ?: ByteArray(0), memo ?: ByteArray(0),
"$pathParamsDir/$SPEND_PARAM_FILE_NAME", "$pathParamsDir/$SPEND_PARAM_FILE_NAME",
"$pathParamsDir/$OUTPUT_PARAM_FILE_NAME", "$pathParamsDir/$OUTPUT_PARAM_FILE_NAME",

View File

@ -11,7 +11,7 @@ import cash.z.ecc.android.sdk.jni.RustBackend
* derived at the time of its creation. As such, it is not suitable for long-term storage, * derived at the time of its creation. As such, it is not suitable for long-term storage,
* export/import, or backup purposes. * export/import, or backup purposes.
*/ */
data class UnifiedSpendingKey internal constructor( class UnifiedSpendingKey private constructor(
val account: Account, val account: Account,
/** /**
@ -22,12 +22,42 @@ data class UnifiedSpendingKey internal constructor(
* inherently unstable, and only intended to be passed between the SDK and the storage * inherently unstable, and only intended to be passed between the SDK and the storage
* backend. Wallets **MUST NOT** allow this encoding to be exported or imported. * backend. Wallets **MUST NOT** allow this encoding to be exported or imported.
*/ */
internal val bytes: FirstClassByteArray private val bytes: FirstClassByteArray
) { ) {
// This constructor exists solely for the JNI
internal constructor(account: Int, bytes: ByteArray) : this(Account(account), FirstClassByteArray(bytes.copyOf()))
/**
* The binary encoding of the [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending
* Key for [account].
*
* This encoding **MUST NOT** be exposed to users. It is an internal encoding that is
* inherently unstable, and only intended to be passed between the SDK and the storage
* backend. Wallets **MUST NOT** allow this encoding to be exported or imported.
*/
fun copyBytes() = bytes.byteArray.copyOf()
// Override to prevent leaking key to logs // Override to prevent leaking key to logs
override fun toString() = "UnifiedSpendingKey(account=$account)" override fun toString() = "UnifiedSpendingKey(account=$account)"
fun copyBytes() = bytes.byteArray.copyOf() override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as UnifiedSpendingKey
if (account != other.account) return false
if (bytes != other.bytes) return false
return true
}
override fun hashCode(): Int {
var result = account.hashCode()
result = 31 * result + bytes.hashCode()
return result
}
companion object { companion object {

View File

@ -45,7 +45,7 @@ class DerivationTool {
network: ZcashNetwork network: ZcashNetwork
): UnifiedFullViewingKey = withRustBackendLoaded { ): UnifiedFullViewingKey = withRustBackendLoaded {
UnifiedFullViewingKey( UnifiedFullViewingKey(
deriveUnifiedFullViewingKey(usk.bytes.byteArray, networkId = network.id) deriveUnifiedFullViewingKey(usk.copyBytes(), networkId = network.id)
) )
} }

View File

@ -6,7 +6,7 @@ import java.math.BigDecimal
import java.math.MathContext import java.math.MathContext
import kotlin.test.assertEquals import kotlin.test.assertEquals
internal class ConversionsTest { class ConversionsTest {
@Test @Test
fun `default right padding is 6`() { fun `default right padding is 6`() {

View File

@ -0,0 +1,13 @@
package cash.z.ecc.android.sdk.model
import org.junit.Test
import kotlin.test.assertFailsWith
class AccountTest {
@Test
fun out_of_bounds() {
assertFailsWith(IllegalArgumentException::class) {
Account(-1)
}
}
}