From e95e43b83b5217b3a3455e03ecd2db42703d6f8b Mon Sep 17 00:00:00 2001 From: Carter Jernigan Date: Tue, 4 Oct 2022 08:25:13 -0400 Subject: [PATCH] Add simple tests for Account and UnifiedSpendingKey test --- .gitignore | 1 + sdk-lib/build.gradle.kts | 2 - .../ecc/android/sdk/fixture/WalletFixture.kt | 18 +++++++ .../z/ecc/android/sdk/jni/TransparentTest.kt | 3 +- .../sdk/model/UnifiedSpendingKeyTest.kt | 51 +++++++++++++++++++ .../android/sdk/tool/DerivationToolTest.kt | 20 ++++++++ .../cash/z/ecc/android/sdk/jni/RustBackend.kt | 4 +- .../android/sdk/model/UnifiedSpendingKey.kt | 36 +++++++++++-- .../z/ecc/android/sdk/tool/DerivationTool.kt | 2 +- .../z/ecc/android/sdk/ext/ConversionsTest.kt | 2 +- .../z/ecc/android/sdk/model/AccountTest.kt | 13 +++++ 11 files changed, 141 insertions(+), 11 deletions(-) create mode 100644 sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt create mode 100644 sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKeyTest.kt create mode 100644 sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/tool/DerivationToolTest.kt create mode 100644 sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt diff --git a/.gitignore b/.gitignore index 62a11d51..b0d34f04 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,4 @@ DecompileChecker.kt backup-dbs/ *.db .DS_Store +/.idea/androidTestResultsUserPreferences.xml diff --git a/sdk-lib/build.gradle.kts b/sdk-lib/build.gradle.kts index 4e3f49ad..197716b1 100644 --- a/sdk-lib/build.gradle.kts +++ b/sdk-lib/build.gradle.kts @@ -287,8 +287,6 @@ dependencies { // Tests testImplementation(libs.kotlin.reflect) testImplementation(libs.kotlin.test) - testImplementation(libs.mockito.junit) - testImplementation(libs.mockito.kotlin) testImplementation(libs.bundles.junit) testImplementation(libs.grpc.testing) diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt new file mode 100644 index 00000000..fc404f7e --- /dev/null +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt @@ -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) +} diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/TransparentTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/TransparentTest.kt index 3a30645d..1cda44b0 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/TransparentTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/TransparentTest.kt @@ -28,8 +28,7 @@ class TransparentTest(val expected: Expected, val network: ZcashNetwork) { expected.tAccountPrivKey, DerivationTool.deriveTransparentAccountPrivateKey( SEED, - network = - network, + network = network, Account.DEFAULT ) ) diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKeyTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKeyTest.kt new file mode 100644 index 00000000..f8c27fb6 --- /dev/null +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKeyTest.kt @@ -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 + } +} diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/tool/DerivationToolTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/tool/DerivationToolTest.kt new file mode 100644 index 00000000..69980c77 --- /dev/null +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/tool/DerivationToolTest.kt @@ -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) + } +} diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackend.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackend.kt index 8032d813..2adbe55e 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackend.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackend.kt @@ -233,7 +233,7 @@ internal class RustBackend private constructor( createToAddress( dataDbFile.absolutePath, usk.account.value, - usk.bytes.byteArray, + usk.copyBytes(), to, value, memo ?: ByteArray(0), @@ -252,7 +252,7 @@ internal class RustBackend private constructor( shieldToAddress( dataDbFile.absolutePath, usk.account.value, - usk.bytes.byteArray, + usk.copyBytes(), memo ?: ByteArray(0), "$pathParamsDir/$SPEND_PARAM_FILE_NAME", "$pathParamsDir/$OUTPUT_PARAM_FILE_NAME", diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKey.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKey.kt index baa11e60..26155cc3 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKey.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKey.kt @@ -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, * export/import, or backup purposes. */ -data class UnifiedSpendingKey internal constructor( +class UnifiedSpendingKey private constructor( 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 * 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 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 { diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt index c533388e..a980476e 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt @@ -45,7 +45,7 @@ class DerivationTool { network: ZcashNetwork ): UnifiedFullViewingKey = withRustBackendLoaded { UnifiedFullViewingKey( - deriveUnifiedFullViewingKey(usk.bytes.byteArray, networkId = network.id) + deriveUnifiedFullViewingKey(usk.copyBytes(), networkId = network.id) ) } diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/ext/ConversionsTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/ext/ConversionsTest.kt index 01a48d06..4818f201 100644 --- a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/ext/ConversionsTest.kt +++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/ext/ConversionsTest.kt @@ -6,7 +6,7 @@ import java.math.BigDecimal import java.math.MathContext import kotlin.test.assertEquals -internal class ConversionsTest { +class ConversionsTest { @Test fun `default right padding is 6`() { diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt new file mode 100644 index 00000000..c2b481fc --- /dev/null +++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt @@ -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) + } + } +}