2019-12-17 13:20:23 -08:00
|
|
|
package cash.z.ecc.android.ui.setup
|
|
|
|
|
|
|
|
import androidx.lifecycle.ViewModel
|
2020-01-06 22:45:24 -08:00
|
|
|
import cash.z.ecc.android.ZcashWalletApp
|
2019-12-23 11:12:34 -08:00
|
|
|
import cash.z.ecc.android.feedback.Feedback
|
|
|
|
import cash.z.ecc.android.feedback.Report.MetricType.*
|
|
|
|
import cash.z.ecc.android.feedback.measure
|
2019-12-17 13:20:23 -08:00
|
|
|
import cash.z.ecc.android.lockbox.LockBox
|
|
|
|
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.*
|
|
|
|
import cash.z.ecc.kotlin.mnemonic.Mnemonics
|
2020-06-10 04:49:38 -07:00
|
|
|
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
|
2019-12-23 11:12:34 -08:00
|
|
|
import kotlinx.coroutines.Dispatchers
|
2019-12-17 13:20:23 -08:00
|
|
|
import kotlinx.coroutines.flow.Flow
|
|
|
|
import kotlinx.coroutines.flow.flow
|
2019-12-23 11:12:34 -08:00
|
|
|
import kotlinx.coroutines.withContext
|
2019-12-17 13:20:23 -08:00
|
|
|
import javax.inject.Inject
|
|
|
|
|
2020-01-05 21:01:06 -08:00
|
|
|
class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
lateinit var mnemonics: Mnemonics
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
lateinit var lockBox: LockBox
|
2019-12-17 13:20:23 -08:00
|
|
|
|
2020-01-06 22:45:24 -08:00
|
|
|
@Inject
|
|
|
|
lateinit var feedback: Feedback
|
|
|
|
|
2019-12-17 13:20:23 -08:00
|
|
|
enum class WalletSetupState {
|
|
|
|
UNKNOWN, SEED_WITH_BACKUP, SEED_WITHOUT_BACKUP, NO_SEED
|
|
|
|
}
|
|
|
|
|
|
|
|
fun checkSeed(): Flow<WalletSetupState> = flow {
|
|
|
|
when {
|
2019-12-23 11:12:34 -08:00
|
|
|
lockBox.getBoolean(LockBoxKey.HAS_BACKUP) -> emit(SEED_WITH_BACKUP)
|
|
|
|
lockBox.getBoolean(LockBoxKey.HAS_SEED) -> emit(SEED_WITHOUT_BACKUP)
|
2019-12-17 13:20:23 -08:00
|
|
|
else -> emit(NO_SEED)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-06 22:45:24 -08:00
|
|
|
/**
|
|
|
|
* 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")
|
2020-02-12 04:55:44 -08:00
|
|
|
return ZcashWalletApp.component.initializerSubcomponent()
|
|
|
|
.create(DefaultBirthdayStore(ZcashWalletApp.instance)).run {
|
|
|
|
initializer().open(birthdayStore().getBirthday())
|
|
|
|
}
|
2020-01-06 22:45:24 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun newWallet(): Initializer {
|
|
|
|
twig("Initializing new wallet")
|
2020-02-12 04:55:44 -08:00
|
|
|
return ZcashWalletApp.component.initializerSubcomponent()
|
|
|
|
.create(NewWalletBirthdayStore(ZcashWalletApp.instance)).run {
|
|
|
|
initializer().apply {
|
|
|
|
new(createWallet(), birthdayStore().getBirthday())
|
|
|
|
}
|
2020-01-06 22:45:24 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun importWallet(seedPhrase: String, birthdayHeight: Int): Initializer {
|
2020-01-31 08:32:36 -08:00
|
|
|
twig("Importing wallet. Requested birthday: $birthdayHeight")
|
2020-02-12 04:55:44 -08:00
|
|
|
return ZcashWalletApp.component.initializerSubcomponent()
|
|
|
|
.create(ImportedWalletBirthdayStore(ZcashWalletApp.instance, birthdayHeight)).run {
|
|
|
|
initializer().apply {
|
|
|
|
import(importWallet(seedPhrase.toCharArray()), birthdayStore().getBirthday())
|
|
|
|
}
|
2020-01-06 22:45:24 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-23 11:12:34 -08:00
|
|
|
/**
|
|
|
|
* Take all the steps necessary to create a new wallet and measure how long it takes.
|
|
|
|
*
|
|
|
|
* @param feedback the object used for measurement.
|
|
|
|
*/
|
2020-02-12 04:55:44 -08:00
|
|
|
private suspend fun createWallet(): ByteArray = withContext(Dispatchers.IO) {
|
2019-12-23 11:12:34 -08:00
|
|
|
check(!lockBox.getBoolean(LockBoxKey.HAS_SEED)) {
|
2019-12-17 13:20:23 -08:00
|
|
|
"Error! Cannot create a seed when one already exists! This would overwrite the" +
|
|
|
|
" existing seed and could lead to a loss of funds if the user has no backup!"
|
|
|
|
}
|
|
|
|
|
2019-12-23 11:12:34 -08:00
|
|
|
feedback.measure(WALLET_CREATED) {
|
|
|
|
mnemonics.run {
|
|
|
|
feedback.measure(ENTROPY_CREATED) { nextEntropy() }.let { entropy ->
|
2020-02-12 04:55:44 -08:00
|
|
|
feedback.measure(SEED_PHRASE_CREATED) { nextMnemonic(entropy) }
|
|
|
|
.let { seedPhrase ->
|
2019-12-23 11:12:34 -08:00
|
|
|
feedback.measure(SEED_CREATED) { toSeed(seedPhrase) }.let { 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)
|
|
|
|
|
|
|
|
bip39Seed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-12-17 13:20:23 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-12 04:55:44 -08:00
|
|
|
suspend fun loadBirthdayHeight(): Int = withContext(Dispatchers.IO) {
|
|
|
|
DefaultBirthdayStore(ZcashWalletApp.instance).getBirthday().height
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-12-23 11:12:34 -08:00
|
|
|
/**
|
|
|
|
* Take all the steps necessary to import a wallet and measure how long it takes.
|
|
|
|
*
|
|
|
|
* @param feedback the object used for measurement.
|
|
|
|
*/
|
2020-01-06 22:45:24 -08:00
|
|
|
private suspend fun importWallet(
|
2019-12-23 11:12:34 -08:00
|
|
|
seedPhrase: CharArray
|
|
|
|
): ByteArray = withContext(Dispatchers.IO) {
|
|
|
|
check(!lockBox.getBoolean(LockBoxKey.HAS_SEED)) {
|
|
|
|
"Error! Cannot import a seed when one already exists! This would overwrite the" +
|
|
|
|
" existing seed and could lead to a loss of funds if the user has no backup!"
|
|
|
|
}
|
|
|
|
|
|
|
|
feedback.measure(WALLET_IMPORTED) {
|
|
|
|
mnemonics.run {
|
|
|
|
feedback.measure(SEED_IMPORTED) { toSeed(seedPhrase) }.let { 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)
|
|
|
|
|
|
|
|
bip39Seed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-10 05:26:21 -07:00
|
|
|
/**
|
|
|
|
* Throw an exception if the seed phrase is bad.
|
|
|
|
*/
|
|
|
|
fun validatePhrase(seedPhrase: String) {
|
|
|
|
mnemonics.validate(seedPhrase.toCharArray())
|
|
|
|
}
|
|
|
|
|
2019-12-17 13:20:23 -08:00
|
|
|
object LockBoxKey {
|
2019-12-23 11:12:34 -08:00
|
|
|
const val SEED = "cash.z.ecc.android.SEED"
|
|
|
|
const val SEED_PHRASE = "cash.z.ecc.android.SEED_PHRASE"
|
|
|
|
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"
|
2019-12-17 13:20:23 -08:00
|
|
|
}
|
|
|
|
}
|