Updated on 2026-08-14
This commit is contained in:
parent
cb23c1dea3
commit
538757a7ce
54 changed files with 622 additions and 488 deletions
|
|
@ -4,14 +4,12 @@ import arrow.core.Either
|
|||
import arrow.core.getOrElse
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.core.*
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toDecompressedPublicKey
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.map
|
||||
import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource
|
||||
|
|
@ -46,7 +44,7 @@ import timber.log.Timber
|
|||
import kotlin.coroutines.resume
|
||||
import kotlin.time.measureTimedValue
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
class VisaCardActivationTask @AssistedInject constructor(
|
||||
@Assisted private val mode: VisaCardActivationTaskMode,
|
||||
@Assisted private val activationInput: VisaActivationInput,
|
||||
|
|
@ -97,7 +95,23 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
context.signAuthorizationChallenge(mode.authorizationChallenge)
|
||||
}
|
||||
is VisaCardActivationTaskMode.SignOnly -> {
|
||||
context.signData(mode.dataToSignByCardWallet)
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -138,9 +152,35 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
private suspend fun SessionContext.processSignedAuthorizationChallenge(
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
when (val createWalletResult = createWallet()) {
|
||||
is CompletionResult.Failure -> return CompletionResult.Failure(createWalletResult.error)
|
||||
is CompletionResult.Success -> {}
|
||||
}
|
||||
|
||||
val card =
|
||||
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val wallet =
|
||||
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)
|
||||
.getOrElse { return CompletionResult.Failure(it.tangemError) }
|
||||
.value
|
||||
|
||||
return coroutineScope {
|
||||
val dataToSignDeferred = async { getDataToSign(signedChallenge) }
|
||||
val otpTaskDeferred = async { createWallet() }
|
||||
val dataToSignDeferred =
|
||||
async { getDataToSign(signedChallenge = signedChallenge, cardWalletAddress = walletAddress) }
|
||||
val otpTaskDeferred = async { createOTP() }
|
||||
|
||||
val dataToSign = dataToSignDeferred.await()
|
||||
.getOrElse {
|
||||
|
|
@ -150,12 +190,16 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
|
||||
otpTaskDeferred.await()
|
||||
|
||||
signData(dataToSign)
|
||||
signData(
|
||||
dataToSign = dataToSign,
|
||||
derivedPublicKey = derivedPublicKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun SessionContext.getDataToSign(
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
cardWalletAddress: String,
|
||||
): Either<TangemError, VisaDataToSignByCardWallet> = either {
|
||||
catch(
|
||||
block = {
|
||||
|
|
@ -168,7 +212,12 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
raise(VisaActivationError.WrongRemoteState.tangemError)
|
||||
}
|
||||
|
||||
visaActivationRepository.getCardWalletAcceptanceData(remoteState.request)
|
||||
visaActivationRepository.getCardWalletAcceptanceData(
|
||||
VisaCardWalletDataToSignRequest(
|
||||
activationOrderInfo = remoteState.activationOrderInfo,
|
||||
cardWalletAddress = cardWalletAddress,
|
||||
),
|
||||
)
|
||||
},
|
||||
catch = {
|
||||
raise(VisaAuthorizationAPIError.tangemError)
|
||||
|
|
@ -182,7 +231,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
return if (card.wallets.any { it.curve == EllipticCurve.Secp256k1 }) {
|
||||
createOTP()
|
||||
CompletionResult.Success(Unit)
|
||||
} else {
|
||||
val createWalletTask = CreateWalletTask(EllipticCurve.Secp256k1)
|
||||
|
||||
|
|
@ -199,7 +248,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
when (val result = timedResult.value) {
|
||||
is CompletionResult.Success -> {
|
||||
Timber.i("CreateWalletTask success")
|
||||
createOTP()
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e("CreateWalletTask failure ${result.error}")
|
||||
|
|
@ -245,26 +294,15 @@ 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())
|
||||
|
||||
val wallet =
|
||||
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)
|
||||
.getOrElse { return CompletionResult.Failure(it.tangemError) }
|
||||
.value
|
||||
|
||||
val task = SignHashCommand(
|
||||
hash = dataToSign.hashToSign.hexToBytes(),
|
||||
walletPublicKey = wallet.publicKey,
|
||||
|
|
@ -286,8 +324,8 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
Timber.i("SignHashCommand success")
|
||||
handleSignedData(
|
||||
dataToSign = dataToSign,
|
||||
walletAddress = walletAddress,
|
||||
response = result.data,
|
||||
derivedPublicKey = derivedPublicKey,
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
@ -313,7 +351,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
|
||||
private suspend fun SessionContext.handleSignedData(
|
||||
dataToSign: VisaDataToSignByCardWallet,
|
||||
walletAddress: String,
|
||||
derivedPublicKey: ExtendedPublicKey,
|
||||
response: SignHashResponse,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
val otp = otpStorage.getOTP(cardId) ?: run {
|
||||
|
|
@ -321,11 +359,16 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
otpStorage.getOTP(cardId) ?: return CompletionResult.Failure(VisaActivationError.MissingRootOTP.tangemError)
|
||||
}
|
||||
|
||||
val rsvSignature = EthereumUtils.prepareSignedMessageData(
|
||||
signedHash = response.signature,
|
||||
hashToSign = dataToSign.hashToSign.hexToBytes(),
|
||||
publicKey = derivedPublicKey.publicKey.toDecompressedPublicKey(),
|
||||
)
|
||||
|
||||
val signedActivationData = dataToSign.sign(
|
||||
cardWalletAddress = walletAddress,
|
||||
rootOTP = otp.rootOTP.toHexString(),
|
||||
otpCounter = otp.counter,
|
||||
signature = response.signature.toHexString(),
|
||||
signature = rsvSignature,
|
||||
)
|
||||
|
||||
return setupAccessCode().map {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.domain.tasks.visa
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.CardWallet
|
||||
|
|
@ -10,7 +11,7 @@ import com.tangem.common.core.CardSessionRunnable
|
|||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.extensions.toDecompressedPublicKey
|
||||
import com.tangem.core.error.ext.tangemError
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
|
|
@ -129,6 +130,7 @@ class VisaCustomerWalletApproveTask(
|
|||
targetWalletPublicKey = wallet.publicKey,
|
||||
derivationPath = derivationPath,
|
||||
session = session,
|
||||
extendedPublicKey = extendedPublicKey,
|
||||
callback = callback,
|
||||
)
|
||||
}
|
||||
|
|
@ -149,6 +151,7 @@ class VisaCustomerWalletApproveTask(
|
|||
signApproveData(
|
||||
targetWalletPublicKey = publicKey,
|
||||
derivationPath = null,
|
||||
extendedPublicKey = null,
|
||||
session = session,
|
||||
callback = callback,
|
||||
)
|
||||
|
|
@ -157,11 +160,14 @@ class VisaCustomerWalletApproveTask(
|
|||
private fun signApproveData(
|
||||
targetWalletPublicKey: ByteArray,
|
||||
derivationPath: DerivationPath?,
|
||||
extendedPublicKey: ExtendedPublicKey?,
|
||||
session: CardSession,
|
||||
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
||||
) {
|
||||
val hashToSign = visaDataForApprove.dataToSign.hashToSign.hexToBytes()
|
||||
|
||||
val signTask = SignHashCommand(
|
||||
hash = visaDataForApprove.dataToSign.hashToSign.hexToBytes(),
|
||||
hash = hashToSign,
|
||||
walletPublicKey = targetWalletPublicKey,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
|
|
@ -169,11 +175,18 @@ class VisaCustomerWalletApproveTask(
|
|||
signTask.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val rsvSignature = EthereumUtils.prepareSignedMessageData(
|
||||
signedHash = result.data.signature,
|
||||
hashToSign = hashToSign,
|
||||
publicKey = extendedPublicKey?.publicKey?.toDecompressedPublicKey()
|
||||
?: targetWalletPublicKey.toDecompressedPublicKey(),
|
||||
)
|
||||
|
||||
scanCard(
|
||||
session = session,
|
||||
callback = callback,
|
||||
signedData = visaDataForApprove.dataToSign.sign(
|
||||
signature = result.data.signature.toHexString(),
|
||||
signature = rsvSignature,
|
||||
customerWalletAddress = visaDataForApprove.targetAddress,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -128,6 +128,8 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
|
||||
Timber.i("Requesting challenge for wallet authorization")
|
||||
val challengeResponse = runCatching {
|
||||
// TODO [REDACTED_TASK_KEY]
|
||||
error("sign and get specific error to switch to card_id flow")
|
||||
visaAuthRepository.getCardWalletAuthChallenge(cardWalletAddress = walletAddress.value)
|
||||
}.getOrElse {
|
||||
Timber.i(
|
||||
|
|
@ -180,6 +182,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
return CompletionResult.Success(VisaCardActivationStatus.Activated(authorizationTokensResponse))
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private suspend fun SessionContext.handleCardAuthorization(
|
||||
cardWalletAddress: String,
|
||||
): CompletionResult<VisaCardActivationStatus> {
|
||||
|
|
@ -232,11 +235,17 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
tokens = authorizationTokensResponse,
|
||||
)
|
||||
|
||||
val activationRemoteState = visaActivationRepository.getActivationRemoteState()
|
||||
val activationRemoteState = runCatching {
|
||||
visaActivationRepository.getActivationRemoteState()
|
||||
}.getOrElse {
|
||||
Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.message}")
|
||||
return CompletionResult.Failure(VisaAuthorizationAPIError.tangemError)
|
||||
}
|
||||
|
||||
val error = when (activationRemoteState) {
|
||||
VisaActivationRemoteState.BlockedForActivation -> VisaActivationError.BlockedForActivation
|
||||
VisaActivationRemoteState.Activated -> VisaActivationError.InvalidActivationState
|
||||
VisaActivationRemoteState.Failed -> VisaActivationError.FailedRemoteState
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue