diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 455a6b973d..449e1735ff 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -15,15 +15,16 @@ import com.tangem.common.core.Config import com.tangem.common.core.TangemSdkError import com.tangem.common.hdWallet.DerivationPath import com.tangem.operations.CommandResponse -import com.tangem.operations.derivation.DeriveWalletPublicKeysTask -import com.tangem.operations.derivation.ExtendedPublicKeyList import com.tangem.operations.pins.CheckUserCodesCommand import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.operations.pins.SetUserCodeCommand import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.AnalyticsHandler import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler +import com.tangem.tap.common.extensions.ByteArrayKey import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask +import com.tangem.tap.domain.tasks.DerivationTask +import com.tangem.tap.domain.tasks.DerivationTaskResponse import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tasks.product.ScanResponse @@ -77,10 +78,9 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun derivePublicKeys( cardId: String, - walletPublicKey: ByteArray, - derivationPaths: List - ): CompletionResult { - return runTaskAsyncReturnOnMain(DeriveWalletPublicKeysTask(walletPublicKey, derivationPaths), cardId) + derivations: Map> + ): CompletionResult { + return runTaskAsyncReturnOnMain(DerivationTask(derivations), cardId) } suspend fun resetToFactorySettings(card: Card): CompletionResult { diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt index 5ec3ea7672..0563eb8482 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt @@ -2,6 +2,7 @@ package com.tangem.tap.domain.extensions import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token +import com.tangem.common.card.EllipticCurve import java.math.BigDecimal @@ -25,4 +26,18 @@ fun Blockchain.minimalAmount(): BigDecimal { return 1.toBigDecimal().movePointLeft(decimals()) } +fun Blockchain.getPrimaryCurve(): EllipticCurve? { + return when { + getSupportedCurves().contains(EllipticCurve.Secp256k1) -> { + EllipticCurve.Secp256k1 + } + getSupportedCurves().contains(EllipticCurve.Ed25519) -> { + EllipticCurve.Ed25519 + } + else -> { + null + } + } +} + private const val NODL = "NODL" \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt new file mode 100644 index 0000000000..8f03e068b5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt @@ -0,0 +1,50 @@ +package com.tangem.tap.domain.tasks + +import com.tangem.common.CompletionResult +import com.tangem.common.core.CardSession +import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.core.CompletionCallback +import com.tangem.common.hdWallet.DerivationPath +import com.tangem.operations.CommandResponse +import com.tangem.operations.derivation.DeriveWalletPublicKeysTask +import com.tangem.operations.derivation.ExtendedPublicKeyList +import com.tangem.tap.common.extensions.ByteArrayKey + +class DerivationTaskResponse( + val entries: Map +): CommandResponse + +class DerivationTask( + private val derivations: Map> +) : CardSessionRunnable { + + val response: MutableMap = mutableMapOf() + + override fun run(session: CardSession, callback: CompletionCallback) { + derive(keys = derivations.keys.toList(), index = 0, session = session, callback = callback) + } + + private fun derive( + keys: List, + index: Int, + session: CardSession, + callback: CompletionCallback + ) { + if (index == keys.count()) { + callback(CompletionResult.Success(DerivationTaskResponse(response.toMap()))) + return + } + + val key = keys[index] + val paths = derivations[key]!! + DeriveWalletPublicKeysTask(key.bytes, paths).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + response[key] = result.data + derive(keys = keys, index = index + 1, session = session, callback = callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index 25e3d98836..89bdb12dd2 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -10,12 +10,12 @@ import com.tangem.common.hdWallet.ExtendedPublicKey import com.tangem.operations.CommandResponse import com.tangem.operations.backup.PrimaryCard import com.tangem.operations.backup.StartPrimaryCardLinkingTask -import com.tangem.operations.derivation.DeriveWalletPublicKeysTask import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask import com.tangem.tap.common.extensions.toMapKey import com.tangem.tap.domain.ProductType import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain +import com.tangem.tap.domain.tasks.DerivationTask import com.tangem.tap.domain.tasks.product.CreateWalletsTask import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey import com.tangem.tap.domain.tasks.product.ProductCommandProcessor @@ -170,16 +170,15 @@ private class CreateWalletTangemWallet : ProductCommandProcessor when (result) { is CompletionResult.Success -> { - val derivedKeys = mapOf(response.wallet.publicKey.toMapKey() to result.data) callback( CompletionResult.Success( CreateProductWalletTaskResponse( card = session.environment.card!!, - derivedKeys = derivedKeys, + derivedKeys = result.data.entries, primaryCard = primaryCard ) ) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 40c7a00ded..506f5aa9c3 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -22,7 +22,6 @@ import com.tangem.operations.PreflightReadTask import com.tangem.operations.ScanTask import com.tangem.operations.backup.PrimaryCard import com.tangem.operations.backup.StartPrimaryCardLinkingTask -import com.tangem.operations.derivation.DeriveWalletPublicKeysTask import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand import com.tangem.tap.common.extensions.ByteArrayKey import com.tangem.tap.common.extensions.toMapKey @@ -32,7 +31,9 @@ import com.tangem.tap.domain.TapWorkarounds import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain import com.tangem.tap.domain.TapWorkarounds.isExcluded import com.tangem.tap.domain.TapWorkarounds.isNotSupportedInThatRelease +import com.tangem.tap.domain.extensions.getPrimaryCurve import com.tangem.tap.domain.extensions.getSingleWallet +import com.tangem.tap.domain.tasks.DerivationTask import com.tangem.tap.domain.tokens.CurrenciesRepository import com.tangem.tap.domain.twins.TwinsHelper import com.tangem.tap.preferencesStorage @@ -237,10 +238,8 @@ private class ScanWalletProcessor( session: CardSession, callback: (result: CompletionResult) -> Unit ) { - val derivationPaths = collectDerivationPaths(card)?.distinct() - val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } - - if (derivationPaths.isNullOrEmpty() || wallet == null || wallet.chainCode == null) { + val derivations = collectDerivations(card) + if (derivations.isEmpty()) { callback( CompletionResult.Success( ScanResponse( @@ -254,15 +253,14 @@ private class ScanWalletProcessor( return } - DeriveWalletPublicKeysTask(wallet.publicKey, derivationPaths).run(session) { result -> + DerivationTask(derivations).run(session) { result -> when (result) { is CompletionResult.Success -> { - val derivedKeys = mapOf(wallet.publicKey.toMapKey() to result.data) val response = ScanResponse( card = card, productType = ProductType.Wallet, walletData = session.environment.walletData, - derivedKeys = derivedKeys, + derivedKeys = result.data.entries, primaryCard = primaryCard ) callback(CompletionResult.Success(response)) @@ -273,8 +271,8 @@ private class ScanWalletProcessor( } } - private fun collectDerivationPaths(card: Card): List? { - val currenciesRepository = currenciesRepository ?: return null + private fun getBlockchainsToDerive(card: Card): List { + val currenciesRepository = currenciesRepository ?: return emptyList() val cardCurrencies = currenciesRepository.loadCardCurrencies(card.cardId) val blockchainsToDerive = if (cardCurrencies == null) { @@ -293,10 +291,31 @@ private class ScanWalletProcessor( ) ) } + return blockchainsToDerive.distinct() + } - return blockchainsToDerive.toSet() - .filter { it.getSupportedCurves()?.contains(EllipticCurve.Secp256k1) == true } - .mapNotNull { it.derivationPath() } + private fun collectDerivations(card: Card): Map> { + val blockchains = getBlockchainsToDerive(card) + val derivations = mutableMapOf>() + + blockchains.forEach { blockchain -> + val curve = blockchain.getPrimaryCurve() + + val wallet = card.wallets.firstOrNull { it.curve == curve } ?: return@forEach + if (wallet.chainCode == null) return@forEach + + val key = wallet.publicKey.toMapKey() + val path = blockchain.derivationPath() + if (path != null) { + val addedDerivations = derivations[key] + if (addedDerivations != null) { + derivations[key] = addedDerivations + path + } else { + derivations[key] = listOf(path) + } + } + } + return derivations } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt index bcf0bf5644..53acef169a 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt @@ -7,7 +7,7 @@ import com.tangem.wallet.R sealed class CurrencyListItem { var isAdded: Boolean = false - var isLock: Boolean = false + var isLocked: Boolean = false data class TokenListItem(val token: Token) : CurrencyListItem() data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem() diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 92fe81b661..30db14cfbe 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -4,6 +4,9 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve +import com.tangem.common.hdWallet.DerivationPath +import com.tangem.common.hdWallet.ExtendedPublicKey +import com.tangem.tap.common.extensions.ByteArrayKey import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.toMapKey import com.tangem.tap.common.redux.AppState @@ -13,6 +16,7 @@ import com.tangem.tap.currenciesRepository import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapWorkarounds.isTestCard +import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.scope @@ -45,27 +49,8 @@ class TokensMiddleware { cardFirmware = scanResponse.card.firmwareVersion, isTestNet = isTestcard ) - val currencies = CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList() - if (scanResponse.supportsHdWallet()) { - currencies.forEach { - when (it) { - is CurrencyListItem.TitleListItem -> { - } - is CurrencyListItem.BlockchainListItem -> { - val firstCurve = it.blockchain.getSupportedCurves()?.firstOrNull() - if (firstCurve == EllipticCurve.Ed25519) { - it.isLock = true - } - } - is CurrencyListItem.TokenListItem -> { - val firstCurve = it.token.blockchain.getSupportedCurves()?.firstOrNull() - if (firstCurve == EllipticCurve.Ed25519) { - it.isLock = true - } - } - } - } - } + val currencies = + CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList() store.dispatch(TokensAction.LoadCurrencies.Success(currencies)) } @@ -94,26 +79,33 @@ class TokensMiddleware { blockchains: List, tokens: List ) { - val wallet = scanResponse.card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: return - - val tokenBlockchains = tokens.map { it.blockchain } - val derivationPathsCandidates = (blockchains + tokenBlockchains).distinct() - .mapNotNull { it.derivationPath() } - - val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() - val alreadyDerivedKeys = scanResponse.derivedKeys[mapKeyOfWalletPublicKey]?.toMutableList() ?: mutableListOf() - val alreadyDerivedPaths = alreadyDerivedKeys.map { it.derivationPath } - - val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) } + val derivationDataList = listOfNotNull( + getDerivations(EllipticCurve.Secp256k1, scanResponse, blockchains, tokens), + getDerivations(EllipticCurve.Ed25519, scanResponse, blockchains, tokens) + ) + val derivations = derivationDataList.map { it.derivations }.toMap() scope.launch { - val result = tangemSdkManager.derivePublicKeys(scanResponse.card.cardId, wallet.publicKey, toDerive) + val result = tangemSdkManager.derivePublicKeys( + scanResponse.card.cardId, + derivations + ) when (result) { is CompletionResult.Success -> { - val newDerivedKeys = result.data - alreadyDerivedKeys.addAll(newDerivedKeys) + val newDerivedKeys = result.data.entries + val updatedDerivedKeys = + mutableMapOf>() + + newDerivedKeys.forEach { entry -> + val derivationData = derivationDataList.find { + it.mapKeyOfWalletPublicKey == entry.key + } ?: return@forEach + updatedDerivedKeys[entry.key] = + derivationData.alreadyDerivedKeys + entry.value.toList() + } + val updatedScanResponse = scanResponse.copy( - derivedKeys = mapOf(mapKeyOfWalletPublicKey to alreadyDerivedKeys.toList()) + derivedKeys = updatedDerivedKeys ) store.dispatch(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) submitAdd(blockchains, tokens) @@ -128,6 +120,37 @@ class TokensMiddleware { } } + private fun getDerivations( + curve: EllipticCurve, + scanResponse: ScanResponse, + blockchains: List, + tokens: List + ): DerivationData? { + val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null + + val tokenBlockchains = tokens.map { it.blockchain } + val derivationPathsCandidates = (blockchains + tokenBlockchains).distinct() + .mapNotNull { it.derivationPath() } + + val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() + val alreadyDerivedKeys = + scanResponse.derivedKeys[mapKeyOfWalletPublicKey]?.toMutableList() ?: mutableListOf() + val alreadyDerivedPaths = alreadyDerivedKeys.map { it.derivationPath } + + val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) } + return DerivationData( + derivations = mapKeyOfWalletPublicKey to toDerive, + alreadyDerivedKeys = alreadyDerivedKeys, + mapKeyOfWalletPublicKey = mapKeyOfWalletPublicKey + ) + } + + private class DerivationData( + val derivations: Pair>, + val alreadyDerivedKeys: List, + val mapKeyOfWalletPublicKey: ByteArrayKey + ) + private fun submitAdd(blockchains: List, tokens: List) { (blockchains.map { WalletAction.MultiWallet.AddBlockchain(it) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt index b3d0ecdc04..f0046353d1 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt @@ -196,7 +196,7 @@ class CurrenciesAdapter : ListAdapter } private fun modifyAddTokenButton(currency: CurrencyListItem) { - if (currency.isLock) { + if (currency.isLocked) { view.btn_add_token.setText(R.string.common_add) view.btn_add_token.isEnabled = false } else {