Updated on 2026-08-14
This commit is contained in:
commit
a270251185
7 changed files with 215 additions and 4 deletions
|
|
@ -10,7 +10,8 @@ data class CardEnvironment(
|
|||
val pin2: String = DEFAULT_PIN2,
|
||||
val cardId: String? = null,
|
||||
val terminalKeys: KeyPair? = null,
|
||||
val encryptionKey: ByteArray? = null
|
||||
val encryptionKey: ByteArray? = null,
|
||||
val cvc: ByteArray? = null
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -122,6 +122,37 @@ class CardManager(
|
|||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* This command will create a new wallet on the card having ‘Empty’ state.
|
||||
* A key pair WalletPublicKey / WalletPrivateKey is generated and securely stored in the card.
|
||||
* App will need to obtain Wallet_PublicKey from the response of [CreateWalletCommand] or [ReadCommand]
|
||||
* and then transform it into an address of corresponding blockchain wallet
|
||||
* according to a specific blockchain algorithm.
|
||||
* WalletPrivateKey is never revealed by the card and will be used by [SignCommand] and [CheckWalletCommand].
|
||||
* RemainingSignature is set to MaxSignatures.
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
*/
|
||||
fun createWallet(cardId: String,
|
||||
callback: (result: TaskEvent<CreateWalletResponse>) -> Unit) {
|
||||
val createWalletCommand = CreateWalletCommand(cardId)
|
||||
val task = SingleCommandTask(createWalletCommand)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* This command deletes all wallet data. If Is_Reusable flag is enabled during personalization,
|
||||
|
||||
* If Is_Reusable flag is disabled, the card switches to ‘Purged’ state.
|
||||
* ‘Purged’ state is final, it makes the card useless.
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
*/
|
||||
fun purgeWallet(cardId: String,
|
||||
callback: (result: TaskEvent<PurgeWalletResponse>) -> Unit) {
|
||||
val purgeWalletCommand = PurgeWalletCommand(cardId)
|
||||
val task = SingleCommandTask(purgeWalletCommand)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
|
|
@ -157,6 +188,6 @@ class CardManager(
|
|||
}
|
||||
|
||||
private fun fetchCardEnvironment(cardId: String?): CardEnvironment {
|
||||
return cardEnvironmentRepository[cardId] ?: CardEnvironment()
|
||||
return cardEnvironmentRepository[cardId] ?: CardEnvironment(cardId = cardId)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
class CreateWalletResponse(
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
*/
|
||||
val cardId: String,
|
||||
/**
|
||||
* Current status of the card [1 - Empty, 2 - Loaded, 3- Purged]
|
||||
*/
|
||||
val status: CardStatus,
|
||||
/**
|
||||
|
||||
*/
|
||||
val walletPublicKey: ByteArray
|
||||
) : CommandResponse
|
||||
|
||||
/**
|
||||
* This command will create a new wallet on the card having ‘Empty’ state.
|
||||
* A key pair WalletPublicKey / WalletPrivateKey is generated and securely stored in the card.
|
||||
* App will need to obtain Wallet_PublicKey from the response of [CreateWalletCommand] or [ReadCommand]
|
||||
* and then transform it into an address of corresponding blockchain wallet
|
||||
* according to a specific blockchain algorithm.
|
||||
* WalletPrivateKey is never revealed by the card and will be used by [SignCommand] and [CheckWalletCommand].
|
||||
* RemainingSignature is set to MaxSignatures.
|
||||
*
|
||||
* @property cardId CID, Unique Tangem card ID number.
|
||||
*/
|
||||
class CreateWalletCommand(
|
||||
private val cardId: String
|
||||
) : CommandSerializer<CreateWalletResponse>() {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvData = mutableListOf(
|
||||
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
|
||||
Tlv(TlvTag.CardId, cardId.hexToBytes()),
|
||||
Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256())
|
||||
)
|
||||
if (cardEnvironment.cvc != null) {
|
||||
tlvData.add(Tlv(TlvTag.Cvc, cardEnvironment.cvc))
|
||||
}
|
||||
|
||||
return CommandApdu(Instruction.CreateWallet, tlvData)
|
||||
}
|
||||
|
||||
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CreateWalletResponse? {
|
||||
val tlvData = responseApdu.getTlvData() ?: return null
|
||||
|
||||
return try {
|
||||
val mapper = TlvMapper(tlvData)
|
||||
CreateWalletResponse(
|
||||
cardId = mapper.map(TlvTag.CardId),
|
||||
status = mapper.map(TlvTag.Status),
|
||||
walletPublicKey = mapper.map(TlvTag.WalletPublicKey)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
throw TaskError.SerializeCommandError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
class PurgeWalletResponse(
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
*/
|
||||
val cardId: String,
|
||||
/**
|
||||
* Current status of the card [1 - Empty, 2 - Loaded, 3- Purged]
|
||||
*/
|
||||
val status: CardStatus
|
||||
) : CommandResponse
|
||||
|
||||
/**
|
||||
* This command deletes all wallet data. If Is_Reusable flag is enabled during personalization,
|
||||
|
||||
* If Is_Reusable flag is disabled, the card switches to ‘Purged’ state.
|
||||
* ‘Purged’ state is final, it makes the card useless.
|
||||
* @property cardId CID, Unique Tangem card ID number.
|
||||
*/
|
||||
class PurgeWalletCommand(
|
||||
private val cardId: String
|
||||
) : CommandSerializer<PurgeWalletResponse>() {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvData = mutableListOf(
|
||||
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
|
||||
Tlv(TlvTag.CardId, cardId.hexToBytes()),
|
||||
Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256())
|
||||
)
|
||||
return CommandApdu(Instruction.PurgeWallet, tlvData)
|
||||
}
|
||||
|
||||
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): PurgeWalletResponse? {
|
||||
val tlvData = responseApdu.getTlvData() ?: return null
|
||||
|
||||
return try {
|
||||
val mapper = TlvMapper(tlvData)
|
||||
PurgeWalletResponse(
|
||||
cardId = mapper.map(TlvTag.CardId),
|
||||
status = mapper.map(TlvTag.Status))
|
||||
} catch (exception: Exception) {
|
||||
throw TaskError.SerializeCommandError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ enum class TlvTag(val code: Int) {
|
|||
Pause(0x1C),
|
||||
|
||||
ManufactureId(0x20),
|
||||
ManufacturerSignature(0x21),
|
||||
ManufacturerSignature(0x86),
|
||||
|
||||
IssuerDataPublicKey(0x30),
|
||||
IssuerTransactionPublicKey(0x31),
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ class MainActivity : AppCompatActivity() {
|
|||
tv_card_cid?.text = cardId
|
||||
btn_sign.isEnabled = true
|
||||
btn_read_issuer_data.isEnabled = true
|
||||
btn_purge_wallet.isEnabled = true
|
||||
btn_create_wallet.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -103,6 +105,32 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
btn_purge_wallet?.setOnClickListener { _ ->
|
||||
cardManager.purgeWallet(
|
||||
cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
tv_card_cid?.text = it.data.status.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_create_wallet?.setOnClickListener { _ ->
|
||||
cardManager.createWallet(
|
||||
cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
tv_card_cid?.text = it.data.status.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSampleHashes(): Array<ByteArray> {
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@
|
|||
|
||||
<TextView
|
||||
android:paddingBottom="48dp"
|
||||
android:layout_marginTop="48dp"
|
||||
android:id="@+id/tv_card_cid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="80dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
|
@ -25,6 +25,7 @@
|
|||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Read and verify card"
|
||||
app:layout_constraintVertical_bias="0.3"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
|
|
@ -63,5 +64,27 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/btn_read_issuer_data"
|
||||
android:enabled="false"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_purge_wallet"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Purge wallet"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btn_write_issuer_data"
|
||||
android:enabled="false"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_create_wallet"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Create wallet"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btn_purge_wallet"
|
||||
android:enabled="false"/>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Loading…
Add table
Add a link
Reference in a new issue