Updated on 2026-08-14
This commit is contained in:
parent
1854fded77
commit
4c0bafcf8c
6 changed files with 135 additions and 45 deletions
|
|
@ -31,12 +31,6 @@ class FeedbackDataBuilder(
|
|||
builder.appendDelimiter()
|
||||
builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
|
||||
builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
|
||||
|
||||
// enable later
|
||||
// if (walletInfo.blockchain == Blockchain.Bitcoin) {
|
||||
// builder.appendKeyValue("XPUB", infoHolder.extendedPublicKey)
|
||||
// }
|
||||
|
||||
builder.appendKeyValue("Outputs count", walletInfo.outputsCount)
|
||||
|
||||
if (walletInfo.tokens.isNotEmpty()) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
|
|||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||
import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase
|
||||
|
|
@ -69,8 +70,9 @@ internal object CardDomainModule {
|
|||
@Singleton
|
||||
fun provideGetExtendedPublicKeyForCurrencyUseCase(
|
||||
derivationsRepository: DerivationsRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): GetExtendedPublicKeyForCurrencyUseCase {
|
||||
return GetExtendedPublicKeyForCurrencyUseCase(derivationsRepository)
|
||||
return GetExtendedPublicKeyForCurrencyUseCase(derivationsRepository, walletManagersFacade)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -8,12 +8,10 @@ import com.tangem.common.doOnSuccess
|
|||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
|
@ -46,12 +44,16 @@ internal class DefaultDerivationsRepository(
|
|||
return
|
||||
}
|
||||
|
||||
derivePublicKeys(userWalletId = userWalletId, derivations = derivations)
|
||||
}
|
||||
|
||||
override suspend fun derivePublicKeys(userWalletId: UserWalletId, derivations: Derivations): DerivedKeys {
|
||||
tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)
|
||||
.doOnSuccess { response ->
|
||||
updatePublicKeys(userWalletId = userWalletId, keys = response.entries)
|
||||
.doOnSuccess {
|
||||
validateDerivations(scanResponse = it.scanResponse, derivations = derivations)
|
||||
return
|
||||
return response.entries
|
||||
}
|
||||
.doOnFailure { throw it }
|
||||
}
|
||||
|
|
@ -60,28 +62,6 @@ internal class DefaultDerivationsRepository(
|
|||
error("This code should never be reached")
|
||||
}
|
||||
|
||||
override suspend fun deriveExtendedPublicKey(
|
||||
userWalletId: UserWalletId,
|
||||
derivation: DerivationPath,
|
||||
): ExtendedPublicKey? {
|
||||
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
|
||||
val walletCard = userWallet.scanResponse.card.wallets.firstOrNull {
|
||||
UserWalletIdBuilder.scanResponse(userWallet.scanResponse).build()?.value
|
||||
.contentEquals(userWallet.walletId.value)
|
||||
} ?: return null
|
||||
|
||||
val result = tangemSdkManager.deriveExtendedPublicKey(
|
||||
cardId = null,
|
||||
walletPublicKey = walletCard.publicKey,
|
||||
derivation = derivation,
|
||||
)
|
||||
|
||||
return when (result) {
|
||||
is CompletionResult.Failure -> throw result.error
|
||||
is CompletionResult.Success -> result.data
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It throws an exception if any of the provided derivations are invalid
|
||||
* Validation for NonHardened moved to application layer, to avoid fails when derive multiple paths
|
||||
|
|
|
|||
|
|
@ -1,30 +1,140 @@
|
|||
package com.tangem.domain.card
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.calculateRipemd160
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.crypto.NetworkType
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
||||
/**
|
||||
* Derivates an exteneded public key (xpub) based on blockchain hardened derivation
|
||||
*/
|
||||
class GetExtendedPublicKeyForCurrencyUseCase(
|
||||
private val derivationsRepository: DerivationsRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
derivation: Network.DerivationPath,
|
||||
): Either<Throwable, String> {
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either<Throwable, String> {
|
||||
return Either.catch {
|
||||
val derivationPath = requireNotNull(derivation.value?.let { DerivationPath(it) }) {
|
||||
error("Derivation is null")
|
||||
val userWallet = walletManagersFacade.getOrCreateWalletManager(userWalletId, network)
|
||||
?: error("Wallet not found")
|
||||
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val isSecp256k1Blockchain = Blockchain.secp256k1Blockchains(network.isTestnet).contains(blockchain)
|
||||
|
||||
val hdKey = if (isSecp256k1Blockchain) {
|
||||
userWallet.wallet.publicKey.derivationType?.hdKey ?: error("No derivation found")
|
||||
} else {
|
||||
error("No derivation found")
|
||||
}
|
||||
val hardenedNodes = derivationPath.nodes.filter { it.isHardened }
|
||||
val hardenedDerivation = DerivationPath(hardenedNodes)
|
||||
derivationsRepository.deriveExtendedPublicKey(userWalletId, hardenedDerivation)
|
||||
?.serialize(NetworkType.Mainnet).orEmpty()
|
||||
|
||||
var childKey = makeChildKey(
|
||||
isBip44DerivationStyleXPUB = blockchain.isBip44DerivationStyleXPUB(),
|
||||
extendedPublicKey = hdKey.extendedPublicKey,
|
||||
derivationPath = hdKey.path,
|
||||
)
|
||||
|
||||
var parentKey = Key(
|
||||
derivationPath = childKey.derivationPath.dropLastNodes(1),
|
||||
extendedPublicKey = null,
|
||||
)
|
||||
|
||||
val pendingDerivations = getPendingDerivations(childKey, parentKey)
|
||||
val derivedKeys = deriveKeys(
|
||||
userWalletId = userWalletId,
|
||||
seedKey = userWallet.wallet.publicKey.seedKey,
|
||||
paths = pendingDerivations,
|
||||
)
|
||||
|
||||
if (childKey.extendedPublicKey == null) {
|
||||
childKey = childKey.copy(
|
||||
extendedPublicKey = derivedKeys[childKey.derivationPath] ?: error("Failed to derive child key"),
|
||||
)
|
||||
}
|
||||
|
||||
if (parentKey.extendedPublicKey == null) {
|
||||
parentKey = parentKey.copy(
|
||||
extendedPublicKey = derivedKeys[parentKey.derivationPath] ?: error("Failed to derive parent key"),
|
||||
)
|
||||
}
|
||||
|
||||
makeExtendedKey(childKey, parentKey, network.isTestnet)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun deriveKeys(
|
||||
userWalletId: UserWalletId,
|
||||
seedKey: ByteArray,
|
||||
paths: MutableList<DerivationPath>,
|
||||
): ExtendedPublicKeysMap {
|
||||
val result = derivationsRepository.derivePublicKeys(userWalletId, mapOf(ByteArrayKey(seedKey) to paths))
|
||||
return result.getValue(ByteArrayKey(seedKey))
|
||||
}
|
||||
|
||||
private fun makeExtendedKey(childKey: Key, parentKey: Key, isTestnet: Boolean): String {
|
||||
val publicKey = childKey.extendedPublicKey?.publicKey ?: error("No public key found")
|
||||
val chainCode = childKey.extendedPublicKey.chainCode
|
||||
val lastChildNode = childKey.derivationPath.nodes.last()
|
||||
val parentPublicKey = parentKey.extendedPublicKey?.publicKey
|
||||
|
||||
val depth = childKey.derivationPath.nodes.size
|
||||
val childNumber = lastChildNode.index
|
||||
val parentFingerprint = parentPublicKey
|
||||
?.calculateSha256()?.calculateRipemd160()
|
||||
?.take(PARENT_FINGERPRINT_SIZE)?.toByteArray()
|
||||
?: error("No parent fingerprint found")
|
||||
|
||||
val net = if (isTestnet) NetworkType.Testnet else NetworkType.Mainnet
|
||||
return ExtendedPublicKey(
|
||||
publicKey = publicKey,
|
||||
chainCode = chainCode,
|
||||
depth = depth,
|
||||
parentFingerprint = parentFingerprint,
|
||||
childNumber = childNumber,
|
||||
).serialize(net)
|
||||
}
|
||||
|
||||
private fun getPendingDerivations(childKey: Key, parentKey: Key): MutableList<DerivationPath> {
|
||||
val pendingDerivations = mutableListOf<DerivationPath>()
|
||||
|
||||
if (childKey.extendedPublicKey == null) {
|
||||
pendingDerivations.add(childKey.derivationPath)
|
||||
}
|
||||
|
||||
if (parentKey.extendedPublicKey == null) {
|
||||
pendingDerivations.add(parentKey.derivationPath)
|
||||
}
|
||||
|
||||
return pendingDerivations
|
||||
}
|
||||
|
||||
private fun makeChildKey(
|
||||
isBip44DerivationStyleXPUB: Boolean,
|
||||
extendedPublicKey: ExtendedPublicKey,
|
||||
derivationPath: DerivationPath,
|
||||
): Key = if (isBip44DerivationStyleXPUB) {
|
||||
Key(derivationPath.dropLastNodes(2), null)
|
||||
} else {
|
||||
Key(derivationPath, extendedPublicKey)
|
||||
}
|
||||
|
||||
private fun DerivationPath.dropLastNodes(count: Int): DerivationPath {
|
||||
return DerivationPath(nodes.dropLast(count))
|
||||
}
|
||||
|
||||
private data class Key(
|
||||
val derivationPath: DerivationPath,
|
||||
val extendedPublicKey: ExtendedPublicKey?,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val PARENT_FINGERPRINT_SIZE = 4
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
package com.tangem.domain.card.repository
|
||||
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
||||
interface DerivationsRepository {
|
||||
|
||||
|
|
@ -11,5 +12,8 @@ interface DerivationsRepository {
|
|||
suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List<CryptoCurrency>)
|
||||
|
||||
@Throws
|
||||
suspend fun deriveExtendedPublicKey(userWalletId: UserWalletId, derivation: DerivationPath): ExtendedPublicKey?
|
||||
suspend fun derivePublicKeys(
|
||||
userWalletId: UserWalletId,
|
||||
derivations: Map<ByteArrayKey, List<DerivationPath>>,
|
||||
): Map<ByteArrayKey, ExtendedPublicKeysMap>
|
||||
}
|
||||
|
|
@ -569,7 +569,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
viewModelScope.launch(dispatchers.main) {
|
||||
val extendedKey = getExtendedPublicKeyForCurrencyUseCase(
|
||||
userWalletId,
|
||||
cryptoCurrency.network.derivationPath,
|
||||
cryptoCurrency.network,
|
||||
).fold(
|
||||
ifLeft = {
|
||||
Timber.e(it.cause?.localizedMessage.orEmpty())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue