Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-19 18:28:10 +04:00
parent 0594e1b680
commit a8faaffcab
24 changed files with 561 additions and 61 deletions

View file

@ -144,6 +144,7 @@ internal class DefaultOnboardingRepository @Inject constructor(
currencyCode = fiatBalance.currency,
customerWalletAddress = paymentAccount.customerWalletAddress,
depositAddress = response.depositAddress,
isPinSet = response.card?.isPinSet == true,
)
} else {
null

View file

@ -82,12 +82,14 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
block = {
val publicKeyBase64 = getPublicKeyBase64()
val (secretKeyBytes, sessionId) = rainCryptoUtil.generateSecretKeyAndSessionId(publicKeyBase64)
val result = requestHelper.performRequest(userWalletId = userWalletId) { authHeader ->
tangemPayApi.revealCardDetails(
authHeader = authHeader,
body = CardDetailsRequest(sessionId = sessionId),
)
}.getOrNull()?.result ?: error("Cannot reveal card details")
val result = requireNotNull(
requestHelper.performRequest(userWalletId = userWalletId) { authHeader ->
tangemPayApi.revealCardDetails(
authHeader = authHeader,
body = CardDetailsRequest(sessionId = sessionId),
)
}.getOrNull()?.result,
)
val pan = rainCryptoUtil.decryptSecret(
base64Secret = result.pan.secret,
@ -113,6 +115,38 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
)
}
override suspend fun getPin(userWalletId: UserWalletId, cardId: String): Either<UniversalError, String?> {
return catch(
block = {
val publicKeyBase64 = getPublicKeyBase64()
val (secretKeyBytes, sessionId) = rainCryptoUtil.generateSecretKeyAndSessionId(publicKeyBase64)
val result = requireNotNull(
requestHelper.performRequest(userWalletId = userWalletId) { authHeader ->
tangemPayApi.revealCardDetails(
authHeader = authHeader,
body = CardDetailsRequest(sessionId = sessionId),
)
}.getOrNull()?.result,
)
val encryptedPin = result.pin
val pin = if (encryptedPin != null) {
rainCryptoUtil.decryptPin(
base64Secret = encryptedPin.secret,
base64Iv = encryptedPin.iv,
secretKeyBytes = secretKeyBytes,
).takeIf { !it.isNullOrEmpty() }
} else {
null
}
secretKeyBytes.fill(0)
pin.right()
},
catch = ::catchException,
)
}
override suspend fun setPin(userWalletId: UserWalletId, pin: String): Either<UniversalError, SetPinResult> {
return requestHelper.runWithErrorLogs(TAG) {
val publicKeyBase64 = getPublicKeyBase64()
@ -120,16 +154,18 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
val encryptedData = rainCryptoUtil.encryptPin(pin = pin, secretKeyBytes = secretKeyBytes)
secretKeyBytes.fill(0)
val status = requestHelper.request(userWalletId) { authHeader ->
tangemPayApi.setPin(
authHeader = authHeader,
body = SetPinRequest(
sessionId = sessionId,
pin = encryptedData.encryptedBase64,
iv = encryptedData.ivBase64,
),
)
}.result?.result ?: error("Cannot set pin code")
val status = requireNotNull(
requestHelper.request(userWalletId) { authHeader ->
tangemPayApi.setPin(
authHeader = authHeader,
body = SetPinRequest(
sessionId = sessionId,
pin = encryptedData.encryptedBase64,
iv = encryptedData.ivBase64,
),
)
}.result?.result,
)
when (status) {
SetPinResult.SUCCESS.name -> SetPinResult.SUCCESS
SetPinResult.PIN_TOO_WEAK.name -> SetPinResult.PIN_TOO_WEAK

View file

@ -37,6 +37,15 @@ internal class RainCryptoUtil @Inject constructor(
secretKeyBytes to sessionId
}
suspend fun decryptPin(base64Secret: String, base64Iv: String, secretKeyBytes: ByteArray): String? {
val pinBlock = decryptSecret(
base64Secret = base64Secret,
base64Iv = base64Iv,
secretKeyBytes = secretKeyBytes,
)
return extractPinFromPinBlock(pinBlock)
}
suspend fun encryptPin(pin: String, secretKeyBytes: ByteArray): EncryptedData = withContext(dispatchers.default) {
val bytes = pinBlockByteArray(pin)
try {
@ -113,6 +122,17 @@ internal class RainCryptoUtil @Inject constructor(
return hex.toByteArray(StandardCharsets.UTF_8)
}
private suspend fun extractPinFromPinBlock(pinBlock: String): String? = withContext(dispatchers.default) {
val pinLength = pinBlock[1].digitToIntOrNull()
require(pinLength == PIN_LENGTH) { "Unexpected PIN length: ${pinBlock[1]}" }
val pinStartIndex = 2
val pinEndIndex = pinStartIndex + pinLength
val pin = pinBlock.substring(pinStartIndex, pinEndIndex)
pin.takeIf { value -> value.all { it.isDigit() } && value.length == PIN_LENGTH }
}
private fun ByteArray.clear() {
for (i in indices) this[i] = 0
}