From effd4025eeff4526323854ddaa3e62f5fb8774c2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Mar 2020 12:13:24 +0300 Subject: [PATCH 01/17] Updated on 2026-08-14 --- .../src/main/java/com/tangem/CardManager.kt | 13 +-- .../com/tangem/commands/CheckWalletCommand.kt | 10 +-- .../com/tangem/commands/CommandSerializer.kt | 2 +- .../tangem/commands/CreateWalletCommand.kt | 10 +-- .../com/tangem/commands/OpenSessionCommand.kt | 41 +++++++++ .../com/tangem/commands/PurgeWalletCommand.kt | 10 +-- .../java/com/tangem/commands/ReadCommand.kt | 7 +- .../tangem/commands/ReadIssuerDataCommand.kt | 7 +- .../commands/ReadIssuerExtraDataCommand.kt | 7 +- .../tangem/commands/ReadUserDataCommand.kt | 7 +- .../java/com/tangem/commands/SignCommand.kt | 10 +-- .../tangem/commands/WriteIssuerDataCommand.kt | 7 +- .../commands/WriteIssuerExtraDataCommand.kt | 7 +- .../tangem/commands/WriteUserDataCommand.kt | 7 +- .../java/com/tangem/common/CardEnvironment.kt | 1 + .../com/tangem/common/apdu/CommandApdu.kt | 62 ++++++++----- .../com/tangem/common/apdu/ResponseApdu.kt | 37 ++++++-- .../com/tangem/common/extensions/ByteArray.kt | 18 ++++ .../main/java/com/tangem/common/tlv/TlvTag.kt | 3 +- .../java/com/tangem/crypto/CryptoUtils.kt | 38 ++++++++ .../main/java/com/tangem/crypto/Ed25519.kt | 2 +- .../com/tangem/crypto/EncryptionHelper.kt | 50 +++++++++++ .../src/main/java/com/tangem/crypto/Pbkdf2.kt | 88 +++++++++++++++++++ .../main/java/com/tangem/crypto/Secp256k1.kt | 2 +- .../src/main/java/com/tangem/tasks/Task.kt | 75 +++++++++++++--- 25 files changed, 431 insertions(+), 90 deletions(-) create mode 100644 tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt create mode 100644 tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt create mode 100644 tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index 4094d935bc..1503f8dc12 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -69,18 +69,7 @@ class CardManager( */ fun sign(hashes: Array, cardId: String, callback: (result: TaskEvent) -> Unit) { - val signCommand: SignCommand - try { - signCommand = SignCommand(hashes) - } catch (error: Exception) { - if (error is TaskError) { - callback(TaskEvent.Completion(error)) - } else { - Log.e(this::class.simpleName!!, error.message ?: "") - callback(TaskEvent.Completion(TaskError.UnknownError())) - } - return - } + val signCommand = SignCommand(hashes) val task = SingleCommandTask(signCommand) runTask(task, cardId, callback) } diff --git a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt index e5376f2cc7..28f0cd2d43 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -4,9 +4,6 @@ import com.tangem.common.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.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -52,11 +49,14 @@ class CheckWalletCommand : CommandSerializer() { tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Challenge, challenge) - return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize()) + return CommandApdu( + Instruction.CheckWallet, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt b/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt index 92ee3d0d90..134474fb23 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt @@ -38,7 +38,7 @@ abstract class CommandSerializer { * @return Remaining security delay in milliseconds. */ fun deserializeSecurityDelay(responseApdu: ResponseApdu, cardEnvironment: CardEnvironment): Int? { - val tlv = responseApdu.getTlvData(cardEnvironment.encryptionKey) + val tlv = responseApdu.getTlvData() return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt index 0c142ee296..18b20f1394 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -4,9 +4,6 @@ import com.tangem.common.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.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -46,11 +43,14 @@ class CreateWalletCommand : CommandSerializer() { tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) - return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize()) + return CommandApdu( + Instruction.CreateWallet, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CreateWalletResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt new file mode 100644 index 0000000000..9b78cb8bea --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt @@ -0,0 +1,41 @@ +package com.tangem.commands + +import com.tangem.common.CardEnvironment +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.Instruction +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.tlv.TlvBuilder +import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvTag +import com.tangem.tasks.TaskError + +class OpenSessionResponse( + val sessionKeyB: ByteArray, + val uid: ByteArray +) : CommandResponse + + +class OpenSessionCommand(private val sessionKeyA: ByteArray) : CommandSerializer() { + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.SessionKeyA, sessionKeyA) + return CommandApdu( + Instruction.OpenSession, tlvBuilder.serialize(), + encryptionMode = cardEnvironment.encryptionMode + ) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): OpenSessionResponse? { + val tlvData = responseApdu.getTlvData() ?: return null + + return try { + val mapper = TlvMapper(tlvData) + OpenSessionResponse( + sessionKeyB = mapper.map(TlvTag.SessionKeyB), + uid = mapper.map(TlvTag.Uid) + ) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt index cef8aca229..4eb379e521 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -4,9 +4,6 @@ import com.tangem.common.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.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -37,11 +34,14 @@ class PurgeWalletCommand : CommandSerializer() { tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) - return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize()) + return CommandApdu( + Instruction.PurgeWallet, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): PurgeWalletResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index 0a161f392c..b735e54d8f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -310,11 +310,14 @@ class ReadCommand : CommandSerializer() { */ tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.TerminalPublicKey, cardEnvironment.terminalKeys?.publicKey) - return CommandApdu(Instruction.Read, tlvBuilder.serialize()) + return CommandApdu( + Instruction.Read, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val tlvMapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt index c681f5fed9..fea83e979f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -60,11 +60,14 @@ class ReadIssuerDataCommand( tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData) - return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.ReadIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt index 4e3f757395..3a6e8309ff 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -67,11 +67,14 @@ class ReadIssuerExtraDataCommand( tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData) tlvBuilder.append(TlvTag.Offset, offset) - return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.ReadIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerExtraDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index 3628d9cad7..ba5954fe01 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -57,11 +57,14 @@ class ReadUserDataCommand: CommandSerializer() { builder.append(TlvTag.CardId, cardEnvironment.cardId) builder.append(TlvTag.Pin, cardEnvironment.pin1) - return CommandApdu(Instruction.ReadUserData, builder.serialize()) + return CommandApdu( + Instruction.ReadUserData, builder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadUserDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt index d76722e96c..6f0b7bfacc 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -4,9 +4,6 @@ import com.tangem.common.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.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -60,7 +57,10 @@ class SignCommand(private val hashes: Array) tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) addTerminalSignature(cardEnvironment, tlvBuilder) - return CommandApdu(Instruction.Sign, tlvBuilder.serialize()) + return CommandApdu( + Instruction.Sign, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } /** @@ -79,7 +79,7 @@ class SignCommand(private val hashes: Array) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): SignResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null val tlvMapper = TlvMapper(tlvData) return SignResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt index 2ca018f2a0..dbc7f15640 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -45,11 +45,14 @@ class WriteIssuerDataCommand( tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature) tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter) - return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.WriteIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt index 7a1b987cd5..753607f0a9 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -60,7 +60,10 @@ class WriteIssuerExtraDataCommand( tlvBuilder.append(TlvTag.IssuerDataSignature, finalizingSignature) } } - return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.WriteIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } private fun getDataToWrite(): ByteArray = @@ -72,7 +75,7 @@ class WriteIssuerExtraDataCommand( } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index d512c4ded7..804abf6601 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -47,11 +47,14 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val if (userProtectedCounter != null || userProtectedData != null) builder.append(TlvTag.Pin2, cardEnvironment.pin2) - return CommandApdu(Instruction.WriteUserData, builder.serialize()) + return CommandApdu( + Instruction.WriteUserData, builder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteUserDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { WriteUserDataResponse(TlvMapper(tlvData).map(TlvTag.CardId)) diff --git a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt index 21728a8c44..c30efb5d30 100644 --- a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt @@ -10,6 +10,7 @@ data class CardEnvironment( val pin2: String = DEFAULT_PIN2, val cardId: String? = null, val terminalKeys: KeyPair? = null, + val encryptionMode: EncryptionMode = EncryptionMode.NONE, val encryptionKey: ByteArray? = null, val cvc: ByteArray? = null ) { diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt index 450618ced5..1a2c2be076 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt @@ -1,6 +1,9 @@ package com.tangem.common.apdu import com.tangem.common.EncryptionMode +import com.tangem.common.extensions.calculateCrc16 +import com.tangem.common.extensions.toByteArray +import com.tangem.crypto.encrypt import java.io.ByteArrayOutputStream /** @@ -8,21 +11,19 @@ import java.io.ByteArrayOutputStream * to a raw data that can be sent to the card. * * @property ins Instruction code that determines the type of request for the card. - * @property tlvList A list of TLVs that are to be sent to the card + * @property tlvs Tlvs encoded to a [ByteArray] that are to be sent to the card. */ class CommandApdu( private val ins: Int, private val tlvs: ByteArray, - private val cla: Byte = ISO_CLA, - private val p1: Byte = 0x00, - private val p2: Byte = 0x00, - private val le: Int = 0x00, private val encryptionMode: EncryptionMode = EncryptionMode.NONE, - private val encryptionKey: ByteArray? = null) { + private val encryptionKey: ByteArray? = null, + + private val cla: Int = ISO_CLA) { constructor( instruction: Instruction, @@ -36,6 +37,19 @@ class CommandApdu( encryptionKey = encryptionKey ) + private val p1: Int + private val p2: Int + + init { + if (ins == Instruction.OpenSession.code) { + p1 = 0x00 + p2 = encryptionMode.code.toInt() + } else { + p1 = encryptionMode.code.toInt() + p2 = 0x00 + } + } + /** * Request converted to a raw data @@ -48,33 +62,37 @@ class CommandApdu( private fun toBytes(): ByteArray { - - val lc = tlvs.size + val data = if (encryptionKey != null) tlvs.encrypt() else tlvs val byteStream = ByteArrayOutputStream() - byteStream.write(cla.toInt()) + byteStream.write(cla) byteStream.write(ins) - byteStream.write(p1.toInt()) - byteStream.write(p2.toInt()) - if (lc != 0) { - writeLength(byteStream, lc) - byteStream.write(tlvs) + byteStream.write(p1) + byteStream.write(p2) + if (data.isNotEmpty()) { + byteStream.writeLength(data.size) + byteStream.write(data) } return byteStream.toByteArray() } - private fun writeLength(stream: ByteArrayOutputStream, lc: Int) { - stream.write(0) - stream.write(lc shr 8) - stream.write(lc and 0xFF) + private fun ByteArrayOutputStream.writeLength(lc: Int) { + this.write(0) + this.write(lc shr 8) + this.write(lc and 0xFF) } - private fun encrypt() { - TODO("not implemented") - } + private fun ByteArray.encrypt(): ByteArray { + val crc: ByteArray = tlvs.calculateCrc16() + val stream = ByteArrayOutputStream() + stream.write(this.size.toByteArray(2)) + stream.write(crc) + stream.write(this) + return stream.toByteArray().encrypt(encryptionKey!!) + } companion object { - const val ISO_CLA = 0x00.toByte() + const val ISO_CLA = 0x00 } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt index e98c78874b..e7e8625384 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt @@ -1,6 +1,9 @@ package com.tangem.common.apdu +import com.tangem.common.extensions.calculateCrc16 import com.tangem.common.tlv.Tlv +import com.tangem.crypto.decrypt +import java.io.ByteArrayInputStream /** * Stores response data from the card and parses it to [Tlv] and [StatusWord]. @@ -25,15 +28,39 @@ class ResponseApdu(private val data: ByteArray) { * (Encryption / decryption functionality is not implemented yet.) */ fun getTlvData(encryptionKey: ByteArray? = null): List? { - return when { - data.size <= 2 -> null - else -> Tlv.deserialize(data.copyOf(data.size - 2)) + return if (data.size <= 2) { + null + } else { + val responseData = data.copyOf(data.size - 2) + return if (encryptionKey != null) { + if (data.size >= 18) { + val decryptedData = decrypt(responseData, encryptionKey) + Tlv.deserialize(decryptedData) + } else { + null + } + } else { + Tlv.deserialize(responseData) + } } } + private fun decrypt(responseData: ByteArray, encryptionKey: ByteArray): ByteArray { + val decryptedData: ByteArray = responseData.decrypt(encryptionKey) - private fun decrypt(encryptionKey: ByteArray) { - TODO("not implemented") + val inputStream = ByteArrayInputStream(decryptedData) + val baLength = ByteArray(2) + inputStream.read(baLength) + val length = (baLength[0].toInt() and 0xFF) * 256 + (baLength[1].toInt() and 0xFF) + if (length > decryptedData.size - 4) throw Exception("Can't decrypt - data size invalid") + val baCRC = ByteArray(2) + inputStream.read(baCRC) + val answerData = ByteArray(length) + inputStream.read(answerData) + val crc: ByteArray = answerData.calculateCrc16() + if (!baCRC.contentEquals(crc)) throw Exception("Can't decrypt - crc invalid") + + return answerData } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt index f7e416d700..ac201f5fcb 100644 --- a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt +++ b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt @@ -6,6 +6,7 @@ import java.nio.ByteBuffer import java.security.MessageDigest import java.util.* import kotlin.experimental.and +import kotlin.experimental.xor /** * Extension functions for [ByteArray]. @@ -53,4 +54,21 @@ fun ByteArray.toCompressedPublicKey(): ByteArray { } else { this } +} + +fun ByteArray.calculateCrc16(): ByteArray { + var chBlock: Byte + // STEP 1 Initialize the CRC-16 value + var wCRC = 0x6363 // ITU-V.41 + var i = 0 + // STEP 2 Update data and Calucuate their CRC + do { + chBlock = this.get(i++) + chBlock = chBlock xor (wCRC and 0x00FF).toByte() + val chBlockInt = (chBlock.toInt() xor (chBlock.toInt() shl 4)) + wCRC = wCRC shr 8 xor (chBlockInt and 0xFF shl 8) and 0xFFFF xor (chBlockInt and 0xFF shl 3 and 0xFFFF) xor (chBlockInt and 0xFF shr 4 and 0xFFFF) + // (wCRC>>8)^((int)chBlock<<8)^((int) chBlock<<3)^((int)chBlock>>4); + } while (i < this.size) + + return byteArrayOf((wCRC and 0xFF).toByte(), (wCRC and 0xFFFF shr 8).toByte()) } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index d37500e221..c9f3275dfe 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -51,6 +51,7 @@ enum class TlvTag(val code: Int) { SessionKeyA(0x1A), SessionKeyB(0x1B), + Uid(0x0B), Pause(0x1C), ManufactureId(0x20), @@ -66,7 +67,6 @@ enum class TlvTag(val code: Int) { Mode(0x23), Offset(0x24), - IsActivated(0x3A), ActivationSeed(0x3B), ResetPin(0x36), @@ -95,7 +95,6 @@ enum class TlvTag(val code: Int) { ProductMask(0x8A), PaymentFlowVersion(0x54), - TokenSymbol(0xA0), TokenContractAddress(0xA1), TokenDecimal(0xA2), diff --git a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt index 734b3d8f7a..d7643bdaae 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt @@ -2,8 +2,12 @@ package com.tangem.crypto import com.tangem.commands.EllipticCurve import net.i2p.crypto.eddsa.EdDSASecurityProvider +import java.security.PublicKey import java.security.SecureRandom import java.security.Security +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec object CryptoUtils { @@ -62,6 +66,16 @@ object CryptoUtils { EllipticCurve.Ed25519 -> Ed25519.generatePublicKey(privateKeyArray) } } + + fun loadPublicKey( + publicKey: ByteArray, + curve: EllipticCurve = EllipticCurve.Secp256k1 + ): PublicKey { + return when (curve) { + EllipticCurve.Secp256k1 -> Secp256k1.loadPublicKey(publicKey) + EllipticCurve.Ed25519 -> Ed25519.loadPublicKey(publicKey) + } + } } /** @@ -79,4 +93,28 @@ fun ByteArray.sign(privateKeyArray: ByteArray, curve: EllipticCurve = EllipticCu } } +fun ByteArray.encrypt(key: ByteArray, usePkcs7: Boolean = true): ByteArray { + val spec = if (usePkcs7) ENCRYPTION_SPEC_PKCS7 else ENCRYPTION_SPEC_NO_PADDING + val secretKeySpec = SecretKeySpec(key, spec) + val cipher = Cipher.getInstance(spec, "SC") + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, IvParameterSpec(ByteArray(16))) + return cipher.doFinal(this) +} + +fun ByteArray.decrypt(key: ByteArray, usePkcs7: Boolean = true): ByteArray { + val spec = if (usePkcs7) ENCRYPTION_SPEC_PKCS7 else ENCRYPTION_SPEC_NO_PADDING + val secretKeySpec = SecretKeySpec(key, spec) + val cipher = Cipher.getInstance(spec) + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, IvParameterSpec(ByteArray(16))) + return cipher.doFinal(this.copyOfRange(0, this.size)) +} + +fun ByteArray.pbkdf2Hash(salt: ByteArray, iterations: Int): ByteArray { + return Pbkdf2().deriveKey(this, salt, iterations) +} + +private const val ENCRYPTION_SPEC_PKCS7 = "AES/CBC/PKCS7PADDING" +private const val ENCRYPTION_SPEC_NO_PADDING = "AES/CBC/NOPADDING" + + diff --git a/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt b/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt index fd0cf14cc1..5c07b5a4e9 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt @@ -24,7 +24,7 @@ object Ed25519 { return signatureInstance.verify(signature) } - private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { + internal fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519) val pubKey = EdDSAPublicKeySpec(publicKeyArray, spec) return EdDSAPublicKey(pubKey) diff --git a/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt b/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt new file mode 100644 index 0000000000..ab3d97445c --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt @@ -0,0 +1,50 @@ +package com.tangem.crypto + +import org.spongycastle.jce.interfaces.ECPublicKey +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.SecureRandom +import java.security.spec.ECGenParameterSpec +import javax.crypto.KeyAgreement + +interface EncryptionHelper { + val keyA: ByteArray + + fun generateSecret(keyB: ByteArray): ByteArray +} + +class StrongEncryptionHelper : EncryptionHelper { + private val keyPair = generateKeyPair() + private val keyAgreement = generateKeyAgreement(keyPair) + override val keyA = provideKeyA(keyPair) + + override fun generateSecret(keyB: ByteArray): ByteArray { + keyAgreement.doPhase(CryptoUtils.loadPublicKey(keyB), true) + return keyAgreement.generateSecret() + } + + private fun generateKeyPair(): KeyPair { + val kpgen = KeyPairGenerator.getInstance("ECDH", "SC") + kpgen.initialize(ECGenParameterSpec("secp256k1"), SecureRandom()) + return kpgen.generateKeyPair() + } + + private fun generateKeyAgreement(keyPair: KeyPair): KeyAgreement { + val keyAgreement = KeyAgreement.getInstance("ECDH", "SC") + keyAgreement.init(keyPair.private) + return keyAgreement + } + + private fun provideKeyA(keyPair: KeyPair): ByteArray { + val eckey = keyPair.public as ECPublicKey + return eckey.q.getEncoded(false) + } +} + +class FastEncryptionHelper : EncryptionHelper { + override val keyA = CryptoUtils.generateRandomBytes(16) + + override fun generateSecret(keyB: ByteArray): ByteArray { + return keyA + keyB + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt b/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt new file mode 100644 index 0000000000..4c532b3692 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt @@ -0,0 +1,88 @@ +package com.tangem.crypto + +import org.spongycastle.crypto.CipherParameters +import org.spongycastle.crypto.digests.SHA256Digest +import org.spongycastle.crypto.macs.HMac +import org.spongycastle.crypto.params.KeyParameter +import java.security.InvalidKeyException +import java.util.* +import kotlin.experimental.xor +import kotlin.math.min +import kotlin.math.pow + +class Pbkdf2 { + private val F: HMac = HMac(SHA256Digest()) + + fun deriveKey(password: ByteArray, salt: ByteArray, iterations: Int): ByteArray { + + val macSize = F.macSize + // Check key length + if (macSize > (2.0.pow(32.0) - 1) * macSize) throw InvalidKeyException("Derived key to long") + + val derivedKey = ByteArray(macSize) + + val J = 0 + val K: Int = macSize + val U: Int = macSize shl 1 + val B = K + U + val workingArray = ByteArray(K + U + 4) + + // Initialize F + val macParams: CipherParameters = KeyParameter(password) + F.init(macParams) + + // Perform iterations + var kpos = 0 + var blk = 1 + while (kpos < macSize) { + storeInt32BE(blk, workingArray, B) + F.update(salt, 0, salt.size) + F.reset() + F.update(salt, 0, salt.size) + F.update(workingArray, B, 4) + F.doFinal(workingArray, U) + System.arraycopy(workingArray, U, workingArray, J, K) + var i = 1 + var j = J + var k = K + while (i < iterations) { + F.init(macParams) + F.update(workingArray, j, K) + F.doFinal(workingArray, k) + var u = U + var v = k + while (u < B) { + workingArray[u] = workingArray[u] xor workingArray[v] + u++ + v++ + } + val swp = k + k = j + j = swp + i++ + } + val tocpy = min(macSize - kpos, K) + System.arraycopy(workingArray, U, derivedKey, kpos, tocpy) + kpos += K + blk++ + } + Arrays.fill(workingArray, 0.toByte()) + return derivedKey + } + + /** + * Convert a 32-bit integer value into a big-endian byte array + * + * @param value The integer value to convert + * @param bytes The byte array to store the converted value + * @param offSet The offset in the output byte array + */ + private fun storeInt32BE(value: Int, bytes: ByteArray, offSet: Int) { + bytes[offSet + 3] = value.toByte() + bytes[offSet + 2] = (value ushr 8).toByte() + bytes[offSet + 1] = (value ushr 16).toByte() + bytes[offSet] = (value ushr 24).toByte() + } + + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt b/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt index 2a62979bed..97eba2a81c 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt @@ -29,7 +29,7 @@ object Secp256k1 { return signatureInstance.verify(sigDer) } - private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { + internal fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { val spec = ECNamedCurveTable.getParameterSpec("secp256k1") val factory = KeyFactory.getInstance("EC", "SC") diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt index 2e193ecc5e..20dee606ee 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -3,14 +3,17 @@ package com.tangem.tasks import com.tangem.CardManagerDelegate import com.tangem.CardReader import com.tangem.Log -import com.tangem.commands.Card -import com.tangem.commands.CommandResponse -import com.tangem.commands.CommandSerializer -import com.tangem.commands.ReadCommand +import com.tangem.commands.* import com.tangem.common.CardEnvironment import com.tangem.common.CompletionResult +import com.tangem.common.EncryptionMode import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.StatusWord +import com.tangem.common.extensions.calculateSha256 +import com.tangem.crypto.EncryptionHelper +import com.tangem.crypto.FastEncryptionHelper +import com.tangem.crypto.StrongEncryptionHelper +import com.tangem.crypto.pbkdf2Hash /** * An error class that represent typical errors that may occur when performing Tangem SDK tasks. @@ -85,6 +88,7 @@ abstract class Task { var reader: CardReader? = null var performPreflightRead: Boolean = true var securityDelayDuration: Int = 0 + private var openSessionRequired = false /** * This method should be called to run the [Task] and perform all its operations. @@ -137,8 +141,43 @@ abstract class Task { Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is initiated") - val commandApdu = command.serialize(cardEnvironment) - sendRequest(command, commandApdu, cardEnvironment, callback) + if (cardEnvironment.encryptionKey != null && !openSessionRequired) { + val commandApdu = command.serialize(cardEnvironment) + sendRequest(command, commandApdu, cardEnvironment, callback) + return + } + + when (cardEnvironment.encryptionMode) { + EncryptionMode.NONE -> { + val commandApdu = command.serialize(cardEnvironment) + sendRequest(command, commandApdu, cardEnvironment, callback) + } + EncryptionMode.FAST, EncryptionMode.STRONG -> { + val encryptionHelper: EncryptionHelper = + if (cardEnvironment.encryptionMode == EncryptionMode.STRONG) { + StrongEncryptionHelper() + } else { + FastEncryptionHelper() + } + val openSessionCommand = OpenSessionCommand(encryptionHelper.keyA) + val openSessionApdu = openSessionCommand.serialize(cardEnvironment) + sendRequest(openSessionCommand, openSessionApdu, cardEnvironment) { result -> + when (result) { + is CompletionResult.Success -> { + val uid = result.data.uid + val protocolKey = cardEnvironment.pin1.calculateSha256().pbkdf2Hash(uid, 50) + val secret = encryptionHelper.generateSecret(result.data.sessionKeyB) + val sessionKey = (secret + protocolKey).calculateSha256() + + val newEnvironment = cardEnvironment.copy(encryptionKey = sessionKey) + openSessionRequired = false + sendCommand(command, newEnvironment, callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + } } private fun sendRequest(command: CommandSerializer, @@ -171,9 +210,25 @@ abstract class Task { StatusWord.InvalidState -> callback(CompletionResult.Failure(TaskError.InvalidState())) StatusWord.InsNotSupported -> callback(CompletionResult.Failure(TaskError.InsNotSupported())) - StatusWord.NeedEncryption -> callback(CompletionResult.Failure(TaskError.NeedEncryption())) + StatusWord.NeedEncryption -> { + openSessionRequired = true + val newEnvironment = when (cardEnvironment.encryptionMode) { + EncryptionMode.NONE -> { + cardEnvironment.copy(encryptionMode = EncryptionMode.FAST) + } + EncryptionMode.FAST -> { + cardEnvironment.copy(encryptionMode = EncryptionMode.STRONG) + } + EncryptionMode.STRONG -> { + Log.e(this::class.simpleName!!, "Encryption doesn't work") + callback(CompletionResult.Failure(TaskError.NeedEncryption())) + return@transceiveApdu + } + } + sendCommand(command, newEnvironment, callback) + } StatusWord.NeedPause -> { - // When NeedPause is returned from the card whenever security delay is triggered. + // NeedPause is returned from the card whenever security delay is triggered. val remainingTime = command.deserializeSecurityDelay(responseApdu, cardEnvironment) if (remainingTime != null) delegate?.onSecurityDelay(remainingTime, securityDelayDuration) Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} triggered security delay of $remainingTime milliseconds") @@ -201,7 +256,6 @@ abstract class Task { callback(TaskEvent.Completion(readResult.error)) } is CompletionResult.Success -> { - val receivedCardId = readResult.data.cardId securityDelayDuration = readResult.data.pauseBeforePin2 ?: 0 @@ -215,10 +269,7 @@ abstract class Task { onRun(newEnvironment, readResult.data, callback) } } - - } - } } From ec2821e222fedd9a1b2fd3ee6fe60b33af893202 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Mar 2020 16:23:42 +0300 Subject: [PATCH 02/17] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/eth/EthEngine.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java index 7175f68f73..0435406297 100644 --- a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java @@ -228,10 +228,12 @@ public class EthEngine extends CoinEngine { @Override public Uri getShareWalletUri() { - if (ctx.getCard().getDenomination() != null) { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); - } else { +// if (ctx.getCard().getDenomination() != null) { +// return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); + if (ctx.getBlockchain() == Blockchain.Ethereum) { return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + } else { + return Uri.parse(ctx.getCoinData().getWallet()); } } From e19785b759b96616df68c141621152ea68456f6d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 11 Mar 2020 16:56:04 +0300 Subject: [PATCH 03/17] Updated on 2026-08-14 --- .../src/main/java/com/tangem/tasks/Task.kt | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt index 20dee606ee..9b5c13df6f 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -88,7 +88,6 @@ abstract class Task { var reader: CardReader? = null var performPreflightRead: Boolean = true var securityDelayDuration: Int = 0 - private var openSessionRequired = false /** * This method should be called to run the [Task] and perform all its operations. @@ -141,18 +140,17 @@ abstract class Task { Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is initiated") - if (cardEnvironment.encryptionKey != null && !openSessionRequired) { - val commandApdu = command.serialize(cardEnvironment) - sendRequest(command, commandApdu, cardEnvironment, callback) - return - } - when (cardEnvironment.encryptionMode) { EncryptionMode.NONE -> { val commandApdu = command.serialize(cardEnvironment) sendRequest(command, commandApdu, cardEnvironment, callback) } EncryptionMode.FAST, EncryptionMode.STRONG -> { + if (cardEnvironment.encryptionKey != null ) { + val commandApdu = command.serialize(cardEnvironment) + sendRequest(command, commandApdu, cardEnvironment, callback) + return + } val encryptionHelper: EncryptionHelper = if (cardEnvironment.encryptionMode == EncryptionMode.STRONG) { StrongEncryptionHelper() @@ -168,10 +166,9 @@ abstract class Task { val protocolKey = cardEnvironment.pin1.calculateSha256().pbkdf2Hash(uid, 50) val secret = encryptionHelper.generateSecret(result.data.sessionKeyB) val sessionKey = (secret + protocolKey).calculateSha256() + cardEnvironment.encryptionKey = sessionKey - val newEnvironment = cardEnvironment.copy(encryptionKey = sessionKey) - openSessionRequired = false - sendCommand(command, newEnvironment, callback) + sendCommand(command, cardEnvironment, callback) } is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } @@ -211,13 +208,14 @@ abstract class Task { StatusWord.InsNotSupported -> callback(CompletionResult.Failure(TaskError.InsNotSupported())) StatusWord.NeedEncryption -> { - openSessionRequired = true - val newEnvironment = when (cardEnvironment.encryptionMode) { + when (cardEnvironment.encryptionMode) { EncryptionMode.NONE -> { - cardEnvironment.copy(encryptionMode = EncryptionMode.FAST) + cardEnvironment.encryptionKey = null + cardEnvironment.encryptionMode = EncryptionMode.FAST } EncryptionMode.FAST -> { - cardEnvironment.copy(encryptionMode = EncryptionMode.STRONG) + cardEnvironment.encryptionKey = null + cardEnvironment.encryptionMode = EncryptionMode.STRONG } EncryptionMode.STRONG -> { Log.e(this::class.simpleName!!, "Encryption doesn't work") @@ -225,7 +223,7 @@ abstract class Task { return@transceiveApdu } } - sendCommand(command, newEnvironment, callback) + sendCommand(command, cardEnvironment, callback) } StatusWord.NeedPause -> { // NeedPause is returned from the card whenever security delay is triggered. From 2badf92d8621f2fc8ff9539382a2aff14871f7c4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 11 Mar 2020 17:14:07 +0300 Subject: [PATCH 04/17] Updated on 2026-08-14 --- app/src/main/java/com/tangem/data/network/Server.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index 97fb0ab7e3..c31e4cf46b 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -150,12 +150,13 @@ public class Server { public static class ApiBlockchair { public static final String URL_BLOCKCHAIR = ServerURL.API_BLOCKCHAIR + "{blockchain}/"; + private static final String API_KEY = "?key=A___0Shpsu4KagE7oSabrw20DfXAqWlT"; public static class Method { - static final String ADDRESS = URL_BLOCKCHAIR + "dashboards/address/{address}"; - static final String TRANSACTION = URL_BLOCKCHAIR + "dashboards/transaction/{transaction}"; - static final String STATS = URL_BLOCKCHAIR + "stats"; - static final String PUSH = URL_BLOCKCHAIR + "push/transaction"; + static final String ADDRESS = URL_BLOCKCHAIR + "dashboards/address/{address}" + API_KEY; + static final String TRANSACTION = URL_BLOCKCHAIR + "dashboards/transaction/{transaction}" + API_KEY; + static final String STATS = URL_BLOCKCHAIR + "stats" + API_KEY; + static final String PUSH = URL_BLOCKCHAIR + "push/transaction" + API_KEY; } } } \ No newline at end of file From 279ec277f9b3fde8d13c50be55ce084a09c08ae0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 11 Mar 2020 17:20:19 +0300 Subject: [PATCH 05/17] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java index edc471e86b..2be3abdcac 100644 --- a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java +++ b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java @@ -141,7 +141,7 @@ public class BtcCashEngine extends CoinEngine { @Override public Uri getWalletExplorerUri() { - return Uri.parse("https://explorer.bitcoin.com/bch/address/" + ctx.getCoinData().getWallet()); + return Uri.parse("https://api.blockchair.com/bitcoin-cash/dashboards/address/" + ctx.getCoinData().getWallet()); } @Override From 2b2ab9888c449f2be5bc2cba21872672710bed9e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 12 Mar 2020 09:15:05 +0300 Subject: [PATCH 06/17] Updated on 2026-08-14 --- .../src/main/java/com/tangem/common/CardEnvironment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt index c30efb5d30..c556f427b3 100644 --- a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt @@ -10,8 +10,8 @@ data class CardEnvironment( val pin2: String = DEFAULT_PIN2, val cardId: String? = null, val terminalKeys: KeyPair? = null, - val encryptionMode: EncryptionMode = EncryptionMode.NONE, - val encryptionKey: ByteArray? = null, + var encryptionMode: EncryptionMode = EncryptionMode.NONE, + var encryptionKey: ByteArray? = null, val cvc: ByteArray? = null ) { From 15399958314fe1836dd2b230927b1808d491b20e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 10:19:31 +0300 Subject: [PATCH 07/17] Updated on 2026-08-14 --- tangem-core/src/main/java/com/tangem/CardManager.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index 4094d935bc..c79a65d3f3 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -5,7 +5,6 @@ import com.tangem.common.CardEnvironment import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils import com.tangem.tasks.* -import java.util.concurrent.Executors /** * The main interface of Tangem SDK that allows your app to communicate with Tangem cards. @@ -24,7 +23,6 @@ class CardManager( private var terminalKeysService: TerminalKeysService? = null private var isBusy = false - private val cardManagerExecutor = Executors.newSingleThreadExecutor() init { CryptoUtils.initCrypto() @@ -262,7 +260,7 @@ class CardManager( task.reader = reader task.delegate = cardManagerDelegate - cardManagerExecutor.execute { + Thread().run { task.run(environment) { taskEvent -> if (taskEvent is TaskEvent.Completion) isBusy = false callback(taskEvent) From 155283a8f4a757f9564890919a172d82abb3fe52 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 10:43:21 +0300 Subject: [PATCH 08/17] Updated on 2026-08-14 --- .../src/main/java/com/tangem/tasks/Task.kt | 140 ++++++++++++++---- 1 file changed, 112 insertions(+), 28 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt index 2e193ecc5e..8be8b1a1a9 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -16,44 +16,128 @@ import com.tangem.common.apdu.StatusWord * An error class that represent typical errors that may occur when performing Tangem SDK tasks. * Errors are propagated back to the caller in callbacks. */ -sealed class TaskError(val code: Int): Exception() { +sealed class TaskError(val code: Int) : Exception() { //Errors in serializing APDU - class SerializeCommandError: TaskError(1001) - class EncodingError: TaskError(1002) - class MissingTag: TaskError(1003) - class WrongType: TaskError(1004) - class ConvertError: TaskError(1005) + /** + * This error is returned when there [CommandSerializer] cannot deserialize [com.tangem.common.tlv.Tlv] + * (this error is a wrapper around internal [com.tangem.common.tlv.TlvMapper] errors). + */ + class SerializeCommandError : TaskError(1001) - //Card errors - class UnknownStatus: TaskError(2001) - class ErrorProcessingCommand: TaskError(2002) - class MissingPreflightRead: TaskError(2003) - class InvalidState: TaskError(2004) - class InsNotSupported: TaskError(2005) - class InvalidParams: TaskError(2006) - class NeedEncryption: TaskError(2007) + class EncodingError : TaskError(1002) + class MissingTag : TaskError(1003) + class WrongType : TaskError(1004) + class ConvertError : TaskError(1005) + + /** + * This error is returned when unknown [StatusWord] is received from a card. + */ + class UnknownStatus : TaskError(2001) + + /** + * This error is returned when a card's reply is [StatusWord.ErrorProcessingCommand]. + * The card sends this status in case of internal card error. + */ + class ErrorProcessingCommand : TaskError(2002) + + /** + * This error is returned when a task (such as [ScanTask]) requires that [ReadCommand] + * is executed before performing other commands. + */ + class MissingPreflightRead : TaskError(2003) + + /** + * This error is returned when a card's reply is [StatusWord.InvalidState]. + * The card sends this status when command can not be executed in the current state of a card. + */ + class InvalidState : TaskError(2004) + + /** + * This error is returned when a card's reply is [StatusWord.InsNotSupported]. + * The card sends this status when the card cannot process the [com.tangem.common.apdu.Instruction]. + */ + class InsNotSupported : TaskError(2005) + + /** + * This error is returned when a card's reply is [StatusWord.InvalidParams]. + * The card sends this status when there are wrong or not sufficient parameters in TLV request, + * or wrong PIN1/PIN2. + * The error may be caused, for example, by wrong parameters of the [Task], [CommandSerializer], + * mapping or serialization errors. + */ + class InvalidParams : TaskError(2006) + + /** + * This error is returned when a card's reply is [StatusWord.NeedEncryption] + * and the encryption was not established by TangemSdk. + */ + class NeedEncryption : TaskError(2007) //Scan errors - class VerificationFailed: TaskError(3000) - class CardError: TaskError(3001) - class WrongCard: TaskError(3002) - class TooMuchHashesInOneTransaction: TaskError(3003) - class EmptyHashes: TaskError(3004) - class HashSizeMustBeEqual: TaskError(3005) + /** + * This error is returned when a [Task] checks unsuccessfully either + * a card's ability to sign with its private key, or the validity of issuer data. + */ + class VerificationFailed : TaskError(3000) - class Busy: TaskError(4000) - class UserCancelled: TaskError(4001) - class UnsupportedDevice: TaskError(4002) + /** + * This error is returned when a [ScanTask] returns a [Card] without some of the essential fields. + */ + class CardError : TaskError(3001) - //NFC error - class NfcReaderError: TaskError(5002) - class TagLost: TaskError(5003) + /** + * This error is returned when a [Task] expects a user to use a particular card, + * and a user tries to use a different card. + */ + class WrongCard : TaskError(3002) - class UnknownError: TaskError(6000) + /** + * Tangem cards can sign currently up to 10 hashes during one [com.tangem.commands.SignCommand]. + * This error is returned when a [com.tangem.commands.SignCommand] receives more than 10 hashes to sign. + */ + class TooMuchHashesInOneTransaction : TaskError(3003) + + /** + * This error is returned when a [com.tangem.commands.SignCommand] + * receives only empty hashes for signature. + */ + class EmptyHashes : TaskError(3004) + + /** + * This error is returned when a [com.tangem.commands.SignCommand] + * receives hashes of different lengths for signature. + */ + class HashSizeMustBeEqual : TaskError(3005) + + /** + * This error is returned when [com.tangem.CardManager] was called with a new [Task], + * while a previous [Task] is still in progress. + */ + class Busy : TaskError(4000) + + /** + * This error is returned when a user manually closes NFC Reading Bottom Sheet Dialog. + */ + class UserCancelled : TaskError(4001) + + //NFC errors + class NfcReaderError : TaskError(5002) + + /** + * This error is returned when Android NFC reader loses a tag + * (e.g. a user detaches card from the phone's NFC module) while the NFC session is in progress. + */ + class TagLost : TaskError(5003) + + class UnknownError : TaskError(6000) //Issuer Data Errors - class MissingCounter: TaskError(7001) + /** + * This error is returned when [ReadIssuerDataTask] or [ReadIssuerExtraDataTask] expects a counter + * (when the card's requires it), but the counter is missing. + */ + class MissingCounter : TaskError(7001) } /** From 1e0f11bcbf7757d0abfd256ba84dbbdede7638b7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 14:50:45 +0300 Subject: [PATCH 09/17] Updated on 2026-08-14 --- .idea/gradle.xml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .idea/gradle.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 280cc6ed9d..0000000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - \ No newline at end of file From 8fa60efcfcc93b228d38aa6c2e57322b45a69039 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 14:52:49 +0300 Subject: [PATCH 10/17] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/btc/BtcEngine.java | 4 ++-- app/src/main/java/com/tangem/wallet/eth/EthEngine.java | 2 +- app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java | 5 ++--- app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java | 4 ++-- .../main/java/com/tangem/wallet/nftToken/NftTokenEngine.java | 2 +- app/src/main/java/com/tangem/wallet/token/TokenEngine.java | 4 ++-- app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java | 2 +- 7 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java index 4bfa331eda..05b489ba07 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -219,9 +219,9 @@ public class BtcEngine extends CoinEngine { @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null && !ctx.getCard().getDenominationText().equals("0.00")) { - return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); + return Uri.parse(ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); } else { - return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java index 0435406297..8c24e30480 100644 --- a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java @@ -231,7 +231,7 @@ public class EthEngine extends CoinEngine { // if (ctx.getCard().getDenomination() != null) { // return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); if (ctx.getBlockchain() == Blockchain.Ethereum) { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } else { return Uri.parse(ctx.getCoinData().getWallet()); } diff --git a/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java b/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java index ebf42f4e21..3c9e6c0017 100644 --- a/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java +++ b/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java @@ -27,7 +27,6 @@ import com.tangem.wallet.EthTransaction; import com.tangem.wallet.Keccak256; import com.tangem.wallet.R; import com.tangem.wallet.TangemContext; -import com.tangem.wallet.eth.EthData; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.SignatureDecodeException; @@ -185,9 +184,9 @@ public class EthIdEngine extends CoinEngine { @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null) { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); + return Uri.parse(ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); } else { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java index 801ed7673e..e8ebfa3fd3 100644 --- a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java @@ -179,9 +179,9 @@ public class LtcEngine extends BtcEngine { @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null) { - return Uri.parse("litecoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); + return Uri.parse(ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); } else { - return Uri.parse("litecoin:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java b/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java index 859d8f3d24..151e631fbd 100644 --- a/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java @@ -189,7 +189,7 @@ public class NftTokenEngine extends CoinEngine { @Override public Uri getShareWalletUri() { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } @Override diff --git a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java index ac453d5ff9..d36abf0480 100644 --- a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java @@ -305,9 +305,9 @@ public class TokenEngine extends CoinEngine { @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null) { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); + return Uri.parse(ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); } else { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index 73db5449a5..c5bd2f5815 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -148,7 +148,7 @@ public class XrpEngine extends CoinEngine { } public Uri getShareWalletUri() { - return Uri.parse("ripple:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } @Override From 8a22cf415e24aa91bfe00350ff4f29b10fda9ab9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 17:22:52 +0300 Subject: [PATCH 11/17] Updated on 2026-08-14 --- .../com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index ca0505d7aa..0f304ba5db 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -307,7 +307,10 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte viewModel.getRateInfo().observe(this, Observer { rate -> ctx.coinData.rate = rate ctx.coinData.rateAlter = rate + requestCounter-- + updateViews() }) + requestCounter++ viewModel.requestRateInfo(ctx) } @@ -717,8 +720,10 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte requestBalanceAndUnspentTransactions() - if (::viewModel.isInitialized) + if (::viewModel.isInitialized) { + requestCounter++ viewModel.requestRateInfo(ctx) + } if (requestCounter == 0) { // if no connection and no requests posted From 6e1c51584bdf735076ed26e6d611f2c7b88f0824 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 20:04:16 +0300 Subject: [PATCH 12/17] Updated on 2026-08-14 --- .../fragment/wallet/LoadedWalletFragment.kt | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index 0f304ba5db..806431837a 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -124,6 +124,16 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte val tag = arguments!!.getParcelable(NfcAdapter.EXTRA_TAG) if (tag != null) onTagDiscovered(tag) } + + viewModel = ViewModelProviders.of(this).get(LoadedWalletViewModel::class.java) + + // set rate info to CoinData + viewModel.getRateInfo().observe(this, Observer { rate -> + ctx.coinData.rate = rate + ctx.coinData.rateAlter = rate + updateViews() + }) + viewModel.requestRateInfo(ctx) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -299,21 +309,6 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte startVerify(lastTag) } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - viewModel = ViewModelProviders.of(this).get(LoadedWalletViewModel::class.java) - - // set rate info to CoinData - viewModel.getRateInfo().observe(this, Observer { rate -> - ctx.coinData.rate = rate - ctx.coinData.rateAlter = rate - requestCounter-- - updateViews() - }) - requestCounter++ - viewModel.requestRateInfo(ctx) - } - override fun onPause() { super.onPause() if (timerHideErrorAndMessage != null) { @@ -720,10 +715,8 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte requestBalanceAndUnspentTransactions() - if (::viewModel.isInitialized) { - requestCounter++ + if (::viewModel.isInitialized) viewModel.requestRateInfo(ctx) - } if (requestCounter == 0) { // if no connection and no requests posted From 38943c837096629e8a80726813c7297882014f76 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 20:42:18 +0300 Subject: [PATCH 13/17] Updated on 2026-08-14 --- .../java/com/tangem/wallet/xrp/XrpData.java | 17 +++++- .../java/com/tangem/wallet/xrp/XrpEngine.java | 58 +++++++++++++++---- 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java index 3df5e26532..1b10a221a3 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java @@ -17,7 +17,7 @@ public class XrpData extends CoinData { private Long reserve = 20000000L; - private Boolean accountNotFound = false; + private Boolean accountNotFound, targetAccountCreated = false; @Override public void loadFromBundle(Bundle B) { @@ -32,7 +32,9 @@ public class XrpData extends CoinData { if (B.containsKey("Reserve")) reserve = B.getLong("Reserve"); else reserve = 20000000L; if (B.containsKey("AccoundNotFound")) accountNotFound = B.getBoolean("AccoundNotFound"); - else reserve = 20000000L; + else accountNotFound = false; + if (B.containsKey("TargetAccountCreated")) targetAccountCreated = B.getBoolean("TargetAccountCreated"); + else targetAccountCreated = false; } @Override @@ -44,6 +46,7 @@ public class XrpData extends CoinData { if (sequence != null) B.putLong("Sequence", sequence); if (reserve != null) B.putLong("Reserve", reserve); if (accountNotFound != null) B.putBoolean("AccoundNotFound", accountNotFound); + if (targetAccountCreated != null) B.putBoolean("TargetAccountCreated", targetAccountCreated); } catch (Exception e) { Log.e("Can't save to bundle ", e.getMessage()); } @@ -57,6 +60,7 @@ public class XrpData extends CoinData { sequence = null; reserve = 20000000L; accountNotFound = false; + targetAccountCreated = false; } // balanceUnconfirmed is just the latest balance, it equals balanceConfirmed if no unconfirmed transaction present @@ -105,6 +109,14 @@ public class XrpData extends CoinData { this.accountNotFound = accountFound; } + public Boolean isTargetAccountCreated() { + return targetAccountCreated; + } + + public void setTargetAccountCreated(boolean targetAccountCreated) { + this.targetAccountCreated = targetAccountCreated; + } + public boolean hasBalanceInfo() { return balanceConfirmed != null || balanceUnconfirmed != null; } @@ -112,5 +124,4 @@ public class XrpData extends CoinData { public boolean hasUnconfirmed() { return !balanceConfirmed.equals(balanceUnconfirmed); } - } diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index 73db5449a5..0e0cc82884 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -342,6 +342,10 @@ public class XrpEngine extends CoinEngine { public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { checkBlockchainDataExists(); + if (!coinData.isTargetAccountCreated() && amountValue.compareTo(BigDecimal.valueOf(20)) < 0) { + throw new Exception("Target account is not created. Amount should be 20 XRP or more"); + } + String amount, fee; if (IncFee) { @@ -522,19 +526,50 @@ public class XrpEngine extends CoinEngine { ServerApiRipple.ResponseListener rippleListener = new ServerApiRipple.ResponseListener() { @Override public void onSuccess(String method, RippleResponse rippleResponse) { - try { - InternalAmount minFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getMinimum_fee()), "Drops"); - InternalAmount normalFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getOpen_ledger_fee()), "Drops"); - InternalAmount maxFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getMedian_fee()), "Drops"); + Log.i(TAG, "onSuccess: " + method); + switch (method) { + case ServerApiRipple.RIPPLE_ACCOUNT_INFO: { + try { + if (rippleResponse.getResult().getError_code().equals(19)) { // "Account not found" + coinData.setTargetAccountCreated(false); + } else { + coinData.setTargetAccountCreated(true); + } + } catch (Exception e) { + coinData.setTargetAccountCreated(true); //expected behaviour, if account exists, there should be no error code -> null pointer + } - coinData.minFee = convertToAmount(minFee); - coinData.normalFee = convertToAmount(normalFee); - coinData.maxFee = convertToAmount(maxFee); + if (serverApiRipple.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + break; - blockchainRequestsCallbacks.onComplete(true); - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "FAIL RIPPLE_FEE Exception"); + case ServerApiRipple.RIPPLE_FEE: { + try { + InternalAmount minFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getMinimum_fee()), "Drops"); + InternalAmount normalFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getOpen_ledger_fee()), "Drops"); + InternalAmount maxFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getMedian_fee()), "Drops"); + + coinData.minFee = convertToAmount(minFee); + coinData.normalFee = convertToAmount(normalFee); + coinData.maxFee = convertToAmount(maxFee); + + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL RIPPLE_FEE Exception"); + ctx.setError(e.getMessage()); + } + + if (serverApiRipple.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + break; } } @@ -548,6 +583,7 @@ public class XrpEngine extends CoinEngine { serverApiRipple.setResponseListener(rippleListener); + serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, targetAddress, ""); serverApiRipple.requestData(ServerApiRipple.RIPPLE_FEE, "", ""); } From fa340346d71255ad64d117639cd3e3ea42b30c17 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 20:51:21 +0300 Subject: [PATCH 14/17] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index 0e0cc82884..f34e34c015 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -342,8 +342,9 @@ public class XrpEngine extends CoinEngine { public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { checkBlockchainDataExists(); - if (!coinData.isTargetAccountCreated() && amountValue.compareTo(BigDecimal.valueOf(20)) < 0) { - throw new Exception("Target account is not created. Amount should be 20 XRP or more"); + Amount reserve = convertToAmount(coinData.getReserveInInternalUnits()); + if (!coinData.isTargetAccountCreated() && amountValue.compareTo(reserve) < 0) { + throw new Exception("Target account is not created. Amount should be " + reserve.toDescriptionString(getDecimals()) + " or more"); } String amount, fee; From 6a20df79293f84cc67bba5029d22f46913033134 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Mar 2020 08:30:12 +0300 Subject: [PATCH 15/17] Updated on 2026-08-14 --- .../java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt b/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt index d87b466385..ed3ba8bf1c 100644 --- a/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt +++ b/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt @@ -43,7 +43,7 @@ enum class NfcLocation(val codename: String, val fullName: String, val orientati model40("HWVTR", "Huawei P10", 0, 50, 0, 0), model41("HWWAS-H", "Huawei P10 lite", 0, 50, 0, 0), model42("HWVKY", "Huawei P10 Plus", 0, 50, 0, 0), - model43("HWEML", "Huawei P20", 0, 50, 50, 0), + model43("HWEML", "Huawei P20", 0, 40, 5, 0), model44("HWANE", "Huawei P20 Lite", 0, 50, 20, 0), model45("HWCLT", "Huawei P20 Pro", 0, 50, 50, 0), model46("HW-01K", "Huawei P20 Pro", 0, 50, 50, 0), @@ -231,8 +231,8 @@ enum class NfcLocation(val codename: String, val fullName: String, val orientati model228("sagit", "Xiaomi Mi 6", 0, 50, 20, 0), model229("dipper", "Xiaomi Mi 8", 0, 45, 20, 0), model230("ursa", "Xiaomi MI 8 Explorer Edition", 0, 50, 40, 0), - model231("cepheus", "Xiaomi MI 9", 0, 40, 20, 0), - model232("grus", "Xiaomi MI 9 SE", 0, 40, 20, 0), + model231("cepheus", "Xiaomi MI 9", 0, 40, 5, 0), + model232("grus", "Xiaomi MI 9 SE", 0, 40, 5, 0), model233("lithium", "Xiaomi Mi MIX", 0, 20, 20, 0), model234("chiron", "Xiaomi Mi MIX 2", 0, 45, 20, 0), model235("polaris", "Xiaomi Mi MIX 2S", 0, 45, 20, 0), From 8b6261007d37d4900cae6845dce08329fc61ad33 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Mar 2020 09:42:27 +0300 Subject: [PATCH 16/17] Updated on 2026-08-14 --- .../fragment/wallet/LoadedWalletFragment.kt | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index 806431837a..2fdf9776d8 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -124,16 +124,6 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte val tag = arguments!!.getParcelable(NfcAdapter.EXTRA_TAG) if (tag != null) onTagDiscovered(tag) } - - viewModel = ViewModelProviders.of(this).get(LoadedWalletViewModel::class.java) - - // set rate info to CoinData - viewModel.getRateInfo().observe(this, Observer { rate -> - ctx.coinData.rate = rate - ctx.coinData.rateAlter = rate - updateViews() - }) - viewModel.requestRateInfo(ctx) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -307,6 +297,17 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte serverApiTangem.setArtworkListener(artworkListener) refresh() startVerify(lastTag) + + + viewModel = ViewModelProviders.of(this).get(LoadedWalletViewModel::class.java) + + // set rate info to CoinData + viewModel.getRateInfo().observe(this, Observer { rate -> + ctx.coinData.rate = rate + ctx.coinData.rateAlter = rate + updateViews() + }) + viewModel.requestRateInfo(ctx) } override fun onPause() { From e78270ada3fa9d1a8db8bbb70cfadcf69913fdac Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Mar 2020 15:59:21 +0300 Subject: [PATCH 17/17] Updated on 2026-08-14 --- .../java/com/tangem/ui/fragment/VerifyCardFragment.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt b/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt index 307a9186b7..421f780cc1 100644 --- a/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt @@ -472,10 +472,14 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter. private fun doPurge() { requestPIN2Count = 0 - val engine = CoinEngineFactory.create(ctx) - if (!engine!!.hasBalanceInfo()) { + val engine = CoinEngineFactory.create(ctx) ?: return + if (!engine.hasBalanceInfo()) { return - } else if (engine.isBalanceNotZero) { + } + if (engine.isBalanceNotZero) { + Toast.makeText(context, R.string.general_error_cannot_erase_wallet_with_non_zero_balance, Toast.LENGTH_LONG).show() + return + } else if (engine.awaitingConfirmation()) { Toast.makeText(context, R.string.general_error_cannot_erase_wallet_with_non_zero_balance, Toast.LENGTH_LONG).show() return }