Updated on 2026-08-14

This commit is contained in:
Tangem 2021-12-15 20:46:37 +03:00
parent e922364e1a
commit 1f0173686f
8 changed files with 167 additions and 61 deletions

View file

@ -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<DerivationPath>
): CompletionResult<ExtendedPublicKeyList> {
return runTaskAsyncReturnOnMain(DeriveWalletPublicKeysTask(walletPublicKey, derivationPaths), cardId)
derivations: Map<ByteArrayKey, List<DerivationPath>>
): CompletionResult<DerivationTaskResponse> {
return runTaskAsyncReturnOnMain(DerivationTask(derivations), cardId)
}
suspend fun resetToFactorySettings(card: Card): CompletionResult<Card> {

View file

@ -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"

View file

@ -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<ByteArrayKey, ExtendedPublicKeyList>
): CommandResponse
class DerivationTask(
private val derivations: Map<ByteArrayKey, List<DerivationPath>>
) : CardSessionRunnable<DerivationTaskResponse> {
val response: MutableMap<ByteArrayKey, ExtendedPublicKeyList> = mutableMapOf()
override fun run(session: CardSession, callback: CompletionCallback<DerivationTaskResponse>) {
derive(keys = derivations.keys.toList(), index = 0, session = session, callback = callback)
}
private fun derive(
keys: List<ByteArrayKey>,
index: Int,
session: CardSession,
callback: CompletionCallback<DerivationTaskResponse>
) {
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))
}
}
}
}

View file

@ -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<CreateProductWa
return
}
DeriveWalletPublicKeysTask(response.wallet.publicKey, derivationPaths)
DerivationTask(mapOf(response.wallet.publicKey.toMapKey() to derivationPaths))
.run(session) { result ->
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
)
)

View file

@ -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<ScanResponse>) -> 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<DerivationPath>? {
val currenciesRepository = currenciesRepository ?: return null
private fun getBlockchainsToDerive(card: Card): List<Blockchain> {
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<ByteArrayKey, List<DerivationPath>> {
val blockchains = getBlockchainsToDerive(card)
val derivations = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
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
}
}

View file

@ -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()

View file

@ -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<Blockchain>,
tokens: List<Token>
) {
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<KeyWalletPublicKey, List<ExtendedPublicKey>>()
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<Blockchain>,
tokens: List<Token>
): 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<ByteArrayKey, List<DerivationPath>>,
val alreadyDerivedKeys: List<ExtendedPublicKey>,
val mapKeyOfWalletPublicKey: ByteArrayKey
)
private fun submitAdd(blockchains: List<Blockchain>, tokens: List<Token>) {
(blockchains.map {
WalletAction.MultiWallet.AddBlockchain(it)

View file

@ -196,7 +196,7 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
}
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 {