[#173] Adopt SDK API changes

This allows the app to consume the snapshot version of the SDK, which contains a number of API changes.

A followup issue of #279 was filed so that the app can be reverted to the release version of the SDK once the SDK API stabilizes again.
This commit is contained in:
Carter Jernigan 2022-03-16 08:03:04 -04:00 committed by Carter Jernigan
parent de44984ea2
commit 45e14e6a4b
9 changed files with 107 additions and 126 deletions

View File

@ -94,7 +94,8 @@ KOTLIN_VERSION=1.6.10
KOTLINX_COROUTINES_VERSION=1.6.0
ZCASH_ANDROID_WALLET_PLUGINS_VERSION=1.0.0
ZCASH_BIP39_VERSION=1.0.2
ZCASH_SDK_VERSION=1.3.0-beta19
# TODO [#279]: Revert to stable SDK before app release
ZCASH_SDK_VERSION=1.4.0-beta01-SNAPSHOT
ZXING_VERSION=3.4.1
# Toolchain is the Java version used to build the application, which is separate from the

View File

@ -1,6 +1,7 @@
package cash.z.ecc.sdk.model
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.type.WalletBirthday
import cash.z.ecc.sdk.fixture.WalletBirthdayFixture
import cash.z.ecc.sdk.test.count
import org.junit.Assert.assertEquals
@ -16,17 +17,17 @@ class WalletBirthdayTest {
val jsonObject = walletBirthday.toJson()
assertEquals(5, jsonObject.keys().count())
assertTrue(jsonObject.has(WalletBirthdayCompanion.KEY_VERSION))
assertTrue(jsonObject.has(WalletBirthdayCompanion.KEY_HEIGHT))
assertTrue(jsonObject.has(WalletBirthdayCompanion.KEY_HASH))
assertTrue(jsonObject.has(WalletBirthdayCompanion.KEY_EPOCH_SECONDS))
assertTrue(jsonObject.has(WalletBirthdayCompanion.KEY_TREE))
assertTrue(jsonObject.has(WalletBirthday.KEY_VERSION))
assertTrue(jsonObject.has(WalletBirthday.KEY_HEIGHT))
assertTrue(jsonObject.has(WalletBirthday.KEY_HASH))
assertTrue(jsonObject.has(WalletBirthday.KEY_EPOCH_SECONDS))
assertTrue(jsonObject.has(WalletBirthday.KEY_TREE))
assertEquals(1, jsonObject.getInt(WalletBirthdayCompanion.KEY_VERSION))
assertEquals(WalletBirthdayFixture.HEIGHT, jsonObject.getInt(WalletBirthdayCompanion.KEY_HEIGHT))
assertEquals(WalletBirthdayFixture.HASH, jsonObject.getString(WalletBirthdayCompanion.KEY_HASH))
assertEquals(WalletBirthdayFixture.EPOCH_SECONDS, jsonObject.getLong(WalletBirthdayCompanion.KEY_EPOCH_SECONDS))
assertEquals(WalletBirthdayFixture.TREE, jsonObject.getString(WalletBirthdayCompanion.KEY_TREE))
assertEquals(1, jsonObject.getInt(WalletBirthday.KEY_VERSION))
assertEquals(WalletBirthdayFixture.HEIGHT, jsonObject.getInt(WalletBirthday.KEY_HEIGHT))
assertEquals(WalletBirthdayFixture.HASH, jsonObject.getString(WalletBirthday.KEY_HASH))
assertEquals(WalletBirthdayFixture.EPOCH_SECONDS, jsonObject.getLong(WalletBirthday.KEY_EPOCH_SECONDS))
assertEquals(WalletBirthdayFixture.TREE, jsonObject.getString(WalletBirthday.KEY_TREE))
}
@Test
@ -36,9 +37,9 @@ class WalletBirthdayTest {
val jsonObject = walletBirthday.toJson()
assertEquals(Long.MAX_VALUE, jsonObject.getLong(WalletBirthdayCompanion.KEY_EPOCH_SECONDS))
assertEquals(Long.MAX_VALUE, jsonObject.getLong(WalletBirthday.KEY_EPOCH_SECONDS))
WalletBirthdayCompanion.from(jsonObject).also {
WalletBirthday.from(jsonObject).also {
assertEquals(Long.MAX_VALUE, it.time)
}
}
@ -48,7 +49,7 @@ class WalletBirthdayTest {
fun round_trip() {
val walletBirthday = WalletBirthdayFixture.new()
val deserialized = WalletBirthdayCompanion.from(walletBirthday.toJson())
val deserialized = WalletBirthday.from(walletBirthday.toJson())
assertEquals(walletBirthday, deserialized)
assertFalse(walletBirthday === deserialized)

View File

@ -1,47 +0,0 @@
package cash.z.ecc.sdk
import android.content.Context
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
import cash.z.ecc.sdk.model.PersistableWallet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
// Synchronizer needs a Companion object
// https://github.com/zcash/zcash-android-wallet-sdk/issues/310
object SynchronizerCompanion {
suspend fun load(context: Context, persistableWallet: PersistableWallet): Synchronizer {
val config = persistableWallet.toConfig()
val initializer = withContext(Dispatchers.IO) { Initializer(context, config) }
return withContext(Dispatchers.IO) { Synchronizer(initializer) }
}
}
private suspend fun PersistableWallet.deriveViewingKey(): UnifiedViewingKey {
// Dispatcher needed because SecureRandom is loaded, which is slow and performs IO
// https://github.com/zcash/kotlin-bip39/issues/13
val bip39Seed = withContext(Dispatchers.IO) {
Mnemonics.MnemonicCode(seedPhrase.joinToString()).toSeed()
}
// Dispatchers needed until an SDK is published with the implementation of
// https://github.com/zcash/zcash-android-wallet-sdk/issues/269
val viewingKey = withContext(Dispatchers.IO) {
DerivationTool.deriveUnifiedViewingKeys(bip39Seed, network)[0]
}
return viewingKey
}
private suspend fun PersistableWallet.toConfig(): Initializer.Config {
val network = network
val vk = deriveViewingKey()
return Initializer.Config {
it.importWallet(vk, birthday?.height, network, network.defaultHost, network.defaultPort)
}
}

View File

@ -34,10 +34,8 @@ data class PersistableWallet(
put(KEY_SEED_PHRASE, seedPhrase.joinToString())
}
override fun toString(): String {
// For security, intentionally override the toString method to reduce risk of accidentally logging secrets
return "PersistableWallet"
}
// For security, intentionally override the toString method to reduce risk of accidentally logging secrets
override fun toString() = "PersistableWallet"
companion object {
private const val VERSION_1 = 1
@ -52,7 +50,7 @@ data class PersistableWallet(
VERSION_1 -> {
val networkId = jsonObject.getInt(KEY_NETWORK_ID)
val birthday = if (jsonObject.has(KEY_BIRTHDAY)) {
WalletBirthdayCompanion.from(jsonObject.getJSONObject(KEY_BIRTHDAY))
WalletBirthday.from(jsonObject.getJSONObject(KEY_BIRTHDAY))
} else {
null
}
@ -71,11 +69,8 @@ data class PersistableWallet(
*/
suspend fun new(application: Application): PersistableWallet {
val zcashNetwork = ZcashNetwork.fromResources(application)
// Dispatchers can be removed once a new SDK is released implementing
// https://github.com/zcash/zcash-android-wallet-sdk/issues/269
val walletBirthday = withContext(Dispatchers.IO) {
WalletBirthdayTool.loadNearest(application, zcashNetwork)
}
val walletBirthday = WalletBirthdayTool.loadNearest(application, zcashNetwork)
val seedPhrase = newSeedPhrase()
return PersistableWallet(zcashNetwork, walletBirthday, seedPhrase)

View File

@ -23,21 +23,19 @@ data class WalletAddresses(
Mnemonics.MnemonicCode(persistableWallet.seedPhrase.joinToString()).toSeed()
}
// Dispatchers needed until an SDK is published with the implementation of
// https://github.com/zcash/zcash-android-wallet-sdk/issues/269
val viewingKey = withContext(Dispatchers.IO) {
DerivationTool.deriveUnifiedViewingKeys(bip39Seed, persistableWallet.network)[0]
}.extpub
val viewingKey = DerivationTool.deriveUnifiedViewingKeys(bip39Seed, persistableWallet.network)[0].extpub
val shieldedSaplingAddress = withContext(Dispatchers.IO) {
DerivationTool.deriveShieldedAddress(bip39Seed, persistableWallet.network)
}.let {
val shieldedSaplingAddress = DerivationTool.deriveShieldedAddress(
bip39Seed,
persistableWallet.network
).let {
WalletAddress.ShieldedSapling.new(it)
}
val transparentAddress = withContext(Dispatchers.IO) {
DerivationTool.deriveTransparentAddress(bip39Seed, persistableWallet.network)
}.let {
val transparentAddress = DerivationTool.deriveTransparentAddress(
bip39Seed,
persistableWallet.network
).let {
WalletAddress.Transparent.new(it)
}

View File

@ -1,41 +0,0 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.android.sdk.type.WalletBirthday
import org.json.JSONObject
// WalletBirthday needs a companion
// https://github.com/zcash/zcash-android-wallet-sdk/issues/310
object WalletBirthdayCompanion {
internal const val VERSION_1 = 1
internal const val KEY_VERSION = "version"
internal const val KEY_HEIGHT = "height"
internal const val KEY_HASH = "hash"
internal const val KEY_EPOCH_SECONDS = "epoch_seconds"
internal const val KEY_TREE = "tree"
fun from(jsonString: String) = from(JSONObject(jsonString))
fun from(jsonObject: JSONObject): WalletBirthday {
when (val version = jsonObject.getInt(KEY_VERSION)) {
VERSION_1 -> {
val height = jsonObject.getInt(KEY_HEIGHT)
val hash = jsonObject.getString(KEY_HASH)
val epochSeconds = jsonObject.getLong(KEY_EPOCH_SECONDS)
val tree = jsonObject.getString(KEY_TREE)
return WalletBirthday(height, hash, epochSeconds, tree)
}
else -> {
throw IllegalArgumentException("Unsupported version $version")
}
}
}
}
fun WalletBirthday.toJson() = JSONObject().apply {
put(WalletBirthdayCompanion.KEY_VERSION, WalletBirthdayCompanion.VERSION_1)
put(WalletBirthdayCompanion.KEY_HEIGHT, height)
put(WalletBirthdayCompanion.KEY_HASH, hash)
put(WalletBirthdayCompanion.KEY_EPOCH_SECONDS, time)
put(WalletBirthdayCompanion.KEY_TREE, tree)
}

View File

@ -0,0 +1,43 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.android.sdk.type.WalletBirthday
import org.json.JSONObject
internal val WalletBirthday.Companion.VERSION_1
get() = 1
internal val WalletBirthday.Companion.KEY_VERSION
get() = "version"
internal val WalletBirthday.Companion.KEY_HEIGHT
get() = "height"
internal val WalletBirthday.Companion.KEY_HASH
get() = "hash"
internal val WalletBirthday.Companion.KEY_EPOCH_SECONDS
get() = "epoch_seconds"
internal val WalletBirthday.Companion.KEY_TREE
get() = "tree"
fun WalletBirthday.Companion.from(jsonString: String) = from(JSONObject(jsonString))
fun WalletBirthday.Companion.from(jsonObject: JSONObject): WalletBirthday {
when (val version = jsonObject.getInt(WalletBirthday.KEY_VERSION)) {
WalletBirthday.VERSION_1 -> {
val height = jsonObject.getInt(WalletBirthday.KEY_HEIGHT)
val hash = jsonObject.getString(WalletBirthday.KEY_HASH)
val epochSeconds = jsonObject.getLong(WalletBirthday.KEY_EPOCH_SECONDS)
val tree = jsonObject.getString(WalletBirthday.KEY_TREE)
return WalletBirthday(height, hash, epochSeconds, tree)
}
else -> {
throw IllegalArgumentException("Unsupported version $version")
}
}
}
fun WalletBirthday.toJson() = JSONObject().apply {
put(WalletBirthday.KEY_VERSION, WalletBirthday.VERSION_1)
put(WalletBirthday.KEY_HEIGHT, height)
put(WalletBirthday.KEY_HASH, hash)
put(WalletBirthday.KEY_EPOCH_SECONDS, time)
put(WalletBirthday.KEY_TREE, tree)
}

View File

@ -79,6 +79,13 @@ dependencyResolutionManagement {
}
}
}
maven("https://oss.sonatype.org/content/repositories/snapshots") {
if (isRepoRestrictionEnabled) {
content {
includeGroup("cash.z.ecc.android")
}
}
}
}
@Suppress("UnstableApiUsage", "MaxLineLength")

View File

@ -1,10 +1,14 @@
package co.electriccoin.zcash.global
import android.content.Context
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.sdk.SynchronizerCompanion
import cash.z.ecc.sdk.model.PersistableWallet
import cash.z.ecc.sdk.type.fromResources
import co.electriccoin.zcash.spackle.LazyWithArgument
import co.electriccoin.zcash.ui.preference.EncryptedPreferenceKeys
@ -26,6 +30,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
class WalletCoordinator(context: Context) {
companion object {
@ -67,9 +72,9 @@ class WalletCoordinator(context: Context) {
.filterNotNull()
.flatMapConcat {
callbackFlow {
val initializer = Initializer.new(context, it.toConfig())
val synchronizer = synchronizerMutex.withLock {
val synchronizer = SynchronizerCompanion.load(applicationContext, it)
val synchronizer = Synchronizer.new(initializer)
synchronizer.start(walletScope)
}
@ -153,3 +158,22 @@ class WalletCoordinator(context: Context) {
return false
}
}
private suspend fun PersistableWallet.deriveViewingKey(): UnifiedViewingKey {
// Dispatcher needed because SecureRandom is loaded, which is slow and performs IO
// https://github.com/zcash/kotlin-bip39/issues/13
val bip39Seed = withContext(Dispatchers.IO) {
Mnemonics.MnemonicCode(seedPhrase.joinToString()).toSeed()
}
return DerivationTool.deriveUnifiedViewingKeys(bip39Seed, network)[0]
}
private suspend fun PersistableWallet.toConfig(): Initializer.Config {
val network = network
val vk = deriveViewingKey()
return Initializer.Config {
it.importWallet(vk, birthday?.height, network, network.defaultHost, network.defaultPort)
}
}