Incorporate new SDK initializer improvements.

The first step in working with viewingKeys directly.
This commit is contained in:
Kevin Gorham 2020-09-25 11:43:36 -04:00
parent 808c124d3b
commit d38626c205
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
5 changed files with 104 additions and 76 deletions

View File

@ -1,7 +1,5 @@
package cash.z.ecc.android.di.component
import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.di.annotation.ActivityScope
import cash.z.ecc.android.di.annotation.SynchronizerScope
import cash.z.ecc.android.di.module.InitializerModule
import cash.z.ecc.android.sdk.Initializer
@ -13,10 +11,10 @@ import dagger.Subcomponent
interface InitializerSubcomponent {
fun initializer(): Initializer
fun birthdayStore(): Initializer.WalletBirthdayStore
fun config(): Initializer.Builder
@Subcomponent.Factory
interface Factory {
fun create(@BindsInstance birthdayStore: Initializer.WalletBirthdayStore = Initializer.DefaultBirthdayStore(ZcashWalletApp.instance)): InitializerSubcomponent
fun create(@BindsInstance config: Initializer.Builder): InitializerSubcomponent
}
}

View File

@ -1,8 +1,6 @@
package cash.z.ecc.android.di.module
import android.content.Context
import cash.z.ecc.android.ext.Const
import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.sdk.Initializer
import dagger.Module
import dagger.Provides
@ -11,16 +9,7 @@ import dagger.Reusable
@Module
class InitializerModule {
companion object {
const val defaultHost = "lightwalletd.electriccoin.co"
const val defaultPort = 9067
}
// TODO: Read SecurePrefs
// private val host: String = sharedPreferences.getString(Const.Pref.SERVER_NAME, defaultHost) ?: defaultHost
// private val port: Int = sharedPreferences.getInt(Const.Pref.SERVER_PORT, defaultPort)
@Provides
@Reusable
fun provideInitializer(appContext: Context) = Initializer(appContext, defaultHost, defaultPort)
fun provideInitializer(appContext: Context, config: Initializer.Builder) = Initializer(appContext, config)
}

View File

@ -13,19 +13,19 @@ import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.ext.*
import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Tap.*
import cash.z.ecc.android.sdk.Synchronizer.Status.*
import cash.z.ecc.android.sdk.ext.*
import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.home.HomeFragment.BannerAction.*
import cash.z.ecc.android.ui.send.SendViewModel
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.NO_SEED
import cash.z.ecc.android.sdk.Synchronizer.Status.*
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import cash.z.ecc.android.sdk.ext.convertZecToZatoshi
import cash.z.ecc.android.sdk.ext.safelyConvertToBigDecimal
import cash.z.ecc.android.sdk.ext.twig
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.scanReduce
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
@ -57,19 +57,17 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
twig("ZZZ ===================== HOME FRAGMENT CREATED ==================================")
super.onAttach(context)
// this will call startSync either now or later (after initializing with newly created seed)
walletSetup.checkSeed().onEach {
twig("Checking seed")
walletSetup.checkSeed().onFirstWith(lifecycleScope) {
if (it == NO_SEED) {
// interact with user to create, backup and verify seed
// leads to a call to startSync(), later (after accounts are created from seed)
twig("Seed not found, therefore, launching seed creation flow")
twig("Previous wallet not found, therefore, launching seed creation flow")
mainActivity?.safeNavigate(R.id.action_nav_home_to_create_wallet)
} else {
twig("Found seed. Re-opening existing wallet")
mainActivity?.startSync(walletSetup.openWallet())
twig("Previous wallet found. Re-opening it.")
mainActivity?.startSync(walletSetup.buildInitializer())
}
}.launchIn(lifecycleScope)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@ -12,18 +12,17 @@ import cash.z.ecc.android.feedback.Report.Issue
import cash.z.ecc.android.feedback.Report.MetricType
import cash.z.ecc.android.feedback.Report.MetricType.*
import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX_STANDARD
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.db.entity.*
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.validate.AddressType
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX_STANDARD
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@ -40,9 +39,6 @@ class SendViewModel @Inject constructor() : ViewModel() {
@Inject
lateinit var synchronizer: Synchronizer
@Inject
lateinit var initializer: Initializer
@Inject
lateinit var feedback: Feedback
@ -63,7 +59,7 @@ class SendViewModel @Inject constructor() : ViewModel() {
fun send(): Flow<PendingTransaction> {
funnel(SendSelected)
val memoToSend = createMemoToSend()
val keys = initializer.deriveSpendingKeys(
val keys = DerivationTool.deriveSpendingKeys(
lockBox.getBytes(WalletSetupViewModel.LockBoxKey.SEED)!!
)
funnel(SpendingKeyFound)

View File

@ -1,23 +1,27 @@
package cash.z.ecc.android.ui.setup
import android.util.Log
import androidx.lifecycle.ViewModel
import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.ext.Const
import cash.z.ecc.android.feedback.Feedback
import cash.z.ecc.android.feedback.Report.MetricType.*
import cash.z.ecc.android.feedback.measure
import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.exception.InitializerException
import cash.z.ecc.android.sdk.ext.toHex
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.*
import cash.z.ecc.kotlin.mnemonic.Mnemonics
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Initializer.DefaultBirthdayStore
import cash.z.ecc.android.sdk.Initializer.DefaultBirthdayStore.Companion.ImportedWalletBirthdayStore
import cash.z.ecc.android.sdk.Initializer.DefaultBirthdayStore.Companion.NewWalletBirthdayStore
import cash.z.ecc.android.sdk.ext.twig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.single
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Named
class WalletSetupViewModel @Inject constructor() : ViewModel() {
@ -27,6 +31,10 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
@Inject
lateinit var lockBox: LockBox
@Inject
@Named(Const.Name.APP_PREFS)
lateinit var prefs: LockBox
@Inject
lateinit var feedback: Feedback
@ -42,37 +50,61 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
}
}
/**
* Re-open an existing wallet. This is the most common use case, where a user has previously
* created or imported their seed and is returning to the wallet. In other words, this is the
* non-FTUE case.
*/
fun openWallet(): Initializer {
twig("Opening existing wallet")
return ZcashWalletApp.component.initializerSubcomponent()
.create(DefaultBirthdayStore(ZcashWalletApp.instance)).run {
initializer().open(birthdayStore().getBirthday())
suspend fun buildInitializer() = ZcashWalletApp.component.initializerSubcomponent().create(loadConfig().single()).initializer()
private fun loadConfig() = flow<Initializer.Builder> {
emit(
Initializer.Builder { builder ->
val vk = lockBox.getCharsUtf8(LockBoxKey.VIEWING_KEY)?.let { String(it) }
?: throw InitializerException.MissingViewingKeyException
val birthdayHeight = loadBirthdayHeight()
?: throw InitializerException.MissingBirthdayException
val host = prefs[Const.Pref.SERVER_HOST] ?: Const.Default.Server.HOST
val port = prefs[Const.Pref.SERVER_PORT] ?: Const.Default.Server.PORT
builder.import(vk, birthdayHeight, host, port)
}
)
}
fun loadBirthdayHeight(): Int? {
val h: Int? = lockBox[LockBoxKey.BIRTHDAY_HEIGHT]
twigFix("Loaded birthday with key ${LockBoxKey.BIRTHDAY_HEIGHT} and found $h")
return h
}
// /**
// * Re-open an existing wallet. This is the most common use case, where a user has previously
// * created or imported their seed and is returning to the wallet. In other words, this is the
// * non-FTUE case.
// */
// fun openWallet(): Initializer {
// twigFix("Opening existing wallet")
// return ZcashWalletApp.component.initializerSubcomponent()
// .create(DefaultBirthdayStore(ZcashWalletApp.instance)).run {
// initializer().open(birthdayStore().getBirthday())
// }
// }
suspend fun newWallet(): Initializer {
twig("Initializing new wallet")
return ZcashWalletApp.component.initializerSubcomponent()
.create(NewWalletBirthdayStore(ZcashWalletApp.instance)).run {
initializer().apply {
new(createWallet(), birthdayStore().getBirthday())
}
}
twigFix("Initializing new wallet")
createWallet()
return buildInitializer()
}
suspend fun importWallet(seedPhrase: String, birthdayHeight: Int): Initializer {
twig("Importing wallet. Requested birthday: $birthdayHeight")
return ZcashWalletApp.component.initializerSubcomponent()
.create(ImportedWalletBirthdayStore(ZcashWalletApp.instance, birthdayHeight)).run {
initializer().apply {
import(importWallet(seedPhrase.toCharArray()), birthdayStore().getBirthday())
}
twigFix("Importing wallet. Requested birthday: $birthdayHeight")
val seedPhraseChars = seedPhrase.toCharArray()
storeSeedPhrase(seedPhraseChars)
storeSeed(mnemonics.toSeed(seedPhraseChars))
WalletBirthdayTool.loadNearest(ZcashWalletApp.instance, birthdayHeight).let { birthday ->
storeBirthday(birthday)
}
return buildInitializer()
}
private fun twigFix(s: String) {
Log.e("@TWIG", s)
}
/**
@ -92,13 +124,12 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
feedback.measure(SEED_PHRASE_CREATED) { nextMnemonic(entropy) }
.let { seedPhrase ->
feedback.measure(SEED_CREATED) { toSeed(seedPhrase) }.let { bip39Seed ->
storeSeedPhrase(seedPhrase)
storeSeed(bip39Seed)
lockBox.setCharsUtf8(LockBoxKey.SEED_PHRASE, seedPhrase)
lockBox.setBoolean(LockBoxKey.HAS_SEED_PHRASE, true)
lockBox.setBytes(LockBoxKey.SEED, bip39Seed)
lockBox.setBoolean(LockBoxKey.HAS_SEED, true)
WalletBirthdayTool.loadNearest(ZcashWalletApp.instance).let { birthday ->
storeBirthday(birthday)
}
bip39Seed
}
}
@ -107,11 +138,23 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
}
}
suspend fun loadBirthdayHeight(): Int = withContext(Dispatchers.IO) {
DefaultBirthdayStore(ZcashWalletApp.instance).getBirthday().height
private fun storeBirthday(birthday: WalletBirthdayTool.WalletBirthday) {
twigFix("Storing birthday ${birthday.height} with and key ${LockBoxKey.BIRTHDAY_HEIGHT}")
lockBox[LockBoxKey.BIRTHDAY_HEIGHT] = birthday.height
}
private fun storeSeed(bip39Seed: ByteArray) {
twigFix("Storing seed: ${bip39Seed.toHex().length}")
lockBox.setBytes(LockBoxKey.SEED, bip39Seed)
lockBox[LockBoxKey.VIEWING_KEY] = DerivationTool.deriveViewingKeys(bip39Seed)[0]
lockBox[LockBoxKey.HAS_SEED] = true
}
private fun storeSeedPhrase(seedPhrase: CharArray) {
twigFix("Storing seedphrase: ${seedPhrase.size}")
lockBox[LockBoxKey.SEED_PHRASE] = seedPhrase
lockBox[LockBoxKey.HAS_SEED_PHRASE] = true
}
/**
* Take all the steps necessary to import a wallet and measure how long it takes.
@ -130,11 +173,10 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
mnemonics.run {
feedback.measure(SEED_IMPORTED) { toSeed(seedPhrase) }.let { bip39Seed ->
lockBox.setCharsUtf8(LockBoxKey.SEED_PHRASE, seedPhrase)
lockBox.setBoolean(LockBoxKey.HAS_SEED_PHRASE, true)
storeSeedPhrase(seedPhrase)
lockBox.setBytes(LockBoxKey.SEED, bip39Seed)
lockBox.setBoolean(LockBoxKey.HAS_SEED, true)
lockBox[LockBoxKey.HAS_SEED] = true
bip39Seed
}
@ -142,6 +184,7 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
}
}
/**
* Throw an exception if the seed phrase is bad.
*/
@ -155,5 +198,9 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
const val HAS_SEED = "cash.z.ecc.android.HAS_SEED"
const val HAS_SEED_PHRASE = "cash.z.ecc.android.HAS_SEED_PHRASE"
const val HAS_BACKUP = "cash.z.ecc.android.HAS_BACKUP"
// Config
const val VIEWING_KEY = "cash.z.ecc.android.VIEWING_KEY"
const val BIRTHDAY_HEIGHT = "cash.z.ecc.android.BIRTHDAY_HEIGHT"
}
}