Skip to content

Commit

Permalink
Support deriving addresses from xpubkey #32
Browse files Browse the repository at this point in the history
  • Loading branch information
MrStahlfelge committed Feb 15, 2022
1 parent fc43d22 commit 67388b6
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,13 @@ class WalletAddressesFragment : AbstractAuthenticationFragment() {
val walletConfig = viewModel.wallet?.walletConfig
binding.buttonAddAddress.setOnClickListener {
viewModel.numAddressesToAdd = getNumAddressesToAdd()
walletConfig?.let {
startAuthFlow(it)
walletConfig?.let { walletConfig ->
walletConfig.secretStorage?.let {
startAuthFlow(walletConfig)
} ?: viewModel.addNextAddresses(requireContext(), null)
}
}
binding.buttonAddAddress.isEnabled = walletConfig?.secretStorage != null
binding.buttonAddAddress.isEnabled = viewModel.uiLogic.canDeriveAddresses()
binding.sliderNumAddresses.progress = 0
refreshAddButtonLabel()
binding.sliderNumAddresses.setOnSeekBarChangeListener(object :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@ class WalletAddressesViewModel : ViewModel() {
}


private fun addNextAddresses(ctx: Context, number: Int, mnemonic: String) {
fun addNextAddresses(ctx: Context, mnemonic: String?) {
uiLogic.addNextAddresses(
RoomWalletDbProvider(AppDatabase.getInstance(ctx)),
Preferences(ctx), number, mnemonic
Preferences(ctx), numAddressesToAdd, mnemonic
)
}

fun addAddressWithBiometricAuth(ctx: Context) {
wallet?.walletConfig?.secretStorage?.let {
val decryptData = AndroidEncryptionManager.decryptDataWithDeviceKey(it)
deserializeSecrets(String(decryptData!!))?.let { mnemonic ->
addNextAddresses(ctx, numAddressesToAdd, mnemonic)
addNextAddresses(ctx, mnemonic)
}
}
}
Expand All @@ -54,7 +54,7 @@ class WalletAddressesViewModel : ViewModel() {
try {
val decryptData = AesEncryptionManager.decryptData(password, it)
deserializeSecrets(String(decryptData!!))?.let { mnemonic ->
addNextAddresses(ctx, numAddressesToAdd, mnemonic)
addNextAddresses(ctx, mnemonic)
return true
}
} catch (t: Throwable) {
Expand Down
2 changes: 1 addition & 1 deletion android/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<string name="label_import_wallet">Import wallet</string>
<string name="desc_import_wallet">Use an exported or saved wallet file to import an existing wallet (keys and addresses).</string>
<string name="label_readonly_wallet">Read-only wallet</string>
<string name="desc_readonly_wallet">Add a wallet address in read-only mode. This won\'t save any keys on this device.</string>
<string name="desc_readonly_wallet">Add a wallet address in read-only mode. This won\'t save any secrets on this device.</string>

<!-- add readonly wallet -->
<string name="intro_add_readonly">Enter your public wallet address or your extended public key to add the
Expand Down
15 changes: 15 additions & 0 deletions common-jvm/src/main/java/org/ergoplatform/ErgoFacade.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.ergoplatform.uilogic.StringProvider
import org.ergoplatform.utils.LogUtils
import org.ergoplatform.wallet.boxes.`ErgoBoxSerializer$`
import org.ergoplatform.wallet.mnemonic.WordList
import org.ergoplatform.wallet.secrets.ExtendedPublicKey
import scala.collection.JavaConversions
import sigmastate.serialization.`SigmaSerializer$`

Expand Down Expand Up @@ -90,6 +91,20 @@ fun getPublicErgoAddressFromMnemonic(mnemonic: SecretString, index: Int = 0): St
).ergoAddress.toString()
}

fun getPublicErgoAddressFromXPubKey(xPubKey: ExtendedPublicKey, index: Int = 0): String {
return Address.createEip3Address(
index,
getErgoNetworkType(),
xPubKey
).ergoAddress.toString()
}

fun deserializeExtendedPublicKeySafe(serializedKey: String) = try {
Bip32Serialization.parseExtendedPublicKeyFromHex(serializedKey, getErgoNetworkType())
} catch (t: Throwable) {
null
}

/**
* loads the word list used to generate new mnemonics into a list
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ package org.ergoplatform.uilogic.wallet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.ergoplatform.NodeConnector
import org.ergoplatform.appkit.Address
import org.ergoplatform.appkit.Bip32Serialization
import org.ergoplatform.getErgoNetworkType
import org.ergoplatform.isValidErgoAddress
import org.ergoplatform.parsePaymentRequest
import org.ergoplatform.*
import org.ergoplatform.persistance.WalletConfig
import org.ergoplatform.persistance.WalletDbProvider
import org.ergoplatform.uilogic.*
import org.ergoplatform.uilogic.STRING_ERROR_ADDRESS_ALREADY_ADDED
import org.ergoplatform.uilogic.STRING_ERROR_INVALID_READONLY_INPUT
import org.ergoplatform.uilogic.STRING_LABEL_READONLY_WALLET_DEFAULT
import org.ergoplatform.uilogic.StringProvider

abstract class AddReadOnlyWalletUiLogic {
suspend fun addWalletToDb(
Expand All @@ -20,14 +18,10 @@ abstract class AddReadOnlyWalletUiLogic {
stringProvider: StringProvider,
displayName: String?
): Boolean {
val xpubkey = try {
Bip32Serialization.parseExtendedPublicKeyFromHex(userInput, getErgoNetworkType())
} catch (t: Throwable) {
null
}
val xpubkey = deserializeExtendedPublicKeySafe(userInput)

val walletAddress = xpubkey?.let {
Address.createEip3Address(0, getErgoNetworkType(), xpubkey).toString()
getPublicErgoAddressFromXPubKey(xpubkey)
} ?: userInput

// check for valid address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.ergoplatform.NodeConnector
import org.ergoplatform.deserializeExtendedPublicKeySafe
import org.ergoplatform.getPublicErgoAddressFromMnemonic
import org.ergoplatform.getPublicErgoAddressFromXPubKey
import org.ergoplatform.persistance.PreferencesProvider
import org.ergoplatform.persistance.Wallet
import org.ergoplatform.persistance.WalletAddress
Expand Down Expand Up @@ -44,13 +46,17 @@ abstract class WalletAddressesUiLogic {
database: WalletDbProvider,
prefs: PreferencesProvider,
number: Int,
mnemonic: String
mnemonic: String?
) {
// firing up appkit for the first time needs some time on medium end devices, so do this on
// background thread while showing infinite progress bar
coroutineScope.launch(Dispatchers.IO) {
val sortedAddresses = addresses
wallet?.let { wallet ->
val xpubkey = wallet.walletConfig.extendedPublicKey?.let {
deserializeExtendedPublicKeySafe(it)
}

val addedAddresses = mutableListOf<String>()

var nextIdx = 0
Expand All @@ -66,7 +72,15 @@ abstract class WalletAddressesUiLogic {
}

// okay, we have the next address idx - now get the address
val nextAddress = getPublicErgoAddressFromMnemonic(mnemonic, nextIdx)
// we either have the mnemonic or the xpubkey (-> canDeriveAddresses() )
val nextAddress = mnemonic?.let {
getPublicErgoAddressFromMnemonic(
mnemonic,
nextIdx
)
} ?: xpubkey?.let {
getPublicErgoAddressFromXPubKey(it, nextIdx)
}!!

// this address could be already added as a read only address - delete it
database.deleteWalletConfigAndStates(nextAddress)
Expand All @@ -89,6 +103,10 @@ abstract class WalletAddressesUiLogic {
}
}

fun canDeriveAddresses(): Boolean {
return wallet?.walletConfig?.secretStorage != null || wallet?.walletConfig?.extendedPublicKey != null
}

abstract fun notifyNewAddresses()
abstract fun notifyUiLocked(locked: Boolean)

Expand Down
2 changes: 1 addition & 1 deletion ios/resources/i18n/strings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ with your signing device to proceed.\n\nWhen present, scan the signed transactio
sent it to the network.
desc_prompt_signing_multiple=Your transaction was prepared. Scan these QR codes \
with your signing device to proceed.
desc_readonly_wallet=Add a wallet address in read-only mode. This won\'t save any keys on this device.
desc_readonly_wallet=Add a wallet address in read-only mode. This won\'t save any secrets on this device.
desc_restore_wallet=Restores a wallet\'s keys and address from a mnemonic phrase.
desc_save_device_encrypted=Authenticate on your device to save the wallet and send funds.\nCurrent authentication method: {0}
desc_save_keychain=Authenticate on your device to send funds.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,12 @@ class WalletAddressesViewController(private val walletId: Int) : CoroutineViewCo

addButton = PrimaryButton(texts.get(STRING_BUTTON_ADD_ADDRESS))
addButton.addOnTouchUpInsideListener { _, _ ->
parentVc?.uiLogic?.wallet?.walletConfig?.let {
parentVc?.startAuthFlow(it) { mnemonic ->
LogUtils.logDebug("WalletAddressesVc", "Adding $addrCount addresses")
parentVc!!.uiLogic.addNextAddresses(
getAppDelegate().database,
getAppDelegate().prefs, addrCount, mnemonic
)
}
parentVc?.uiLogic?.wallet?.walletConfig?.let { walletConfig ->
walletConfig.secretStorage?.let {
parentVc?.startAuthFlow(walletConfig) { mnemonic ->
addAddresses(mnemonic)
}
} ?: addAddresses(null)
}
}

Expand All @@ -186,6 +184,15 @@ class WalletAddressesViewController(private val walletId: Int) : CoroutineViewCo

}

private fun addAddresses(mnemonic: String?) {
LogUtils.logDebug("WalletAddressesVc", "Adding $addrCount addresses")
val appDelegate = getAppDelegate()
parentVc!!.uiLogic.addNextAddresses(
appDelegate.database,
appDelegate.prefs, addrCount, mnemonic
)
}

private fun refreshButtonText(texts: I18NBundle) {
addButton.setTitle(
if (addrCount == 1) texts.get(STRING_BUTTON_ADD_ADDRESS)
Expand All @@ -195,7 +202,7 @@ class WalletAddressesViewController(private val walletId: Int) : CoroutineViewCo
}

fun bind(vc: WalletAddressesViewController) {
addButton.isEnabled = vc.uiLogic.wallet?.walletConfig?.secretStorage != null
addButton.isEnabled = vc.uiLogic.canDeriveAddresses()
this.parentVc = vc
}
}
Expand Down

0 comments on commit 67388b6

Please sign in to comment.