Cleanup and bugfixes.

- Corrected logic for splitting address into 8 parts
  - Corrected bug in loading seed phrase
This commit is contained in:
Kevin Gorham 2019-12-23 14:34:38 -05:00
parent cdcc39121b
commit e1bbf1b6e8
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
6 changed files with 61 additions and 33 deletions

View File

@ -40,6 +40,8 @@ class WalletDetailFragment : BaseFragment<FragmentDetailBinding>() {
mainActivity?.showSnackbar("Feedback not yet implemented.")
}
}
private fun onViewLogs() {
loadLogFileAsText().let { logText ->
if (logText == null) {

View File

@ -16,10 +16,14 @@ import cash.z.ecc.android.ext.onClickNavTo
import cash.z.ecc.android.ext.onClickNavUp
import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.util.AddressPartNumberSpan
import cash.z.wallet.sdk.ext.twig
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.android.synthetic.main.fragment_receive.*
import kotlinx.coroutines.launch
import kotlin.math.floor
import kotlin.math.round
import kotlin.math.roundToInt
class ReceiveFragment : BaseFragment<FragmentReceiveBinding>() {
override fun inflate(inflater: LayoutInflater): FragmentReceiveBinding =
@ -51,23 +55,39 @@ class ReceiveFragment : BaseFragment<FragmentReceiveBinding>() {
override fun onResume() {
super.onResume()
lifecycleScope.launch {
onAddressLoaded("zs1qduvdyuv83pyygjvc4cfcuc2wj5flnqn730iigf0tjct8k5ccs9y30p96j2gvn9gzyxm6q0vj12c4")
resumedScope.launch {
mainActivity?.synchronizer?.getAddress()?.let { address ->
onAddressLoaded(address)
}
}
}
private fun onAddressLoaded(address: String) {
Log.e("TWIG", "onAddressLoaded: $address length: ${address.length}")
twig("address loaded: $address length: ${address.length}")
qrecycler.load(address)
.withQuietZoneSize(3)
.withCorrectionLevel(QRecycler.CorrectionLevel.MEDIUM)
.into(receive_qr_code)
address.chunked(address.length/8).forEachIndexed { i, part ->
address.distribute(8) { i, part ->
setAddressPart(i, part)
}
}
private fun <T> String.distribute(chunks: Int, block: (Int, String) -> T) {
val charsPerChunk = length / 8.0
val wholeCharsPerChunk = charsPerChunk.toInt()
val chunksWithExtra = ((charsPerChunk - wholeCharsPerChunk) * chunks).roundToInt()
repeat(chunks) { i ->
val part = if (i < chunksWithExtra) {
substring(i * (wholeCharsPerChunk + 1), (i + 1) * (wholeCharsPerChunk + 1))
} else {
substring(i * wholeCharsPerChunk + chunksWithExtra, (i + 1) * wholeCharsPerChunk + chunksWithExtra)
}
block(i, part)
}
}
private fun setAddressPart(index: Int, addressPart: String) {
Log.e("TWIG", "setting address for part $index) $addressPart")
val thinSpace = "\u2005" // 0.25 em space

View File

@ -15,18 +15,21 @@ import cash.z.ecc.android.R
import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.databinding.FragmentBackupBinding
import cash.z.ecc.android.di.annotation.FragmentScope
import cash.z.ecc.android.ext.onClick
import cash.z.ecc.android.feedback.Report.MetricType.SEED_PHRASE_LOADED
import cash.z.ecc.android.feedback.measure
import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.LockBoxKey
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITHOUT_BACKUP
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITH_BACKUP
import cash.z.ecc.android.ui.util.AddressPartNumberSpan
import cash.z.ecc.kotlin.mnemonic.Mnemonics
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
class BackupFragment : BaseFragment<FragmentBackupBinding>() {
@ -79,7 +82,7 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
mainActivity?.navController?.popBackStack(R.id.wallet_setup_navigation, true)
}
private fun applySpan(vararg textViews: TextView) {
private fun applySpan(vararg textViews: TextView) = lifecycleScope.launch {
val words = loadSeedWords()
val thinSpace = "\u2005" // 0.25 em space
textViews.forEachIndexed { index, textView ->
@ -92,11 +95,14 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
}
}
private fun loadSeedWords(): List<CharArray> {
val lockBox = LockBox(ZcashWalletApp.instance)
val mnemonics = Mnemonics()
val seed = lockBox.getBytes(LockBoxKey.SEED)!!
return mnemonics.nextMnemonicList(seed)
private suspend fun loadSeedWords(): List<CharArray> = withContext(Dispatchers.IO) {
mainActivity!!.feedback.measure(SEED_PHRASE_LOADED) {
val lockBox = LockBox(ZcashWalletApp.instance)
val mnemonics = Mnemonics()
val seedPhrase = lockBox.getCharsUtf8(LockBoxKey.SEED_PHRASE)!!
val result = mnemonics.toWordList(seedPhrase)
result
}
}
}

View File

@ -13,28 +13,28 @@
<!-- TODO: redo these keylines to match the designs, exactly -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guieline_bottom_buttons"
android:id="@+id/guideline_bottom_buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.7017784" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guieline_keyline_start"
android:id="@+id/guideline_keyline_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.054" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guieline_keyline_end"
android:id="@+id/guideline_keyline_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.946" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guieline_keyline_bottom"
android:id="@+id/guideline_keyline_bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
@ -73,7 +73,7 @@
android:layout_height="wrap_content"
android:text="Backup\nWallet"
android:textColor="@color/text_light"
app:layout_constraintEnd_toEndOf="@id/guieline_keyline_end"
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
app:layout_constraintTop_toTopOf="@id/back_button" />
<View
@ -81,10 +81,10 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/background_banner_large"
app:layout_constraintEnd_toEndOf="@id/guieline_keyline_end"
app:layout_constraintStart_toStartOf="@id/guieline_keyline_start"
app:layout_constraintTop_toBottomOf="@id/guieline_bottom_buttons"
app:layout_constraintBottom_toBottomOf="@id/guieline_keyline_bottom"/>
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
app:layout_constraintStart_toStartOf="@id/guideline_keyline_start"
app:layout_constraintTop_toBottomOf="@id/guideline_bottom_buttons"
app:layout_constraintBottom_toBottomOf="@id/guideline_keyline_bottom"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/button_feedback"
@ -97,8 +97,8 @@
android:layout_marginStart="24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/guieline_keyline_start"
app:layout_constraintEnd_toEndOf="@id/guieline_keyline_end"
app:layout_constraintStart_toStartOf="@id/guideline_keyline_start"
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
app:layout_constraintVertical_bias="0.8"/>
<com.google.android.material.button.MaterialButton
@ -109,7 +109,7 @@
android:text="View Logs"
android:textColor="@color/text_light"
app:layout_constraintTop_toBottomOf="@id/button_feedback"
app:layout_constraintBottom_toBottomOf="@id/guieline_keyline_bottom"
app:layout_constraintBottom_toBottomOf="@id/guideline_keyline_bottom"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintVertical_bias="0.2"/>

View File

@ -9,7 +9,7 @@ interface MnemonicProvider {
/**
* Generate a random seed.
*/
fun nextSeed(): ByteArray
fun nextEntropy(): ByteArray
/**
* Generate a random 24-word mnemonic phrase.

View File

@ -12,19 +12,19 @@ import javax.inject.Inject
// which expects a string so for that reason, we just use Strings here)
class Mnemonics @Inject constructor(): MnemonicProvider {
override fun nextSeed(): ByteArray {
override fun nextEntropy(): ByteArray {
return ByteArray(Words.TWENTY_FOUR.byteLength()).apply {
SecureRandom().nextBytes(this)
}
}
override fun nextMnemonic(): CharArray {
return nextMnemonic(nextSeed())
return nextMnemonic(nextEntropy())
}
override fun nextMnemonic(seed: ByteArray): CharArray {
override fun nextMnemonic(entropy: ByteArray): CharArray {
return StringBuilder().let { builder ->
MnemonicGenerator(English.INSTANCE).createMnemonic(seed) { c ->
MnemonicGenerator(English.INSTANCE).createMnemonic(entropy) { c ->
builder.append(c)
}
builder.toString().toCharArray()
@ -32,12 +32,12 @@ class Mnemonics @Inject constructor(): MnemonicProvider {
}
override fun nextMnemonicList(): List<CharArray> {
return nextMnemonicList(nextSeed())
return nextMnemonicList(nextEntropy())
}
override fun nextMnemonicList(seed: ByteArray): List<CharArray> {
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> {
return WordListBuilder().let { builder ->
MnemonicGenerator(English.INSTANCE).createMnemonic(seed) { c ->
MnemonicGenerator(English.INSTANCE).createMnemonic(entropy) { c ->
builder.append(c)
}
builder.wordList
@ -46,7 +46,7 @@ class Mnemonics @Inject constructor(): MnemonicProvider {
override fun toSeed(mnemonic: CharArray): ByteArray {
// TODO: either find another library that allows for doing this without strings or modify this code to leverage SecureCharBuffer (which doesn't work well with SeedCalculator.calculateSeed, which expects a string so for that reason, we just use Strings here)
return SeedCalculator().calculateSeed(mnemonic.toString(), "")
return SeedCalculator().calculateSeed(String(mnemonic), "")
}
override fun toWordList(mnemonic: CharArray): List<CharArray> {