diff --git a/blockchain/build.gradle b/blockchain/build.gradle index e74dfefe20..26e6f2400c 100644 --- a/blockchain/build.gradle +++ b/blockchain/build.gradle @@ -47,6 +47,9 @@ dependencies { implementation 'org.bitcoinj:bitcoinj-core:0.15.2' implementation 'com.github.stellar:java-stellar-sdk:0.13.0' + implementation "com.madgag.spongycastle:core:1.58.0.0" + implementation "com.madgag.spongycastle:prov:1.58.0.0" + ext.kethereum_version = '0.79.5' implementation "com.github.walleth.kethereum:functions:$kethereum_version" implementation "com.github.walleth.kethereum:keccak_shortcut:$kethereum_version" diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinAddress.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinAddress.kt index 1836223aee..01cdd30770 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinAddress.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinAddress.kt @@ -33,18 +33,18 @@ class BitcoinAddressValidator { if (testNet && firstLettersNonTestNet.contains(address.first())) return false if (address.length !in 26..35) return false val decoded = address.decodeBase58() ?: return false - val hash = sha256(decoded, 0, 21, 2) + val hash = recursiveSha256(decoded, 0, 21, 2) return hash.sliceArray(0..3).contentEquals(decoded.sliceArray(21..24)) } else { return validateSegwitAddress(address, testNet) } } - private fun sha256(data: ByteArray, start: Int, len: Int, recursion: Int): ByteArray { + private fun recursiveSha256(data: ByteArray, start: Int, len: Int, recursion: Int): ByteArray { if (recursion == 0) return data val md = MessageDigest.getInstance("SHA-256") md.update(data.sliceArray(start until start + len)) - return sha256(md.digest(), 0, 32, recursion - 1) + return recursiveSha256(md.digest(), 0, 32, recursion - 1) } private fun String.decodeBase58(): ByteArray? { diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinTransactionBuilder.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinTransactionBuilder.kt index 31faf256a9..d066fa259e 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinTransactionBuilder.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinTransactionBuilder.kt @@ -58,6 +58,18 @@ class BitcoinTransactionBuilder(private val testNet: Boolean) { val signature = TransactionSignature(r, canonicalS) return ScriptBuilder.createInputScript(signature, ECKey.fromPublicOnly(publicKey)) } + + fun getEstimateSize(transactionData: TransactionData, walletPublicKey: ByteArray): Result { + val buildTransactionResult = buildToSign(transactionData) + when (buildTransactionResult) { + is Result.Failure -> return buildTransactionResult + is Result.Success -> { + val hashes = buildTransactionResult.data + val finalTransaction = buildToSend(ByteArray(64 * hashes.size) { 1 }, walletPublicKey) + return Result.Success(finalTransaction.size) + } + } + } } internal fun TransactionData.toBitcoinJTransaction(networkParameters: NetworkParameters?, diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt index 8820c732d9..a1779ce5ae 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt @@ -18,7 +18,6 @@ class BitcoinWalletManager( walletConfig: WalletConfig, isTestNet: Boolean = false ) : WalletManager, - TransactionEstimator, TransactionSender, FeeProvider { @@ -48,9 +47,9 @@ class BitcoinWalletManager( null, "unknown", currencyWallet.address)) - } else { - currencyWallet.pendingTransactions.clear() } + } else { + currencyWallet.pendingTransactions.clear() } } @@ -58,18 +57,6 @@ class BitcoinWalletManager( Log.e(this::class.java.simpleName, error?.message ?: "") } - override suspend fun getEstimateSize(transactionData: TransactionData): Result { - val buildTransactionResult = transactionBuilder.buildToSign(transactionData) - when (buildTransactionResult) { - is Result.Failure -> return buildTransactionResult - is Result.Success -> { - val hashes = buildTransactionResult.data - val finalTransaction = transactionBuilder.buildToSend(ByteArray(64 * hashes.size) {1}, walletPublicKey) - return Result.Success(finalTransaction.size) - } - } - } - override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult { val buildTransactionResult = transactionBuilder.buildToSign(transactionData) when (buildTransactionResult) { @@ -90,10 +77,11 @@ class BitcoinWalletManager( when (val result = networkManager.getFee()) { is Result.Failure -> return result is Result.Success -> { - val sizeResult = getEstimateSize( + val sizeResult = transactionBuilder.getEstimateSize( TransactionData(amount, Amount(1.toBigDecimal().divide(SATOSHI_IN_BTC), blockchain), - address, destination) + address, destination), + walletPublicKey ) when (sizeResult) { is Result.Failure -> return sizeResult diff --git a/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoAddress.kt b/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoAddress.kt index f8b6667362..3aecd18ff1 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoAddress.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoAddress.kt @@ -5,7 +5,6 @@ import co.nstant.`in`.cbor.CborDecoder import co.nstant.`in`.cbor.CborEncoder import co.nstant.`in`.cbor.model.Array import co.nstant.`in`.cbor.model.ByteString -import co.nstant.`in`.cbor.model.DataItem import co.nstant.`in`.cbor.model.UnsignedInteger import com.tangem.blockchain.cardano.crypto.Blake2b import com.tangem.blockchain.common.extensions.decodeBase58 diff --git a/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoTransactionBuilder.kt b/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoTransactionBuilder.kt index cbf54fc93d..a251e0068c 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoTransactionBuilder.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoTransactionBuilder.kt @@ -9,6 +9,7 @@ import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.extensions.decodeBase58 import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream +import java.math.BigDecimal class CardanoTransactionBuilder() { var unspentOutputs: List = listOf() @@ -143,6 +144,21 @@ class CardanoTransactionBuilder() { return fullAmount - (amount + fee) } + fun getEstimateSize(transactionData: TransactionData, walletPublicKey: ByteArray): Int { + val dummyFeeValue = BigDecimal.valueOf(0.1) + + val dummyFee = transactionData.amount.copy(value = dummyFeeValue) + val dummyAmount = + transactionData.amount.copy(value = transactionData.amount.value!! - dummyFeeValue) + + val dummyTransactionData = transactionData.copy( + amount = dummyAmount, + fee = dummyFee + ) + buildToSign(dummyTransactionData) + return buildToSend(ByteArray(64), walletPublicKey).size + } + companion object { private const val PROTOCOL_MAGIC: Long = 764824073 } diff --git a/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoWalletManager.kt index 421eb5d996..e5c7b71a70 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoWalletManager.kt @@ -17,7 +17,6 @@ class CardanoWalletManager( private val walletPublicKey: ByteArray, walletConfig: WalletConfig ) : WalletManager, - TransactionEstimator, TransactionSender, FeeProvider { @@ -47,21 +46,6 @@ class CardanoWalletManager( Log.e(this::class.java.simpleName, error?.message ?: "") } - override suspend fun getEstimateSize(transactionData: TransactionData): Int { - val dummyFeeValue = BigDecimal.valueOf(0.1) - - val dummyFee = transactionData.amount.copy(value = dummyFeeValue) - val dummyAmount = - transactionData.amount.copy(value = transactionData.amount.value!! - dummyFeeValue) - - val dummyTransactionData = transactionData.copy( - amount = dummyAmount, - fee = dummyFee - ) - transactionBuilder.buildToSign(dummyTransactionData) - return transactionBuilder.buildToSend(ByteArray(64), walletPublicKey).size - } - override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult { val transactionHash = transactionBuilder.buildToSign(transactionData) @@ -77,8 +61,9 @@ class CardanoWalletManager( override suspend fun getFee(amount: Amount, source: String, destination: String): Result> { val a = 0.155381 val b = 0.000043946 - val size = getEstimateSize(TransactionData(amount, null, source, destination)) - + val size = transactionBuilder.getEstimateSize( + TransactionData(amount, null, source, destination), walletPublicKey + ) val fee = (a + b * size).toBigDecimal() return Result.Success(listOf(Amount(blockchain.currency, fee, source, blockchain.decimals))) } diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt index 0b55b6fba3..967385e85b 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt @@ -13,10 +13,6 @@ interface WalletManager { suspend fun update() } -interface TransactionEstimator { - suspend fun getEstimateSize(transactionData: TransactionData): Result -} - interface TransactionSender { suspend fun send(transactionData: TransactionData, signer: TransactionSigner) : SimpleResult } diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt index 3f57d8e038..187a068cee 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt @@ -15,35 +15,40 @@ object WalletManagerFactory { val blockchainName: String = card.cardData?.blockchainName ?: return null when { - blockchainName.contains("btc") || blockchainName.contains("bitcoin") -> { + blockchainName == Blockchain.Bitcoin.id -> { + return BitcoinWalletManager( + cardId = card.cardId, + walletPublicKey = walletPublicKey, + walletConfig = WalletConfig(true, true) + ) + } + blockchainName == Blockchain.BitcoinTestnet.id -> { return BitcoinWalletManager( cardId = card.cardId, walletPublicKey = walletPublicKey, walletConfig = WalletConfig(true, true), - isTestNet = isTestNet(blockchainName)) + isTestNet = true + ) } - blockchainName.contains("eth") -> { - val chain = if (isTestNet(blockchainName)) { - Chain.EthereumClassicTestnet - } else { - Chain.Mainnet - } + blockchainName == Blockchain.Ethereum.id -> { + val chain = Chain.Mainnet return EthereumWalletManager( cardId = card.cardId, walletPublicKey = walletPublicKey, walletConfig = WalletConfig(true, true), - chain = chain) + chain = chain + ) } - blockchainName.contains("xlm") -> { + blockchainName == Blockchain.Stellar.id -> { val token = getToken(card) return StellarWalletManager( cardId = card.cardId, walletPublicKey = walletPublicKey, walletConfig = WalletConfig(true, token == null), - token = token, - isTestNet = isTestNet(blockchainName)) + token = token + ) } - blockchainName.contains("cardano") -> { + blockchainName == Blockchain.Cardano.id -> { return CardanoWalletManager( cardId = card.cardId, walletPublicKey = walletPublicKey, @@ -61,8 +66,6 @@ object WalletManagerFactory { } } - private fun isTestNet(blockchainName: String) = blockchainName.contains("test") - private fun getToken(card: Card): Token? { val symbol = card.cardData?.tokenSymbol ?: return null val contractAddress = card.cardData?.tokenContractAddress ?: return null diff --git a/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt b/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt new file mode 100644 index 0000000000..bf5109f133 --- /dev/null +++ b/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt @@ -0,0 +1,59 @@ +package com.tangem.blockchain.common + +import com.google.common.truth.Truth +import com.tangem.CardEnvironment +import com.tangem.blockchain.bitcoin.BitcoinWalletManager +import com.tangem.blockchain.cardano.CardanoWalletManager +import com.tangem.blockchain.ethereum.EthereumWalletManager +import com.tangem.blockchain.stellar.StellarWalletManager +import com.tangem.commands.ReadCommand +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.extensions.hexToBytes +import org.junit.Test + +internal class WalletManagerFactoryTest { + + @Test + fun createBitcoinWalletManager() { + val data = "0108bb00000000000304200754414e47454d00020102800a322e3432642053444b000341040876bdec26b89bd2159a668b9af3d9fe86370f318717c92b8d6c1186fb3648c32a5f9321998cc2d042901c91d40601e79a641e1cbcebe7a2358be6054e1b6e5d0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034254438640e17ceec48c5be36240c98019f95ad8b6e56acfebe60d11979c6279f715d607d76a860a137da8d109e805753f3f56b0130709f4bbf4cb9974b4c57b8469bf4b873041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b310008040000006407010009020bb8604104752a727e14bba5bd73b6714d72500f61ffd11026ad1196d2e1c54577cbeeac3d11fc68a64700f8d533f4e311964ea8fb3aa26c588295f2133868d69c3e62869362040000005c6304000000090f01009000" + val responseApdu = ResponseApdu(data.hexToBytes()) + val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val walletManager = WalletManagerFactory.makeWalletManager(card!!) + + Truth.assertThat(walletManager) + .isInstanceOf(BitcoinWalletManager::class.java) + } + + @Test + fun createEthereumWalletManager() { + val data = "0108bb00000000000536200754414e47454d00020102800a322e3432642053444b000341046c8aea0d5a850b0a608acf9a0c453c39ea86131e88bfa78800de3cfb5bf1007aeaa7b9ffc184212255758605c2461be343c0a661d73cabafa4c9c175b3f0e59a0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034554488640431b6244acfeac479becdff201a7f720a7d70a97edc4e019fb678596baf52dfe9d0e8faf08ceb4443b82d4e66815541f2dc8ec6dd3ff83eb42f06e5eab07f25f3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b3100080400000064070100090205dc60410464dddc3f356744aaecfa07427f9eb996ff537d65f20fb5be3abccf0354352a6b5f8a1942e0f8ddeea3a170eda78d060be8162ad60e94e4e91fbbdf0a7054785562040000005b6304000000090f01009000" + val responseApdu = ResponseApdu(data.hexToBytes()) + val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val walletManager = WalletManagerFactory.makeWalletManager(card!!) + + Truth.assertThat(walletManager) + .isInstanceOf(EthereumWalletManager::class.java) + } + + @Test + fun createStellarWalletManager() { + val data = "0108bb00000000000379200754414e47454d00020102800a322e3432642053444b0003410487d7bb51b189213e3cedc3fcfa3fc047b3b71b7805b5b215e14639b3a8ebb1952c9dd5ea4354441b6ada4e8b8327674bb102ddae69df55be69643a2c916edf650a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b008403584c4d86409a4bc2baf0e5836887da21167cf33458d5249d1a610bced0e31dc053f23729ed24d715912bf89e6804669430dfe396ed83274e0031f6803e2bdb8c041fa993413041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc6020e078212d58b2b9d0edc9c936830d10081cd38b90c31778c56dfb1171027e294e62040000003863040000002c0f01009000" + val responseApdu = ResponseApdu(data.hexToBytes()) + val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val walletManager = WalletManagerFactory.makeWalletManager(card!!) + + Truth.assertThat(walletManager) + .isInstanceOf(StellarWalletManager::class.java) + } + + @Test + fun createCardanoWalletManager() { + val data = "0108bb00000000000502200754414e47454d00020102800a322e3432642053444b0003410402c1e39257d60583489da2d67d35d1cc2a1c005cc05c1021f44838edcaf25d5615cad7c9d11c2e23f5efa93e50904d33c88808d0e169060508df840992e31f4d0a04041e76310c658102ffff8a0101820407e30b0d830b54414e47454d2053444b00840743415244414e4f8640f24ef5c8c6eba0ff97560d5b013edb4a452594270db9647bd0a3543df8104dec75731d4db3ebe0fc493f2afee00195e560b51e3c41189b7c61ba7895d6434b9d3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc60208a71161cfdf1e0a85d8e7ff372aa4a01136046292aceb5f9ad7ebdb98d3f60a86204000000646304000000000f01009000" + val responseApdu = ResponseApdu(data.hexToBytes()) + val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val walletManager = WalletManagerFactory.makeWalletManager(card!!) + + Truth.assertThat(walletManager) + .isInstanceOf(CardanoWalletManager::class.java) + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index e381f57e3d..ad9651f6b0 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -1,6 +1,8 @@ package com.tangem import com.tangem.commands.* +import com.tangem.common.CardEnvironment +import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils import com.tangem.tasks.* import java.util.concurrent.Executors @@ -16,10 +18,12 @@ import java.util.concurrent.Executors */ class CardManager( private val reader: CardReader, - private val cardManagerDelegate: CardManagerDelegate? = null) { + private val cardManagerDelegate: CardManagerDelegate? = null, + private val config: Config = Config() +) { + private var terminalKeysService: TerminalKeysService? = null private var isBusy = false - private val cardEnvironmentRepository = mutableMapOf() private val cardManagerExecutor = Executors.newSingleThreadExecutor() init { @@ -67,7 +71,7 @@ class CardManager( callback: (result: TaskEvent) -> Unit) { val signCommand: SignCommand try { - signCommand = SignCommand(hashes, cardId) + signCommand = SignCommand(hashes) } catch (error: Exception) { if (error is TaskError) { callback(TaskEvent.Completion(error)) @@ -91,7 +95,7 @@ class CardManager( */ fun readIssuerData(cardId: String, callback: (result: TaskEvent) -> Unit) { - val getIssuerDataCommand = ReadIssuerDataCommand(cardId) + val getIssuerDataCommand = ReadIssuerDataCommand() val task = SingleCommandTask(getIssuerDataCommand) runTask(task, cardId, callback) } @@ -114,10 +118,10 @@ class CardManager( issuerDataCounter: Int? = null, callback: (result: TaskEvent) -> Unit) { val writeIssuerDataCommand = WriteIssuerDataCommand( - cardId, - issuerData, - issuerDataSignature, - issuerDataCounter) + issuerData, + issuerDataSignature, + issuerDataCounter + ) val task = SingleCommandTask(writeIssuerDataCommand) runTask(task, cardId, callback) } @@ -134,7 +138,7 @@ class CardManager( */ fun createWallet(cardId: String, callback: (result: TaskEvent) -> Unit) { - val createWalletCommand = CreateWalletCommand(cardId) + val createWalletCommand = CreateWalletCommand() val task = SingleCommandTask(createWalletCommand) runTask(task, cardId, callback) } @@ -148,7 +152,7 @@ class CardManager( */ fun purgeWallet(cardId: String, callback: (result: TaskEvent) -> Unit) { - val purgeWalletCommand = PurgeWalletCommand(cardId) + val purgeWalletCommand = PurgeWalletCommand() val task = SingleCommandTask(purgeWalletCommand) runTask(task, cardId, callback) } @@ -163,7 +167,7 @@ class CardManager( return } - val environment = fetchCardEnvironment(cardId) + val environment = prepareCardEnvironment(cardId) isBusy = true task.reader = reader @@ -187,7 +191,21 @@ class CardManager( runTask(task, cardId, callback) } - private fun fetchCardEnvironment(cardId: String?): CardEnvironment { - return cardEnvironmentRepository[cardId] ?: CardEnvironment(cardId = cardId) + /** + * Allows to set a particular [TerminalKeysService] to retrieve terminal keys. + * Default implementation is provided in tangem-sdk module: [TerminalKeysStorage]. + */ + fun setTerminalKeysService(terminalKeysService: TerminalKeysService) { + this.terminalKeysService = terminalKeysService } + + private fun prepareCardEnvironment(cardId: String?): CardEnvironment { + val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null + return CardEnvironment( + cardId = cardId, + terminalKeys = terminalKeys + ) + } + + companion object } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt b/tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt index 52f30a5e91..973ced5dd2 100644 --- a/tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt +++ b/tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt @@ -13,13 +13,13 @@ interface CardManagerDelegate { /** * It is called when user is expected to scan a Tangem Card with an Android device. */ - fun onNfcSessionStarted() + fun onNfcSessionStarted(cardId: String?) /** * It is called when security delay is triggered by the card. * A user is expected to hold the card until the security delay is over. */ - fun onSecurityDelay(ms: Int) + fun onSecurityDelay(ms: Int, totalDurationSeconds: Int) /** * It is called when user takes the card away from the Android device during the scanning diff --git a/tangem-core/src/main/java/com/tangem/Config.kt b/tangem-core/src/main/java/com/tangem/Config.kt new file mode 100644 index 0000000000..7689e77697 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/Config.kt @@ -0,0 +1,5 @@ +package com.tangem + +data class Config( + val linkedTerminal: Boolean = true +) \ No newline at end of file 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 1fc2521d2d..e5376f2cc7 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -1,14 +1,16 @@ package com.tangem.commands -import com.tangem.CardEnvironment +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 +import com.tangem.crypto.CryptoUtils import com.tangem.tasks.TaskError /** @@ -22,7 +24,16 @@ class CheckWalletResponse( val cardId: String, val salt: ByteArray, val walletSignature: ByteArray -) : CommandResponse +) : CommandResponse { + + fun verify(curve: EllipticCurve, publicKey: ByteArray, challenge: ByteArray): Boolean { + return CryptoUtils.verify( + publicKey, + challenge + salt, + walletSignature, + curve) + } +} /** * This command proves that the wallet private key from the card corresponds to the wallet public key. @@ -32,18 +43,16 @@ class CheckWalletResponse( * @property cardId Unique Tangem card ID number * @property challenge Random challenge generated by application */ -class CheckWalletCommand( - private val cardId: String, - private val challenge: ByteArray -) : CommandSerializer() { +class CheckWalletCommand : CommandSerializer() { + + val challenge = CryptoUtils.generateRandomBytes(16) override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - val tlvData = listOf( - Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()), - Tlv(TlvTag.CardId, cardId.hexToBytes()), - Tlv(TlvTag.Challenge, challenge) - ) - return CommandApdu(Instruction.CheckWallet, tlvData) + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Challenge, challenge) + return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize()) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? { 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 aad7f01b1b..92ee3d0d90 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt @@ -1,6 +1,6 @@ package com.tangem.commands -import com.tangem.CardEnvironment +import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.ResponseApdu import com.tangem.common.extensions.toInt 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 833795cbbf..0c142ee296 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -1,12 +1,13 @@ package com.tangem.commands -import com.tangem.CardEnvironment +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 import com.tangem.tasks.TaskError @@ -37,21 +38,15 @@ class CreateWalletResponse( * * @property cardId CID, Unique Tangem card ID number. */ -class CreateWalletCommand( - private val cardId: String -) : CommandSerializer() { +class CreateWalletCommand : CommandSerializer() { 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) + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) + tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) + return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize()) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CreateWalletResponse? { 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 f8c3feaf7f..cef8aca229 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -1,12 +1,13 @@ package com.tangem.commands -import com.tangem.CardEnvironment +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 import com.tangem.tasks.TaskError @@ -23,23 +24,20 @@ class PurgeWalletResponse( ) : CommandResponse /** -* This command deletes all wallet data. If Is_Reusable flag is enabled during personalization, + * 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() { +class PurgeWalletCommand : CommandSerializer() { 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) + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) + return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize()) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): PurgeWalletResponse? { 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 d12548c92f..e6e2558685 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -1,11 +1,12 @@ package com.tangem.commands -import com.tangem.CardEnvironment +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.tlv.Tlv +import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag import com.tangem.tasks.TaskError @@ -182,7 +183,7 @@ class Card( /** * Current status of the card. */ - val status: CardStatus, + val status: CardStatus?, /** * Version of Tangem COS. @@ -296,19 +297,16 @@ class Card( class ReadCommand : CommandSerializer() { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val tlvBuilder = TlvBuilder() /** * [CardEnvironment] stores the pin1 value. If no pin1 value was set, it will contain * default value of ‘000000’. * In order to obtain card’s data, [ReadCommand] should use the correct pin 1 value. * The card will not respond if wrong pin 1 has been submitted. */ - val tlvData = mutableListOf(Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256())) - - cardEnvironment.terminalKeys?.let { terminalKeys -> - Tlv(TlvTag.TerminalPublicKey, terminalKeys.publicKey) - } - - return CommandApdu(Instruction.Read, tlvData) + tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) + tlvBuilder.append(TlvTag.TerminalPublicKey, cardEnvironment.terminalKeys?.publicKey) + return CommandApdu(Instruction.Read, tlvBuilder.serialize()) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { @@ -318,9 +316,9 @@ class ReadCommand : CommandSerializer() { val tlvMapper = TlvMapper(tlvData) Card( - cardId = tlvMapper.map(TlvTag.CardId), - manufacturerName = tlvMapper.map(TlvTag.ManufactureId), - status = tlvMapper.map(TlvTag.Status), + cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "", + manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "", + status = tlvMapper.mapOptional(TlvTag.Status), firmwareVersion = tlvMapper.mapOptional(TlvTag.Firmware), cardPublicKey = tlvMapper.mapOptional(TlvTag.CardPublicKey), @@ -349,7 +347,7 @@ class ReadCommand : CommandSerializer() { private fun deserializeCardData(tlvData: List): CardData? { val cardDataTlvs = tlvData.find { it.tag == TlvTag.CardData }?.let { - Tlv.tlvListFromBytes(it.value) + Tlv.deserialize(it.value) } if (cardDataTlvs.isNullOrEmpty()) return null 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 3772d178c8..a045692a10 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -1,12 +1,13 @@ package com.tangem.commands -import com.tangem.CardEnvironment +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 import com.tangem.tasks.TaskError @@ -49,16 +50,13 @@ class ReadIssuerDataResponse( * wallet balance signed by the issuer or additional issuer’s attestation data. * @property cardId CID, Unique Tangem card ID number. */ -class ReadIssuerDataCommand( - private val cardId: String -) : CommandSerializer() { +class ReadIssuerDataCommand : CommandSerializer() { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - val tlvData = listOf( - Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()), - Tlv(TlvTag.CardId, cardId.hexToBytes()) - ) - return CommandApdu(Instruction.ReadIssuerData, tlvData) + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerDataResponse? { 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 a81a179f49..9a473aa9c6 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -1,12 +1,13 @@ package com.tangem.commands -import com.tangem.CardEnvironment +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 import com.tangem.crypto.sign @@ -32,7 +33,7 @@ class SignResponse( * @property hashes Array of transaction hashes. * @property cardId CID, Unique Tangem card ID number */ -class SignCommand(private val hashes: Array, private val cardId: String) +class SignCommand(private val hashes: Array) : CommandSerializer() { private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0 @@ -50,17 +51,16 @@ class SignCommand(private val hashes: Array, private val cardId: Stri } override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - val tlvData = mutableListOf( - Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()), - Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256()), - Tlv(TlvTag.CardId, cardId.hexToBytes()), - Tlv(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())), - Tlv(TlvTag.TransactionOutHash, dataToSign) - ) + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) + tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())) + tlvBuilder.append(TlvTag.TransactionOutHash, dataToSign) + tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) - addTerminalSignature(cardEnvironment, tlvData) - - return CommandApdu(Instruction.Sign, tlvData) + addTerminalSignature(cardEnvironment, tlvBuilder) + return CommandApdu(Instruction.Sign, tlvBuilder.serialize()) } /** @@ -70,11 +70,11 @@ class SignCommand(private val hashes: Array, private val cardId: Stri * TerminalTransactionSignature parameter containing a correct signature of raw data to be signed made with TerminalPrivateKey * (this key should be generated and securily stored by the application). */ - private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvData: MutableList) { + private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvBuilder: TlvBuilder) { cardEnvironment.terminalKeys?.let { terminalKeyPair -> val signedData = dataToSign.sign(terminalKeyPair.privateKey) - tlvData.add(Tlv(TlvTag.TerminalTransactionSignature, signedData)) - tlvData.add(Tlv(TlvTag.TerminalPublicKey, terminalKeyPair.publicKey)) + tlvBuilder.append(TlvTag.TerminalTransactionSignature, signedData) + tlvBuilder.append(TlvTag.TerminalPublicKey, terminalKeyPair.publicKey) } } 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 7c8e27ef4f..9066040e81 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -1,6 +1,6 @@ package com.tangem.commands -import com.tangem.CardEnvironment +import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -8,6 +8,7 @@ import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toByteArray import com.tangem.common.tlv.Tlv +import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag import com.tangem.tasks.TaskError @@ -30,24 +31,20 @@ class WriteIssuerDataResponse( * @property issuerDataCounter An optional counter that protect issuer data against replay attack. */ class WriteIssuerDataCommand( - private val cardId: String, private val issuerData: ByteArray, private val issuerDataSignature: ByteArray, private val issuerDataCounter: Int? = null ) : CommandSerializer() { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - val tlvData = mutableListOf( - Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()), - Tlv(TlvTag.CardId, cardId.hexToBytes()), - Tlv(TlvTag.IssuerData, issuerData), - Tlv(TlvTag.IssuerDataSignature, issuerDataSignature) - ) - if (issuerDataCounter != null) { - tlvData.add(Tlv(TlvTag.IssuerDataCounter, issuerDataCounter.toByteArray())) - } + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.IssuerData, issuerData) + tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature) + tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter) - return CommandApdu(Instruction.WriteIssuerData, tlvData) + return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { diff --git a/tangem-core/src/main/java/com/tangem/CardEnvironment.kt b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt similarity index 96% rename from tangem-core/src/main/java/com/tangem/CardEnvironment.kt rename to tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt index 39e077006e..21728a8c44 100644 --- a/tangem-core/src/main/java/com/tangem/CardEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt @@ -1,4 +1,4 @@ -package com.tangem +package com.tangem.common /** diff --git a/tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt b/tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt new file mode 100644 index 0000000000..d389c172b3 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt @@ -0,0 +1,12 @@ +package com.tangem.common + +/** + * Interface for a service for managing Terminal keypair, used for Linked Terminal feature. + * Its implementation Needs to be provided to [com.tangem.CardManager] + * by calling [com.tangem.CardManager.setTerminalKeysService]. + * Default implementation is provided in tangem-sdk module: [TerminalKeysStorage]. + * Linked Terminal feature can be disabled manually by editing [com.tangem.Config]. + */ +interface TerminalKeysService { + fun getKeys(): KeyPair +} \ No newline at end of file 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 f13c726438..450618ced5 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,8 +1,6 @@ package com.tangem.common.apdu -import com.tangem.EncryptionMode -import com.tangem.common.tlv.Tlv -import com.tangem.common.tlv.toBytes +import com.tangem.common.EncryptionMode import java.io.ByteArrayOutputStream /** @@ -15,7 +13,7 @@ import java.io.ByteArrayOutputStream class CommandApdu( private val ins: Int, - private val tlvList: List, + private val tlvs: ByteArray, private val cla: Byte = ISO_CLA, private val p1: Byte = 0x00, @@ -28,12 +26,12 @@ class CommandApdu( constructor( instruction: Instruction, - tlvList: List, + tlvs: ByteArray, encryptionMode: EncryptionMode = EncryptionMode.NONE, encryptionKey: ByteArray? = null ) : this( instruction.code, - tlvList, + tlvs, encryptionMode = encryptionMode, encryptionKey = encryptionKey ) @@ -51,13 +49,7 @@ class CommandApdu( private fun toBytes(): ByteArray { - val data = if (tlvList.isNotEmpty()) { - tlvList.toBytes() - } else { - byteArrayOf() - } - - val lc = data.size + val lc = tlvs.size val byteStream = ByteArrayOutputStream() byteStream.write(cla.toInt()) @@ -66,7 +58,7 @@ class CommandApdu( byteStream.write(p2.toInt()) if (lc != 0) { writeLength(byteStream, lc) - byteStream.write(data) + byteStream.write(tlvs) } return byteStream.toByteArray() } 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 c85f2ab9ba..e98c78874b 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 @@ -27,7 +27,7 @@ class ResponseApdu(private val data: ByteArray) { fun getTlvData(encryptionKey: ByteArray? = null): List? { return when { data.size <= 2 -> null - else -> Tlv.tlvListFromBytes(data.copyOf(data.size - 2)) + else -> Tlv.deserialize(data.copyOf(data.size - 2)) } } diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt index f17ab7e3cb..544317c358 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt @@ -53,17 +53,17 @@ class Tlv { } - fun tlvListFromBytes(mData: ByteArray): List? { + fun deserialize(data: ByteArray, nfcV: Boolean = false): List? { val tlvList = mutableListOf() - val stream = ByteArrayInputStream(mData) - var tlv: Tlv? = null + val stream = ByteArrayInputStream(data) + var tlv: Tlv? do { try { - tlv = Tlv.tlvFromBytes(stream) + tlv = tlvFromBytes(stream) if (tlv != null) tlvList.add(tlv) } catch (e: IOException) { Log.e(this::class.java.simpleName,"TLVError: " + e.message) - return null + if (nfcV) break else return null } } while (tlv != null) @@ -73,10 +73,10 @@ class Tlv { } -fun List.toBytes(): ByteArray = - this.map { it.toBytes() }.reduce { arr1, arr2 -> arr1 + arr2 } +fun List.serialize(): ByteArray = + this.map { it.serialize() }.reduce { arr1, arr2 -> arr1 + arr2 } -fun Tlv.toBytes(): ByteArray { +fun Tlv.serialize(): ByteArray { val tag = byteArrayOf(this.tag.code.toByte()) val length = getLengthInBytes(this.value.size) val value = if (this.value.isNotEmpty()) this.value else byteArrayOf(0x00) diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt new file mode 100644 index 0000000000..6ec643d00a --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt @@ -0,0 +1,15 @@ +package com.tangem.common.tlv + +class TlvBuilder { + private val tlvs = mutableListOf() + private val encoder = TlvEncoder() + + internal inline fun append(tag: TlvTag, value: T?) { + if (value == null) return + + tlvs.add(encoder.encode(tag, value)) + } + + fun serialize(): ByteArray = tlvs.serialize() + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt new file mode 100644 index 0000000000..3db20cd2eb --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt @@ -0,0 +1,81 @@ +package com.tangem.common.tlv + +import com.tangem.commands.* +import com.tangem.common.extensions.* +import com.tangem.tasks.TaskError +import java.time.Year +import java.util.* + +class TlvEncoder { + internal inline fun encode(tag: TlvTag, value: T?): Tlv { + if (value != null) { + return Tlv(tag, encodeValue(value, tag)) + } else { + throw TaskError.SerializeCommandError("Encoding error. Value for tag $tag is null") + } + } + + private inline fun encodeValue(value: T, tag: TlvTag): ByteArray { + return when (tag.valueType()) { + TlvValueType.HexString -> { + typeCheck(tag) + return if (tag == TlvTag.Pin || tag == TlvTag.Pin2) { + (value as String).calculateSha256() + } else { + (value as String).hexToBytes() + } + } + TlvValueType.Utf8String -> { + typeCheck(tag) + (value as String).toByteArray() + } + TlvValueType.IntValue -> { + typeCheck(tag) + (value as Int).toByteArray() + } + TlvValueType.BoolValue -> { + typeCheck(tag) + throw ConversionException("Usopported operation: Boolean to ByteArray for tag $tag") + } + TlvValueType.ByteArray -> { + typeCheck(tag) + value as ByteArray + } + TlvValueType.EllipticCurve -> { + typeCheck(tag) + (value as EllipticCurve).curve.plus("\\0").toByteArray() + } + TlvValueType.DateTime -> { + typeCheck(tag) + val calendar = Calendar.getInstance().apply { time = (value as Date) } + val year = calendar.get(Calendar.YEAR) + val month = calendar.get(Calendar.MONTH) + 1 + val day = calendar.get(Calendar.DAY_OF_MONTH) + return year.toByteArray() + month.toByteArray() + day.toByteArray() + } + TlvValueType.ProductMask -> { + typeCheck(tag) + byteArrayOf( + (value as ProductMask).code + ) + } + TlvValueType.SettingsMask -> { + typeCheck(tag) + (value as SettingsMask).rawValue.toByteArray() + } + TlvValueType.CardStatus -> { + typeCheck(tag) + (value as CardStatus).code.toByteArray() + } + TlvValueType.SigningMethod -> { + typeCheck(tag) + (value as SigningMethod).rawValue.toByteArray() + } + } + } + + private inline fun typeCheck(tag: TlvTag) { + if (T::class != ExpectedT::class) + throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}") + } +} \ 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 ca292d37f4..92ca2a8885 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 @@ -108,7 +108,7 @@ enum class TlvTag(val code: Int) { */ fun valueType(): TlvValueType { return when (this) { - CardId, Pin, Batch -> TlvValueType.HexString + CardId, Pin, Pin2, Batch -> TlvValueType.HexString ManufactureId, Firmware, IssuerId, BlockchainId, TokenSymbol, TokenContractAddress -> TlvValueType.Utf8String CurveId -> TlvValueType.EllipticCurve diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index e80da5189f..742adc0d3f 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -1,9 +1,7 @@ package com.tangem.tasks -import com.tangem.CardEnvironment -import com.tangem.commands.Card -import com.tangem.commands.CheckWalletCommand -import com.tangem.commands.ReadCommand +import com.tangem.commands.* +import com.tangem.common.CardEnvironment import com.tangem.common.CompletionResult import com.tangem.crypto.CryptoUtils @@ -31,64 +29,51 @@ sealed class ScanEvent { internal class ScanTask : Task() { override fun onRun(cardEnvironment: CardEnvironment, + currentCard: Card?, callback: (result: TaskEvent) -> Unit) { - val readCommand = ReadCommand() - sendCommand(readCommand, cardEnvironment) { readResult -> + if (currentCard != null) callback(TaskEvent.Event(ScanEvent.OnReadEvent(currentCard))) - when (readResult) { + if (currentCard == null) { + completeNfcSession(true, TaskError.MissingPreflightRead()) + callback(TaskEvent.Completion(TaskError.MissingPreflightRead())) - is CompletionResult.Failure -> { - if (readResult.error !is TaskError.UserCancelledError) { - completeNfcSession(true, readResult.error) - } - callback(TaskEvent.Completion(readResult.error)) - } + } else if (currentCard.cardData?.productMask == ProductMask.Tag) { + completeNfcSession() + callback(TaskEvent.Completion()) - is CompletionResult.Success -> { - val card = readResult.data + } else if (currentCard.status != CardStatus.Loaded) { + completeNfcSession() + callback(TaskEvent.Completion()) - callback(TaskEvent.Event(ScanEvent.OnReadEvent(card))) + } else if (currentCard.curve == null || currentCard.walletPublicKey == null) { + completeNfcSession(true, TaskError.CardError()) + callback(TaskEvent.Completion(TaskError.CardError())) - if (card.curve == null || card.walletPublicKey == null) { - completeNfcSession(true) - callback(TaskEvent.Completion(TaskError.CardError())) - return@sendCommand - } + } else { - val challenge = CryptoUtils.generateRandomBytes(16) - val checkWalletCommand = CheckWalletCommand( - card.cardId, - challenge) + val checkWalletCommand = CheckWalletCommand() - sendCommand(checkWalletCommand, cardEnvironment) { result -> - when (result) { - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelledError) { - completeNfcSession(true, result.error) - } - callback(TaskEvent.Completion(result.error)) - } - - is CompletionResult.Success -> { - completeNfcSession() - val checkWalletResponse = result.data - val verified = CryptoUtils.verify( - card.walletPublicKey, - challenge + checkWalletResponse.salt, - checkWalletResponse.walletSignature, - card.curve) - if (verified) { - callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(true))) - callback(TaskEvent.Completion()) - } else { - callback(TaskEvent.Completion(TaskError.VefificationFailed())) - } - } + sendCommand(checkWalletCommand, cardEnvironment) { result -> + when (result) { + is CompletionResult.Failure -> { + if (result.error !is TaskError.UserCancelledError) { + completeNfcSession(true, result.error) } - + callback(TaskEvent.Completion(result.error)) + } + is CompletionResult.Success -> { + completeNfcSession() + val verified = result.data.verify( + currentCard.curve, + currentCard.walletPublicKey, + checkWalletCommand.challenge + ) + callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(verified))) + callback(TaskEvent.Completion()) } } + } } } diff --git a/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt b/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt index 34ed9b0a69..60b0129aad 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt @@ -1,6 +1,7 @@ package com.tangem.tasks -import com.tangem.CardEnvironment +import com.tangem.commands.Card +import com.tangem.common.CardEnvironment import com.tangem.commands.CommandResponse import com.tangem.commands.CommandSerializer import com.tangem.common.CompletionResult @@ -14,8 +15,11 @@ class SingleCommandTask( private val command: CommandSerializer ) : Task() { - override fun onRun(cardEnvironment: CardEnvironment, - callback: (result: TaskEvent) -> Unit) { + override fun onRun( + cardEnvironment: CardEnvironment, + currentCard: Card?, + callback: (result: TaskEvent) -> Unit + ) { sendCommand(command, cardEnvironment) { result -> when (result) { is CompletionResult.Success -> { 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 3998586946..9f3bf8b836 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -1,11 +1,13 @@ package com.tangem.tasks -import com.tangem.CardEnvironment +import com.tangem.common.CardEnvironment 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.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.StatusWord @@ -22,7 +24,7 @@ sealed class TaskError(description: String? = null) : Exception(description) { class Busy() : TaskError() class TagLost() : TaskError() - class ErrorProcessingCommand : TaskError() + class ErrorProcessingCommand(description: String? = null) : TaskError(description) class InvalidState : TaskError() class InsNotSupported : TaskError() class InvalidParams : TaskError() @@ -32,12 +34,15 @@ sealed class TaskError(description: String? = null) : Exception(description) { class VefificationFailed : TaskError() class CardError : TaskError() class ReaderError() : TaskError() - class SerializeCommandError() : TaskError() + class SerializeCommandError(description: String? = null) : TaskError(description) class CardIsMissing() : TaskError() class EmptyHashes() : TaskError() class TooMuchHashes() : TaskError() class HashSizeMustBeEqual() : TaskError() + + class WrongCard() : TaskError() + class MissingPreflightRead() : TaskError() } /** @@ -67,6 +72,8 @@ abstract class Task { var delegate: CardManagerDelegate? = null var reader: CardReader? = null + var performPreflightRead: Boolean = true + var securityDelayDuration: Int = 0 /** * This method should be called to run the [Task] and perform all its operations. @@ -76,10 +83,15 @@ abstract class Task { */ fun run(cardEnvironment: CardEnvironment, callback: (result: TaskEvent) -> Unit) { - delegate?.onNfcSessionStarted() + delegate?.onNfcSessionStarted(cardEnvironment.cardId) reader?.openSession() Log.i(this::class.simpleName!!, "Nfc task is started") - onRun(cardEnvironment, callback) + + if (performPreflightRead) { + runWithPreflightRead(cardEnvironment, callback) + } else { + onRun(cardEnvironment, null, callback) + } } /** @@ -101,6 +113,7 @@ abstract class Task { * In this method the individual Tasks' logic should be implemented. */ protected abstract fun onRun(cardEnvironment: CardEnvironment, + currentCard: Card?, callback: (result: TaskEvent) -> Unit) /** @@ -149,7 +162,7 @@ abstract class Task { StatusWord.NeedPause -> { // When NeedPause is returned from the card whenever security delay is triggered. val remainingTime = command.deserializeSecurityDelay(responseApdu, cardEnvironment) - if (remainingTime != null) delegate?.onSecurityDelay(remainingTime) + if (remainingTime != null) delegate?.onSecurityDelay(remainingTime, securityDelayDuration) Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} triggered security delay of $remainingTime milliseconds") sendRequest(command, commandApdu, cardEnvironment, callback) } @@ -165,6 +178,35 @@ abstract class Task { } } } + + private fun runWithPreflightRead( + environment: CardEnvironment, callback: (result: TaskEvent) -> Unit) { + sendCommand(ReadCommand(), environment) { readResult -> + when (readResult) { + is CompletionResult.Failure -> { + completeNfcSession(true, readResult.error) + callback(TaskEvent.Completion(readResult.error)) + } + is CompletionResult.Success -> { + + val receivedCardId = readResult.data.cardId + securityDelayDuration = readResult.data.pauseBeforePin2 ?: 0 + + if (environment.cardId != null && environment.cardId != receivedCardId) { + completeNfcSession(true, TaskError.WrongCard()) + callback(TaskEvent.Completion(TaskError.WrongCard())) + return@sendCommand + } + + val newEnvironment = environment.copy(cardId = receivedCardId) + onRun(newEnvironment, readResult.data, callback) + } + } + + + } + + } } diff --git a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt index f2228f497b..61d6ea71d3 100644 --- a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt @@ -2,7 +2,9 @@ package com.tangem.common.apdu import com.google.common.truth.Truth.assertThat import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.toHexString import com.tangem.common.tlv.Tlv +import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvTag import org.junit.Test @@ -11,12 +13,13 @@ class CommandApduTest { @Test fun `simple READ command to bytes`() { - val pinInBytes = byteArrayOf(-111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, -10, -111, - 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, -55, -94, 3) + val pin = "000000" + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Pin, pin) val commandApdu = CommandApdu( Instruction.Read, - mutableListOf(Tlv(TlvTag.Pin, pinInBytes))) - + tlvBuilder.serialize() + ) val expected = byteArrayOf(0, -14, 0, 0, 0, 0, 34, 16, 32, -111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, -55, -94, 3) @@ -27,17 +30,18 @@ class CommandApduTest { @Test fun `READ with terminal key to bytes`() { - val pinInBytes = byteArrayOf(-111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, -10, -111, - 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, -55, -94, 3) + val pin = "000000" val terminalPublicKey = byteArrayOf(4, 80, -122, 58, -42, 74, -121, -82, -118, 47, -24, 60, 26, -15, -88, 64, 60, -75, 63, 83, -28, -122, -40, 81, 29, -83, -118, 4, -120, 126, 91, 35, 82, 44, -44, 112, 36, 52, 83, -94, -103, -6, -98, 119, 35, 119, 22, 16, 58, -68, 17, -95, -33, 56, -123, 94, -42, -14, -18, 24, 126, -100, 88, 43, -90) + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Pin, pin) + tlvBuilder.append(TlvTag.TerminalPublicKey, terminalPublicKey) val commandApdu = CommandApdu( Instruction.Read, - mutableListOf( - Tlv(TlvTag.Pin, pinInBytes), - Tlv(TlvTag.TerminalPublicKey, terminalPublicKey))) + tlvBuilder.serialize() + ) val expected = byteArrayOf(0, -14, 0, 0, 0, 0, 101, 16, 32, -111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt index 3666e03120..2c8334e752 100644 --- a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt @@ -11,12 +11,12 @@ class TlvMapperTest { private val rawData = byteArrayOf(1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 32, 11, 83, 77, 65, 82, 84, 32, 67, 65, 83, 72, 0, 2, 1, 2, -128, 6, 50, 46, 49, 49, 114, 0, 3, 65, 4, -49, 11, -50, -66, -121, -25, -2, 65, 65, -13, 14, 49, 27, -82, -33, -85, -113, 65, 20, 8, -39, -75, 57, 45, 65, -31, 35, 44, 38, 40, 63, -44, 113, -45, -75, -95, -118, 118, 29, 65, 117, -24, -53, 82, -72, 91, -20, -96, -77, -103, -14, -63, 52, -127, -123, -27, -16, -128, -67, -3, -104, -26, -22, 65, 10, 4, 0, 0, 126, 33, 12, 90, -127, 2, 0, 41, -126, 4, 7, -29, 5, 2, -125, 7, 84, 65, 78, 71, 69, 77, 0, -124, 3, 69, 84, 72, -122, 64, 111, -103, 48, -114, -40, 18, -103, 26, -102, -12, -38, -78, -90, -9, -98, 88, -47, -100, -24, 24, -105, -70, -72, 6, 94, -96, -77, 11, -123, -28, -118, 37, 63, 107, -55, -11, 23, -12, 13, -23, -121, -63, 36, -59, 70, 116, 91, -125, -34, -69, 23, -112, 6, 17, 4, -49, 68, -56, 29, -45, 81, 10, 97, 83, 48, 65, 4, -127, -106, -86, 75, 65, 10, -60, 74, 59, -100, -50, 24, -25, -66, 34, 106, -22, 7, 10, -52, -125, -87, -49, 103, 84, 15, -84, 73, -81, 37, 18, -97, 106, 83, -118, 40, -83, 99, 65, 53, -114, 60, 79, -103, 99, 6, 79, 126, 54, 83, 114, -90, 81, -45, 116, -27, -62, 60, -35, 55, -3, 9, -101, -14, 5, 10, 115, 101, 99, 112, 50, 53, 54, 107, 49, 0, 8, 4, 0, 15, 66, 64, 7, 1, 0, 9, 2, 11, -72, 96, 65, 4, -42, -5, -41, -84, -23, 88, 2, 86, -63, -118, -123, -10, -66, -82, -107, -68, -93, 111, 47, 93, -20, -86, 74, 28, 21, 81, 93, -21, -124, -57, -102, 55, 17, 84, -66, -68, -22, -128, 126, -99, -65, -54, -42, 59, -25, -21, -124, 5, 59, -16, -72, 73, 48, 16, -27, 103, -112, -73, 2, 96, -51, 41, -42, 116, 98, 4, 0, 15, 66, 52, 99, 4, 0, 0, 0, 13, 15, 1, 0) - private val tlvData = Tlv.tlvListFromBytes(rawData) + private val tlvData = Tlv.deserialize(rawData) private val tlvMapper = TlvMapper(tlvData!!) private val cardDataRaw: ByteArray = tlvMapper.map(TlvTag.CardData) - private val cardDataMapper = TlvMapper(Tlv.tlvListFromBytes(cardDataRaw)!!) + private val cardDataMapper = TlvMapper(Tlv.deserialize(cardDataRaw)!!) @Test fun `map optional when value is present`() { @@ -88,7 +88,7 @@ class TlvMapperTest { @Test fun `map SigningMethods set of methods returns correct value`() { - val localMapper = TlvMapper(Tlv.tlvListFromBytes("070195".hexToBytes())!!) + val localMapper = TlvMapper(Tlv.deserialize("070195".hexToBytes())!!) val signingMethod: SigningMethod = localMapper.map(TlvTag.SigningMethod) assertThat(signingMethod.contains(SigningMethod.signHash)) diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt index 4dcdecf66a..c2d1e0e09d 100644 --- a/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt @@ -17,7 +17,7 @@ class TlvTest { -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, -55, -94, 3) - assertThat(tlvs.toBytes()) + assertThat(tlvs.serialize()) .isEqualTo(expected) } @@ -34,7 +34,7 @@ class TlvTest { -55, -94, 3, 1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 22, 16, -82, -78, -31, 34, 66, -19, -86, -1, 26, 8, 100, -126, -74, 20, -28, 83) - assertThat(tlvs.toBytes()) + assertThat(tlvs.serialize()) .isEqualTo(expected) } @@ -44,14 +44,14 @@ class TlvTest { -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, -55, -94, 3) - val tlvs = Tlv.tlvListFromBytes(bytes) + val tlvs = Tlv.deserialize(bytes) assertThat(tlvs) .isNotNull() assertThat(tlvs) .isNotEmpty() - val pin = tlvs!!.find { it.tag == TlvTag.Pin }?.value + val pin = tlvs!!.find { it.tag == TlvTag.Pin }?.value val pinExpected = "000000".calculateSha256() assertThat(pin) @@ -65,14 +65,14 @@ class TlvTest { -55, -94, 3, 1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 22, 16, -82, -78, -31, 34, 66, -19, -86, -1, 26, 8, 100, -126, -74, 20, -28, 83) - val tlvs = Tlv.tlvListFromBytes(bytes) + val tlvs = Tlv.deserialize(bytes) assertThat(tlvs) .isNotNull() assertThat(tlvs) .isNotEmpty() - val pin = tlvs!!.find { it.tag == TlvTag.Pin }?.value + val pin = tlvs!!.find { it.tag == TlvTag.Pin }?.value val pinExpected = "000000".calculateSha256() assertThat(pin) .isEqualTo(pinExpected) @@ -91,13 +91,21 @@ class TlvTest { @Test fun `Bytes to TLVs, wrong values`() { val bytes = byteArrayOf(0) - val tlvs = Tlv.tlvListFromBytes(bytes) - assertThat(tlvs) - .isNull() + val tlvs = Tlv.deserialize(bytes) + assertThat(tlvs) + .isNull() val bytes1 = byteArrayOf(0, 0, 0, 0, 0, 0, 0) - val tlvs1 = Tlv.tlvListFromBytes(bytes1) + val tlvs1 = Tlv.deserialize(bytes1) assertThat(tlvs1) .isNull() } + + @Test + fun `parse Slix tag response`() { + val response = "03ff010f91010b550474616e67656d2e636f6d140f11616e64726f69642e636f6d3a706b67636f6d2e74616e67656d2e77616c6c65745411c974616e67656d2e636f6d3a77616c6c657490000c618102ffff8a0102820407e40109830b54414e47454d2053444b008403584c4d86400e71c1f060387029688254320b90abeae471bcafbbe8ea3880903bdb8d1cc389d032b982e1ffd7ef49e66f1780123b763dd2f3a9a9494eb0fad4ae8cf306672c60207c967a51077c14fc49d867f23b8d0eaf60cad479a56587e894571b7fb33690176140345fbe53f5be0ec871e91c317cde2bd0396d47e4b945c138c153b0271f636a73cf531df1bc54ac4fcdbce42f81b40d58e0265d34e28121a4c50fdfe329a97f6000fe000000000000000000000000000000000000000000000000000000000000000000000000000000" + val tlvs = Tlv.deserialize(response.hexToBytes(), true) + assertThat(tlvs) + .isNotEmpty() + } } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt index 52b2951309..6cd62173e3 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt @@ -3,9 +3,7 @@ package com.tangem.tangemtest import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.tangem.CardManager -import com.tangem.tangem_sdk_new.DefaultCardManagerDelegate -import com.tangem.tangem_sdk_new.NfcLifecycleObserver -import com.tangem.tangem_sdk_new.nfc.NfcManager +import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tasks.ScanEvent import com.tangem.tasks.TaskError import com.tangem.tasks.TaskEvent @@ -13,10 +11,7 @@ import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { - private val nfcManager = NfcManager() - private val cardManagerDelegate: DefaultCardManagerDelegate = DefaultCardManagerDelegate(nfcManager.reader) - private val cardManager = CardManager(nfcManager.reader, cardManagerDelegate) - + private lateinit var cardManager: CardManager private lateinit var cardId: String private lateinit var issuerData: ByteArray private lateinit var issuerDataSignature: ByteArray @@ -25,10 +20,7 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - nfcManager.setCurrentActivity(this) - cardManagerDelegate.activity = this - - lifecycle.addObserver(NfcLifecycleObserver(nfcManager)) + cardManager = CardManager.init(this) btn_scan?.setOnClickListener { _ -> cardManager.scanCard { taskEvent -> @@ -38,6 +30,10 @@ class MainActivity : AppCompatActivity() { is ScanEvent.OnReadEvent -> { // Handle returned card data cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId + runOnUiThread { + tv_card_cid?.text = cardId + btn_create_wallet.isEnabled = true + } } is ScanEvent.OnVerifyEvent -> { //Handle card verification @@ -127,6 +123,11 @@ class MainActivity : AppCompatActivity() { } is TaskEvent.Event -> runOnUiThread { tv_card_cid?.text = it.data.status.name + btn_sign.isEnabled = true + btn_read_issuer_data.isEnabled = true + btn_purge_wallet.isEnabled = true + btn_create_wallet.isEnabled = false + } } } diff --git a/tangem-demo/src/main/res/values/colors.xml b/tangem-demo/src/main/res/values/colors.xml index 69b22338c6..eb11907849 100644 --- a/tangem-demo/src/main/res/values/colors.xml +++ b/tangem-demo/src/main/res/values/colors.xml @@ -1,6 +1,6 @@ - #008577 - #00574B - #D81B60 + #027aff + #027AFF + #027AFF diff --git a/tangem-demo/src/main/res/xml/nfc_tech_filter.xml b/tangem-demo/src/main/res/xml/nfc_tech_filter.xml index 2b794e0d65..1773c9e9b4 100644 --- a/tangem-demo/src/main/res/xml/nfc_tech_filter.xml +++ b/tangem-demo/src/main/res/xml/nfc_tech_filter.xml @@ -3,5 +3,6 @@ android.nfc.tech.IsoDep android.nfc.tech.Ndef + android.nfc.tech.NfcV \ No newline at end of file diff --git a/tangem-sdk/build.gradle b/tangem-sdk/build.gradle index ca1b2e5123..f25dfed9f9 100644 --- a/tangem-sdk/build.gradle +++ b/tangem-sdk/build.gradle @@ -40,16 +40,19 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'com.google.android.material:material:1.1.0-beta01' + implementation 'com.google.android.material:material:1.1.0' implementation 'com.skyfishjy.ripplebackground:library:1.0.1' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.core:core-ktx:1.1.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' - implementation "androidx.lifecycle:lifecycle-runtime:2.1.0" - implementation "androidx.lifecycle:lifecycle-common-java8:2.1.0" + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation "androidx.lifecycle:lifecycle-runtime:2.2.0" + implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0" + implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.61" + + implementation 'at.favre.lib:armadillo:0.9.0' + testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.50" } diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultCardManagerDelegate.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultCardManagerDelegate.kt index 8f37eb0c1c..d390b9ee99 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultCardManagerDelegate.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultCardManagerDelegate.kt @@ -1,12 +1,16 @@ package com.tangem.tangem_sdk_new +import android.animation.ObjectAnimator import android.view.View +import android.view.animation.DecelerateInterpolator import androidx.fragment.app.FragmentActivity import com.google.android.material.bottomsheet.BottomSheetDialog import com.tangem.CardManagerDelegate import com.tangem.Log import com.tangem.LoggerInterface import com.tangem.common.CompletionResult +import com.tangem.tangem_sdk_new.extensions.hide +import com.tangem.tangem_sdk_new.extensions.show import com.tangem.tangem_sdk_new.nfc.NfcReader import com.tangem.tangem_sdk_new.ui.NfcEnableDialog import com.tangem.tangem_sdk_new.ui.TouchCardAnimation @@ -28,13 +32,13 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel setLogger() } - override fun onNfcSessionStarted() { + override fun onNfcSessionStarted(cardId: String?) { reader.readingCancelled = false - postUI { showReadingDialog(activity) } + postUI { showReadingDialog(activity, cardId) } if (!reader.nfcEnabled) showNFCEnableDialog() } - private fun showReadingDialog(activity: FragmentActivity) { + private fun showReadingDialog(activity: FragmentActivity, cardId: String?) { val dialogView = activity.getLayoutInflater().inflate(R.layout.nfc_bottom_sheet, null) readingDialog = BottomSheetDialog(activity) readingDialog?.setContentView(dialogView) @@ -46,6 +50,11 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel activity, readingDialog!!.ivHandCardHorizontal, readingDialog!!.ivHandCardVertical, readingDialog!!.llHand, readingDialog!!.llNfc) nfcDeviceAntenna.init() + if (cardId != null) { + readingDialog?.tvCard?.visibility = View.VISIBLE + readingDialog?.tvCardId?.visibility = View.VISIBLE + readingDialog?.tvCardId?.text = cardId + } } readingDialog?.setOnCancelListener { reader.readingCancelled = true @@ -60,21 +69,36 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel activity.supportFragmentManager.let { nfcEnableDialog?.show(it, NfcEnableDialog.TAG) } } - override fun onSecurityDelay(ms: Int) { + override fun onSecurityDelay(ms: Int, totalDurationSeconds: Int) { postUI { - readingDialog?.lTouchCard?.visibility = View.GONE + readingDialog?.lTouchCard?.hide() readingDialog?.tvRemainingTime?.text = ms.div(100).toString() - readingDialog?.flSecurityDelay?.visibility = View.VISIBLE + readingDialog?.flSecurityDelay?.show() readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_security_delay) readingDialog?.tvTaskText?.text = activity.getText(R.string.dialog_security_delay_description) +// readingDialog?.pbSecurityDelay?.isIndeterminate = false + + if (readingDialog?.pbSecurityDelay?.max != totalDurationSeconds) { + readingDialog?.pbSecurityDelay?.max = totalDurationSeconds + } + readingDialog?.pbSecurityDelay?.progress = totalDurationSeconds - ms + 100 + + val animation = ObjectAnimator.ofInt( + readingDialog?.pbSecurityDelay, + "progress", + totalDurationSeconds - ms, + totalDurationSeconds - ms + 100) + animation.duration = 500 + animation.interpolator = DecelerateInterpolator() + animation.start() } } override fun onTagLost() { postUI { - readingDialog?.lTouchCard?.visibility = View.VISIBLE - readingDialog?.flSecurityDelay?.visibility = View.GONE + readingDialog?.lTouchCard?.show() + readingDialog?.flSecurityDelay?.hide() readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_ready_to_scan) readingDialog?.tvTaskText?.text = activity.getText(R.string.dialog_scan_text) } @@ -82,9 +106,9 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel override fun onNfcSessionCompleted() { postUI { - readingDialog?.lTouchCard?.visibility = View.GONE - readingDialog?.flSecurityDelay?.visibility = View.GONE - readingDialog?.flCompletion?.visibility = View.VISIBLE + readingDialog?.lTouchCard?.hide() + readingDialog?.flSecurityDelay?.hide() + readingDialog?.flCompletion?.show() readingDialog?.ivCompletion?.setImageDrawable(activity.getDrawable(R.drawable.ic_done_135dp)) } postUI(300) { readingDialog?.dismiss() } @@ -92,10 +116,10 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel override fun onError(error: TaskError?) { postUI { - readingDialog?.lTouchCard?.visibility = View.GONE - readingDialog?.flSecurityDelay?.visibility = View.GONE - readingDialog?.flCompletion?.visibility = View.VISIBLE - readingDialog?.ivCompletion?.setImageDrawable(activity.getDrawable(R.drawable.ic_error_outline_135dp)) + readingDialog?.lTouchCard?.hide() + readingDialog?.flSecurityDelay?.hide() + readingDialog?.flCompletion?.hide() + readingDialog?.flError?.show() readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_error) readingDialog?.tvTaskText?.text = if (error != null) error::class.simpleName else "" } diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/TerminalKeysStorage.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/TerminalKeysStorage.kt new file mode 100644 index 0000000000..70b2cfa431 --- /dev/null +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/TerminalKeysStorage.kt @@ -0,0 +1,56 @@ +package com.tangem.tangem_sdk_new + +import android.app.Application +import android.content.SharedPreferences +import at.favre.lib.armadillo.Armadillo +import com.tangem.common.KeyPair +import com.tangem.common.TerminalKeysService +import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toHexString +import com.tangem.crypto.CryptoUtils + + +/** + * Service for managing Terminal keypair, used for Linked Terminal feature. + * Needs to be provided to [com.tangem.CardManager] by calling [com.tangem.CardManager.setTerminalKeysService] + * Linked Terminal feature can be disabled manually by editing [com.tangem.Config]. + * @param applicationContext is required to retrieve an instance of [SharedPreferences] + */ +class TerminalKeysStorage(applicationContext: Application): TerminalKeysService { + + private val preferences: SharedPreferences by lazy { + Armadillo.create(applicationContext, PREFERENCES_KEY) + .encryptionFingerprint(applicationContext) + .build() + } + + /** + * Retrieves generated keys from encrypted shared preferences if the keys exist. + * Generates new and stores them in encrypted shared preferences otherwise + */ + override fun getKeys(): KeyPair { + val privateKey = preferences.getString(TERMINAL_PRIVATE_KEY, null)?.hexToBytes() + val publicKey = preferences.getString(TERMINAL_PUBLIC_KEY, null)?.hexToBytes() + return if (privateKey == null || publicKey == null) { + generateAndSaveKeys() + } else { + KeyPair(publicKey, privateKey) + } + } + + private fun generateAndSaveKeys(): KeyPair { + val privateKey = CryptoUtils.generateRandomBytes(32) + val publicKey = CryptoUtils.generatePublicKey(privateKey) + preferences.edit().putString(TERMINAL_PRIVATE_KEY, privateKey.toHexString()).apply() + preferences.edit().putString(TERMINAL_PUBLIC_KEY, publicKey.toHexString()).apply() + return KeyPair(publicKey, privateKey) + } + + + companion object { + const val PREFERENCES_KEY = "myPrefs" + const val TERMINAL_PRIVATE_KEY = "terminalPrivateKey" + const val TERMINAL_PUBLIC_KEY = "terminalPublicKey" + } + +} \ No newline at end of file diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/CardManager.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/CardManager.kt new file mode 100644 index 0000000000..b2364c13cf --- /dev/null +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/CardManager.kt @@ -0,0 +1,21 @@ +package com.tangem.tangem_sdk_new.extensions + +import androidx.fragment.app.FragmentActivity +import com.tangem.CardManager +import com.tangem.tangem_sdk_new.DefaultCardManagerDelegate +import com.tangem.tangem_sdk_new.NfcLifecycleObserver +import com.tangem.tangem_sdk_new.TerminalKeysStorage +import com.tangem.tangem_sdk_new.nfc.NfcManager + +fun CardManager.Companion.init(activity: FragmentActivity): CardManager { + val nfcManager = NfcManager().apply { + this.setCurrentActivity(activity) + activity.lifecycle.addObserver(NfcLifecycleObserver(this)) + } + val cardManagerDelegate = DefaultCardManagerDelegate(nfcManager.reader).apply { + this.activity = activity + } + return CardManager(nfcManager.reader, cardManagerDelegate).apply { + this.setTerminalKeysService(TerminalKeysStorage(activity.application)) + } +} \ No newline at end of file diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/View.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/View.kt new file mode 100644 index 0000000000..008f194709 --- /dev/null +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/View.kt @@ -0,0 +1,11 @@ +package com.tangem.tangem_sdk_new.extensions + +import android.view.View + +internal fun View.show() { + this.visibility = View.VISIBLE +} + +internal fun View.hide() { + this.visibility = View.GONE +} \ No newline at end of file diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt index cdcb94c1c3..d8436d8e3b 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt @@ -13,7 +13,7 @@ import android.os.Bundle import com.tangem.Log /** - * Helps use of NFC, leveraging Android NFC functionality. + * Helps use NFC, leveraging Android NFC functionality. * Launches [NfcAdapter], manages it with [Activity] lifecycle, * enables and disables Nfc Reading Mode, receives NFC [Tag]. */ @@ -87,6 +87,7 @@ class NfcManager : NfcAdapter.ReaderCallback { companion object { // reader mode flags: listen for type A (not B), skipping ndef check private const val READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A or + NfcAdapter.FLAG_READER_NFC_V or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK or NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS } diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt index 9b54bd9ffe..02518ad5f9 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt @@ -3,6 +3,7 @@ package com.tangem.tangem_sdk_new.nfc import android.nfc.Tag import android.nfc.TagLostException import android.nfc.tech.IsoDep +import android.nfc.tech.NfcV import com.tangem.CardReader import com.tangem.Log import com.tangem.common.CompletionResult @@ -41,8 +42,8 @@ class NfcReader : CardReader { } } - var data: ByteArray? = null - var callback: ((response: CompletionResult) -> Unit)? = null + private var data: ByteArray? = null + private var callback: ((response: CompletionResult) -> Unit)? = null override fun openSession() { readingActive = true @@ -90,6 +91,7 @@ class NfcReader : CardReader { } fun onTagDiscovered(tag: Tag?) { + NfcV.get(tag)?.let { onNfcVDiscovered(it) } isoDep = IsoDep.get(tag) transceiveData() } @@ -103,4 +105,17 @@ class NfcReader : CardReader { Log.i(this::class.simpleName!!, "Nfc session is started") } + private fun onNfcVDiscovered(nfcV: NfcV) { + val response = SlixTagReader().transceive(nfcV) + when (response) { + is SlixReadResult.Failure -> { + callback?.invoke(CompletionResult.Failure( + TaskError.ErrorProcessingCommand(response.exception.message)) + ) + } + is SlixReadResult.Success -> { + callback?.invoke(CompletionResult.Success(ResponseApdu(response.data))) + } + } + } } \ No newline at end of file diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/SlixTagReader.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/SlixTagReader.kt new file mode 100644 index 0000000000..587ded11a3 --- /dev/null +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/SlixTagReader.kt @@ -0,0 +1,123 @@ +package com.tangem.tangem_sdk_new.nfc + +import android.nfc.NdefMessage +import android.nfc.NdefRecord +import android.nfc.tech.NfcV +import com.tangem.common.apdu.StatusWord +import com.tangem.common.extensions.toByteArray +import com.tangem.common.extensions.toHexString +import com.tangem.common.tlv.Tlv +import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvTag +import java.io.ByteArrayOutputStream +import java.io.IOException + +class SlixTagReader() { + + private lateinit var nfcV: NfcV + + fun transceive(nfcV: NfcV): SlixReadResult { + this.nfcV = nfcV + if (!nfcV.isConnected) { + try { + nfcV.connect() + } catch (e: Exception) { + return SlixReadResult.Failure(e) + } + } + return try { + runRead() + } catch (e: Exception) { + nfcV.close() + SlixReadResult.Failure(e) + } + } + + private fun runRead(): SlixReadResult { + val ndefMessage = runReadNDEF() + val records: Array = ndefMessage.records + for (record in records) { + if (record.toUri() != null && record.toUri().toString() == "vnd.android.nfc://ext/tangem.com:wallet") { + val payload = record.payload + val status = StatusWord.ProcessCompleted.code.toByteArray() + val data = payload.copyOfRange(2, payload.size) + status + nfcV.close() + return SlixReadResult.Success(data) + } + } + return SlixReadResult.Failure(Exception("No relevant data was found.")) + } + + private fun runReadNDEF(): NdefMessage { + val answerCC = readSingleBlock(0x00) + + if (answerCC.size != 4 || answerCC[0].toInt() != 0xE1 || ((answerCC[1].toInt() and 0xF0) != 0x40)) { + + } else { + throw Exception("Failed! Invalid CC read " + answerCC.toHexString()) + } + if ((answerCC[3].toInt() and 0x01) != 0x01) { + throw Exception("Multiple block read unsupported!") + } + val areaSize = 8 * answerCC[2] + val blocksCount = areaSize / 4 + + val areaBuf = readMultipleBlocks(1, blocksCount) + + val tlvNdef = TlvMapper(Tlv.deserialize(areaBuf, true) ?: listOf()) + + return NdefMessage(tlvNdef.map(TlvTag.CardPublicKey)) + } + + private fun readSingleBlock(blockNo: Int): ByteArray { + return performRead(0x20, blockNo)!! + } + + private fun readMultipleBlocks(startBlock: Int, blocksCount: Int): ByteArray { + val resultBuf = ByteArrayOutputStream() + val maxBlocksAtOnce = 32 + var blocksRemaining = blocksCount + var firstBlockToRead = startBlock + while (blocksRemaining > 0) { + val blocksToRead = if (blocksRemaining > maxBlocksAtOnce) { + maxBlocksAtOnce + } else { + blocksRemaining + } + val blocks = performRead(0x23.toByte(), firstBlockToRead, blocksToRead - 1) + blocksRemaining -= blocksToRead + firstBlockToRead += blocksToRead + resultBuf.write(blocks!!) + } + return resultBuf.toByteArray() + } + + private fun performRead(cmd: Byte, p1: Int?, p2: Int? = null, params: ByteArray? = null): ByteArray? { + val command: ByteArray + val res: ByteArray? + val os = ByteArrayOutputStream() + os.write(REQ_FLAG.toInt()) + os.write(cmd.toInt()) + p1?.let { os.write(p1) } + p2?.let { os.write(it) } + params?.let { os.write(params, 0, params.size) } + command = os.toByteArray() + if (!nfcV.isConnected) throw IOException("Connection lost") + res = nfcV.transceive(command) + val errorCode = res[0].toInt() + if (errorCode != 0) { + throw IOException("Error! Code: " + String.format("0x%02x", errorCode)) + } + return res.copyOfRange(1, res.size) + } + + companion object { + // iso15693 flags + private const val REQ_FLAG: Byte = 0x02 + } +} + +sealed class SlixReadResult { + data class Success(val data: ByteArray) : SlixReadResult() + data class Failure(val exception: Exception) : SlixReadResult() +} \ No newline at end of file diff --git a/tangem-sdk/src/main/res/drawable/ic_error.xml b/tangem-sdk/src/main/res/drawable/ic_error.xml new file mode 100644 index 0000000000..d35c5b94ac --- /dev/null +++ b/tangem-sdk/src/main/res/drawable/ic_error.xml @@ -0,0 +1,6 @@ + + + + diff --git a/tangem-sdk/src/main/res/drawable/ic_nfc.xml b/tangem-sdk/src/main/res/drawable/ic_nfc.xml new file mode 100644 index 0000000000..c91a1240d6 --- /dev/null +++ b/tangem-sdk/src/main/res/drawable/ic_nfc.xml @@ -0,0 +1,10 @@ + + + diff --git a/tangem-sdk/src/main/res/drawable/pb_circle.xml b/tangem-sdk/src/main/res/drawable/pb_circle.xml new file mode 100644 index 0000000000..a3e74eb416 --- /dev/null +++ b/tangem-sdk/src/main/res/drawable/pb_circle.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml index 496d0453bf..318e05a0d6 100644 --- a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml +++ b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml @@ -1,11 +1,60 @@ + + + + + + + + + + + + android:fontFamily="sans-serif-light" + android:textColor="@color/colorAccent" + android:textSize="60dp" + android:textStyle="normal" /> + style="?android:attr/progressBarStyleHorizontal" + android:layout_width="200dp" + android:layout_height="200dp" + android:layout_gravity="center" + android:indeterminate="false" + android:progressDrawable="@drawable/pb_circle" + android:secondaryProgress="100" /> @@ -55,8 +111,47 @@ android:id="@+id/ivCompletion" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:src="@drawable/ic_done_135dp" - android:layout_gravity="center"/> + android:layout_gravity="center" + android:src="@drawable/ic_done_135dp" /> + + + + + + + + + + diff --git a/tangem-sdk/src/main/res/values/strings.xml b/tangem-sdk/src/main/res/values/strings.xml index 1b84a3024b..aaabeff63d 100644 --- a/tangem-sdk/src/main/res/values/strings.xml +++ b/tangem-sdk/src/main/res/values/strings.xml @@ -8,5 +8,6 @@ Error Ready to Scan Scan card with your phone as shown above + Card