Updated on 2026-08-14

This commit is contained in:
Tangem 2018-07-16 13:03:51 +03:00
commit ab13ff8636
16 changed files with 530 additions and 175 deletions

View file

@ -19,7 +19,6 @@ android {
versionName "0.85.5." + generateVersionName()
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
debuggable false
@ -34,11 +33,11 @@ android {
signingConfig signingConfigs.debug
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildToolsVersion '27.0.3'
}
dependencies {

View file

@ -0,0 +1,70 @@
[
{
"fw":"1.24r",
"challenge":"00000000000000000000000000000001",
"sha-256":[
{"block":0,"count":1,"digest":"9B091DF443FE951D27AA8843517096DC421433F5603EBC566B11891F867E3D18"},
{"block":1,"count":1,"digest":"B4F723EE79075C8C00FA17D037BF10528BB0C7CF421539EE15F4A397204C87AF"},
{"block":2,"count":1,"digest":"1258585B438B6BC8F41FC22AFF383C86690E984E84E96AA752C9A54954DF731A"},
{"block":3,"count":1,"digest":"14082C75D6AA533A06E9B764642F97B5783FED91BD1173EFADC3CF57D8769AAE"},
{"block":4,"count":1,"digest":"A8F6CB02D56933361127072BB0D89999020CE196AE9718E9CF019DCCC9437E85"},
{"block":5,"count":1,"digest":"491604BA2A2F6EE03A2B4812958F0356D745678FB0ABABF0109A9E8F59099D9D"},
{"block":6,"count":1,"digest":"B2F057FF8B83343BC40058CA459F3C63B9670ABCD059730BE70223AEBDC88D77"},
{"block":7,"count":1,"digest":"73F4391010345E27745A1AFEC93C02B2CA460D9EAB437605F82A18281DF17DCE"},
{"block":8,"count":1,"digest":"74588EBB6704DD3DB9ECDFE6B85EC2872BD6A1F7C73E080E57697D3FF8DDD470"},
{"block":9,"count":1,"digest":"3B382C35ADBBF206B0A6B216EE95AF2321EE34148E93BBFECCF275AB13ED08A2"},
{"block":11500,"count":1,"digest":"21DDEC9788E3EA04663297AD0208F2714F139434E3540EBB7C96150EE1D320C9"},
{"block":11501,"count":1,"digest":"C545C5D092D1EBC4BA266C139CE0F2A399E4D23E74EB4EBD0E53019AB58CB486"},
{"block":0,"count":0,"digest":"C689C50352E9F6E41BF2E6EC852D8466814A51018F40BE32BAADEE9C2A9E6B6D"}
],
"crc-16":[
{"block":0,"count":1,"digest":"1BFC"},
{"block":1,"count":1,"digest":"D58D"},
{"block":2,"count":1,"digest":"7E3E"},
{"block":3,"count":1,"digest":"14EA"},
{"block":4,"count":1,"digest":"014F"},
{"block":5,"count":1,"digest":"5FF0"},
{"block":6,"count":1,"digest":"7D3A"},
{"block":7,"count":1,"digest":"702C"},
{"block":8,"count":1,"digest":"CC22"},
{"block":9,"count":1,"digest":"392C"},
{"block":11500,"count":1,"digest":"5FCB"},
{"block":11501,"count":1,"digest":"FF77"},
{"block":0,"count":0,"digest":"D07B"}
]
},
{
"fw":"1.24d SDK",
"challenge":"00000000000000000000000000000001",
"sha-256":[
{"block":0,"count":1,"digest":"6B360B47FFB16641EF9CC778746FE6B1010AB60C69281CE431A46ECC29A72304"},
{"block":1,"count":1,"digest":"648F50D44E9A31CB4C2682EAD7B8E2E570E5F252035849CD9B08BD4F9106B432"},
{"block":2,"count":1,"digest":"BAA31588F599E2DBC8AC06CFBF4E710B9B0E3FEA0201AE67E5A9E605DB50D1ED"},
{"block":3,"count":1,"digest":"48CFA13758F0B8018A97831C11BB1668A39CC76B141C90038551D950F83A43BA"},
{"block":4,"count":1,"digest":"8932B8E9D93A28EEEAC8FD3477F38A8A384AC895B06E09B8396A7806C3DCFC09"},
{"block":5,"count":1,"digest":"A3E7F2911AB380B366E47EB9E7DCC0C3D8667EEE3CF355FCCD5B113918873EFC"},
{"block":6,"count":1,"digest":"32CFCDC12C3AAA6C8FE4935C76B795A81206141AE7C7AB941A4B5F74EB02D844"},
{"block":7,"count":1,"digest":"1BA7B6ED5F28A0744E177843F2A960CB757B27FF6052C17F9E57B266AEC4F564"},
{"block":8,"count":1,"digest":"7C1A2C88529DA038C3A124CC049B9A5CD73089C2CD9C66B15DF91020E981B94B"},
{"block":9,"count":1,"digest":"5F14E10CBE14230EEF9B8896C84998197E56738952A1ECF22202801B9AE64340"},
{"block":10,"count":1,"digest":"8FF4F496E7C68A654E574F13517EFA4F2058FDFC9E949421DA5ACC855C876F63"},
{"block":11491,"count":1,"digest":"E45CFEA35B1C4C217A55B78B0D78C841978F1008C3C1917D228EECB0324A0942"},
{"block":0,"count":0,"digest":"F4199A74F2D4BAF2E282A670989A50B9186665D8EC453D1CED02F4E4019E1C0F"}
],
"crc-16":[
{"block":0,"count":1,"digest":"22C3"},
{"block":1,"count":1,"digest":"ECB2"},
{"block":2,"count":1,"digest":"4701"},
{"block":3,"count":1,"digest":"2DD5"},
{"block":4,"count":1,"digest":"3870"},
{"block":5,"count":1,"digest":"66CF"},
{"block":6,"count":1,"digest":"4405"},
{"block":7,"count":1,"digest":"4913"},
{"block":8,"count":1,"digest":"F51D"},
{"block":9,"count":1,"digest":"0013"},
{"block":10,"count":1,"digest":"5E68"},
{"block":11491,"count":1,"digest":"4078"},
{"block":0,"count":0,"digest":"75C3"}
]
}
]

View file

@ -0,0 +1,39 @@
[
{
"id": "TANGEM SDK",
"dataKey": {
"privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92"
},
"transactionKey": {
"privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312"
}
},
{
"id": "TANGEM SDK OLD",
"dataKey": {
"privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92"
},
"transactionKey": {
"privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92"
}
},
{
"id": "TANGEM",
"dataKey": {
"publicKey": "048196AA4B410AC44A3B9CCE18E7BE226AEA070ACC83A9CF67540FAC49AF25129F6A538A28AD6341358E3C4F9963064F7E365372A651D374E5C23CDD37FD099BF2"
},
"transactionKey": {
"publicKey": "04343D40496CBE1FE8A8C026575C435A29141EA3BC335DA5549AB6C64685A646848036D481CF9A989390A8B034B229D99BD49E6F07D2FF02746EA265EF99380A80"
}
},
{
"id": "SUPERBLOOM",
"dataKey": {
"publicKey": "04EAB7F2444CFFA2E9CB4AA01CF1CA3694888811BDF0F8E0CDBCCFB5F497F14A5B86655FEF0C7E8F86841B0C14660E09F28DDFB9690F7302DA56F661A6A064564E"
},
"transactionKey": {
"publicKey": "04D292BFC802FFA70ABB1C094A19F6C6766DD353114C25415871BE4586129648CFD77B22FBC1AF3C612497732A04CF845B41D760AFDD820051AB250798503DB340"
}
}
]

View file

@ -21,16 +21,23 @@ public class ReadCardInfoTask extends Thread {
private Context mContext;
private NfcManager mNfcManager;
private ArrayList<String> lastRead_UnsuccessfullPINs = new ArrayList<>();
private TangemCard.EncryptionMode lastRead_Encryption = null;
private String lastRead_UID;
// this fields are static to optimize process when need enter pin and scan card again
private static ArrayList<String> lastRead_UnsuccessfullPINs = new ArrayList<>();
private static TangemCard.EncryptionMode lastRead_Encryption = null;
private static String lastRead_UID;
public ReadCardInfoTask(Context context, NfcManager nfcManager, String lastRead_UID, IsoDep isoDep, CardProtocol.Notifications notifications) {
public static void resetLastReadInfo() {
lastRead_UID="";
lastRead_Encryption=null;
lastRead_UnsuccessfullPINs.clear();
}
public ReadCardInfoTask(Context context, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) {
mContext = context;
mIsoDep = isoDep;
mNotifications = notifications;
mNfcManager = nfcManager;
this.lastRead_UID = lastRead_UID;
// ReadCardInfoTask.lastRead_UID = lastRead_UID;
}
@Override
@ -56,9 +63,7 @@ public class ReadCardInfoTask extends Thread {
byte[] UID = mIsoDep.getTag().getId();
String sUID = Util.byteArrayToHexString(UID);
if (!lastRead_UID.equals(sUID)) {
lastRead_UID = sUID;
lastRead_UnsuccessfullPINs.clear();
lastRead_Encryption = null;
resetLastReadInfo();
}
Log.i(TAG, "[-- Start read card info --]");
@ -146,6 +151,7 @@ public class ReadCardInfoTask extends Thread {
}
} catch (Exception e) {
e.printStackTrace();
mNfcManager.notifyReadResult(false);
}
}

View file

@ -5,10 +5,13 @@ import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.FW;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.domain.wallet.PINStorage;
import com.tangem.domain.wallet.TangemCard;
import java.util.Arrays;
/**
* Created by dvol on 04.02.2018.
*/
@ -59,23 +62,26 @@ public class VerifyCardTask extends Thread {
protocol.setPIN(PIN);
protocol.run_Read(false);
PINStorage.setLastUsedPIN(PIN);
mNotifications.OnReadProgress(protocol, 30);
mNotifications.OnReadProgress(protocol, 20);
if (isCancelled) return;
protocol.run_VerifyCard();
mNotifications.OnReadProgress(protocol, 60);
mNotifications.OnReadProgress(protocol, 50);
Log.i(TAG, "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName());
if (isCancelled) return;
if (protocol.getCard().getStatus() == TangemCard.Status.Loaded) {
protocol.run_CheckWalletWithSignatureVerify();
mNotifications.OnReadProgress(protocol, 90);
mNotifications.OnReadProgress(protocol, 80);
}
// if (isCancelled) return;
// if (protocol.getCard().getStatus() == TangemCard.Status.Loaded) {
// protocol.run_CheckWithSignatureVerify();
// }
if (isCancelled) return;
FW.VerifyCodeRecord record=FW.selectRandomVerifyCodeBlock(mCard.getFirmwareVersion());
if (isCancelled) return;
if( record!=null ) {
byte[] returnedDigest = protocol.run_VerifyCode(record.hashAlg, record.blockIndex, record.blockCount, record.challenge);
mCard.setCodeConfirmed(Arrays.equals(returnedDigest, record.digest));
}else{
mCard.setCodeConfirmed(null);
}
mNotifications.OnReadProgress(protocol, 90);
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);

View file

@ -5,5 +5,4 @@ enum class BitcoinNodeTestNet(val host: String, val port: Int) {
n2("testnet.hsmiths.com", 53011),
n3("testnet.qtornado.com", 51001),
n4("testnet1.bauerj.eu", 50001),
}

View file

@ -179,12 +179,12 @@ public class CardCrypto {
Log.e("cardCrypto","enc:" +Util.bytesToHex(enc));
throw new Exception("unsupported s-length - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc));
}
if(!VerifySignature(GeneratePublicKey(privateKeyArray), data, res))
{
throw new Exception("Signature self verify failed - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc)+",res:"+Util.bytesToHex(res));
}
return res;
}
@ -206,7 +206,7 @@ public class CardCrypto {
* @return the PBDKF2 hash of the password
*/
public static byte[] pbkdf2(byte[] password, byte[] salt, int iterations)
throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
throws InvalidKeyException {
return PBKDF2.deriveKey(password, salt, iterations);
}
@ -219,22 +219,20 @@ public class CardCrypto {
return mEncryptedData;
}
public static byte[] Decrypt(byte[] key, byte[] data) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
try {
public static byte[] Decrypt(byte[] key, byte[] data, boolean UsePKCS7)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException {
if (UsePKCS7) {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length ));
byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length));
return decryptedData;
}
catch (Exception e)
{
} else {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/NOPADDING");
Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length ));
Log.e("decrypt",Util.bytesToHex(decryptedData));
throw e;
byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length));
return decryptedData;
}
}
}

View file

@ -470,7 +470,11 @@ public class CardProtocol {
// try read denomination
if (tlvIssuerData != null && tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination) != null) {
mCard.setDenomination(tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination).Value);
if(tlvIssuerData.getTLV(TLV.Tag.TAG_DenominationText) != null) {
mCard.setDenomination(tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination).Value, tlvIssuerData.getTLV(TLV.Tag.TAG_DenominationText).getAsString());
}else{
mCard.setDenomination(tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination).Value);
}
} else {
mCard.clearDenomination();
}
@ -504,6 +508,8 @@ public class CardProtocol {
TLVList tlvCardData = TLVList.fromBytes(readResult.getTLV(TLV.Tag.TAG_CardData).Value);
mCard.setBatch(tlvCardData.getTLV(TLV.Tag.TAG_Batch).getAsHexString());
TLV tokenSymbol = tlvCardData.getTLV(TLV.Tag.TAG_Token_Symbol);
TLV contractAddress = tlvCardData.getTLV(TLV.Tag.TAG_Token_Contract_Address);
TLV tokens_decimal = tlvCardData.getTLV(TLV.Tag.TAG_Token_Decimal);
@ -553,7 +559,7 @@ public class CardProtocol {
} catch (Exception ee) {
ee.printStackTrace();
Log.e(logTag, "Cannot get issuer");
mCard.setIssuer(Issuer.Unknown);
mCard.setIssuer(Issuer.Unknown());
}
}
@ -848,7 +854,7 @@ public class CardProtocol {
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Transaction_Signature, issuerSignature);
}
if (issuerData != null) {
if (issuer == null || issuer == Issuer.Unknown)
if (issuer == null || issuer == Issuer.Unknown())
throw new Exception("Need known Issuer to write issuer Data");
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData);
byte[] issuerSignature = CardCrypto.Signature(issuer.getPrivateTransactionKey(), issuerData);
@ -905,7 +911,7 @@ public class CardProtocol {
}
}
private byte[] run_VerifyCode(String hashAlgID, int codePageAddress, int codePageCount, byte[] challenge) throws Exception {
public byte[] run_VerifyCode(String hashAlgID, int codePageAddress, int codePageCount, byte[] challenge) throws Exception {
if (readResult == null) run_Read();
CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCode);
rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, hashAlgID.getBytes("US-ASCII"));
@ -978,6 +984,9 @@ public class CardProtocol {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
TLV issuerData = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data);
TLV issuerDataSignature = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Signature);
TLV issuerDataCounter = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Counter);
boolean protectIssuerDataAgainstReplay=(readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask)&SettingsMask.ProtectIssuerDataAgainstReplay)!=0;
if (issuerData == null || issuerDataSignature == null)
throw new TangemException("Invalid answer format (GetIssuerData)");
@ -985,6 +994,9 @@ public class CardProtocol {
ByteArrayOutputStream bsDataToVerify = new ByteArrayOutputStream();
bsDataToVerify.write(mCard.getCID());
bsDataToVerify.write(issuerData.Value);
if( protectIssuerDataAgainstReplay ) {
bsDataToVerify.write(issuerDataCounter.Value);
}
try {
if (CardCrypto.VerifySignature(mCard.getIssuer().getPublicDataKey(), bsDataToVerify.toByteArray(), issuerDataSignature.Value)) {
mCard.setIssuerData(issuerData.Value, issuerDataSignature.Value);

View file

@ -0,0 +1,62 @@
package com.tangem.domain.cardReader;
import android.content.Context;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.tangem.util.Util;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class FW {
private static JsonArray jaFirmwares=null;
public static boolean needInit() {
return jaFirmwares == null;
}
public static void Init(Context context) {
try(InputStream is=context.getAssets().open("fw_hashes.json")) {
try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
JsonParser parser = new JsonParser();
jaFirmwares = parser.parse(reader).getAsJsonArray();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static class VerifyCodeRecord
{
public String hashAlg;
public int blockIndex;
public int blockCount;
public byte[] challenge;
public byte[] digest;
}
public static VerifyCodeRecord selectRandomVerifyCodeBlock(String firmwareVersion) throws IOException {
for(int i=0; i<jaFirmwares.size(); i++) {
JsonObject jsVersion = jaFirmwares.get(i).getAsJsonObject();
if( jsVersion.get("fw").getAsString().equals(firmwareVersion) )
{
VerifyCodeRecord result=new VerifyCodeRecord();
result.hashAlg = "sha-256";
JsonArray jsHashes = jsVersion.get(result.hashAlg).getAsJsonArray();
result.challenge = Util.hexToBytes(jsVersion.get("challenge").getAsString());
int caseIndex= Util.byteArrayToInt(Util.generateRandomBytes(4))%jsHashes.size();
JsonObject jsRecord = jsHashes.get(caseIndex).getAsJsonObject();
result.blockIndex = jsRecord.get("block").getAsInt();
result.blockCount = jsRecord.get("count").getAsInt();
result.digest = Util.hexToBytes(jsRecord.get("digest").getAsString());
return result;
}
}
return null;
}
}

View file

@ -15,6 +15,7 @@ import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.widget.Toast;
import com.tangem.presentation.dialog.NFCEnableDialog;
@ -76,6 +77,27 @@ public class NfcManager {
// }
}
int errorCount=0;
public void notifyReadResult(boolean success)
{
if(success)
{
errorCount=0;
}else{
errorCount++;
}
if( errorCount>=3 )
{
disableReaderMode();
mNfcAdapter=null;
Toast.makeText(mActivity,"NFC restarted!",Toast.LENGTH_SHORT).show();
mActivity.runOnUiThread(()->{
mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity);
enableReaderMode();
});
}
}
private void showNFCEnableDialog() {
mEnableNfcDialog = new NFCEnableDialog();
mEnableNfcDialog.show(mActivity.getFragmentManager(), NFCEnableDialog.TAG);

View file

@ -59,7 +59,7 @@ public class ResponseApdu {
responseApdu.mSw2 = ((int) data[1] & 0xFF);
return responseApdu;
}else if( data.length>=18 ){
byte[] decryptedData = CardCrypto.Decrypt(key, Arrays.copyOfRange(data, 0, data.length - 2));
byte[] decryptedData = CardCrypto.Decrypt(key, Arrays.copyOfRange(data, 0, data.length - 2),true);
ByteArrayInputStream inputStream = new ByteArrayInputStream(decryptedData);
byte[] baLength = new byte[2];

View file

@ -90,7 +90,8 @@ public class TLV {
TAG_Token_Decimal(0xA2),
TAG_Denomination(0xC0),
TAG_ValidatedBalance(0xC1),
TAG_LastSign_Date(0xC2);
TAG_LastSign_Date(0xC2),
TAG_DenominationText(0xC3);
Tag(int Code) {

View file

@ -1,130 +1,192 @@
package com.tangem.domain.wallet;
import com.tangem.domain.cardReader.CardCrypto;
import android.content.Context;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.tangem.domain.cardReader.CardCrypto;
import com.tangem.util.Util;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Created by dvol on 14.11.2017.
*/
public enum Issuer {
Unknown("Unknown", "Unknown", null, null, null, null),
SMART_CASH_AG("SMART CASH AG", "SMART CASH AG",
IssuerKeyStorage.sdkPrivateDataKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateDataKey),
IssuerKeyStorage.sdkPrivateTransactionKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateTransactionKey)),
TANGEM_SDK("TANGEM SDK", "TANGEM SDK",
IssuerKeyStorage.sdkPrivateDataKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateDataKey),
IssuerKeyStorage.sdkPrivateTransactionKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateTransactionKey)),
TANGEM("TANGEM", "TANGEM", null, IssuerKeyStorage.tangemPublicDataKey, null, IssuerKeyStorage.tangemPublicTransactionKey)
;
public class Issuer {
static class KeyPair {
String privateKey;
String publicKey;
static class IssuerKeyStorage {
private static final byte[] sdkPrivateDataKey = new byte[]{
(byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18,
(byte) 0x47, (byte) 0x71, (byte) 0xED, (byte) 0x81, (byte) 0xF2, (byte) 0xBA, (byte) 0xCF, (byte) 0x57,
(byte) 0x47, (byte) 0x9E, (byte) 0x47, (byte) 0x35, (byte) 0xEB, (byte) 0x14, (byte) 0x05, (byte) 0x08,
(byte) 0x39, (byte) 0x27, (byte) 0x37, (byte) 0x2D, (byte) 0x40, (byte) 0xDA, (byte) 0x9E, (byte) 0x92};
private static final byte[] sdkPrivateTransactionKey = new byte[]{
(byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18,
(byte) 0x47, (byte) 0x71, (byte) 0xED, (byte) 0x81, (byte) 0xF2, (byte) 0xBA, (byte) 0xCF, (byte) 0x57,
(byte) 0x47, (byte) 0x9E, (byte) 0x47, (byte) 0x35, (byte) 0xEB, (byte) 0x14, (byte) 0x05, (byte) 0x08,
(byte) 0x19, (byte) 0x18, (byte) 0x17, (byte) 0x16, (byte) 0x15, (byte) 0x14, (byte) 0x13, (byte) 0x12};
private static byte[] tangemPublicDataKey = {
(byte) 0x04 ,
(byte) 0x81 ,(byte) 0x96 ,(byte) 0xAA ,(byte) 0x4B ,(byte) 0x41 ,(byte) 0x0A ,(byte) 0xC4 ,(byte) 0x4A,
(byte) 0x3B ,(byte) 0x9C ,(byte) 0xCE ,(byte) 0x18 ,(byte) 0xE7 ,(byte) 0xBE ,(byte) 0x22 ,(byte) 0x6A,
(byte) 0xEA ,(byte) 0x07 ,(byte) 0x0A ,(byte) 0xCC ,(byte) 0x83 ,(byte) 0xA9 ,(byte) 0xCF ,(byte) 0x67,
(byte) 0x54 ,(byte) 0x0F ,(byte) 0xAC ,(byte) 0x49 ,(byte) 0xAF ,(byte) 0x25 ,(byte) 0x12 ,(byte) 0x9F,
(byte) 0x6A ,(byte) 0x53 ,(byte) 0x8A ,(byte) 0x28 ,(byte) 0xAD ,(byte) 0x63 ,(byte) 0x41 ,(byte) 0x35,
(byte) 0x8E ,(byte) 0x3C ,(byte) 0x4F ,(byte) 0x99 ,(byte) 0x63 ,(byte) 0x06 ,(byte) 0x4F ,(byte) 0x7E,
(byte) 0x36 ,(byte) 0x53 ,(byte) 0x72 ,(byte) 0xA6 ,(byte) 0x51 ,(byte) 0xD3 ,(byte) 0x74 ,(byte) 0xE5,
(byte) 0xC2 ,(byte) 0x3C ,(byte) 0xDD ,(byte) 0x37 ,(byte) 0xFD ,(byte) 0x09 ,(byte) 0x9B ,(byte) 0xF2};
private static byte[] tangemPublicTransactionKey = {
(byte) 0x04 ,
(byte) 0x34 ,(byte) 0x3D ,(byte) 0x40 ,(byte) 0x49 ,(byte) 0x6C ,(byte) 0xBE ,(byte) 0x1F ,(byte) 0xE8,
(byte) 0xA8 ,(byte) 0xC0 ,(byte) 0x26 ,(byte) 0x57 ,(byte) 0x5C ,(byte) 0x43 ,(byte) 0x5A ,(byte) 0x29,
(byte) 0x14 ,(byte) 0x1E ,(byte) 0xA3 ,(byte) 0xBC ,(byte) 0x33 ,(byte) 0x5D ,(byte) 0xA5 ,(byte) 0x54,
(byte) 0x9A ,(byte) 0xB6 ,(byte) 0xC6 ,(byte) 0x46 ,(byte) 0x85 ,(byte) 0xA6 ,(byte) 0x46 ,(byte) 0x84,
(byte) 0x80 ,(byte) 0x36 ,(byte) 0xD4 ,(byte) 0x81 ,(byte) 0xCF ,(byte) 0x9A ,(byte) 0x98 ,(byte) 0x93,
(byte) 0x90 ,(byte) 0xA8 ,(byte) 0xB0 ,(byte) 0x34 ,(byte) 0xB2 ,(byte) 0x29 ,(byte) 0xD9 ,(byte) 0x9B,
(byte) 0xD4 ,(byte) 0x9E ,(byte) 0x6F ,(byte) 0x07 ,(byte) 0xD2 ,(byte) 0xFF ,(byte) 0x02 ,(byte) 0x74,
(byte) 0x6E ,(byte) 0xA2 ,(byte) 0x65 ,(byte) 0xEF ,(byte) 0x99 ,(byte) 0x38 ,(byte) 0x0A ,(byte) 0x80};
public static byte[] GeneratePublicKey(byte[] privateKey) {
try {
return CardCrypto.GeneratePublicKey(privateKey);
byte[] getPrivateKey() throws Exception {
if (privateKey != null) {
return CardCrypto.GeneratePublicKey(Util.hexToBytes(privateKey));
} else {
throw new Exception("No private key!");
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
byte[] getPublicKey() throws Exception {
if (publicKey == null) {
if (privateKey != null) {
return CardCrypto.GeneratePublicKey(Util.hexToBytes(privateKey));
} else {
throw new Exception("Invalid key format: no public and no private");
}
} else {
return Util.hexToBytes(publicKey);
}
}
}
private String id;
private String officialName;
private KeyPair dataKey;
private KeyPair transactionKey;
public String getID() {
return id;
}
private static List<Issuer> instances=new ArrayList<>();
public static boolean needInit() {
return instances.size() == 0;
}
public static void Init(Context context) {
try {
// JsonArray jaIssuers=loadIssuersFile();
//
Issuer unknown = new Issuer();
unknown.id = "UNKNOWN";
unknown.officialName = "UNKNOWN";
try(InputStream is=context.getAssets().open("issuers.json")) {
try ( InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
Type listType = new TypeToken<List<Issuer>>() {
}.getType();
instances = new Gson().fromJson(reader, listType);
}
}
instances.add(0, unknown);
// for (JsonElement jeIssuer : jaIssuers) {
// Issuer instance = new Gson().fromJson(jeIssuer, Issuer.class);
// instances.add(instance);
// }
} catch (Exception e) {
e.printStackTrace();
}
}
private String ID;
private String officialName;
private byte[] privateDataKeyArray;
private byte[] publicDataKeyArray;
private byte[] privateTransactionKeyArray;
private byte[] publicTransactionKeyArray;
// private static JsonArray loadIssuersFile() throws IOException {
// String result;
// JsonParser jsonParser = new JsonParser();
// try (BufferedReader reader = Files.newReader(new File("Issuers.json"), StandardCharsets.UTF_8)) {
// String str;
// StringBuilder buf = new StringBuilder();
// while ((str = reader.readLine()) != null) {
// buf.append(str);
// buf.append("\n");
// }
// result = buf.toString();
// }
// return jsonParser.parse(result).getAsJsonArray();
// }
//
// static String ReadJSONResource(Context mContext, int id) {
// Resources resources = mContext.getResources();
// InputStream resourceReader = resources.openRawResource(id);
// Writer writer = new StringWriter();
// try {
// try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceReader, "UTF-8"))) {
// String line = reader.readLine();
// while (line != null) {
// writer.write(line);
// line = reader.readLine();
// }
// }
// } catch (Exception e) {
// Log.e("ReadJSONResource", "Unhandled exception while using JSONResourceReader", e);
// } finally {
// try {
// resourceReader.close();
// } catch (Exception e) {
// Log.e("ReadJSONResource", "Unhandled exception while using JSONResourceReader", e);
// }
// }
//
// return writer.toString();
// }
Issuer(String id, String officialName, byte[] privateDataKey, byte[] publicDataKey, byte[] privateTransactionKey, byte[] publicTransactionKey) {
this.ID = id;
this.officialName = officialName;
this.privateDataKeyArray = privateDataKey;
this.privateTransactionKeyArray = privateTransactionKey;
this.publicDataKeyArray = publicDataKey;
this.publicTransactionKeyArray = publicTransactionKey;
public byte[] getPublicDataKey() throws Exception {
if (dataKey == null)
throw new Exception("Data key not specified!");
return dataKey.getPublicKey();
}
public byte[] getPublicDataKey() {
return publicDataKeyArray;
public byte[] getPublicTransactionKey() throws Exception {
if (dataKey == null)
throw new Exception("Transaction key not specified!");
return transactionKey.getPublicKey();
}
public byte[] getPublicTransactionKey() {
return publicTransactionKeyArray;
public byte[] getPrivateDataKey() throws Exception {
if (dataKey == null)
throw new Exception("Data key not specified!");
return dataKey.getPrivateKey();
}
public byte[] getPrivateDataKey() {
return privateDataKeyArray;
}
public byte[] getPrivateTransactionKey() {
return privateTransactionKeyArray;
}
public byte[] getID() {
return ID.getBytes();
public byte[] getPrivateTransactionKey() throws Exception {
if (transactionKey == null)
throw new Exception("Transaction key not specified!");
return transactionKey.getPrivateKey();
}
public String getOfficialName() {
return officialName;
}
public static Issuer FindIssuer(String ID, byte[] publicDataKey) {
Issuer[] issuers = Issuer.values();
for (int i = 1; i < issuers.length; i++) {
if (issuers[i].ID.equals(ID) && Arrays.equals(issuers[i].getPublicDataKey(), publicDataKey)) {
return issuers[i];
}
}
return Issuer.Unknown;
return officialName != null ? officialName : id;
}
public static Issuer FindIssuer(String ID) {
Issuer[] issuers = Issuer.values();
for (int i = 1; i < issuers.length; i++) {
if (issuers[i].ID.equals(ID)) {
return issuers[i];
for (int i = 0; i < instances.size(); i++) {
try {
if (instances.get(i).id.equals(ID)) {
return instances.get(i);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return Issuer.Unknown;
return Unknown();
}
public static Issuer FindIssuer(String ID, byte[] publicDataKey) {
for (int i = 0; i < instances.size(); i++) {
try {
if (instances.get(i).id.equals(ID) && Arrays.equals(instances.get(i).getPublicDataKey(), publicDataKey)) {
return instances.get(i);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return Unknown();
}
public static Issuer Unknown() {
return instances.get(0);
}
}

View file

@ -11,10 +11,10 @@ import com.tangem.wallet.R;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by dvol on 16.07.2017.
@ -271,6 +271,7 @@ public class TangemCard {
}
private Boolean codeConfirmed;
public void setCodeConfirmed(Boolean codeConfirmed) {
this.codeConfirmed = codeConfirmed;
}
@ -280,6 +281,7 @@ public class TangemCard {
}
private Boolean onlineVerified;
public void setOnlineVerified(Boolean verified) {
this.onlineVerified = verified;
}
@ -289,6 +291,7 @@ public class TangemCard {
}
private Boolean onlineValidated;
public void setOnlineValidated(Boolean validated) {
this.onlineValidated = validated;
}
@ -297,15 +300,6 @@ public class TangemCard {
return onlineValidated;
}
private boolean balanceRecieved = false;
public boolean isBalanceRecieved() {
return balanceRecieved;
}
public void setBalanceRecieved(boolean balanceRecieved)
{
this.balanceRecieved = balanceRecieved;
}
public int getRemainingSignatures() {
return remainingSignatures;
}
@ -615,7 +609,7 @@ public class TangemCard {
return health == 0;
}
private Issuer issuer = Issuer.Unknown;
private Issuer issuer = Issuer.Unknown();
public void setIssuer(Issuer issuer) {
this.issuer = issuer;
@ -663,8 +657,8 @@ public class TangemCard {
return issuer.getOfficialName();
}
byte[] issuerData;
byte[] issuerDataSignature;
private byte[] issuerData;
private byte[] issuerDataSignature;
public byte[] getIssuerData() {
return issuerData;
@ -676,9 +670,10 @@ public class TangemCard {
public void setIssuerData(byte[] value, byte[] signature) {
issuerData = value;
issuerDataSignature = signature;
}
boolean needWriteIssuerData = false;
private boolean needWriteIssuerData = false;
public boolean getNeedWriteIssuerData() {
return needWriteIssuerData;
@ -862,6 +857,16 @@ public class TangemCard {
return numbers1[0] > numbers2[0] || (numbers1[0] == numbers2[0] && numbers1[1] > numbers2[1]);
}
private String batch;
public void setBatch(String batch) {
this.batch = batch;
}
public String getBatch() {
return batch;
}
private String validationNodeDescription = "";
public String getValidationNodeDescription() {
@ -1187,9 +1192,22 @@ public class TangemCard {
}
private byte[] Denomination;
private String DenominationText;
public void setDenomination(byte[] denomination) {
public void setDenomination(byte[] denomination, String denominationText) {
this.Denomination = denomination;
this.DenominationText=denominationText;
}
public void setDenomination(byte[] denomination)
{
this.Denomination=denomination;
try {
this.DenominationText=CoinEngineFactory.Create(getBlockchain()).ConvertByteArrayToAmount(this, denomination);
} catch (Exception e) {
e.printStackTrace();
this.DenominationText="N/A";
}
}
public byte[] getDenomination() {
@ -1206,8 +1224,17 @@ public class TangemCard {
}
public String getDenominationText() {
return DenominationText;
}
public void setDenominationText(String denominationText) {
DenominationText = denominationText;
}
public void clearDenomination() {
Denomination = null;
DenominationText=null;
}
public void setError(String error) {
@ -1274,9 +1301,10 @@ public class TangemCard {
if (signingMethod != null) B.putString("signingMethod", signingMethod.name());
if (manufacturer != null) B.putString("Manufacturer", manufacturer.name());
if (encryptionMode != null) B.putString("EncryptionMode", encryptionMode.name());
if (issuer != null) B.putString("Issuer", issuer.name());
if (issuer != null) B.putString("Issuer", issuer.getID());
if (firmwareVersion != null) B.putString("FirmwareVersion", firmwareVersion);
if(balanceEqual != null) B.putBoolean("isBalanceEqual", balanceEqual);
if (batch != null) B.putString("Batch", batch);
B.putString("BalanceDecimal", balanceDecimal);
B.putString("BalanceDecimalAlter", balanceDecimalAlter);
B.putBoolean("ManufacturerConfirmed", manufacturerConfirmed);
@ -1346,6 +1374,19 @@ public class TangemCard {
if( onlineValidated!=null )
B.putBoolean("onlineValidated", onlineValidated);
if (codeConfirmed != null)
B.putBoolean("codeConfirmed", codeConfirmed);
if (codeConfirmed != null)
B.putBoolean("codeConfirmed", codeConfirmed);
if (onlineVerified != null)
B.putBoolean("onlineVerified", onlineVerified);
if (onlineValidated != null)
B.putBoolean("onlineValidated", onlineValidated);
}
public void LoadFromBundle(Bundle B) {
@ -1388,6 +1429,7 @@ public class TangemCard {
if(B.containsKey("FailedBalance")) failedBalanceRequestCounter = new AtomicInteger(B.getInt("FailedBalance"));
if (B.containsKey("Issuer")) issuer = Issuer.FindIssuer(B.getString("Issuer"));
if (B.containsKey("FirmwareVersion")) firmwareVersion = B.getString("FirmwareVersion");
if (B.containsKey("Batch")) batch = B.getString("Batch");
if(B.containsKey("isBalanceEqual")) setIsBalanceEqual(B.getBoolean("isBalanceEqual"));
cardPublicKeyValid = B.getBoolean("CardPublicKeyValid");
@ -1406,7 +1448,7 @@ public class TangemCard {
if (B.containsKey("Denomination")) setDenomination(B.getByteArray("Denomination"));
else clearDenomination();
if (B.containsKey("IssuerData"))
if (B.containsKey("IssuerData") && B.containsKey("IssuerDataSignature"))
setIssuerData(B.getByteArray("IssuerData"), B.getByteArray("IssuerDataSignature"));
else setIssuerData(null, null);
@ -1461,26 +1503,47 @@ public class TangemCard {
if (B.containsKey("confirmTx"))
countConfirmTX = new BigInteger(B.getString("confirmTx"), 16);
if( B.containsKey("codeConfirmed") )
codeConfirmed=B.getBoolean("codeConfirmed");
if (B.containsKey("codeConfirmed"))
codeConfirmed = B.getBoolean("codeConfirmed");
if( B.containsKey("onlineVerified") )
onlineVerified=B.getBoolean("onlineVerified");
if (B.containsKey("onlineVerified"))
onlineVerified = B.getBoolean("onlineVerified");
if( B.containsKey("onlineValidated") )
onlineValidated=B.getBoolean("onlineValidated");
if (B.containsKey("onlineValidated"))
onlineValidated = B.getBoolean("onlineValidated");
}
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
if (bytes == null) return "";
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public int getCardImageResource() {
switch (getBlockchainID()) {
case "BTC":
return R.drawable.card_btc001;
if (bytesToHex(getDenomination()).equals("40420F0000000000"))
return R.drawable.card_btc001;
else if (bytesToHex(getDenomination()).equals("404B4C0000000000"))
return R.drawable.card_btc005;
else
return R.drawable.card_default;
case "ETH\\XTZ":
return R.drawable.card_seed;
case "Token":
if (getTokenSymbol().equals("SEED"))
return R.drawable.card_seed;
else
return R.drawable.card_default;
default:
return R.drawable.card_btc001;
return R.drawable.card_default;
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.presentation.activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
@ -24,7 +25,9 @@ import android.widget.TextView;
import com.scottyab.rootbeer.RootBeer;
import com.skyfishjy.library.RippleBackground;
import com.tangem.domain.cardReader.FW;
import com.tangem.domain.wallet.DeviceNFCAntennaLocation;
import com.tangem.domain.wallet.Issuer;
import com.tangem.domain.wallet.LastSignStorage;
import com.tangem.domain.wallet.Logger;
import com.tangem.domain.wallet.PINStorage;
@ -244,6 +247,8 @@ public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuI
if (LastSignStorage.needInit()) {
LastSignStorage.Init(context);
}
if( Issuer.needInit() ) Issuer.Init(context);
if( FW.needInit() ) FW.Init(context);
}
public void showCleanButton() {

View file

@ -76,7 +76,7 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis
private int unsuccessReadCount = 0;
private Tag lastTag = null;
private String lastRead_UID = "";
// private String lastRead_UID = "";
public Main() {
}
@ -119,9 +119,9 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis
int cardIndex = viewHolder.getAdapterPosition();
if (cardIndex < 0 || cardIndex >= mCardListAdapter.getItemCount()) return;
slCardUIDs.remove(mCardListAdapter.getCard(cardIndex).getUID());
if (mCardListAdapter.getCard(cardIndex).getUID() == lastRead_UID) {
lastRead_UID = "";
}
// if (mCardListAdapter.getCard(cardIndex).getUID() == lastRead_UID) {
// lastRead_UID = "";
// }
mCardListAdapter.removeCard(cardIndex);
if (mCardListAdapter.getItemCount() == 0 && getActivity().getClass() == MainActivity.class) {
((MainActivity) getActivity()).hideCleanButton();
@ -142,6 +142,7 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis
@Override
public void onResume() {
super.onResume();
ReadCardInfoTask.resetLastReadInfo();
mNfcManager.onResume();
}
@ -201,8 +202,13 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis
}
}
} else if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_ENTER_PIN_ACTIVITY) {
if (lastTag != null) onTagDiscovered(lastTag);
} else if (requestCode == REQUEST_CODE_ENTER_PIN_ACTIVITY) {
if( resultCode == Activity.RESULT_OK && lastTag != null)
{
onTagDiscovered(lastTag);
}else{
ReadCardInfoTask.resetLastReadInfo();
}
}
}
@ -233,13 +239,14 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis
}
lastTag = tag;
readCardInfoTask = new ReadCardInfoTask(getActivity(), mNfcManager, lastRead_UID, isoDep, this);
readCardInfoTask = new ReadCardInfoTask(getActivity(), mNfcManager, isoDep, this);
readCardInfoTask.start();
Log.i(TAG, "onTagDiscovered " + Arrays.toString(tag.getId()));
} catch (Exception e) {
e.printStackTrace();
mNfcManager.notifyReadResult(false);
}
}
@ -290,6 +297,7 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis
readCardInfoTask = null;
if (cardProtocol != null) {
if (cardProtocol.getError() == null) {
mNfcManager.notifyReadResult(true);
progressBar.post(() -> {
rlProgressBar.setVisibility(View.GONE);
progressBar.setProgress(100);
@ -334,7 +342,6 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis
for (RequestWalletInfoTask rt : requestTasks) {
rt.cancel(true);
}
lastRead_UID = "";
});
@ -349,12 +356,15 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis
slCardUIDs.remove(cardProtocol.getCard().getUID());
if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) {
doEnterPIN();
} else if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) {
if (!NoExtendedLengthSupportDialog.allreadyShowed) {
new NoExtendedLengthSupportDialog().show(Objects.requireNonNull(getActivity()).getFragmentManager(), NoExtendedLengthSupportDialog.TAG);
}
} else {
if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) {
if (!NoExtendedLengthSupportDialog.allreadyShowed) {
new NoExtendedLengthSupportDialog().show(Objects.requireNonNull(getActivity()).getFragmentManager(), NoExtendedLengthSupportDialog.TAG);
}
}
lastTag = null;
ReadCardInfoTask.resetLastReadInfo();
mNfcManager.notifyReadResult(false);
}
}
});
@ -376,6 +386,7 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis
public void OnReadCancel() {
readCardInfoTask = null;
ReadCardInfoTask.resetLastReadInfo();
progressBar.postDelayed(() -> {
try {
rlProgressBar.setVisibility(View.GONE);
@ -410,7 +421,7 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis
for (RequestWalletInfoTask rt : requestTasks) {
rt.cancel(true);
}
lastRead_UID = "";
ReadCardInfoTask.resetLastReadInfo();
}
public void refreshCard(TangemCard card) {