Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-23 11:16:28 +03:00
commit ca49abf2b1
892 changed files with 17979 additions and 6555 deletions

View file

@ -2,7 +2,6 @@ package com.tangem.tap.domain.tasks.visa
import arrow.core.Either
import arrow.core.getOrElse
import arrow.core.raise.catch
import arrow.core.raise.either
import com.tangem.blockchain.common.UnmarshalHelper
import com.tangem.common.CompletionResult
@ -14,22 +13,18 @@ import com.tangem.common.extensions.toHexString
import com.tangem.common.map
import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource
import com.tangem.core.error.ext.tangemError
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
import com.tangem.datasource.local.visa.VisaOTPStorage
import com.tangem.datasource.local.visa.VisaOtpData
import com.tangem.datasource.local.visa.hasSavedOTP
import com.tangem.domain.common.visa.VisaUtilities
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.visa.error.VisaActivationError
import com.tangem.domain.visa.error.VisaAuthorizationAPIError
import com.tangem.domain.visa.model.*
import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.domain.visa.repository.VisaAuthRepository
import com.tangem.operations.GenerateOTPCommand
import com.tangem.operations.attestation.AttestCardKeyCommand
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
import com.tangem.operations.pins.SetUserCodeCommand
import com.tangem.operations.sign.SignHashCommand
import com.tangem.operations.sign.SignHashResponse
@ -95,23 +90,7 @@ class VisaCardActivationTask @AssistedInject constructor(
context.signAuthorizationChallenge(mode.authorizationChallenge)
}
is VisaCardActivationTaskMode.SignOnly -> {
val wallet =
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
val derivedPublicKey = when (val deriveKeyResult = context.deriveKey(wallet.publicKey)) {
is CompletionResult.Failure -> {
return CompletionResult.Failure(deriveKeyResult.error)
}
is CompletionResult.Success -> {
deriveKeyResult.data
}
}
context.signData(
mode.dataToSignByCardWallet,
derivedPublicKey,
)
context.signData(mode.dataToSignByCardWallet)
}
}
}
@ -164,16 +143,7 @@ class VisaCardActivationTask @AssistedInject constructor(
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
val derivedPublicKey = when (val deriveKeyResult = deriveKey(wallet.publicKey)) {
is CompletionResult.Failure -> {
return CompletionResult.Failure(deriveKeyResult.error)
}
is CompletionResult.Success -> {
deriveKeyResult.data
}
}
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey)
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(wallet.publicKey)
.getOrElse { return CompletionResult.Failure(it.tangemError) }
.value
@ -190,10 +160,7 @@ class VisaCardActivationTask @AssistedInject constructor(
otpTaskDeferred.await()
signData(
dataToSign = dataToSign,
derivedPublicKey = derivedPublicKey,
)
signData(dataToSign = dataToSign)
}
}
@ -201,28 +168,24 @@ class VisaCardActivationTask @AssistedInject constructor(
signedChallenge: VisaAuthSignedChallenge,
cardWalletAddress: String,
): Either<TangemError, VisaDataToSignByCardWallet> = either {
catch(
block = {
val tokens = visaAuthRepository.getAccessTokens(signedChallenge)
val tokens = visaAuthRepository.getAccessTokens(signedChallenge)
.getOrElse { raise(it.tangemError) }
visaAuthTokenStorage.store(cardId, tokens)
visaAuthTokenStorage.store(cardId, tokens)
val remoteState = visaActivationRepository.getActivationRemoteState()
if (remoteState !is VisaActivationRemoteState.CardWalletSignatureRequired) {
raise(VisaActivationError.WrongRemoteState.tangemError)
}
val remoteState = visaActivationRepository.getActivationRemoteState()
.getOrElse { raise(it.tangemError) }
visaActivationRepository.getCardWalletAcceptanceData(
VisaCardWalletDataToSignRequest(
activationOrderInfo = remoteState.activationOrderInfo,
cardWalletAddress = cardWalletAddress,
),
)
},
catch = {
raise(VisaAuthorizationAPIError.tangemError)
},
)
if (remoteState !is VisaActivationRemoteState.CardWalletSignatureRequired) {
return raise(VisaActivationError.WrongRemoteState.tangemError)
}
visaActivationRepository.getCardWalletAcceptanceData(
VisaCardWalletDataToSignRequest(
activationOrderInfo = remoteState.activationOrderInfo,
cardWalletAddress = cardWalletAddress,
),
).getOrElse { raise(it.tangemError) }
}
private suspend fun SessionContext.createWallet(): CompletionResult<Unit> {
@ -294,7 +257,6 @@ class VisaCardActivationTask @AssistedInject constructor(
private suspend fun SessionContext.signData(
dataToSign: VisaDataToSignByCardWallet,
derivedPublicKey: ExtendedPublicKey,
): CompletionResult<VisaCardActivationResponse> {
val card =
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
@ -306,7 +268,6 @@ class VisaCardActivationTask @AssistedInject constructor(
val task = SignHashCommand(
hash = dataToSign.hashToSign.hexToBytes(),
walletPublicKey = wallet.publicKey,
derivationPath = VisaUtilities.visaDefaultDerivationPath,
)
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
@ -325,7 +286,7 @@ class VisaCardActivationTask @AssistedInject constructor(
handleSignedData(
dataToSign = dataToSign,
response = result.data,
derivedPublicKey = derivedPublicKey,
walletPublicKey = wallet.publicKey,
)
}
is CompletionResult.Failure -> {
@ -335,23 +296,9 @@ class VisaCardActivationTask @AssistedInject constructor(
}
}
private suspend fun SessionContext.deriveKey(publicKey: ByteArray): CompletionResult<ExtendedPublicKey> {
val derivationPath = VisaUtilities.visaDefaultDerivationPath
?: return CompletionResult.Failure(VisaActivationError.FailedToCreateAddress.tangemError)
val derivationTask = DeriveWalletPublicKeyTask(publicKey, derivationPath)
val derivationTaskResult = suspendCancellableCoroutine { continuation ->
derivationTask.run(session) { result ->
continuation.resume(result)
}
}
return derivationTaskResult
}
private suspend fun SessionContext.handleSignedData(
dataToSign: VisaDataToSignByCardWallet,
derivedPublicKey: ExtendedPublicKey,
walletPublicKey: ByteArray,
response: SignHashResponse,
): CompletionResult<VisaCardActivationResponse> {
val otp = otpStorage.getOTP(cardId) ?: run {
@ -362,7 +309,7 @@ class VisaCardActivationTask @AssistedInject constructor(
val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended(
signature = response.signature,
hash = dataToSign.hashToSign.hexToBytes(),
publicKey = derivedPublicKey.publicKey.toDecompressedPublicKey(),
publicKey = walletPublicKey.toDecompressedPublicKey(),
).asRSVLegacyEVM().toHexString().lowercase()
val signedActivationData = dataToSign.sign(

View file

@ -40,17 +40,12 @@ class VisaCustomerWalletApproveTask(
}
if (VisaUtilities.isVisaCard(card.firmwareVersion.doubleValue, card.batchId)) {
// TODO TVF-21
callback(CompletionResult.Failure(TangemSdkError.Underlying("Can't use Visa card for approve")))
callback(CompletionResult.Failure(VisaActivationError.VisaCardForApproval.tangemError))
return
}
if (visaDataForApprove.customerWalletCardId != null && card.cardId != visaDataForApprove.customerWalletCardId) {
callback(
CompletionResult.Failure(
TangemSdkError.Underlying("Use tangem wallet specified during visa registration"), // TODO TVF-21
),
)
callback(CompletionResult.Failure(VisaActivationError.CardIdNotMatched.tangemError))
return
}