Skip to content

Commit 2263b10

Browse files
committed
feat: seed phrase backup page
1 parent 9563aec commit 2263b10

File tree

15 files changed

+349
-169
lines changed

15 files changed

+349
-169
lines changed

app/src/main/java/com/flowfoundation/wallet/manager/account/AccountManager.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import com.flowfoundation.wallet.utils.storeWalletPassword
5252
import com.flowfoundation.wallet.manager.walletdata.WalletDataManager
5353

5454
import com.flowfoundation.wallet.manager.walletdata.MainWallet
55+
import com.flowfoundation.wallet.page.restore.keystore.model.KeystoreAddress
5556
import kotlin.text.isNullOrEmpty
5657

5758
object AccountManager {
@@ -226,6 +227,16 @@ object AccountManager {
226227
return get()?.walletNodes
227228
}
228229

230+
fun encryptedMnemonic(): String? {
231+
val account = currentAccount
232+
if (account == null) {
233+
logd(TAG, "No active account found")
234+
return null
235+
}
236+
val keyStoreAddress = Gson().fromJson(account.keyStoreInfo, KeystoreAddress::class.java)
237+
return keyStoreAddress?.encryptedMnemonic
238+
}
239+
229240
fun removeCurrentAccount() {
230241
logd(TAG, "removeCurrentAccount() called - performing comprehensive state reset")
231242
ioScope {

app/src/main/java/com/flowfoundation/wallet/page/account/AccountListActivity.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ fun AccountListScreen(
133133
Icon(
134134
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
135135
contentDescription = "Back",
136-
tint = colorResource(id = R.color.icon)
136+
tint = colorResource(id = R.color.text)
137137
)
138138
}
139139
},
@@ -142,15 +142,15 @@ fun AccountListScreen(
142142
Icon(
143143
imageVector = Icons.Default.Add,
144144
contentDescription = "Add Profile",
145-
tint = colorResource(id = R.color.icon)
145+
tint = colorResource(id = R.color.text)
146146
)
147147
}
148148
},
149149
colors = TopAppBarDefaults.topAppBarColors(
150150
containerColor = colorResource(id = R.color.background),
151151
titleContentColor = colorResource(id = R.color.text),
152-
navigationIconContentColor = colorResource(id = R.color.icon),
153-
actionIconContentColor = colorResource(id = R.color.icon)
152+
navigationIconContentColor = colorResource(id = R.color.text),
153+
actionIconContentColor = colorResource(id = R.color.text)
154154
)
155155
)
156156
}

app/src/main/java/com/flowfoundation/wallet/page/profile/ProfileFragmentViewModel.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@ import com.flowfoundation.wallet.utils.viewModelIOScope
1313
class ProfileFragmentViewModel : ViewModel() {
1414

1515
val profileLiveData = MutableLiveData<UserInfoData>()
16-
val inboxCountLiveData = MutableLiveData<Int>()
1716

1817
fun load() {
1918
viewModelIOScope(this) {
2019
requestUserInfo()
21-
// requestInboxCount()
2220
}
2321
}
2422

@@ -39,4 +37,4 @@ class ProfileFragmentViewModel : ViewModel() {
3937
loge(e)
4038
}
4139
}
42-
}
40+
}

app/src/main/java/com/flowfoundation/wallet/page/profile/compose/SettingScreen.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ fun SettingScreen(
5959

6060
// Observe user info
6161
val userInfo by viewModel.profileLiveData.observeAsState()
62-
val inboxCount by viewModel.inboxCountLiveData.observeAsState(0)
6362

6463
// State variables
6564
var isSignedIn by remember { mutableStateOf(false) }

app/src/main/java/com/flowfoundation/wallet/page/profile/subpage/wallet/WalletSettingActivity.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@ class WalletSettingActivity : BaseActivity(), OnEmojiUpdate {
7474
securityOpen(SecurityPrivateKeyActivity.launchIntent(this@WalletSettingActivity))
7575
}
7676
} else if (CryptoProviderManager.getCurrentCryptoProvider() is PrivateKeyStoreCryptoProvider) {
77-
llRecoveryLayout.gone()
77+
if (AccountManager.encryptedMnemonic().isNullOrBlank()) {
78+
llRecoveryLayout.gone()
79+
} else {
80+
llRecoveryLayout.visible()
81+
securityOpen(SecurityRecoveryActivity.launchIntent(this@WalletSettingActivity))
82+
}
7883
privatePreference.setOnClickListener {
7984
securityOpen(SecurityPrivateKeyActivity.launchIntent(this@WalletSettingActivity))
8085
}
@@ -172,4 +177,4 @@ class WalletSettingActivity : BaseActivity(), OnEmojiUpdate {
172177
}
173178
}
174179

175-
}
180+
}

app/src/main/java/com/flowfoundation/wallet/page/restore/keystore/PrivateKeyStoreCryptoProvider.kt

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import com.flow.wallet.keys.PrivateKey
55
import com.flow.wallet.keys.KeyFormat
66
import com.flowfoundation.wallet.page.restore.keystore.model.KeystoreAddress
77
import com.google.gson.Gson
8-
import com.google.gson.JsonObject
98
import org.onflow.flow.models.DomainTag
109
import org.onflow.flow.models.HashingAlgorithm
1110
import org.onflow.flow.models.SigningAlgorithm
@@ -20,8 +19,8 @@ fun List<Byte>.toHexString(): String = joinToString("") { "%02x".format(it) }
2019

2120
class PrivateKeyStoreCryptoProvider(private val keystoreInfo: String) : CryptoProvider {
2221
private val TAG = "PrivateKeyStoreCryptoProvider"
23-
private val keyInfo: JsonObject = Gson().fromJson(keystoreInfo, JsonObject::class.java)
24-
private val signingAlgorithm = when (keyInfo.get("signAlgo").asInt) {
22+
private val keyInfo: KeystoreAddress = Gson().fromJson(keystoreInfo, KeystoreAddress::class.java)
23+
private val signingAlgorithm = when (keyInfo.signAlgo) {
2524
1 -> SigningAlgorithm.ECDSA_P256
2625
2 -> SigningAlgorithm.ECDSA_secp256k1
2726
else -> SigningAlgorithm.ECDSA_P256
@@ -39,15 +38,15 @@ class PrivateKeyStoreCryptoProvider(private val keystoreInfo: String) : CryptoPr
3938
}
4039

4140
init {
42-
logd(TAG, "Init keystore provider. signAlgo=${keyInfo.get("signAlgo").asInt}, hashAlgo=${keyInfo.get("hashAlgo").asInt}, address=${keyInfo.get("address").asString}")
41+
logd(TAG, "Init keystore provider. signAlgo=${keyInfo.signAlgo}, hashAlgo=${keyInfo.hashAlgo}, address=${keyInfo.address}")
4342
logd(TAG, "KeyStore info details:")
44-
logd(TAG, " Address: ${keyInfo.get("address").asString}")
45-
logd(TAG, " Public Key (from keystore): ${keyInfo.get("publicKey").asString}")
43+
logd(TAG, " Address: ${keyInfo.address}")
44+
logd(TAG, " Public Key (from keystore): ${keyInfo.publicKey}")
4645
logd(TAG, " Private Key loaded successfully")
47-
logd(TAG, " Sign Algorithm: ${keyInfo.get("signAlgo").asInt} -> $signingAlgorithm")
48-
logd(TAG, " Hash Algorithm: ${keyInfo.get("hashAlgo").asInt}")
49-
logd(TAG, " Key ID: ${keyInfo.get("keyId").asInt}")
50-
logd(TAG, " Weight: ${keyInfo.get("weight").asInt}")
46+
logd(TAG, " Sign Algorithm: ${keyInfo.signAlgo} -> $signingAlgorithm")
47+
logd(TAG, " Hash Algorithm: ${keyInfo.hashAlgo}")
48+
logd(TAG, " Key ID: ${keyInfo.keyId}")
49+
logd(TAG, " Weight: ${keyInfo.weight}")
5150

5251
// Verify keystore consistency: check if private key generates the same public key
5352
verifyKeystoreConsistency()
@@ -56,7 +55,7 @@ class PrivateKeyStoreCryptoProvider(private val keystoreInfo: String) : CryptoPr
5655
private fun verifyKeystoreConsistency() {
5756
try {
5857
logd(TAG, "=== KEYSTORE CONSISTENCY VERIFICATION ===")
59-
val keystorePublicKey = keyInfo.get("publicKey").asString
58+
val keystorePublicKey = keyInfo.publicKey
6059
logd(TAG, "Keystore stored public key: $keystorePublicKey")
6160

6261
// Get the public key derived from the private key
@@ -108,7 +107,7 @@ class PrivateKeyStoreCryptoProvider(private val keystoreInfo: String) : CryptoPr
108107
}
109108

110109
override fun getPublicKey(): String {
111-
val rawPublicKey = keyInfo.get("publicKey").asString
110+
val rawPublicKey = keyInfo.publicKey
112111
// Match the format used in keystore restore: remove "04" prefix if present, no "0x" prefix for server
113112
val formattedPublicKey = if (rawPublicKey.startsWith("04")) {
114113
rawPublicKey.substring(2)
@@ -171,8 +170,8 @@ class PrivateKeyStoreCryptoProvider(private val keystoreInfo: String) : CryptoPr
171170
override fun getSigner(hashingAlgorithm: HashingAlgorithm): org.onflow.flow.models.Signer {
172171
logd(TAG, "getSigner() called with hashingAlgorithm: $hashingAlgorithm")
173172
return object : org.onflow.flow.models.Signer {
174-
override var address: String = keyInfo.get("address").asString
175-
override var keyIndex: Int = keyInfo.get("keyId").asInt
173+
override var address: String = keyInfo.address
174+
override var keyIndex: Int = keyInfo.keyId
176175

177176
override suspend fun sign(bytes: ByteArray, transaction: Transaction?): ByteArray {
178177
logd(TAG, "*** KEYSTORE SIGNER: sign() called - TRUSTWALLET CORE ***")
@@ -207,7 +206,7 @@ class PrivateKeyStoreCryptoProvider(private val keystoreInfo: String) : CryptoPr
207206
}
208207

209208
override fun getHashAlgorithm(): HashingAlgorithm {
210-
val code = keyInfo.get("hashAlgo").asInt
209+
val code = keyInfo.hashAlgo
211210
val algo = when (code) {
212211
1 -> HashingAlgorithm.SHA2_256 // SHA2-256
213212
2 -> HashingAlgorithm.SHA2_256 // Legacy value for SHA2-256
@@ -223,6 +222,6 @@ class PrivateKeyStoreCryptoProvider(private val keystoreInfo: String) : CryptoPr
223222
}
224223

225224
override fun getKeyWeight(): Int {
226-
return keyInfo.get("weight").asInt
225+
return keyInfo.weight
227226
}
228227
}

app/src/main/java/com/flowfoundation/wallet/page/security/recovery/SecurityRecoveryActivity.kt

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,19 @@ import com.flowfoundation.wallet.wallet.Wallet
2020
import com.flowfoundation.wallet.widgets.itemdecoration.GridSpaceItemDecoration
2121
import kotlinx.coroutines.Dispatchers
2222
import kotlinx.coroutines.withContext
23+
import com.flowfoundation.wallet.manager.key.CryptoProviderManager
24+
import com.flowfoundation.wallet.page.restore.keystore.PrivateKeyStoreCryptoProvider
25+
import com.flowfoundation.wallet.manager.account.AccountManager
26+
import com.google.gson.Gson
27+
import com.flowfoundation.wallet.page.restore.keystore.model.KeystoreAddress
28+
import com.flowfoundation.wallet.utils.secret.EncryptedMnemonicUtils
29+
import com.flowfoundation.wallet.firebase.auth.firebaseUid
30+
import com.flowfoundation.wallet.utils.logd
2331

2432
class SecurityRecoveryActivity : BaseActivity() {
2533

34+
private val TAG = "SecurityRecoveryActivity"
35+
2636
private lateinit var binding: ActivitySecurityRecoveryBinding
2737

2838
private val adapter by lazy { MnemonicAdapter() }
@@ -52,21 +62,59 @@ class SecurityRecoveryActivity : BaseActivity() {
5262
Instabug.addPrivateViews(this)
5363
}
5464
loadMnemonic()
55-
binding.stringContainer.setVisible(false)
56-
binding.copyButton.setOnClickListener { copyToClipboard(Wallet.store().mnemonic()) }
5765
}
5866

5967
private fun loadMnemonic() {
6068
ioScope {
61-
val str = Wallet.store().mnemonic()
69+
val str = getMnemonicFromProvider()
6270
withContext(Dispatchers.Main) {
63-
val list = str.split(" ").mapIndexed { index, s -> MnemonicModel(index + 1, s) }
64-
val result = mutableListOf<MnemonicModel>()
65-
(0 until list.size / 2).forEach { i ->
66-
result.add(list[i])
67-
result.add(list[i + list.size / 2])
71+
if (str.isNotBlank()) {
72+
val list = str.split(" ").mapIndexed { index, s -> MnemonicModel(index + 1, s) }
73+
val result = mutableListOf<MnemonicModel>()
74+
// Retain existing list formatting logic
75+
(0 until list.size / 2).forEach { i ->
76+
result.add(list[i])
77+
result.add(list[i + list.size / 2])
78+
}
79+
adapter.setNewDiffData(result)
80+
binding.mnemonicContainer.setVisible(true)
81+
binding.copyButton.setOnClickListener { copyToClipboard(str) } // Update listener
82+
} else {
83+
// Handle case where mnemonic is not found or decryption failed
84+
finish()
6885
}
69-
adapter.setNewDiffData(result)
86+
}
87+
}
88+
}
89+
90+
private fun getMnemonicFromProvider(): String {
91+
val cryptoProvider = CryptoProviderManager.getCurrentCryptoProvider()
92+
return when (cryptoProvider) {
93+
is PrivateKeyStoreCryptoProvider -> {
94+
try {
95+
val encrypted = AccountManager.encryptedMnemonic()
96+
if (!encrypted.isNullOrBlank()) {
97+
val uid = firebaseUid()
98+
if (!uid.isNullOrBlank()) {
99+
EncryptedMnemonicUtils.decrypt(encrypted, uid) ?: ""
100+
} else {
101+
logd(TAG, "getMnemonicFromProvider: No Firebase UID available for decryption.")
102+
""
103+
}
104+
} else {
105+
logd(TAG, "getMnemonicFromProvider: No encrypted mnemonic found in keystoreInfo.")
106+
""
107+
}
108+
} catch (e: Exception) {
109+
logd(TAG, "getMnemonicFromProvider: Error parsing or decrypting keystoreInfo: ${e.message}")
110+
""
111+
}
112+
}
113+
else -> {
114+
// Fallback to Wallet.store() for HDWalletCryptoProvider and others
115+
val mnemonic = Wallet.store().mnemonic()
116+
logd(TAG, "getMnemonicFromProvider: Falling back to Wallet.store().mnemonic(). Is blank: ${mnemonic.isBlank()}")
117+
mnemonic
70118
}
71119
}
72120
}
@@ -90,4 +138,4 @@ class SecurityRecoveryActivity : BaseActivity() {
90138

91139
fun launchIntent(context: Context): Intent = Intent(context, SecurityRecoveryActivity::class.java)
92140
}
93-
}
141+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<shape xmlns:android="http://schemas.android.com/apk/res/android">
3-
<solid android:color="@color/warning5"/>
3+
<solid android:color="@color/error_15"/>
44
<corners android:radius="12dp"/>
5-
</shape>
5+
</shape>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:viewportWidth="24"
5+
android:viewportHeight="24">
6+
<path
7+
android:pathData="M4.012,16.737C3.705,16.562 3.45,16.309 3.273,16.004C3.095,15.7 3.001,15.353 3,15V5C3,3.9 3.9,3 5,3H15C15.75,3 16.158,3.385 16.5,4M7,9.667C7,8.96 7.281,8.281 7.781,7.781C8.281,7.281 8.96,7 9.667,7H18.333C18.683,7 19.03,7.069 19.354,7.203C19.677,7.337 19.971,7.533 20.219,7.781C20.466,8.029 20.663,8.323 20.797,8.646C20.931,8.97 21,9.317 21,9.667V18.333C21,18.683 20.931,19.03 20.797,19.354C20.663,19.677 20.466,19.971 20.219,20.219C19.971,20.466 19.677,20.663 19.354,20.797C19.03,20.931 18.683,21 18.333,21H9.667C9.317,21 8.97,20.931 8.646,20.797C8.323,20.663 8.029,20.466 7.781,20.219C7.533,19.971 7.337,19.677 7.203,19.354C7.069,19.03 7,18.683 7,18.333V9.667Z"
8+
android:strokeAlpha="1"
9+
android:strokeLineJoin="round"
10+
android:strokeWidth="2"
11+
android:fillColor="#00000000"
12+
android:strokeColor="@color/accent_green"
13+
android:strokeLineCap="round"/>
14+
</vector>

0 commit comments

Comments
 (0)