Updated on 2026-08-14
This commit is contained in:
commit
c36eab59be
8 changed files with 107 additions and 57 deletions
|
|
@ -13,6 +13,6 @@
|
|||
},
|
||||
{
|
||||
"name": "isCreatingTwinCardsAllowed",
|
||||
"value": false
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
|
|
@ -13,6 +13,6 @@
|
|||
},
|
||||
{
|
||||
"name": "isCreatingTwinCardsAllowed",
|
||||
"value": false
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
|
|
@ -7,20 +7,16 @@ import com.tangem.TangemSdkError
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.ReadIssuerDataCommand
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.commands.common.card.CardStatus
|
||||
import com.tangem.commands.common.card.masks.Product
|
||||
import com.tangem.commands.file.ReadFileDataCommand
|
||||
import com.tangem.commands.verifycard.VerifyCardCommand
|
||||
import com.tangem.commands.verifycard.VerifyCardResponse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvDecoder
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tasks.ScanTask
|
||||
|
||||
data class ScanNoteResponse(
|
||||
|
|
@ -75,7 +71,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
when (verifyResult) {
|
||||
is CompletionResult.Success -> {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(
|
||||
walletManager, card, verifyResult.data
|
||||
walletManager, card, verifyResult.data, publicKey
|
||||
)))
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
@ -89,20 +85,21 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
card: Card, session: CardSession,
|
||||
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
|
||||
) {
|
||||
ReadFileDataCommand().run(session) { filesResult ->
|
||||
when (filesResult) {
|
||||
ReadIssuerDataCommand().run(session) { readDataResult ->
|
||||
when (readDataResult) {
|
||||
is CompletionResult.Success -> {
|
||||
val decoder = Tlv.deserialize(filesResult.data.fileData)?.let { TlvDecoder(it) }
|
||||
val name = decoder?.decodeOptional<String>(TlvTag.FileName)
|
||||
if (name != null && name == TwinsHelper.TWIN_FILE_NAME) {
|
||||
val publicKey = decoder.decodeOptional<ByteArray>(TlvTag.FileData)?.toHexString()
|
||||
val verified = TwinCardsManager.verifyTwinPublicKey(
|
||||
readDataResult.data.issuerData, card.walletPublicKey
|
||||
)
|
||||
if (verified) {
|
||||
val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65)
|
||||
val walletManager = try {
|
||||
WalletManagerFactory.makeMultisigWalletManager(card, publicKey!!.hexToBytes())
|
||||
WalletManagerFactory.makeMultisigWalletManager(card, twinPublicKey)
|
||||
} catch (exception: Exception) {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(null, card)))
|
||||
return@run
|
||||
}
|
||||
verifyCard(walletManager, card, publicKey, session, callback)
|
||||
verifyCard(walletManager, card, twinPublicKey.toHexString(), session, callback)
|
||||
return@run
|
||||
} else {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(null, card)))
|
||||
|
|
@ -111,7 +108,6 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
is CompletionResult.Failure ->
|
||||
callback(CompletionResult.Success(ScanNoteResponse(null, card)))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.tangem.commands.PurgeWalletCommand
|
|||
import com.tangem.commands.common.card.CardStatus
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tasks.CreateWalletTask
|
||||
import com.tangem.tasks.file.DeleteFilesTask
|
||||
|
||||
class CreateFirstTwinWalletTask : CardSessionRunnable<CreateWalletResponse> {
|
||||
override val requiresPin2 = false
|
||||
|
|
@ -18,24 +17,13 @@ class CreateFirstTwinWalletTask : CardSessionRunnable<CreateWalletResponse> {
|
|||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
session.environment.card = session.environment.card?.copy(status = CardStatus.Empty)
|
||||
finishTask(session, callback)
|
||||
CreateWalletTask().run(session) { callback(it) }
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finishTask(session, callback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishTask(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
DeleteFilesTask().run(session) { deleteResponse ->
|
||||
when (deleteResponse) {
|
||||
is CompletionResult.Success ->
|
||||
CreateWalletTask().run(session) { callback(it) }
|
||||
is CompletionResult.Failure ->
|
||||
callback(CompletionResult.Failure(deleteResponse.error))
|
||||
}
|
||||
CreateWalletTask().run(session) { callback(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,9 @@ import com.tangem.CardSessionRunnable
|
|||
import com.tangem.commands.CreateWalletResponse
|
||||
import com.tangem.commands.PurgeWalletCommand
|
||||
import com.tangem.commands.common.card.CardStatus
|
||||
import com.tangem.commands.file.WriteFileDataCommand
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.tasks.CreateWalletTask
|
||||
import com.tangem.tasks.file.DeleteFilesTask
|
||||
|
||||
class CreateSecondTwinWalletTask(private val firstPublicKey: String) : CardSessionRunnable<CreateWalletResponse> {
|
||||
override val requiresPin2 = true
|
||||
|
|
@ -31,20 +30,23 @@ class CreateSecondTwinWalletTask(private val firstPublicKey: String) : CardSessi
|
|||
}
|
||||
|
||||
private fun finishTask(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
DeleteFilesTask().run(session) { deleteResponse ->
|
||||
when (deleteResponse) {
|
||||
CreateWalletTask().run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val file = TwinCardsManager.createFileWithPublicKey(firstPublicKey)
|
||||
WriteFileDataCommand(file).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> CreateWalletTask().run(session, callback)
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
session.environment.card =
|
||||
session.environment.card?.copy(status = CardStatus.Loaded)
|
||||
WriteProtectedIssuerDataTask(
|
||||
firstPublicKey.hexToBytes(), TwinCardsManager.issuerKeys
|
||||
).run(session) { writeResult ->
|
||||
when (writeResult) {
|
||||
is CompletionResult.Success -> callback(result)
|
||||
is CompletionResult.Failure ->
|
||||
callback(CompletionResult.Failure(writeResult.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure ->
|
||||
callback(CompletionResult.Failure(deleteResponse.error))
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
package com.tangem.tap.domain.twins
|
||||
|
||||
import com.tangem.KeyPair
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.commands.file.FileData
|
||||
import com.tangem.commands.file.WriteFileDataCommand
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.tlv.TlvEncoder
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.common.tlv.serialize
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
||||
|
|
@ -54,7 +51,7 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
|
|||
|
||||
suspend fun complete(message: Message): Result<ScanNoteResponse> {
|
||||
val response = tangemSdkManager.runTaskAsync(
|
||||
WriteFileDataCommand(createFileWithPublicKey(secondCardPublicKey!!)),
|
||||
WriteProtectedIssuerDataTask(secondCardPublicKey!!.hexToBytes(), issuerKeys),
|
||||
currentCardId, message
|
||||
)
|
||||
return when (response) {
|
||||
|
|
@ -72,11 +69,19 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
|
|||
}
|
||||
|
||||
companion object {
|
||||
fun createFileWithPublicKey(publicKey: String): FileData {
|
||||
val nameTlv = TlvEncoder().encode(TlvTag.FileName, "TwinPublicKey")
|
||||
val pubKey = TlvEncoder().encode(TlvTag.FileData, publicKey.hexToBytes())
|
||||
val tlvs = listOf(nameTlv, pubKey).serialize()
|
||||
return FileData.DataProtectedByPasscode(tlvs)
|
||||
val issuerKeys = KeyPair(
|
||||
privateKey = "F9F4C50636C9E6FC65F92655BD5C21C85A5F6A34DCD0F1E75FCEA1980FE242F5".hexToBytes(),
|
||||
publicKey = ("048196AA4B410AC44A3B9CCE18E7BE226AEA070ACC83A9CF67540F" +
|
||||
"AC49AF25129F6A538A28AD6341358E3C4F9963064F" +
|
||||
"7E365372A651D374E5C23CDD37FD099BF2").hexToBytes()
|
||||
)
|
||||
|
||||
fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean {
|
||||
if (issuerData.size < 65) return false
|
||||
val publicKey = issuerData.sliceArray(0 until 65)
|
||||
val signedKey = issuerData.sliceArray(65 until issuerData.size)
|
||||
return (cardWalletPublicKey != null &&
|
||||
CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.tap.domain.twins
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.CardSessionRunnable
|
||||
import com.tangem.KeyPair
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.files.FileHashHelper
|
||||
|
||||
class WriteProtectedIssuerDataTask(
|
||||
private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair
|
||||
) : CardSessionRunnable<WriteIssuerDataResponse> {
|
||||
override val requiresPin2 = true
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit) {
|
||||
val hashes = arrayOf(twinPublicKey.calculateSha256())
|
||||
SignCommand(hashes).run(session) { signResult ->
|
||||
when (signResult) {
|
||||
is CompletionResult.Success -> {
|
||||
ReadIssuerDataCommand().run(session) { readResult ->
|
||||
when (readResult) {
|
||||
is CompletionResult.Success -> {
|
||||
writeIssuerData(
|
||||
twinPublicKey, issuerKeys, signResult.data.signature,
|
||||
readResult.data, session, callback
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(signResult.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeIssuerData(
|
||||
twinPublicKey: ByteArray, issuerKeys: KeyPair, cardSignature: ByteArray,
|
||||
readResponse: ReadIssuerDataResponse,
|
||||
session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>
|
||||
) -> Unit) {
|
||||
val cardId = session.environment.card?.cardId.guard {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardError()))
|
||||
return
|
||||
}
|
||||
val counter = (readResponse.issuerDataCounter ?: 0) + 1
|
||||
val data = twinPublicKey + cardSignature
|
||||
val signedByIssuer = FileHashHelper.prepareHashes(
|
||||
cardId, data, counter, issuerKeys.privateKey
|
||||
)
|
||||
WriteIssuerDataCommand(
|
||||
data, signedByIssuer.finalizingSignature!!,
|
||||
counter, issuerKeys.publicKey
|
||||
).run(session, callback)
|
||||
}
|
||||
}
|
||||
|
|
@ -38,12 +38,12 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:gravity="start"
|
||||
android:text="@string/details_twins_recreate_step_format"
|
||||
android:textColor="@color/colorSecondary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="@+id/v_step_1"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="@id/v_step_1"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue